编程

使用 Playwright 自动进行端到端测试

275 2024-09-28 01:54:00

1. 概述

端到端测试是确定软件产品整体情况的重要方式之一。它有助于发现在单元和集成测试阶段可能未被注意到的问题,并有助于确定软件是否按预期工作。

执行可能包括多个用户步骤和路程的端到端测试是乏味的。因此,一种可行的方法是对端到端测试用例进行自动化测试。

本文中,我们将学习如何使用 Playwright 和 TypeScript 自动化端到端测试。

2. 什么是 Playwright 端到端测试?

Playwright 端到端测试是帮助开发人员和测试人员模拟真实用户与网站交互的过程。有了 Playwright,我们可以自动化点击按钮、填写表单和浏览页面等任务,以检查一切是否按预期运行。它适用于 Chrome、Firefox、Safari 和 Edge 等流行浏览器。

3. Playwright 端到端测试先决条件

要使用 Playwright,请安装 NodeJS 版本18 或更高版本以及 TypeScript。安装 Playwright 有两种方法:

  • 使用命令行
  • 使用 VS Code

本文中我们将使用 VS Code 来安装 Playwright。

  1. 从 VS Code 市场安装 Playwright 后,让我们打开命令面板并运行命令 Install Playwright
  2. 然后安装浏览器。点击 OK:
  3. 安装后,我们将在 package.json 文件中获得包含依赖项的文件夹结构:

4. 如何使用 Playwright 进行端到端测试?

端到端测试涵盖了最终用户理想遵循的用例。假设我们为 LambdaTest eCommerce Playground 编写端到端测试。

我们将使用基于云的测试平台,如 LambdaTest,以使端到端测试具有的更大可扩展性和可靠性。LambdaTest 是一个基于人工智能的测试执行平台,它使用 Playwright 在 3000 多种真实浏览器和操作系统上提供自动化测试。

4.1. 测试场景 1

  1. 在 LambdaTest eCommerce Playground 网站上注册新用户。
  2. 执行断言以检查用户是否已成功注册。

4.2. 测试场景 2

  1. 执行断言以检查用户是否已登录。
  2. 在主页上搜索产品。
  3. 选择产品,并将其添加到购物车。
  4. 执行断言以检查是否将正确的产品添加到购物车中。

4.3. 测试配置

让我们通过重写  storageState fixture 创建一个 fixture 文件,对每个 Worker 进行一次身份验证。我们可以使用 testInfo.parallelIndex 来区分不同 worker。

此外,我们可以使用同一个的 fixture 文件来配置 LambdaTest 功能。现在,让我们创建一个名为 base 的新文件夹和一个新文件 page-object-model-fiture.ts

第一个代码块包含来自其他项目目录的 npm 包和文件的导入语句。我们将导入 expectchromiumtest 作为 baseTest 变量,并使用 dotenv 获取环境变量。然后,我们直接在 fixture  文件中声明页面对象类实例并进行测试。

下一个模块涉及添加 LambdaTest 功能:

const modifyCapabilities = (configName, testName) => {
    let config = configName.split("@lambdatest")[0];
    let [browserName, browserVersion, platform] = config.split(":");
    capabilities.browserName = browserName;
    capabilities.browserVersion = browserVersion;
    capabilities["LT:Options"]["platform"] =
        platform || capabilities["LT:Options"]["platform"];
    capabilities["LT:Options"]["name"] = testName;
};

我们可以使用 LambdaTest 能力生成器轻松生成这些能力。下一行菜吗将通过自定义和创建项目名称来使用 LambdaTest 功能。理想情况下,项目名称是浏览器、浏览器版本和平台名称的组合,可以以 chrome:latest:macOS Sonoma@lambdatest 格式:

