Skip to content

Playwright 备忘录

Published:

安装

# 初始化安装
npm init playwright@latest
# 更新playwright
npm install -D @playwright/test@latest
# 安装浏览器
npx playwright install --with-deps

执行测试

# 运行 Playwright 测试套件中的所有测试
npx playwright test
# 在ui模式
npx playwright test --ui
# 运行测试并启用追踪功能
npx playwright test --trace on
# 展示最近一次测试运行的报告
npx playwright show-report
# 运行测试生成器
npx playwright codegen <url>

导航

// 导航到指定页面
await page.goto("https://juejin.cn/");

// 等待导航到指定页面 大多数情况可以省略 因为框架会自动适应
await page.waitForURL("**/frontend");

// 导航到指定页面,点击交互,但是页面打开新选项卡或窗口
const popupPromise = page.waitForEvent("popup");
await page.goto("https://www.baidu.com/");
await page.getByRole("link", { name: "新闻" }).click();
const newPage = await popupPromise;
await newPage.waitForLoadState("domcontentloaded");
console.log(await newPage.title());

定位元素

这些是推荐的内置定位器:

示例代码:

test("测试定位器 - 使用百度", async ({ page }) => {
  await page.goto("https://www.baidu.com/");
  await page.getByRole("link", { name: "新闻" }).focus();
  await page.getByText("换一换").click();
  await page.getByRole("link", { name: "登录" }).click();
  await page.getByLabel("阅读并接受").locator("visible=true").click();
  await page.getByPlaceholder("手机号/用户名/邮箱").fill("13111111111");
  await page.getByTitle("微信").click();
});

test("测试定位器 - 自定义页面", async ({ page }) => {
  await page.goto("http://127.0.0.1:5500/demo.html");
  await page.getByTestId("testid").click();
  expect(await page.getByTestId("testid").getAttribute("class")).toBe("red-bg");

  await page.getByAltText("test").click();
  expect(await page.getByAltText("test").getAttribute("style")).toContain(
    "border: 10px solid red"
  );
});

除了使用推荐的定位器,我们还可以直接使用page.locator结合 css 选择器进行定位。

示例代码:

test("测试locator定位器 - 自定义页面", async ({ page }) => {
  await page.goto("http://127.0.0.1:5500/demo.html");
  // 使用css选择器
  await page.locator(".test-class").click();
  // 使用id选择器
  await page.locator("#test-id").click();

  // 使用css选择器
  await page.locator("css=button").first().click();
  // 使用标签选择器
  await page.locator("button").first().click();
  // 使用has-text选择器
  await page.locator("button:has-text('submit')").click();
  // 使用text选择器
  await page.locator("button:text('submit')").click();
  // 使用后代选择器
  await page.locator(".btn-wrapper :has-text('submit')").click();

  // 使用nth选择器
  await page.locator(".btn-wrapper2 button").locator("nth=1").click();
  // 匹配可见元素
  await page.locator(".btn-wrapper2 button:visible").click();

  await page.waitForTimeout(5000);
});

交互

上面我们我们讲述了如何定位到元素,接下来我们需要了解元素的交互。

常见的交互有:

示例代码:

test("测试交互 - 自定义页面", async ({ page }) => {
  await page.goto("http://127.0.0.1:5500/demo.html");
  await page.locator(".interaction").getByRole("checkbox").check();
  await page.locator(".interaction").getByRole("checkbox").uncheck();

  await page.locator(".interaction").getByTestId("hover").hover();
  await page
    .locator(".interaction")
    .getByTestId("fill")
    .locator("input")
    .fill("test");
  await page
    .locator(".interaction")
    .getByTestId("fill")
    .locator("input")
    .focus();

  await page
    .locator(".interaction")
    .getByTestId("setInputFiles")
    .locator("input")
    .setInputFiles(path.join(__dirname, "test.txt"));

  await page
    .locator(".interaction")
    .getByTestId("selectOption")
    .locator("select")
    .selectOption("option2");

  await page.waitForTimeout(5000);
});

