有没有好的方法来做到这一点?我正在编写一个扩展,它作为内容脚本与网站交互并使用本地存储保存数据。是否有任何工具、框架等可用于测试此行为?我意识到有一些用于测试 javascript 的通用工具,但是这些工具足以测试扩展吗?单元测试是最重要的,但我也对其他类型的测试(例如集成测试)感兴趣。
7 回答
是的,现有的框架非常有用..
在最近的过去,我将所有测试都放在了嵌入到应用程序中但无法访问的“测试”页面上,除非进行物理输入。
例如,我可以在一个页面中访问所有测试chrome-extension://asdasdasdasdad/unittests.html
测试可以访问localStorage
等。对于访问内容脚本,理论上您可以通过测试页面中的嵌入式 IFRAME 进行测试,但是这些是更多集成级别的测试,单元测试将要求您将其从真实页面中抽象出来,以便您不要依赖它们,同样可以访问 localStorage。
如果您想直接测试页面,您可以编排扩展程序以打开新选项卡 (chrome.tab.create({"url" : "someurl"})。对于每个新选项卡,您的内容脚本应该运行并且您可以使用你的测试框架来检查你的代码是否已经完成了它应该做的事情。
在处理几个 chrome 扩展时,我提出了sinon-chrome
一个允许使用 和 运行单元测试mocha
的nodejs
项目phantomjs
。
基本上,它会创建所有 API 的 sinon 模拟chrome.*
,您可以在其中放置任何预定义的 json 响应。
接下来,您使用节点的vm.runInNewContext
后台页面和phantomjs
渲染弹出/选项页面加载脚本。
最后,您断言使用所需的参数调用了 chrome api。
举个例子:
假设我们有一个简单的 chrome 扩展,它在按钮徽章中显示打开的选项卡的数量。
背景页面:
chrome.tabs.query({}, function(tabs) {
chrome.browserAction.setBadgeText({text: String(tabs.length)});
});
为了测试它,我们需要:
- 模拟
chrome.tabs.query
返回预定义的响应,例如两个选项卡。 - 将我们模拟的
chrome.*
api 注入到某个环境中 - 在这个环境中运行我们的扩展代码
- 断言按钮标记等于“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 中已有的工具:
在 chrome 开发者工具中,有用于本地存储的资源部分。
开发人员工具 > 资源 > 本地存储
在那里查看本地存储的变化。
您可以使用 console.profile 来测试性能并观察运行时调用堆栈。
- 对于 fileSystem 您可以使用此 URL 来检查您的文件是否已上传:filesystem:chrome-extension:///temporary/
如果您在没有背景页面/脚本且没有消息传递的情况下一起使用内容脚本和本地存储,则只能从该站点访问本地存储。因此,要测试这些页面,您必须在这些选项卡中注入您的测试脚本。
要测试端到端,您可以使用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();
});
});
我发现我可以使用Selenium Web 驱动程序启动带有预安装扩展程序的新浏览器实例,并使用 pyautogui进行点击 - 因为 Selenium 无法驱动扩展程序的“视图”。点击后,您可以制作屏幕截图并将它们与“预期”的截图进行比较,预计有 95% 的相似度(因为在不同的浏览器上,标记移动到几个像素是可以接受的)。
为了确认之前的几个答案,Jasmine 似乎与 Chrome 扩展配合得很好。我使用的是 3.4.0 版。
您可以使用Jasmine 间谍轻松地为各种 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 版本。当然,当我为生产构建扩展时,我会省略它以及我的测试用例。
我还没有研究如何在命令行中执行我的测试。这对于自动化部署工具会很方便。