当前位置:首页 > 文章列表 > Golang > Go教程 > Go-DOM - 用 Go 编写的无头浏览器

Go-DOM - 用 Go 编写的无头浏览器

来源:dev.to 2024-11-29 15:22:02 0浏览 收藏

编程并不是一个机械性的工作,而是需要有思考,有创新的工作,语法是固定的,但解决问题的思路则是依靠人的思维,这就需要我们坚持学习和更新自己的知识。今天golang学习网就整理分享《Go-DOM - 用 Go 编写的无头浏览器》,文章讲解的知识点主要包括,如果你对Golang方面的知识点感兴趣,就不要错过golang学习网,在这可以对大家的知识积累有所帮助,助力开发能力的提升。

Go-DOM - 用 Go 编写的无头浏览器

无事可做有时会导致疯狂的想法,而这一次;目的是通过嵌入 v8 引擎,用 go 编写一个无头浏览器,具有完整的 dom 实现和 javascript 支持。

这一切都是从编写 htmx 应用程序开始的,测试它的需要让我很好奇是否有无头浏览器的纯 go 实现。

搜索“go headless browser”只会导致搜索结果谈论自动化无头浏览器,即在无头模式下使用真正的浏览器,例如 firefox 的 chrome。

但是在纯 go 中什么都没有。

所以我开始建造一个。

为什么 go 中使用无头浏览器?

这可能看起来很愚蠢,因为编写无头浏览器永远不会像真正的浏览器一样工作;因此并不能真正验证您的应用程序是否在您决定支持的所有浏览器中正常工作。这也不允许您在停止工作时获得良好的功能,例如应用程序的屏幕截图。

那为什么呢?

极品速度!

为了在有效的 tdd 循环中工作,测试必须很快。缓慢的测试执行会阻碍 tdd,并且您会失去快速反馈循环提供的效率优势。

使用浏览器自动化进行此类验证会产生严重的开销,并且此类测试通常是在代码编写后编写;因此,它们不再有助于编写正确的实现;但事后却减少了维护负担;只是偶尔会在您的付费客户之前检测到错误。

目标是创建一个支持 tdd 流程的工具。为了可用,它需要在进程内运行。

需要用go编写。

减少不稳定的测试

让 dom 处于进程内可以在 dom 之上编写更好的包装器;这可以帮助为您的测试提供一个不太不稳定的界面,就像测试库为 javascript 所做的那样。

您不用依赖 css 类名、元素 id 或 dom 结构,而是使用以用户为中心的语言编写测试,如下所示。

在带有“电子邮件”标签的文本框中输入“me@example.com”

或者用假设的代码。

testing.getelement(query{
  role: "textbox",
  // the accessibility "name" of a textbox _is_ the label
  name: "email",
}).type("me@example.com")

此测试不关心标签是否实现为

这将行为验证与 ui 更改解耦;但它确实强制文本“电子邮件”以可访问的方式与输入字段关联。这将测试与用户如何与页面交互结合起来;包括那些依赖屏幕阅读器使用您的页面的人。

这实现了tdd最重要的方面;编写与具体行为相结合的测试。1

虽然在技术上可能可以为进程外浏览器编写相同的测试;原生代码的好处对于这些类型的助手最可能需要的 dom 随机访问类型至关重要。

示例:javascript

为了举例说明测试类型,我将使用 javascript 中的类似示例;也是一个使用 htmx 的应用程序。该测试验证请求需要身份验证的页面的一般登录流程。

有点长,因为我在这里将所有设置和帮助程序代码合并到一个测试函数中。

it("Redirects to /local after a successful login", async () => {
  // Setup - stub the authentication, and create a stubbed user
  // using a test helper
  sinon
    .stub(auth, "authenticate")
    .withArgs({
      email: "jd@example.com",
      // matchPassword helper is used, as passwords are wrapped in a class
      // preventing accidental disclosure in logs, console out, etc.
      password: matchPassword("s3cret"),
    })
    .resolves(
      auth.AuthenticateResult.success(createUser({ firstName: "John" })),
    );
  const url = `http://127.0.0.1:${port}/auth/login?redirectUrl=%2Flocal`;
  // Request private page. This _should_ generate a redirect
  const wrapper = await DOMWrapper.open(url); // Just a helper around jsdom
  const browser = wrapper.browser;
  // Once HTMX is ready, it emits an `htmx:load` event. Then verify that it was 
  // correctly redirected.
  await wrapper.waitFor("htmx:load");
  expect(wrapper.url.pathname).to.equal("/auth/login");
  // Use testing-library to fill out and submit the form
  let screen = wrapper.screen;
  const username = screen.getByRole("textbox", { name: "Email" });
  const password = screen.getByLabelText("Password");
  await userEvent.type(username, "jd@example.com");
  await userEvent.type(password, "s3cret"); // password has no role
  // Wait for a new `htmx:load` event, while clicking the submit button
  // at the same time.
  await wrapper.runAndWaitFor(
    ["htmx:load"],
    userEvent.click(screen.getByRole("button", { name: "Sign in" })),
  );
  // After the new new page has been loaded, verify that the username
  // is displayed (i.e. the stubbed user is used), and the correct
  // URL is used.
  screen = testingLibrary.within(browser.window.document.body);
  const heading = screen.getByRole("heading", { level: 1 });
  expect(heading.innerHTML).to.equal("Hi, John");
  expect(wrapper.url.pathname).to.equal("/local");
});

简单来说,测试执行以下操作:

  1. 删除身份验证函数,模拟成功的响应。
  2. 请求需要身份验证的页面
  3. 验证浏览器是否重定向到登录页面,并且浏览器 url 是否已更新。 2
  4. 在表格中填写预期值,然后提交。
  5. 验证浏览器是否重定向到最初请求的页面,并且它显示了存根用户的信息。

测试在内部启动 http 服务器。因为 this 在测试过程中运行,所以可以对业务逻辑进行模拟和存根。测试使用jsdom与http服务器通信;它既将 html 响应解析为 dom,又在已初始化的沙箱中执行客户端脚本,例如以 window 作为全局范围。3

这使得能够编写 http 层的测试,其中验证响应的内容是不够的。在这种情况下; htmx 按预期处理响应。

但是除了等待一些 htmx 事件,以免过早(或太晚)进行之外,测试实际上并不关心 htmx。事实上,如果我从表单中删除 htmx,采用经典重定向,测试仍然可以通过。

(如果我完全删除 htmx