在 Playwright 中处理不同测试间的多种登录状态
· 2,155 词 · 11 分钟 读完 playwright进阶 翻译
本周,我受到了与同事 Joel 的对话以及在 Discord 频道 中被提到的问题的启发,写下了如何在 Playwright 项目中处理不同登录状态的文章。
我按照 https://playwright.dev/docs/auth 中的文档将“已登录”状态存储在 JSON 文件中。接下来的测试一切顺利,直到我测试注销功能的那一步。在注销测试场景之后,接下来的测试中用户的登录态没了!!。为什么会这样?请帮我理解并修复它。谢谢!
注意:有许多不同的方法可以解决这个问题,我将介绍我选择的解决方法,这取决于我正在测试的 web 应用程序。如果你有更简单或更健壮的方法来解决这个问题,请与我联系,我很想听听你的意见!
注意 2:我在下面测试用例中的断言是不太完美的。我在这里投入了最少的时间,以突出 sessionStorage
和测试设置。
探索我们将要测试的网站
但在开始之前,我们必须了解我们将要编写自动化测试的网站。我们将使用 https://practicesoftwaretesting.com/ 作为我们的测试系统(有关该站点的更多详细信息,请参阅 GitHub 项目页面。
在我的探索性测试过程中,我发现管理员 auth-token
在登录时生成,并且可以在同一浏览器的不同标签页中用于保持认证状态。如果我打开一个新的隐身窗口,会话不会激活,我没有登录,但我可以使用相同的用户名和密码创建第二个已认证会话。这两个会话可以同时存在。一旦我使用注销功能,我发现与之对应的 auth-token
现在失效了,而另一个 auth-token
仍然可以使用。这告诉我,当我要编写验证注销功能的测试时,直接依赖于当前登录用户的测试是行不通的。
这告诉我,我们正在测试的应用程序具有良好的安全性实践。当我们从系统注销时,auth-token
会失效。因此,当我们创建注销测试时,可能应该避免使用我们的默认 auth-token
。
在 Playwright 中创建一个设置项目
我使用的提交代码的仓库可以在下面找到。
使用 Playwright 测试 https://practicesoftwaretesting.com 示例 - GitHub
我们需要做的第一步是建立一个新的 setup project
。在下面的配置文件中,我们有两个项目,第一个是 setup
,它查找 *.setup.ts
文件并运行它们,第二个项目是 ui-tests
,它依赖于 setup
项目成功运行才能继续。有关 defineConfig 的更多信息,请参阅 playwright 文档中的 测试配置 部分。
// playwright.config.ts
import { defineConfig } from "@playwright/test";
import type { APIRequestOptions } from "./lib/fixtures/apiRequest";
import { TestOptions } from "./lib/pages";
require("dotenv").config();
export default (defineConfig < APIRequestOptions) &
(TestOptions >
{
projects: [
{ name: "setup", testMatch: /.*\.setup\.ts/, fullyParallel: true },
{
name: "ui-tests",
dependencies: ["setup"],
},
],
testDir: "./tests",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [["html"], ["list"]],
use: {
testIdAttribute: "data-test",
baseURL: process.env.UI_URL,
apiURL: process.env.API_URL,
apiBaseURL: process.env.API_URL,
trace: "on",
},
});
添加我们的设置脚本
如你所见,auth.setup.ts
依赖于我在此文件(.env 和 LoginPage)下的一些代码块。因为这个设置文件被设置为一个项目,我们可以访问重命名为 setup
的测试块,以表明这些是初始化步骤而不是真正的测试。
auth.setup.ts
文件的前半部分为不同的电子邮件、密码和文件名设置变量。这些变量在每个设置块中使用。分解实际的 setup
步骤,包括:
- 创建一个 LoginPage 类,以便我们可以利用页面对象
- 访问登录页面
- 使用登录异步函数,传入指定的电子邮件和密码
- 验证用户已登录
- 将
storageState
保存到指定文件
在三个不同用户(管理员、customer01 和 customer02)的三个设置块中重复这些步骤。
另一个需要注意的是,我在 playwright.config.ts
中设置了 fullyParallel: true
,这将在多个工作线程中运行时同时运行每个 setup 步骤。这将有助于加快 setup 步骤的速度。
// tests/auth.setup.ts
// 通过设置测试将你的存储状态保存到 .auth 目录中的文件
import { LoginPage } from "@pages";
import { test as setup, expect } from "@playwright/test";
let adminEmail = process.env.ADMIN_USERNAME;
let adminPassword = process.env.ADMIN_PASSWORD;
const adminAuthFile = ".auth/admin.json";
let customer01Email = process.env.CUSTOMER_01_USERNAME;
let customer01Password = process.env.CUSTOMER_01_PASSWORD;
const customer01AuthFile = ".auth/customer01.json";
let customer02Email = process.env.CUSTOMER_02_USERNAME;
let customer02Password = process.env.CUSTOMER_02_PASSWORD;
const customer02AuthFile = ".auth/customer02.json";
setup("Create Admin Auth", async ({ page, context }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login(adminEmail, adminPassword);
expect(await loginPage.navAdminMenu.innerText()).toContain("John Doe");
await context.storageState({ path: adminAuthFile });
});
setup("Create Customer 01 Auth", async ({ page, context }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login(customer01Email, customer01Password);
expect(await loginPage.navUserMenu.innerText()).toContain("Jane Doe");
await context.storageState({ path: customer01AuthFile });
});
setup("Create Customer 02 Auth", async ({ page, context }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login(customer02Email, customer02Password);
expect(await loginPage.navUserMenu.innerText()).toContain("Jack Howe");
await context.storageState({ path: customer02AuthFile });
});
首先要注意的是,我使用了 dotenv
包来管理存储在 .env
文件中的环境变量。
// .env
# URLS
UI_URL=https://practicesoftwaretesting.com
API_URL=https://api.practicesoftwaretesting.com
# Logins
CUSTOMER_01_USERNAME=customer@practicesoftwaretesting.com
CUSTOMER_01_PASSWORD=welcome01
CUSTOMER_02_USERNAME=customer2@practicesoftwaretesting.com
CUSTOMER_02_PASSWORD=welcome01
ADMIN_USERNAME=admin@practicesoftwaretesting.com
ADMIN_PASSWORD=welcome01
我还使用了 登录页面
的页面对象。这是从我在 tsconfig.json
文件中设置的 @pages
路径导入的。我在任何指南中都没有涵盖这一点,但计划很快写一些文章说明一下,现在只需知道它是一个不错的快捷方式,我可以使用它,而无需在导入中使用完整路径。在页面文件中,我们有一些定位器和两个方法。一个用于访问登录页面,另一个用于登录,需要传入电子邮件和密码作为变量。
这样做可以简化我的测试和设置文件。
// lib/pages/loginPage.ts
import { Page } from "@playwright/test";
export class LoginPage {
readonly username = this.page.getByTestId("email");
readonly password = this.page.getByTestId("password");
readonly submit = this.page.getByTestId("login-submit");
readonly navUserMenu = this.page.getByTestId("nav-user-menu");
readonly navAdminMenu = this.page.getByTestId("nav-admin-menu");
readonly navSignOut = this.page.getByTestId("nav-sign-out");
readonly navSignIn = this.page.getByTestId("nav-sign-in");
async goto() {
await this.page
.goto(`${process.env.UI_URL}/sign-in`);
}
async login(email: string, password: string) {
await this.username.fill(email);
await this.password.fill(password);
await this.submit.click();
await this.page.waitForTimeout(1000); // 添加一个短暂的延迟以确保登录过程完成
}
async logout() {
await this.navSignOut.click();
await this.navSignIn.waitFor({ state: "visible" });
}
}
从页面对象中,我们返回到 auth.setup.ts
文件。我们创建了三个用于认证的文件,这些文件会随着页面一起使用。
// auth.setup.ts
setup("Create Admin Auth", async ({ page, context }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login(adminEmail, adminPassword);
expect(await loginPage.navAdminMenu.innerText()).toContain("John Doe");
await context.storageState({ path: adminAuthFile });
});
一旦我们将 setup
设置为一个项目,我们就可以在 ui-tests
项目中使用这些存储的文件。我们可以在 playwright.config.ts
文件中将其作为存储状态的一部分提供给 context
对象。我们通过设置 storageState
为 authFile
路径中的 storageState
属性来实现。
// playwright.config.ts
export default defineConfig({
projects: [
{
name: "setup",
testMatch: /.*\.setup\.ts/,
fullyParallel: true,
},
{
name: "ui-tests",
dependencies: ["setup"],
use: {
storageState: ".auth/admin.json",
},
},
],
});
在此示例中,我们可以使用相同的方法为 ui-tests
项目提供任何 authFile
。authFile
由实际测试使用,并且可以在存储状态下的 use
属性中设置。
创建我们的测试
以下是我们需要的一个简单示例,展示如何在项目中实现这一点。
// tests/auth.spec.ts
import { test, expect } from "@playwright/test";
test("Verify that the authenticated user can log out", async ({ page }) => {
// 通过使用存储状态文件,登录用户的状态会自动加载到页面中
await page.goto("/");
// 断言用户已登录
await expect(page.getByTestId("nav-admin-menu")).toContainText("John Doe");
// 调用登出方法
const loginPage = new LoginPage(page);
await loginPage.logout();
// 断言用户已注销
await expect(page.getByTestId("nav-sign-in")).toBeVisible();
});
在此示例中,我们导入 test
和 expect
来自 @playwright/test
,并编写了一个简单的测试,展示了如何使用存储状态来验证用户是否已登录并能够注销。
总结
如你所见,我们有很多不同的方法可以用来在 Playwright 项目中处理不同的登录状态。通过创建一个 setup 项目,并在 ui-tests
项目中使用存储状态文件,我们能够有效地管理多个用户的登录状态。这样可以确保每个测试都在正确的登录状态下执行,并且在注销测试后,其他测试不会受到影响。
我们希望这篇文章对你有所帮助,并激发你在 Playwright 项目中处理类似问题的灵感。如果你有任何问题或建议,请随时与我们联系!
来源
URL 来源:https://playwrightsolutions.com/handling-multiple-login-states-between-different-tests-in-playwright/
发布时间:2023-07-17T12:30:51.000Z