断言

断言用于验证选定元素的实际状态是否与预期结果相符。

常见的断言有:

test("测试断言 - 自定义页面", async ({ page }) => {
  await page.goto("http://127.0.0.1:5500/demo.html");

  // 自动重试断言 - 将重试,直到断言通过或达到断言超时
  // 容器是空的
  await expect(page.getByTestId("toBeEmpty")).toBeEmpty();
  // 元素可见
  await expect(page.getByTestId("toBeVisible")).toBeVisible();
  // 元素包含文本
  await expect(page.getByTestId("toContainText")).toContainText(
    "toContainText"
  );
  // 元素有属性
  await expect(page.getByTestId("toContainText")).toHaveAttribute(
    "data-testid",
    "toContainText"
  );
  // 元素有文本 - 需要准确匹配,可以使用正则
  await expect(page.getByTestId("toContainText")).toHaveText("toContainText");
  // 输入元素有值
  await expect(page.getByTestId("toHaveValue")).toHaveValue("test");

  // 不重试断言 - 允许测试任何条件,但不会自动重试
  expect(1 + 1).toBe(2);

  expect(1 + 1).toBeDefined();
  expect(1 + 1).toBeTruthy();
  expect(1 + 1).toBeGreaterThan(1);
  expect(1 + 1).toBeLessThan(3);

  // 否定匹配器 -通过在匹配器前面添加`.not`,预期相反的情况成立
  expect(1 + 1).not.toBeNull();
  expect(1 + 1).not.toBeUndefined();
  expect(1 + 1).not.toBeFalsy();
  expect(1 + 1).not.toBeNaN();
  await expect(page.getByTestId("toHaveValue")).not.toHaveValue("test1");

  // 软断言 - 断言失败不会抛出错误,而是记录在案,继续执行
  await expect.soft(page.getByTestId("toHaveValue")).toHaveValue("test1");
  console.log("++++++++++++++++++++first");
  // 软断言
});

鉴权

详细内容可以参考官方文档Authentication 部分。有点复杂。

我个人建议直接使用test.beforeAll/test.beforeEach。方便省事。

参考

附录

下面是教程用到的demo.html的代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>

    <style>
      * {
        font-size: 30px;
      }
      .red-bg {
        background-color: red;
      }

      [data-testid="hover"]:hover {
        color: red;
      }
    </style>
  </head>
  <body>
    <div>
      <div data-testid="testid" onclick="this.classList.toggle('red-bg')">
        <h1>TEST ID</h1>
      </div>

      <div>
        <img
          src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
          alt="test"
          style="width: 400px; height: 400px"
          onclick="this.style.border='10px solid red'"
        />
      </div>

      <div class="test-class" onclick="this.classList.toggle('red-bg')">
        test-class
      </div>
      <div id="test-id" onclick="this.classList.toggle('red-bg')">test-id</div>

      <div class="btn-wrapper">
        <button onclick="console.log('submit')">submit</button>
      </div>

      <div class="btn-wrapper2">
        <button onclick="console.log('button2')" style="visibility: hidden">
          button2
        </button>
        <button onclick="console.log('button1')">button1</button>
      </div>

      <div class="interaction">
        <label>
          多选框
          <input type="checkbox" />
        </label>

        <div data-testid="hover">hover</div>

        <div data-testid="fill">
          <input type="text" />
        </div>

        <div data-testid="setInputFiles">
          <input type="file" />
        </div>

        <div data-testid="selectOption">
          <select id="dropdown">
            <option value="option1">选项 1</option>
            <option value="option2">选项 2</option>
            <option value="option3">选项 3</option>
          </select>
        </div>
      </div>

      <div data-testid="toBeEmpty"></div>
      <div data-testid="toBeVisible">toBeVisible</div>
      <div data-testid="toContainText">toContainText</div>
      <input type="text" data-testid="toHaveValue" value="test" />
    </div>
    <div style="height: 500px"></div>
  </body>
</html>

Next Post
JavaScript模块化方案介绍