Headless Recorder:一种加快浏览器自动化的新工具

2020年12月30日11:13:14 发表评论 34 次浏览

本文概述

古老的谚语说, 只有一小部分的开发时间用于实际开发。虽然编写软件当然是很重要的事情, 但不应忽略文档或测试等领域, 尤其是后者可能会是一个沉重的负担, 并且会消耗大量开发人员时间。

当然, 这是自动测试派上用场的地方。虽然单元测试构成了坚实的基础, 但它们对内部变更既不是完全结论性的也不是可靠的。通常, 我们发现自己要么出于过度工程的目的而进行了更加健壮和结论性的测试, 要么不得不模仿太多的实现细节。

解决单元测试的许多陷阱的一种好方法是混合使用组件或集成测试。在这里, 执行完整的端到端测试的想法当然很有吸引力。

最后, 我们可以进行设置, 使我们获得80/20(覆盖率/工作量)分布。不幸的是, 端到端测试可能比单元测试还要脆弱, 并且需要更复杂的设置。更糟的是, 编写它们也可能会更困难。

这是无头记录仪的救援地点。

什么是无头录音机?

无头这个词通常表示缺少适当的前端或用户界面;而是使用了更多技术接口。为了无头录音机项目, 事实并非如此。在这里, 作者希望找到其应用程序中所有潜在目标的共同特征。我们稍后再讲。

首先, 让我们解决一下Headless Recorder的实际含义:这是一个小型工具, 可以在浏览器中使用它来记录网站上执行的所有操作。然后将这些操作转换为可与剧作家或木偶(实际上, Headless Recorder以前被称为Puppeteer Recorder)。

如果你还没有听说过这些工具, 那么它们就是浏览器自动化应用程序。它们用于对浏览器应该执行的操作进行编程, 例如单击链接或填写某些表格。

这已经阐明了为什么作者选择使用"无头"记录器的名称。它是用于所谓的浏览器自动化框架的记录器, 通常用于无头模式的浏览器, 即在没有UI的情况下运行。记录器实际上有一个(非常小的)UI。它带有用于暂停或修改录音的按钮。

该工具以浏览器扩展的形式出现, 可以是通过Chrome Webstore安装。安装后, 滚动起来非常容易。

我们为制作了一个自定义演示.
不完全是。点击这里查看.

Headless Recorder:一种加快浏览器自动化的新工具1

在以下各节中, 我们将使用Playwright作为我们选择的框架。

使用剧作家

剧作家是木偶戏的现代替代品。在大多数情况下, API方法几乎完全相同, 而在设计其他方法时, 其目标是不同的。

为了使Playwright运行, 我们需要一个现有的Node.js项目并将其安装为依赖项。

npm i playwright

看起来像:

无头记录仪安装

如我们所见, 该安装程序还提供了所有必需的浏览器驱动程序。其中包括Webkit, Chrome和Firefox。

使用Playwright的简单脚本可能如下所示:

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('http://whatsmyuseragent.org/');
  await page.screenshot({ path: `example.png` });
  await browser.close();
})();

尽管此脚本看起来简单易写, 但现实世界中的脚本又长又难于正确。这里的主要问题是稳定性-即使很少改变或时间变化, 也要指示工具可靠地工作。

Playwright之类的工具有多种用例。如最初所述, 编写端到端测试是其中之一。在这里, Playwright可以与Jest这样的测试框架结合起来, 以代表一种强大的方法, 可以应对任何端到端的测试挑战。

通用工作流程

现在, 我们对无头记录器及其在Playwright中的潜在目标有了一些了解, 我们可以看看如何正确使用它。

让我们从一个简单的任务开始:在Amazon上搜索商品并获取结果数。首先, 我们需要手动了解(并能够执行)此任务。否则, 任何使其自动化的尝试最终都会失败。

标识的步骤可能如下所示:

  1. 打开亚马逊页面
  2. 输入搜索文字
  3. 点击搜索
  4. 选择结果数

这些步骤听起来很简单, 但诀窍在于教Playwright如何做到这一点。幸运的是, 我们可以使用Headless Recorder在这里为我们提供帮助。

让我们重新开始并记录步骤。完成这四个步骤后, 我们停止录制并使用Playwright版本提取代码。

我们得到:

const { chromium } = require('playwright');
(async () => {
  const browser = await chromium.launch()
  const context = await browser.newContext()
  const page = await context.newPage()

  const navigationPromise = page.waitForNavigation()

  await page.goto('https://www.amazon.com/')

  await page.setViewportSize({ width: 1920, height: 969 })

  await navigationPromise

  await page.waitForSelector('#nav-search #twotabsearchtextbox')
  await page.click('#nav-search #twotabsearchtextbox')

  await page.waitForSelector('.nav-searchbar > .nav-right > .nav-search-submit > #nav-search-submit-text > .nav-input')
  await page.click('.nav-searchbar > .nav-right > .nav-search-submit > #nav-search-submit-text > .nav-input')

  await navigationPromise

  await page.waitForSelector('.s-desktop-width-max > .sg-col-14-of-20 > .sg-col-inner > .a-section > span:nth-child(1)')
  await page.click('.s-desktop-width-max > .sg-col-14-of-20 > .sg-col-inner > .a-section > span:nth-child(1)')

  await browser.close()
})()

