如何在 Cypress 中无限期延迟拦截的路由?

时间:2021-05-06 09:38:21

标签: cypress model-based-testing

问题

我正在尝试使用 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 的 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 函数中)得到这个响应,我更愿意暂停一切并用适当的响应解决。

0 个答案:

没有答案