我正在尝试使用基于模型的测试方法,将 xstate 与 playwright 集成。我们的系统使用一个工作流程,其中 2 个用户可以登录系统并响应彼此的操作以完成工作流程。
当我尝试使用同一浏览器注销并再次登录时,浏览器以第一个用户身份重新登录,就好像当我注销并默认返回到它时无法清除经过身份验证的会话,即使我输入了不同用户的详细信息
添加一个在测试之间完全关闭浏览器的 afterEach 似乎是单次登录测试的一个解决方案,因为这将清除所有 cookie,并且我能够完成第二个测试,以第二个用户身份登录,但是这很快又失败了,因为我没有在各个测试步骤之间的不同登录之间关闭浏览器。
问题是我找不到能够控制/引用浏览器或在测试模型中打开多个浏览器的方法,因为浏览器实例是稍后在测试挂钩中创建的。我想找到一种方法,能够在步骤之间关闭并重新打开浏览器窗口,最好将其合并到我的登录/注销功能中,或者当我注销以正确清除会话时,我可能会错过一些步骤,然后使用新用户重新登录
下面是我的钩子,然后是 xstate 用于运行测试的内容,位于我的脚本底部
`test.beforeEach(async ({ browser }) => {
page = await browser.newPage();
await page.goto(URL);
testNum++
});
test.afterEach(async ({browser}) => {
for(let context of browser.contexts())
await context.close()
});
test.describe('CE workflows', () => {
const testPlans = testModel.getSimplePathPlans();
testPlans.forEach((plan, i) => {
plan.paths.forEach((path, i) => {
test(path.description, async ({}) => {
await path.test(page);
});
});
});
})`
这些是我在每个测试步骤中使用的登录/注销功能
`async function login(email) {
await page.goto(URL);
await page.getByTestId('loginEmail').fill(email);
await page.getByTestId('loginPassword').fill('password');
await page.getByTestId('btnLogin').click();
if (await page.getByRole('button', { name: 'Allow cookies' }).isVisible())
await page.getByRole('button', { name: 'No thanks' }).click();
}
async function logout() {
await page.getByTestId('btnOptions').click();
await page.getByRole('button', { name: 'Logout' }).click();
}`
这是我模型中的第一个边 每个步骤都与此类似,即登录,执行用户操作,然后注销,准备传递给第二个用户
`const testModel = createModel(CEWorkflowMachine, {
events: {
CREATE_CE_CLIENT: async () => {
await login()
await page.goto(URL);
await page.getByRole('link', { name: 'Create compensation event' }).click();
await page.getByLabel('Title').fill(`Test for model based CE ${testNum}`);
await page.getByLabel('Description').fill('Test Description');
await page.getByLabel('Project manager\'s assumptions').fill('Test Assumption');
await page.getByText('The Project Manager gives an').click();
await page.getByText('Notify').click();
await logout()
},`
这里很少有标志,其中一个或多个可能会导致这种行为。最终,如果它以以前的用户身份登录,则您会遇到测试隔离或某些异步/执行顺序问题。
混乱的测试隔离
您当前执行此操作是为了管理浏览器页面:
test.beforeEach(async ({ browser }) => {
page = await browser.newPage();
await page.goto(URL);
testNum++
})
这意味着
page
是范围内可用的变量。如果它不是由 let page
定义的,情况会更糟,因为这意味着它实际上完全是全球性的。
即使不是全局的,保留可变引用也会带来麻烦。如果您有文件内并行化或想稍后添加它,您将会遇到问题。添加您可能会意外引用此变量;或用封口将其封闭;考虑到很多事情都是异步的,你可以得出结论,这是非常危险的,管理起来也很糟糕。
虽然有可能拥有这一点并完美地管理它,但您正在向一整类现在可能出现的错误敞开大门。
page
附加到浏览器上下文,这是剧作家提供的用于隔离测试的构造。如果你有机会在测试之间重用它,那就有问题了。
我强烈怀疑您的情况很可能如此。状态机正在“关闭”您设置的
page
变量。即使您重新分配 page
,它们仍然会引用旧的 page
对象引用。
您应该完全废弃这个范围广泛的
page
var。相反,请确保您使用 page
传递给测试处理程序/挂钩:
test.beforeEach(async ({ page }) => {
await page.goto(URL);
});
test.describe('CE workflows', () => {
const testPlans = testModel.getSimplePathPlans();
testPlans.forEach((plan, i) => {
plan.paths.forEach((path, i) => {
test(path.description, async ({page}) => {
await path.test(page);
});
});
});
})`
如果您的测试被正确隔离,您不需要手动关闭浏览器。所以我删除了它。提供给
page
方法(和钩子)的 test
已经自动确定了相关测试的范围。确保将此传递到任何共享方法,并且您没有在任何地方使用全局或广泛范围的页面。
这意味着您的状态机将需要提供页面。可能那些机器需要通过它。请注意,这是官方文档中的做法。
无需致电
newPage
,因为这已经发生了。
登录/注销不等待
当您在注销和登录按钮上等待
click()
时,单击该按钮后就会解决该问题。它不会自动等到下一页。
您的登录情况没问题,因为之后它当然会等待与登录页面进行交互。
但是对于注销,取决于流程是否复杂或较长,这意味着它单击注销按钮并立即结束测试并杀死浏览器上下文。注销可能没有时间完成。