Cypress.io如何测试多账户之间的真实互动? or How Cypress.io Tests Real Interaction between Multiple Accounts?
背景
cypress.io 是个极好的东西。尤其是用 cypress.io 来进行端到端测试。 不过,凡事也都有其局限性。cypress.io 目前只支持单实例运行同一个测试文件。 而且,官方固执的认为,你不需要一次性开启多个浏览器实例来测试交互。 在众多开发者提出的疑问里面,巴拉巴拉一大堆,最后都是不了了之。
以上官方不支持多浏览器实例的理由
而且,它的请求和响应是被封装为自动完成的。也就是说,你企图在实例内发出的请求, 其返回的头信息中包含的 cookie 都是自动被设置到实例中的。 因为它这样的封装,导致我们无法同时登录两个账户并完成交互操作。
办法
-
用 cy.task() 方法在后台开启一个单独的 node 任务,直接发请求给后端。 以达到通过后台登录和操作其他账户的目的。比如将 axios 封装到一个 task 的监听事件。 这样就可以在测试实例中调用这些事件,从而在后台发起独立于测试实例的请求。
-
启动多个 cypress.io 服务,分别运行不同账户的对应测试文件。 比如依次启动两个 cypress.io 的服务,一个监听 8080,一个监听 8081.
-
借助 webdriver 或者 puppeteer 来实现,也是封装相应的调用为 task 事件监听。
实现 (VUE 项目为例)
关于上述三种方法的具体实现,下面以 vue 项目为例,大致说一下前两种。至于第三种,请自行搜索其他参考。
第一种方案:
编写针对 task 的 plugin,目录如下

index.js 代码如下
const request = require('./request');
module.exports = (on, config) => {
// 注册 request.js 中的事件监听方法
on('task', {
doGet: request.doGet,
doPost: request.doPost,
});
return Object.assign({}, config, {
fixturesFolder: 'tests/e2e/fixtures',
integrationFolder: 'tests/e2e/specs',
screenshotsFolder: 'tests/e2e/screenshots',
videosFolder: 'tests/e2e/videos',
supportFile: 'tests/e2e/support/index.js',
});
};
request.js 代码如下
const axios = require('axios'); // 引入 axios
const users = require('../fixtures/user'); // 请先在 fixtures 中建立用户列表
// ------------------------------------------------------------------------------
const TEMP = {};
const base = { url: '/', baseURL: '你的基本url地址' };
// ------------------------------------------------------------------------------
// check exists
const hasWho = who => Object.hasOwnProperty.call(TEMP, who);
// ------------------------------------------------------------------------------
// create http
const createHttp = (who) => {
if (!hasWho(who)) {
throw new Error(`user ${who} not logined yet!`);
}
let Cookie = '';
TEMP[who].forEach((cookie) => {
Cookie += `${cookie.split(' ')[0]} `;
});
return Object.assign({}, base, { headers: { Cookie: Cookie.trim() } });
};
// ------------------------------------------------------------------------------
// doing login
const doLogin = async (who) => {
const user = users[who];
if (!user) { throw new Error(`user ${who} not found!`); }
if (hasWho(who)) { return createHttp(who); }
// 请修改为你自己的登录参数
const params = {
password: user.password,
useremail: user.username,
};
const result = await axios.create(base).post('你的登录API地址', params);
TEMP[who] = result.headers['set-cookie'];
return createHttp(who);
};
// ------------------------------------------------------------------------------
// doing get request
const doGet = async ({ url, who = null }) => {
let options = null;
if (who) { options = await doLogin(who); }
const result = await axios.get(url, options);
return {
data: result.data,
status: result.status,
statusText: result.statusText,
headers: result.headers,
};
};
// ------------------------------------------------------------------------------
// doing post request
const doPost = async ({ url, data, who = null }) => {
let options = null;
if (who) { options = await doLogin(who); }
const result = await axios.post(url, data, options);
return {
data: result.data,
status: result.status,
statusText: result.statusText,
headers: result.headers,
};
};
// ------------------------------------------------------------------------------
/**
* @type {{doPost(*=): *, doGet(*=): *}}
*/
module.exports = { doGet, doPost };
测试用例中调用方式
it('测试第二个账户', () =>
{
cy.task('doGet', {
who: 'zhangsan', // 在 fixtures/user.json 中配置
url: '你要请求的API地址',
}).then((data) => {
console.log(data);
});
});
tests/e2e/fixtures/user.json示例
{
"zhangsan": {"username": "[email protected]", "password": "123456"},
"lisi": {"username": "[email protected]", "password": "123456"},
}
第二种方案:
我假定你正常运行时,项目的端口地址是 8080。 下面的步骤是让你启动另一个 Cypress.io 服务,并运行在 8081 下面。
- 在 vue 项目的根目录,建一个叫做 .env.port 的文件。在文件中加入以下代码
NODE_ENV=development
VUE_APP_PORT=8081
- 在项目根目录的 vue.config.js 中进行以下修改
// 根据环境变量,决定端口号
module.exports = {
devServer: {
port: process.env.VUE_APP_PORT || 8080,
public: '你的域名' + (process.env.VUE_APP_PORT || 8080),
}
};
- 如果在 vue 代码中的url地址也有端口号的,同样以相同的方式进行判断
process.env.VUE_APP_PORT || 8080
- 修改根目录 package.json
"scripts": {
"test-cypress1": "vue-cli-service test:e2e",
"test-cypress2": "vue-cli-service test:e2e --mode port",
}
- 依次启动两个 cypress.io 服务
> npm run test-cypress1
> npm run test-cypress2
- 注意不同的 Cypress.io 服务尽量不要使用相同的浏览器,避免冲突导致无法运行测试。
可以一个选择 Chrome,一个选择 Electron. 如下如图


第三种方案

参见各种 issues 里面官方和民间的扯皮。 https://github.com/cypress-io/cypress/issues/1207 https://github.com/cypress-io/cypress/issues/590 Multiple browsers open at the same time
官方文档关于多浏览器的说明
基本上官方的说法就是,你爱用不用,有本事你自己写一个...

这个问题确实让人很恼火,官方建议你把跨账户交互写成了单元测试。 我TM是要进行 e2e 测试啊,各种 stub,mock,以及用别家的东西来辅助。 那还不如直接用别家的东西啊。怪自己能力有限啊...
如果大家有什么更简单有效的疗法,欢迎补充!