繁体   English   中英

如何测试chrome扩展?

[英]How to test chrome extensions?

有没有好的方法可以做到这一点? 我正在编写一个与网站交互的扩展作为内容脚本并使用 localstorage 保存数据。 是否有任何工具、框架等可用于测试此行为? 我意识到有一些用于测试 javascript 的通用工具,但这些工具是否足以测试扩展? 单元测试是最重要的,但我也对其他类型的测试(例如集成测试)感兴趣。

是的,现有的框架非常有用..

最近,我将所有测试放在嵌入到应用程序中的“测试”页面上,但除非实际键入,否则无法访问。

例如,我将在chrome-extension://asdasdasdasdad/unittests.html下访问的页面中进行所有测试

测试可以访问localStorage等。对于访问内容脚本,理论上您可以通过测试页面中的嵌入式 IFRAME 进行测试,但是这些是更多的集成级别测试,单元测试需要您将其从真实页面中抽象出来,以便您不依赖它们,同样可以访问 localStorage。

如果您想直接测试页面,您可以编排您的扩展程序以打开新标签(chrome.tab.create({"url" : "someurl"})。对于每个新标签,您的内容脚本应该运行,您可以使用您的测试框架来检查您的代码是否完成了它应该做的事情。

至于框架, JsUnit或更新的Jasmine应该可以正常工作。

在处理几个 chrome 扩展时,我提出了sinon-chrome项目,该项目允许使用mochanodejsphantomjs运行单元测试。

基本上,它创建了所有chrome.* API 的 sinon 模拟,您可以在其中放置任何预定义的 json 响应。

接下来,您使用节点的vm.runInNewContext为背景页面和phantomjs为渲染弹出/选项页面加载脚本。

最后,您断言使用所需的参数调用了 chrome api。

让我们举个例子:
假设我们有一个简单的 chrome 扩展,它在按钮徽章中显示打开的选项卡的数量。

背景页面:

chrome.tabs.query({}, function(tabs) {
  chrome.browserAction.setBadgeText({text: String(tabs.length)});
});

为了测试它,我们需要:

  1. 模拟chrome.tabs.query返回预定义的响应,例如两个选项卡。
  2. 将我们模拟的chrome.* api 注入一些环境
  3. 在这个环境中运行我们的扩展代码
  4. 断言按钮标志等于“2”

代码片段如下:

const vm = require('vm');
const fs = require('fs');
const chrome = require('sinon-chrome');

// 1. mock `chrome.tabs.query` to return predefined response 
chrome.tabs.query.yields([
  {id: 1, title: 'Tab 1'}, 
  {id: 2, title: 'Tab 2'}
]);

// 2. inject our mocked chrome.* api into some environment
const context = {
  chrome: chrome
};

// 3. run our extension code in this environment
const code = fs.readFileSync('src/background.js');
vm.runInNewContext(code, context);

// 4. assert that button badge equals to '2'
sinon.assert.calledOnce(chrome.browserAction.setBadgeText);
sinon.assert.calledWithMatch(chrome.browserAction.setBadgeText, {
  text: "2"
});

现在我们可以将它包装到 mocha 的describe..it函数中并从终端运行:

$ mocha

background page
  ✓ should display opened tabs count in button badge

1 passing (98ms)

你可以在这里找到完整的例子。

此外,sinon-chrome 允许触发具有预定义响应的任何 chrome 事件,例如

chrome.tab.onCreated.trigger({url: 'http://google.com'});

虽然sinon.js似乎工作得很好,但您也可以只使用普通的 Jasmine 并模拟您需要的 Chrome 回调。 例子:

嘲笑

chrome = {
  runtime: {
    onMessage : {
      addListener : function() {}
    }
  }
}

测试

describe("JSGuardian", function() {

  describe("BlockCache", function() {

    beforeEach(function() {
      this.blockCache = new BlockCache();
    });

    it("should recognize added urls", function() {
      this.blockCache.add("http://some.url");
      expect(this.blockCache.allow("http://some.url")).toBe(false);
    });
} // ... etc

只需修改默认的SpecRunner.html即可运行您的代码。

关于 Chrome 中已有的工具:

  1. 在 chrome 开发者工具中,有一个用于本地存储的资源部分。

    开发者工具 > 资源 > 本地存储

    在那里查看 localstorage 的变化。

  2. 您可以使用 console.profile 来测试性能并观察运行时调用堆栈。

  3. 对于文件系统,您可以使用此 URL 来检查您的文件是否已上传:filesystem:chrome-extension:///temporary/

如果您在没有后台页面/脚本和消息传递的情况下同时使用内容脚本和本地存储,则只能从该站点访问本地存储。 因此,要测试这些页面,您必须在这些选项卡中注入测试脚本。

我发现我可以使用Selenium Web 驱动程序来启动带有预安装扩展的新浏览器实例,并使用pyautogui进行点击——因为 Selenium 无法驱动“查看”扩展。 单击后,您可以制作屏幕截图并将它们与“预期”的屏幕截图进行比较,预计相似度为 95%(因为在不同的浏览器上,可以接受几个像素的标记移动)。

为了确认之前的几个答案,Jasmine 似乎与 Chrome 扩展程序配合得很好。 我正在使用 3.4.0 版。

您可以使用Jasmine spies轻松地为各种 API 创建测试替身。 无需从头开始构建自己的。 例如:

describe("Test suite", function() {

  it("Test case", function() {

    // Set up spies and fake data.
    spyOn(chrome.browserAction, "setPopup");
    spyOn(chrome.identity, "removeCachedAuthToken");
    fakeToken = "faketoken-faketoken-faketoken";
    fakeWindow = jasmine.createSpyObj("window", ["close"]);

    // Call the function under test.
    logout(fakeWindow, fakeToken);

    // Perform assertions.
    expect(chrome.browserAction.setPopup).toHaveBeenCalledWith({popup: ""});
    expect(chrome.identity.removeCachedAuthToken).toHaveBeenCalledWith({token: fakeToken});
    expect(fakeWindow.close.calls.count()).toEqual(1);

  });

});

更多细节,如果有帮助的话:

正如另一个答案中提到的,我创建了一个 HTML 页面,作为运行我的测试的浏览器扩展的一部分。 HTML 页面包括 Jasmine 库、我的扩展程序的 JavaScript 代码以及我的测试套件。 测试会自动运行,结果会为您设置格式。 无需构建测试运行程序或结果格式化程序。 只需按照安装说明,并使用那里记录的 HTML 来创建您的测试运行器页面,并将您的测试套件也包含在页面中。

我不认为你可以从另一个主机动态获取 Jasmine 框架,所以我只是在我的扩展中包含了 Jasmine 版本。 当然,当我为生产构建扩展时,我将省略它以及我的测试用例。

我还没有研究如何在命令行执行我的测试。 这对于自动化部署工具来说非常方便。

要测试端到端,您可以使用puppeteer 这是我为扩展程序编写的代码段,用于检查加载的扩展程序title并验证扩展程序是否在隐身模式下启用。

 const path = require("path"); const puppeteer = require("puppeteer"); const assert = require("assert"); const Constants = require("../contants"); const Utils = require("./util"); const extensionID = Constants.EXTENSION_ID; const extensionPath = path.join(__dirname, "../dist"); const extensionOptionHtml = "option.html"; const extPage = `chrome-extension://${extensionID}/${extensionOptionHtml}`; let extensionPage = null; let browser = null; async function boot() { browser = await puppeteer.launch({ // slowMo: 250, headless: false, // extension are allowed only in head-full mode args: [ `--disable-extensions-except=${extensionPath}`, `--load-extension=${extensionPath}`, "--no-sandbox", "--disable-setuid-sandbox" ] }); extensionPage = await browser.newPage(); await extensionPage.goto(extPage); } describe("Extension UI Testing", function() { this.timeout(20000); // default is 2 seconds and that may not be enough to boot browsers and pages. before(async function() { await boot(); }); describe("option page home", async function() { it("check title", async function() { const h1 = "Allow extension in Incognito Mode"; const extH1 = await extensionPage.evaluate(() => document.querySelector("h1").textContent.trim() ); assert.equal(extH1, h1); }); it("show option ui after enabling extension in incognito", async () => { await extensionPage.goto(`chrome://extensions/?id=${extensionID}`); extensionPage.evaluate(() => document .querySelector("body > extensions-manager") .shadowRoot.querySelector("#viewManager > extensions-detail-view") .shadowRoot.querySelector("#allow-incognito") .shadowRoot.querySelector("#crToggle") .click() ); await Utils.sleep(2000); await extensionPage.goto( `chrome-extension://${extensionID}/${extensionOptionHtml}` ); const h3 = "Mark Incognito"; const headingID = `#${Constants.OPTION_SCRIPT_HOST_ID} > div > div > header > div > h6`; await extensionPage.waitFor(headingID); console.log({ headingID }); const extH3 = await extensionPage.evaluate(headingID => { return document.querySelector(headingID).textContent.trim(); }, headingID); console.log({ extH3 }); assert.equal(extH3, h3); }); }); after(async function() { await browser.close(); }); });

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM