我正在尝试使用 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 的 Generated tests with XState and 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
。这将有效地使测试失败(这本身很好)。在我添加默认存根之后,我必须添加我计划稍后正确执行的初始错误处理,因为当 Cypress 在请求过程中重新加载页面时,我现在得到 AbortError
。
不确定暂停请求是否有助于解决错误(可能不会),但我仍然不喜欢我必须深入研究 path
的内部结构,解析它的段和事件以查找路由并预先存根.一旦我从我自己的代码(在事件 exec 函数中)得到这个响应,我更愿意暂停一切并用适当的响应解决。