projects: [
    {
        name: "chrome:latest:macOS Sonoma@lambdatest",
        use: {
            viewport: {
                width: 1920,
                height: 1080,
            },
        },
    },
    {
        name: "chrome:latest:Windows 10@lambdatest",
        use: {
            viewport: {
                width: 1280,
                height: 720,
            },
        },
    },

下一个代码块被分为两部分。在第一部分中,声明了 testPages 常量变量并将其分配给扩展最初在 fixture 文件中声明的 page 类型的 baseTest 以及 workerStorageState

const testPages = baseTest.extend<pages, { workerStorageState: string; }>({
    page: async ({}, use, testInfo) => {
        if (testInfo.project.name.match(/lambdatest/)) {
            modifyCapabilities(testInfo.project.name, `${testInfo.title}`);
            const browser =
                await chromium.connect(
                    `wss://cdp.lambdatest.com/playwright?capabilities=
                    ${encodeURIComponent(JSON.stringify(capabilities))}`
                );
            const context = await browser.newContext(testInfo.project.use);
            const ltPage = await context.newPage();
            await use(ltPage);

            const testStatus = {
                action: "setTestStatus",
                arguments: {
                    status: testInfo.status,
                    remark: getErrorMessage(testInfo, ["error", "message"]),
                },
            };
            await ltPage.evaluate(() => {},
                `lambdatest_action: ${JSON.stringify(testStatus)}`
            );
            await ltPage.close();
            await context.close();
            await browser.close();
        } else {
            const browser = await chromium.launch();
            const context = await browser.newContext();
            const page = await context.newPage();
            await use(page);
        }
    },

    homePage: async ({ page }, use) => {
        await use(new HomePage(page));
    },
    registrationPage: async ({ page }, use) => {
        await use(new RegistrationPage(page));
    },
});

在代码块的第二部分中,设置 workerStorageState,每个并行 worker 都在其中进行一次身份验证。所有测试都使用一个 worker 运行的相同身份验证状态:

storageState: ({ workerStorageState }, use) =>
    use(workerStorageState),
    
workerStorageState: [
    async ({ browser }, use) => {
        const id = test.info().parallelIndex;
        const fileName = path.resolve(
            test.info().project.outputDir,
            `.auth/${id}.json`
        );
    },
],

每个 worker 将使用 worker 作用域的 fixture 进行一次身份验证。我们需要通过复位存储状态来确保在干净的环境中进行身份验证:

const page = await browser.newPage({ storageState: undefined });

接下来,应在 fixture 文件中更新身份验证过程。它包括用户注册步骤,如测试场景 1 中所述。

4.4. 实现:测试场景 1 

首先,我们将创建两个页面对象类,以持有用于与每个页面元素交互的定位器和函数。让我们在 tests 文件夹中创建一个名为 pageobjects 的新文件夹。第一个页面对象类将用于主页:

import { Page, Locator } from "@playwright/test";
import { SearchResultPage } from "./search-result-page";

export class HomePage {
    readonly myAccountLink: Locator;
    readonly registerLink: Locator;
    readonly searchProductField: Locator;
    readonly searchBtn: Locator;
    readonly logoutLink: Locator;
    readonly page: Page;

    constructor(page: Page) {
        this.page = page;
        this.myAccountLink = page.getByRole("button", { name: " My account" });
        this.registerLink = page.getByRole("link", { name: "Register" });
        this.logoutLink = page.getByRole("link", { name: " Logout" });
        this.searchProductField = page.getByPlaceholder("Search For Products");
        this.searchBtn = page.getByRole("button", { name: "Search" });
    }

    async hoverMyAccountLink(): Promise<void> {
        await this.myAccountLink.hover({ force: true });
    }

    async navigateToRegistrationPage(): Promise<void> {
        await this.hoverMyAccountLink();
        await this.registerLink.click();
    }
}

在主页上,我们首先需要将鼠标悬停在“My account”链接上打开菜单下拉列表,然后单击注册链接打开注册页面:

在 Chrome 的“开发者工具”窗口中,“My account” Web 元素的角色是一个按钮。因此,让我们使用以下代码定位此链接:

this.myAccountLink = page.getByRole("button", { name: " My account" });

让我们将鼠标悬停在 MyAccountLink 上,打开下拉菜单进行查看,然后单击注册链接:

async hoverMyAccountLink(): Promise<void> {
    await this.myAccountLink.hover({ force: true });
}

必须定位并单击注册链接才能打开注册页面。我们可以在 Chrome 开发者工具中注意到 registerLink 定位器;该 Web 元素的的就是注册的链接:

以下功能将悬停在 MyAccountLink 上,当下拉菜单打开时,它将定位并单击 registerLink

async navigateToRegistrationPage(): Promise<void> {
    await this.hoverMyAccountLink();
    await this.registerLink.click();
}

让我们为注册页面创建第二个页面对象类,它将包含执行交互的所有字段和函数:

async registerUser(
    firstName: string,
    lastName: string,
    email: string,
    telephoneNumber: string,
    password: string
): Promise<MyAccountPage> {
    await this.firstNameField.fill(firstName);
    await this.lastNameField.fill(lastName);
    await this.emailField.fill(email);
    await this.telephoneField.fill(telephoneNumber);
    await this.passwordField.fill(password);
    await this.confirmPassword.fill(password);
    await this.agreePolicy.click();
    await this.continueBtn.click();

    return new MyAccountPage(this.page);
}

我们可以使用 getByLabel() 函数来定位字段,然后创建 registerUser() 函数进行交互并完成注册。

让我们创建 my-account-page.ts 用来 header 断言,并更新注册场景的 fixture 文件。我们将使用 navigateToRegistrationPage() 访问注册页面并断言注册帐户标题。然后,我们将使用 register-user-data.json 中的数据从 RegistrationPage 类中调用 registerUser()

注册后,我们将断言检查页面标题 “Your Account Has Been Created!” 是否在 “ My Account” 页面上可见。

4.5. 实现: 测试场景 2

我们将在第二个测试场景中添加一个产品,并验证购物车详细信息是否显示了正确的值。

第一个断言检查用户是否已登录。它通过用鼠标悬停在 MyAccountLink 上并检查 Logout 链接是否在菜单中可见来实现。

现在,我们将使用主页上的搜索框搜索产品。

我们将通过在搜索框中键入值并单击搜索按钮来搜索 iPhone。searchForProduct() 函数将帮助我们搜索产品并返回 SearchResultPage 的新实例:

const searchResultPage = await homePage.searchForProduct("iPhone");
await searchResultPage.addProductToCart();

搜索结果将显示在 searchResultPage 上。addProductToPart() 函数将鼠标悬停在搜索结果中检索到的页面上的第一个产品上。当鼠标悬停在产品上时,它将单击 “Add to Cart” 按钮。

将出现一个通知弹出窗口,显示确认文本:

await expect(searchResultPage.successMessage).toContainText(
    "Success: You have added iPhone to your shopping cart!"
);
const shoppingCart = await searchResultPage.viewCart();

要确认购物车中有产品,请先在弹出窗口中确认文本,然后单击 viewCart 按钮导航到购物车页面。

通过断言最终验证了购物车中存在名为 iPhone 的产品,确认了所搜索产品已成功添加:

await expect(shoppingCart.productName).toContainText("iPhone");

4.6. 测试执行

以下命令将会在本地机器的 Google Chorme 上运行测试:

$ npx playwright test --project=\"Google Chrome\"

以下命令将在 LambdaTest 云网格上的 macOS Sonoma 上的最新 Google Chrome 版本上运行测试:

$ npx playwright test --project=\"chrome:latest:macOS Sonoma@lambdatest\"

让我们在 package.json 文件的 “scripts” 代码块中更新相应的命令:

"scripts": {
    "test_local": "npx playwright test --project=\"Google Chrome\"",
    "test_cloud": "npx playwright test --project=\"chrome:latest:macOS Sonoma@lambdatest\""
}

如果你想要本地运行测试,请运行如下命令:

$ npm run test_local

要在 LambdaTest 云网格上运行测试,请允许如下命令: 

$ npm run test_cloud

测试执行后,我们可以在 LambdaTest Web Automation 仪表板和构建详细信息窗口中查看测试结果:

构建详细信息屏幕提供平台、浏览器名称及其相应版本、视频、日志、执行的命令以及运行测试所需的时间等信息。

5. 结语

Playwright 是一个轻量级且易于使用的测试自动化框架。开发人员和测试人员可以使用多种编程语言轻松配置它。

将 Playwright 与 TypeScript 结合使用更加灵活和简单,因为我们不必为配置和设置编写太多的样板代码。我们需要运行一个简单的安装命令,然后就能立即开始编写测试。