尽管上面的代码不是很漂亮, 但它也存在一些问题:

  • 输入丢失
  • 一些选择器似乎非常脆弱
  • 的navigationPromise已使用多次, 但可能只产生一次效果
  • 仅单击结果, 未选择结果

好消息是, 生成的代码已经提供了增强的强大基础。

我们首先删除navigationPromise。这部分也可以通过Headless Recorder中的设置完成。接下来, 不只是使用点击在搜索文本框中, 我们将使用填搜索"无头"一词的功能。最后, 我们将点击通过获取结果文本textContent.

修改后的变体如下所示:

const { chromium } = require('playwright');
(async () => {
  const browser = await chromium.launch()
  const context = await browser.newContext()
  const page = await context.newPage()

  await page.goto('https://www.amazon.com/')

  await page.setViewportSize({ width: 1920, height: 969 })

  await page.waitForSelector('#nav-search #twotabsearchtextbox')
  await page.fill('#nav-search #twotabsearchtextbox', 'headless')

  await page.waitForSelector('.nav-searchbar > .nav-right > .nav-search-submit > #nav-search-submit-text > .nav-input')
  await page.click('.nav-searchbar > .nav-right > .nav-search-submit > #nav-search-submit-text > .nav-input')

  await page.waitForSelector('.s-desktop-width-max > .sg-col-14-of-20 > .sg-col-inner > .a-section > span:nth-child(1)')
  const content = await page.textContent('.s-desktop-width-max > .sg-col-14-of-20 > .sg-col-inner > .a-section > span:nth-child(1)')

  console.log(content)

  await browser.close()
})();

同样, 大多数呼叫保持不变;因此, 大部分工作都在这里得以保留。

结果, 这将打印1-48个超过2, 000个结果, 在控制台中。

另一个流行的用例是使浏览器自动化以进行测试。在这里, 我们可以使用诸如Jest之类的测试运行程序来实际收集结果。

添加Jest的一种方法是安装两个软件包:

npm i jest jest-cli --save-dev

添加后缀为.test.js使用默认配置将被识别为测试。使用之前的代码, 我们将得出以下结果:

const { chromium } = require('playwright');

test('has the right search results', async () => {
    const browser = await chromium.launch()
    const context = await browser.newContext()
    const page = await context.newPage()

    await page.goto('https://www.amazon.com/')

    await page.setViewportSize({ width: 1920, height: 969 })

    await page.waitForSelector('#nav-search #twotabsearchtextbox')
    await page.fill('#nav-search #twotabsearchtextbox', 'headless')

    await page.waitForSelector('.nav-searchbar > .nav-right > .nav-search-submit > #nav-search-submit-text > .nav-input')
    await page.click('.nav-searchbar > .nav-right > .nav-search-submit > #nav-search-submit-text > .nav-input')

    await page.waitForSelector('.s-desktop-width-max > .sg-col-14-of-20 > .sg-col-inner > .a-section > span:nth-child(1)')
    const content = await page.textContent('.s-desktop-width-max > .sg-col-14-of-20 > .sg-col-inner > .a-section > span:nth-child(1)')

    await browser.close()

    expect(content).toBe('1-48 of over 2, 000 results for');
});

更多的断言也是可能的。通常, 我们可以通过将共享功能放在所有测试都可以重用的方法中来简化其中一些测试。

在进行优化之前, 让我们在命令行中检查输出:

$ npx jest
 PASS  ./my.test.js (14.371 s)
  ✓ has the right search results (2160 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        25.854 s
Ran all test suites.

现在, 让我们完善上面的代码。我们获得:

const { chromium } = require('playwright');

let browser, context, page;

beforeEach(async () => {
    browser = await chromium.launch()
    context = await browser.newContext()
    page = await context.newPage()
});

afterEach(async () => {
    await browser.close()
});

test('has the right search results', async () => {
    await page.goto('https://www.amazon.com/')

    // ... (as above)

    expect(content).toBe('1-48 of over 2, 000 results for');
});

太棒了-无需使用任何E2E测试框架, 我们就能利用E2E测试来防止回归并使用可靠的冒烟测试。

浏览器自动化的另一个重要用途是增强了日志记录功能。在这里, 我们本质上是希望允许使用用户操作的重放来简化错误报告并促进发展。

总结

浏览器自动化已经走了很长一段路, 但是仍然面临一个问题, 即要确保自动执行正确的操作, 必须进行大量的工作。使用Headless Recorder, 我们可以大幅度改善情况, 让我们专注于功能而不是技术。尽管我们仍然需要注意一些技术细节, 但是该工具极大地提高了我们的生产率。

你将如何使用Headless Recorder?

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: