问题
我正在尝试使用 Cypress 进行基于模型的测试方法。这意味着所有测试用例都是从提供的“状态”和“事件”动态生成的。状态正在正确检查并且 DOM 事件按预期触发,但我无法拦截网络请求。我需要做什么:
- 在我转到页面之前设置请求拦截,因为第一个请求是在页面加载后立即开始的
- 等待模型使用适当的响应数据执行事件
- 在事件执行期间,向请求提供响应数据并“取消暂停”它。
我试过的
我认为如果你只是打电话cy.intercept
,请求将暂停,直到其他cy.intercept
人reply()
或continue()
将被调用。我拦截了所有路由beforeAll
并尝试了它,但请求只是按照它的自然请求生命周期进行。
我的第二次尝试是从拦截处理程序返回 Promise。文档指出“如果处理程序返回了 Promise,请等待 Promise 解决。”。但是,当我req.reply
在解决此承诺之前调用时,赛普拉斯抛出错误:“req.reply()
在请求处理程序完成执行后调用,但在请求完成req.reply()
后无法调用。”
有没有办法完全暂停所有拦截的请求并在需要时使用我需要的状态、正文和标头解决它们?
代码
// App.tsx
import { useState, useEffect } from 'preact/hooks';
export function App() {
let [authorizationStatus, setAuthorizationStatus] = useState('unknown');
useEffect(function () {
fetch('/api/auth/v1').then(response => response.json()).then(body => {
if (body.authorization_status === 'OK') {
setAuthorizationStatus('authorized');
return;
}
setAuthorizationStatus('unauthorized');
});
}, []);
if (authorizationStatus === 'unknown') return <div data-testid="unknown"> Getting auth status </div>;
if (authorizationStatus === 'authorized') return <div data-testid="authorized">Congratulations, you are authorized</div>;
if (authorizationStatus === 'unauthorized') return <div data-testid="unauthorized">Sorry, but you don't have rights to be here</div>;
return null;
}
// cypress/integration/integration.spec.ts
import { createMachine } from "xstate";
import { createModel } from "@xstate/test";
let machine = createMachine(
{
id: "test-machine",
initial: "loading",
states: {
loading: {
meta: {
test() {
return new Cypress.Promise((resolve) => {
cy.get('[data-testid="unknown"]').then(() => resolve());
});
},
},
on: {
response: [
{ target: "authorized", cond: "is_authorized" },
{ target: "unauthorized" },
],
},
},
authorized: {
meta: {
test() {
return new Cypress.Promise((resolve) => {
cy.get('[data-testid="authorized"]').then(() => resolve());
});
},
},
},
unauthorized: {
meta: {
test() {
return new Cypress.Promise((resolve) => {
cy.get('[data-testid="unauthorized"]').then(() => resolve());
});
},
},
},
},
},
{
guards: {
is_authorized: (_, event) => event.authorization_status === "OK",
},
}
);
let routes = {
api: "/api/auth/v1",
};
beforeEach(() => {
Object.entries(routes).forEach(([name, url]) => {
// Here I want to pause request execution somehow.
// I'm ok with moving it somewhere else
cy.intercept(url).as(name);
});
});
let model = createModel(machine).withEvents({
response: {
exec(_, event) {
return new Cypress.Promise((resolve) => {
// Here I want to resume request with provided parameters
cy.intercept(routes.api, (req) => {
req.reply(event);
}).then(() => resolve());
});
},
cases: [{ authorization_status: "OK" }, { authorization_status: "NOT_OK" }],
},
});
let plans = model.getShortestPathPlans();
plans.forEach((plan) => {
describe(plan.description, () => {
plan.paths.forEach((path) => {
it(path.description, () => {
cy.visit("http://localhost:3000");
return new Cypress.Promise(async (resolve) => {
await path.test(cy);
resolve();
});
});
});
});
});
更新
我终于设法让它几乎按照我的意愿工作,但仍然没有找到我需要的东西。
根据Tim Deschryver 的使用 XState 和 Cypress 生成的测试文章,我摆脱了测试中的所有承诺,它开始工作得更好一些,更像我所期待的。
接下来要做的是将beforeEach
调用移动到测试套件中,并根据当前路径段内的事件拦截所有请求。我route
向事件添加了键以指示应该拦截该事件,然后添加必须拦截它的响应主体。它看起来像这样(仅相关代码):
let model = createModel<Cypress.cy>(machine).withEvents({
response: {
exec(cy, event) {
cy.wait(`@${event.route}`);
},
cases: [
{ route: "api", body: { authorization_status: "NOT_OK" } },
{ route: "api", body: { authorization_status: "OK" } },
],
},
});
let plans = model.getShortestPathPlans();
plans.forEach((plan) => {
describe(plan.description, () => {
plan.paths.forEach((path, i) => {
beforeEach(function () {
path.segments.forEach((segment) => {
if (!segment.event.route) return;
cy.intercept(routes[segment.event.route], {
body: segment.event.body,
statusCode: 200,
}).as(segment.event.route);
});
});
it(path.description, () => {
cy.visit("http://localhost:3000").then(async () => await path.test(cy));
});
});
});
});
在我正在处理的应用程序中,有一些更复杂的设置,但这就是使它起作用的原因。之后我必须为每条路由添加默认存根,因为Unexpected token < in JSON at position 0
当非存根路由失败时我会得到其他方式。这将有效地使测试失败(这本身就很好)。在我添加默认存根之后,我必须添加我计划稍后正确执行的初始错误处理,因为当赛普拉斯在请求中间重新加载页面时,我现在得到AbortError
.
不确定暂停请求是否有助于解决错误(可能不会),但我仍然不喜欢我必须深入研究内部path
,解析它的段和事件以查找路由并预先存根它们。一旦我从自己的代码(在事件 exec 函数中)获得此响应,我宁愿暂停一切并以适当的响应解决。