0

问题

我正在尝试使用 Cypress 进行基于模型的测试方法。这意味着所有测试用例都是从提供的“状态”和“事件”动态生成的。状态正在正确检查并且 DOM 事件按预期触发,但我无法拦截网络请求。我需要做什么:

  1. 在我转到页面之前设置请求拦截,因为第一个请求是在页面加载后立即开始的
  2. 等待模型使用适当的响应数据执行事件
  3. 在事件执行期间,向请求提供响应数据并“取消暂停”它。

我试过的

我认为如果你只是打电话cy.intercept,请求将暂停,直到其他cy.interceptreply()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 函数中)获得此响应,我宁愿暂停一切并以适当的响应解决。

4

0 回答 0