Playwright 小技巧 #4

2024-12-13   出处: martioli  作/译者:Adrian Maciuc/ 溜的一比

继续这一系列的小技巧,在 #1、#​2 和 #3 大获成功后,迎来了我们的最新内容 #4。希望大家喜欢,别忘了也要阅读代码片段中的注释。

  1. 如何拦截相同路由的多个请求?

我记得在 Cypress 中,这个功能非常容易实现,但在 Playwright 中,要找到这种特定场景的详细信息并不那么简单。想象一下,你在浏览一个 Web 应用时,想验证多个具有相同路由的请求。例如,当你打开产品页面时,执行了一个 API 调用到 api/id/1​ 来获取数据,然后稍后你需要拦截同样的 api/id/1​ 调用。如何拦截两次请求并验证它们的数据呢?

你可以尝试使用 waitForRequest()​,但有一个更通用的方式,这将为你打开更多可能性:

import { test } from "@playwright/test";

test("验证相同路由的多次调用", async ({ page }) => {
  const requests: string[] = [];

  // 使用 route 来区分网络请求
  await page.route("**/api/**", async (route) => {
    // 使用 url().includes 来过滤出你需要的特定调用
    if (route.request().url().includes("id/1")) {
      await route.continue();
      const response = await (await route.request().response())?.json();
      requests.push(response);
    } else await route.continue();
  });

  // 上面的代码相当于设置了一个监听器,放在触发网络调用之前
  await page.goto("www.yourapp.com");
  // 假设这一步触发了第一次 id/1 调用
  await product.click();
  // 这一步触发了第二次 id/1 调用
  await productExtraDetails.click();

  // 现在你可以等待它们并做任何需要的断言
  await expect(async () => {
    expect(requests.length).toBe(2);
  }).toPass({
    intervals: [1_000, 2_000, 5_000],
    timeout: 15_000,
  });
});

// 以下是一个额外步骤
// 因为有时候你的测试会比应用的响应更快完成,所以需要取消路由
test.afterEach("取消所有路由", async ({ page }) => {
  await page.unrouteAll({ behavior: "wait" });
});

这种场景最常见的用途是验证追踪器。

  1. 在断言元素数量之前,不要使用 .all()

假设你有一个选择器会返回多个元素,你想遍历这些元素并断言某些值。如果不先断言元素的数量,你的测试可能会出现误报。原因如下:

如果你有 3 个元素,并且执行了以下操作:

import { test } from "@playwright/test";

test("在 .all() 上的误报", async ({ page }) => {
  const expectedValues = ["a", "b", "c"];
  // 其他操作

  const allElements = await page.getByTestId(locator).all();

  for (const item of allElements) {
    await expect(item).toHaveAttribute("any-attribute", expectedValues);
  }
});

测试会按预期工作。然而,如果你的定位器找不到页面上的任何元素,测试仍然会通过。因为 .all()​ 在数量为零时不会报错,并且如果没有元素,forEach​ 也不会运行。

为了正确处理这个问题,你必须在迭代之前先断言元素的数量。记得使用自动重试的 toHaveCount​,而不是像 (allElements.length).toBe(3)​ 这样的代码。

import { test, expect } from "@playwright/test";

test("正确使用 .all()", async ({ page }) => {
  const expectedValues = ["a", "b", "c"];
  // 其他操作

  await expect(page.getByTestId(locator)).toHaveCount(5);
  const allElements = await page.getByTestId(locator).all();

  for (const item of allElements) {
    await expect(item).toHaveAttribute("any-attribute", expectedValues);
  }
});

即使是 Playwright 官方文档也警告过 .all()​ 的使用问题,但现在你知道一个具体场景,如果在使用 .all()​ 之前没有断言数量,会导致错误。

专业提示:在 Playwright 的 async/await​ 风格中,不推荐使用 forEach​。使用 for...of​ 可以避免问题。

  1. 在点击之前无需断言 toBeVisible

我觉得有必要说这个,因为我见到这种情况太多次了。大家习惯在与元素交互之前断言其可见性,这是在任何教程或课程中学到的一种行为,曾经是每个人必须学习的重要步骤。但是在 Playwright 中,有一个特定场景不需要这样做,那就是点击 (click()​) 操作。下面是 Playwright 在点击元素之前所做的事情:

现在这些被称为可操作性检查 (actionability checks​),它们不仅限于点击操作。

