【问题标题】:How to test chrome extensions?如何测试 chrome 扩展?
【发布时间】:2011-02-21 15:02:06
【问题描述】:

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

【问题讨论】:

标签: unit-testing google-chrome google-chrome-extension


【解决方案1】:

要测试端到端,您可以使用puppeteer。 这是我为扩展程序编写的 sn-p,用于检查加载的扩展程序 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();
  });
});

【讨论】:

    【解决方案2】:

    在处理几个 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”

    sn-p 代码如下:

    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)
    

    你可以找到完整的例子here

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

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

    【讨论】:

    • 该示例的链接似乎已失效 - 您能否更新一下?
    • 更新了示例链接。 sinon-chrome 现在也移到了github.com/acvetkov,很快就会有新的例子
    • 注意:项目似乎已过时:最后一次提交是在 2019 年 11 月 26 日,问题仍未得到解答。
    【解决方案3】:

    为了确认之前的几个答案,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 代码以及我的测试套件。测试会自动运行,结果会为您格式化。无需构建测试运行程序或结果格式化程序。只需关注installation instructions,并使用其中记录的 HTML 来创建您的测试运行器页面,并将您的测试套件也包含在该页面中。

    我认为您不能从其他主机动态获取 Jasmine 框架,因此我只是将 Jasmine 版本包含在我的扩展中。当然,当我为生产构建扩展时,我会省略它以及我的测试用例。

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

        Mock

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

        Test

        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 即可运行您的代码。

        【讨论】:

          【解决方案6】:

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

          最近,我将所有测试都放在了嵌入到应用程序中但无法访问的“测试”页面上,除非进行物理输入。

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

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

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

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

          【讨论】:

          • 你说得对,测试真实页面不属于单元测试。我应该让我的问题更广泛。但这仍然是我想测试的东西,特别是因为网站的 html 结构随时可能发生变化。我已经修改了问题。
          • 我仍然会在您的单元测试页面中通过 IFrame 进行测试。内容脚本仍应触发(如果您允许脚本在 iFrame 中运行)
          • 代理示例扩展有一些测试,这些测试只是模拟了一些必要的 Chrome API:code.google.com/chrome/extensions/samples.html#chrome.proxy .. 我们的同事 Boris 也使用 QUnit 来测试他的“模型”层: github.com/borismus/Question-Monitor-for-Stack-Exchange/tree/…
          【解决方案7】:

          关于 Chrome 中已有的工具:

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

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

            查看本地存储的变化。

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

          3. for fileSystem 您可以使用此 URL 检查您的文件是否已上传: 文件系统:chrome-extension:///temporary/

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

          【讨论】:

          • 对我不起作用,但它确实让我在我的 javascript 中走得更远。为此 +1。
          • 对于文件系统可以使用:filesystem:chrome-extension:///temporary/
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-12-31
          • 1970-01-01
          • 1970-01-01
          • 2013-05-08
          • 2017-11-20
          • 2017-04-09
          相关资源
          最近更新 更多