有没有好的方法可以做到这一点?我正在编写一个扩展程序,作为内容脚本与网站进行交互,并使用localstorage保存数据。是否有任何工具、框架等可以用来测试此行为?我知道有一些通用的测试JavaScript的工具,但它们足以测试扩展程序吗?单元测试是最重要的,但我也对其他类型的测试(如集成测试)感兴趣。
有没有好的方法可以做到这一点?我正在编写一个扩展程序,作为内容脚本与网站进行交互,并使用localstorage保存数据。是否有任何工具、框架等可以用来测试此行为?我知道有一些通用的测试JavaScript的工具,但它们足以测试扩展程序吗?单元测试是最重要的,但我也对其他类型的测试(如集成测试)感兴趣。
是的,现有的框架非常有用。
最近,我把所有的测试放在一个“测试”页面上,这个页面被嵌入到应用程序中,但除非物理输入,否则无法访问。
例如,我会把所有的测试放在一个页面上,可以通过 chrome-extension://asdasdasdasdad/unittests.html
访问。
测试将可以访问 localStorage
等。如果您想访问内容脚本,则理论上可以通过测试页面中的嵌入IFRAME来测试,但这些更像是集成级别的测试,单元测试需要将其从真实页面中抽象出来,以便您不要依赖它们,同样地,请考虑局限于localStorage的访问。
如果您想直接测试页面,可以编排您的扩展程序以打开新标签页(chrome.tab.create({"url" :" someurl "})。对于每个新标签页,您的内容脚本都应该运行,并且您可以使用测试框架来检查您的代码是否做了应该做的事情。
我开发了几个Chrome扩展程序,其中包括sinon-chrome
项目,它允许使用mocha
,nodejs
和phantomjs
运行单元测试。
基本上,它创建了所有chrome.*
API的sinon mocks,您可以放置任何预定义的JSON响应。
接下来,您可以使用node的vm.runInNewContext
加载后台页面,使用phantomjs
加载弹出窗口/选项页面。
最后,您可以断言Chrome API是否被调用并传入所需的参数。
让我们来看一个例子:
假设我们有一个简单的Chrome扩展程序,它在按钮徽章中显示打开标签页的数量。
后台页面:
chrome.tabs.query({}, function(tabs) {
chrome.browserAction.setBadgeText({text: String(tabs.length)});
});
为测试它,我们需要:
chrome.tabs.query
以返回预定义的响应,例如两个选项卡。chrome.*
API注入到某个环境中以下是代码片段:
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中已存在的工具:
在Chrome的开发者工具中,有一个用于本地存储的资源部分。
开发者工具 > 资源 > 本地存储
可以在那里查看localstorage的更改。
您可以使用console.profile来测试性能并查看运行时间调用堆栈。
如果您同时使用内容脚本和本地存储,而没有背景页/脚本和没有消息传递,则只有该站点才能访问local-storage。因此,要测试这些页面,您必须将测试脚本注入到这些选项卡中。
要测试端到端,您可以使用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();
});
});
确认之前的回答,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版本。当然,在生产环境中构建我的扩展时,我会省略它以及我的测试用例。
我还没有研究如何在命令行上执行我的测试。这对于自动化部署工具非常方便。