如何将 Cypress 配置为更长时间(或无限期)等待 BaseUrl?

How To Configure Cypress To Wait Longer (or Indefinitely) for BaseUrl?

我在 docker-compose.yml 到 运行 端到端测试中使用这个 Cypress 图像:cypress/included:6.1.0

当测试 运行ner 启动时,它将验证是否可以访问位于 baseUrl 的服务器。如果不是,则重试 3 次。

我的服务和 Web 服务器需要更多时间才能启动。

如何增加此检查的超时 and/or 重试次数。

最好,在我的例子中,我想要一个重试直到成功的策略,即无限期 retries/wait。

我检查了 Timeouts 部分和 cypress.json 文档。但是 none 这些超时或重试似乎与此行为有关。

有这个设置吗?


澄清一下:这不是我作为规范的一部分实施(或想要)的检查。据我所知,这是图像中默认命令 cyprus run 的一个特性。如果可能的话,我想在不添加或修改测试本身的情况下进行配置。

这是 cypress 在容器中启动时的 docker-compose 控制台输出:

cypress_1         | Cypress could not verify that this server is running:
cypress_1         |
cypress_1         |   > http://localhost:5000
cypress_1         |
cypress_1         | We are verifying this server because it has been configured as your `baseUrl`.
cypress_1         |
cypress_1         | Cypress automatically waits until your server is accessible before running tests.
cypress_1         |
cypress_1         | We will try connecting to it 3 more times...
cypress_1         | We will try connecting to it 2 more times...
cypress_1         | We will try connecting to it 1 more time...
cypress_1         |
cypress_1         | Cypress failed to verify that your server is running.
cypress_1         |
cypress_1         | Please start this server and then run Cypress again.
cypress_1 exited with code 1

在使用 wait-on or start-server-and-test.

之类的实用程序调用 cypress run 之前,您应该确保您的服务器处于 运行ning 状态

Cypress 对 baseUrl 的检查是最后的礼貌检查,这样您就不会 运行 在不是 运行 的服务器上通过整个测试套件。

有关确保服务器 运行ning 在 运行ning 赛普拉斯之前的提示,请在此处查看赛普拉斯文档:https://on.cypress.io/continuous-integration#Boot-your-server

免责声明

;在大多数情况下,您应该确保服务器在启动 cypress 之前 运行。

但是

我们主要使用 Cypress 进行 API 测试(目前),我们需要支持的工作流程之一包括服务器重启。当然,我们可以在继续下一步之前插入任意长的 cy.wait(30000);,但这并不优雅,而且会浪费很多时间,尤其是如果你像我一样最终 运行一遍又一遍的测试。

由于 Cypress 并不真正以我们通常习惯的异步方式工作,我们提出的解决方案是使用 task.

将此添加到 plugins/index.js:

const https = require("https");
const { URL } = require("url");

/**
 * @type {Cypress.PluginConfig}
 */
module.exports = (on, config) => {
    require('@cypress/code-coverage/task')(on, config)

    on("task", {
        async waitForServerResponse({ server_url }) {
            function sleep(ms) {
                return new Promise(resolve => setTimeout(resolve, ms));
            }
            function makeRequest({ hostname, port, path }) {
                return new Promise((resolve, reject) => {
                    const options = {
                        hostname,
                        port,
                        path,
                        body: {},
                        method: 'POST'
                    }
                    const req = https.request(options, response => {
                        response.on('data', d => {
                            resolve(d.toString());
                        });
                    });
                    req.on('error', error => {
                        reject(error);
                    });
                      
                    req.end();
                });
            }
            async function recursiveGet(retry = 1) {
                try {
                    const res = await makeRequest({ hostname, port, path });
                    if (res?.code?.includes("ECONNREFUSED") || res?.code?.includes("ECONNRESET")) {
                        await sleep(1000);
                        await recursiveGet(retry + 1);
                    }
                }
                catch(error) {
                    if (error?.code?.includes("ECONNREFUSED") || error?.code?.includes("ECONNRESET")) {
                        await sleep(1000);
                        await recursiveGet(retry + 1);
                    }
                }
            }
            if (!server_url) {
                server_url = config.baseUrl;
            }
            const parsedUrl = new URL(server_url);

            const hostname = parsedUrl?.hostname ?? "localhost";
            const port = parsedUrl?.port ?? 443;
            const path = parsedUrl?.pathname ?? "/";
            return new Promise(async (resolve, reject) => {
                // tasks should not resolve with undefined
                setTimeout(() => reject(new Error("Timeout")), 60000);
                await recursiveGet();
                resolve(true);
            });
        }
    });

    return config;
};

并在你的测试中调用它:

it("Restarts the server", () => {
    // Restart the server
    cy.systemRestart().then(({ body, status }) => { // server returns success before actually restarting
        expect(status).to.equal(200);
        expect(body).property("success").to.eq(true);

        cy.wait(1000); // initial wait 
        cy.task("waitForServerResponse", { server_url: server_url + "/auth/token" });

        cy.login();
        cy.adminIndex().then(({ body, status }) => {
            if (body?.properties) {
                expect(status).to.equal(200);
                expect(body).property("properties").to.be.a("object");
                const bootedAt = new Date(body.properties.system.bootedAt).getTime();
                const now = new Date().getTime();
                const diff = Math.ceil(Math.abs(now - bootedAt) / 1000); // ms -> s
                expect(diff).to.be.lessThan(20); // seconds
            }
        });
    });
});

这将轮询服务器(任何给定端点,我选择了 /auth/token),如果连接被拒绝或重置,它将等待 1 秒并重试。该任务只会在收到服务器响应后 return。