一个关键点是,不要将其与 toHaveText​ 的断言混淆。使用 toHaveText​ 时要小心,因为 toHaveText​ 不会先等待元素可见。

  1. 如何只运行我在开发的测试?

大多数关注 Playwright 更新的人已经知道这个功能,但仍然有部分人不知道你可以使用一个特殊的 CLI 命令,只运行你修改过的测试。

假设你执行了 git pull​,然后你创建了自己的分支来开始开发一些测试,但你修复了多个测试中的错误,甚至创建了多个新的测试。那么与其像这样运行测试:

npx playwright test path/to/test1.spec.ts path/to/test2.spec.ts path/to/test3.spec.ts path/to/test4.spec.ts path/to/test5.spec.ts path/to/test6.spec.ts path/to/test7.spec.ts

你可以这样做:

npx playwright test --only-changed=main

这将对比你的分支与主分支,并运行在你的分支中被修改过的所有测试。

  1. 如何在 Playwright 中验证表格数据?

假设你有一个表格,需要验证渲染的值。这里不是验证某个文本是否存在于任意单元格中,而是验证某个值是否出现在指定列和指定行的精确单元格中。

你可以通过创建一个辅助函数来实现这个目标。为了方便大家自己运行下面的代码进行测试,我将所有内容都放在了一个文件中,当然你可以按照自己的方式存储工具/辅助函数。

这个辅助方法如何工作呢?你给它三个参数,第一个是表头的文本(列),第二个是你要验证的行,第三个是你想要的单元格中的期望文本。

import { test, expect } from "@playwright/test";

class WebTableHelper {
  constructor(page) {
    this.page = page;
  }

  async validateCellValueReferenceToHeader(
    headerText,
    rowToValidate,
    expectedValue
  ) {
    const elementsOfHeader = await this.page
      .getByRole("table")
      .getByRole("row")
      .first()
      .getByRole("cell")
      .all();

    const elementsOfNRow = await this.page
      .getByRole("table")
      .getByRole("row")
      .nth(rowToValidate)
      .getByRole("cell")
      .all();

    for (let i = 0; i < elementsOfHeader.length; i++) {
      const headerValueFound = await elementsOfHeader[i].innerText();
      if (headerValueFound.toLowerCase() === headerText.trim().toLowerCase()) {
        const cellValue = await elementsOfNRow[i].innerText();
        expect(cellValue).toBe(expectedValue);
        return;
      }
    }
    throw new Error(`未找到表头文本 "${headerText}"`);
  }
}

test("测试表格", async ({ page }) => {
  await page.goto("https://cosmocode.io/automation-practice-webtable");
  const helper = new WebTableHelper(page);
  await expect(page.getByRole("table").getByRole("row")).toHaveCount(197);
  await helper.validateCellValueReferenceToHeader("Currency", 4, "Euro");
});

可能还需要一些数据清理和修剪,具体取决于表格中的数据。但对于大多数情况,这个方法可以开箱即用。

最初的代码其实是由 Srdan Mutlak 创建的。所以,功劳归于他。他挑战我创建类似的东西,他先给了我他的代码版本,然后我尝试用自己的风格实现。

现在我也来挑战你。你能让它变得更好吗?创建一个 Github gist 并在评论中写下你的想法。

  1. 如何在 Playwright 中获取元素的背景色?

我看到很多例子中,人们会用类似下面的代码来获取背景颜色:

const color = await btn.evaluate((element) =>
  window.getComputedStyle(element).getPropertyValue("background-color")
);
expect(color).toBe("rgb(69, 186, 75)");

但其实不需要再这样做了,我们可以直接用:

await expect(locator).toHaveCSS('background-color',"rgb(69, 186, 75)");

声明:本文为本站编辑转载,文章版权归原作者所有。文章内容为作者个人观点,本站只提供转载参考(依行业惯例严格标明出处和作译者),目的在于传递更多专业信息,普惠测试相关从业者,开源分享,推动行业交流和进步。 如涉及作品内容、版权和其它问题,请原作者及时与本站联系(QQ:1017718740),我们将第一时间进行处理。本站拥有对此声明的最终解释权!欢迎大家通过新浪微博(@测试窝)或微信公众号(测试窝)关注我们,与我们的编辑和其他窝友交流。
/63 人阅读/0 条评论 发表评论

登录 后发表评论
最新文章