news 2026/9/27 21:30:23

Brave browser-laptop 测试体系实战指南:基于 Mocha、Spectron 与 WebdriverIO 的 UI 与单元测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Brave browser-laptop 测试体系实战指南:基于 Mocha、Spectron 与 WebdriverIO 的 UI 与单元测试
  • 桌面应用

【免费下载链接】browser-laptop

[DEPRECATED] Please see https://github.com/brave/brave-browser for the current version of Brave

项目地址:https://gitcode.com/gh_mirrors/br/browser-laptop
点击查看免费下载

本文是 Brave(browser-laptop,即早期基于 Electron/Muon 的桌面浏览器)官方测试文档(docs/tests.md)的完整深度解读。它面向需要在当前仓库中编写、运行与调试测试的开发者:你将掌握测试框架选型(Mocha + Spectron + WebdriverIO)、测试的启动与筛选命令、自定义辅助方法(IPC、标签页、窗口、状态、站点、偏好设置)、以及 UI 测试的防竞态最佳实践。文章以仓库中的 package.json、test/mocha.opts、test/lib/brave.js 等源码为依据,补充了文档之外的实现细节与可运行命令。

测试体系概览:一套覆盖单元与端到端的双层测试架构

Brave browser-laptop 的测试体系分为两大块:

  • 单元测试(unit tests):针对纯逻辑模块(如状态管理、工具函数、reducer)的隔离测试,不启动浏览器实例,速度快、稳定性高。
  • UI / 端到端测试(webdriver tests):通过 Spectron 拉起真实浏览器进程,用 webdriver.io 的 API 驱动界面交互,覆盖标签页、书签、导航栏、Bravery 面板等完整用户流程。

官方文档明确指出,绝大多数测试基于webdriver.io框架(经 Spectron 与 Electron 集成),且测试 API 全部遵循 webdriver.io 官方 API 文档 中列出的命令集合。

测试代码统一放在仓库顶层的test目录下,文件名必须以Test.js作为后缀,例如 test/navbar-components/urlBarTest.js、test/bravery-components/braveryPanelTest.js。这一命名约定与package.json中的 mocha 通配符"test/**/*Test.js"严格对应。

环境准备:安装 Mocha 与构建测试包

全局安装 Mocha

测试框架选用 mocha。官方推荐先全局安装:

npm install --global mocha

Mocha 的全局安装是为了方便在命令行直接调用 mocha;而package.json的devDependencies中同样声明了"mocha": "^5.2.0"(参见 package.json),以保证本地开发与 CI 环境的版本一致。

保持 webpack 测试包最新

为了贴近生产环境,测试不使用 webpack dev server(即npm run watch启动的webpack-dev-server不服务于测试)。因此开发时修改源码后,需要手动让测试用的 webpack bundle 持续重建:

npm run watch-test

该脚本在package.json中定义为cross-env NODE_ENV=test webpack --watch,即监听源码变化,以NODE_ENV=test环境重新打包(package.json)。若需要同时跑开发与测试两套构建,可使用npm run watch-all(npm run watch & npm run watch-test)。

底层 mocha 配置

所有测试通过 test/mocha.opts 统一配置,其内容决定了测试的执行方式:

--ui tdd --timeout 600s --reporter spec --check-leaks --require babel-register --require babel-polyfill --recursive

要点解读:

  • --ui tdd:使用 TDD 风格的接口(suite/test),但测试代码中同样可以使用 BDD 风格的describe/it;
  • --timeout 600s:单条测试超时高达 10 分钟——因为每条 webdriver 测试要经历"启动浏览器 → 初始化 Spectron → 执行断言"的完整过程;
  • --check-leaks:检测全局变量泄漏;
  • --require babel-register/babel-polyfill:让 mocha 直接运行 ES6+/generator 语法的测试源码(测试中大量使用function * ()generator 与yield,配合co-mocha支持)。

运行测试:全部、仅单元、还是按关键字筛选

运行全部测试

npm run test

该脚本等价于cross-env NODE_ENV=test mocha "test/**/*Test.js"(package.json),会递归执行test目录下所有*Test.js——包括 UI 测试与单元测试。

仅运行单元测试

npm run unittest

单元测试要快得多。unittest脚本为python tools/test && cross-env NODE_ENV=test mocha "test/unit/**/*Test.js" --globals chrome,DOMParser,XMLSerializer(package.json),其中--globals声明了浏览器全局对象以避免--check-leaks误报。仓库的 test/unit 目录按about/、app/、common/、js/、lib/、state/等子目录组织,如 test/unit/app 下就包含 100+ 个单元测试文件。

运行子集测试(--grep 筛选)

可以通过 mocha 的--grep按测试的description或it描述筛选:

npm run test -- --grep="expression"

例如--grep="^tabs"会匹配所有以单词tabs开头的测试描述。该用法对test、unittest等所有测试模式均生效。

其他相关入口

  • 按目录运行(工具脚本):tools/test.js 支持TEST_DIR环境变量(lint、tools、performance、unit等),默认执行mocha "test/${TEST_DIR}/**/*Test.js" --globals chrome,DOMParser,XMLSerializer;
  • 集成测试:npm run inttest仅运行 test/integration 下的测试;
  • 覆盖率:npm run unittest-cov基于 istanbul 收集单元测试覆盖率。

编写测试前须知:新标签页背景图默认关闭

为了给测试提速,新标签页(New Tab Page)的背景图片在测试环境中默认禁用。如果某个 webdriver 测试需要验证背景图可见,必须先在测试内重新打开该设置,官方给出了 generator 风格的示例:

it('shows new tab page background', function * () { yield this.app.client // enable setting again: .changeSetting('tabs.show-dashboard-images', true) // keep testing... })

这里的changeSetting是 Brave 自定义的 webdriver 命令(见下文"偏好设置"小节),它会派发changeSettingaction 并等待设置值真正反映到应用状态中(对应源码 test/lib/brave.js 中changeSetting→waitForSettingValue的调用链)。这种"命令 + 等待结果"的写法正是避免竞态的核心手段。

编写测试的最佳实践:来自官方文档的七条军规

文档总结了多条实战中踩坑得出的经验,逐条展开如下。

1. 打开新标签后必须先验证标签已存在

任何会打开新标签页的操作,都必须先验证标签已经打开,再切换标签/窗口——这是测试中最常见的竞态(race condition)来源。

原因在于 webdriver 会自动把上下文切换到任何新建的"window",而 chromedriver 在 Brave 中无法区分"标签页"和"窗口",因此必须使用仓库自定义的辅助方法来处理。特别提醒:

  • 如果存在多个相同 URL 的标签,不能用waitForUrl,因为它会命中已存在的那个标签/窗口而不会等待新标签;
  • 此时getTabCount是更好的选择,因为它不依赖特定窗口上下文即可执行。

2. 与 DOM 元素交互前必须确认元素存在

不要尝试对一个尚未确认存在的元素执行click、moveTo等操作。仅验证页面或某个父元素存在是不够的,需要验证目标元素本身。

3. 除非绝对必要,避免使用外部站点

例如 HTTPS Everywhere 与 SSL 相关测试之所以必须访问外部站点,是因为本地测试服务器(test/lib/server.js)当前不具备 SSL 能力。除此之外,引入外部依赖会带来三个问题:

  • 测试变慢(需要发出外部请求);
  • 断网时测试完全无法运行;
  • 外部站点内容一旦变更,测试随之挂掉。

4. 绝不假设测试的执行顺序

每条测试运行前,都应自行完成所需的 setup 或 cleanup,而不能依赖前一条测试留下的现场。

5. 优先使用 before() 而非 beforeEach()

webdriver 初始化(拉起 Electron 应用、注册命令)耗时较长,因此用before()只初始化一次、让多个测试复用,能显著提速。但需要注意限制条件:如果测试会改变环境导致后续测试需要重新 setup,则应改回beforeEach(),或者把该测试挪进独立的describe()/before()块中。

6. 状态变化不会立刻反映——用等待型命令

永远不要假设"加载了 URL,应用状态里的sites条目就已写入"。应使用waitForSiteEntry之类的等待型命令来轮询应用状态。这与第 1、2 条共同构成"显式等待"三原则。

7. 主动添加带日志、会等待结果的 webdriver 命令

优先以 Webdriver IO 命令的形式封装常用操作(尤其是等待操作),并为其添加日志输出;命令应尽量等待动作完成(例如"修改设置"同时等待新值反映到状态),而不是发出指令就返回。

实用辅助方法:仓库自带的 webdriver 命令全家桶

文档列出了一批 Brave 自有的、可与 webdriver.io 原生方法混用的辅助方法。这些方法的注册入口在 test/lib/brave.js 的addCommands()(test/lib/brave.js),新方法用this.app.client.addCommand(name, fn)即可追加,非常容易扩展。

按功能分组如下:

IPC(进程间通信)

方法用途
ipcSend向当前窗口的 webContents 发送 IPC 消息(test/lib/brave.js)
ipcSendRenderer从 renderer 进程发送 IPC 消息(test/lib/brave.js)

文档提示:想了解可发送的完整消息类型,可查看 js/components/main.js 中各组件的componentDidMount对 IPC 的监听。

标签页管理

  • tabHandles:枚举当前所有标签页句柄(会过滤掉chrome-extension与chrome://brave等内部 URL,见 test/lib/brave.js);
  • tabByIndex(index):按索引切换到指定标签(test/lib/brave.js);
  • getTabCount():返回当前标签页数量(不依赖窗口上下文,test/lib/brave.js);
  • tabByUrl(url):按 URL 定位标签(test/lib/brave.js);
  • waitForTabCount(count):等待标签数达到期望值(test/lib/brave.js);
  • pinTabByIndex(index, isPinned):固定/取消固定标签页(test/lib/brave.js)。

窗口管理

  • waitForBrowserWindow:等待浏览器主窗口出现(test/lib/brave.js);
  • setContextMenuDetail:隐藏当前正在显示的上下文菜单(test/lib/brave.js);
  • showFindbar(show):显示/隐藏查找栏(test/lib/brave.js);
  • getDefaultWindowHeight/getDefaultWindowWidth:读取主显示器工作区尺寸(test/lib/brave.js);
  • resizeWindow(width, height):调整窗口大小(test/lib/brave.js);
  • windowParentByUrl(url)/windowByUrl(url):按 URL 定位父窗口/窗口(test/lib/brave.js)。

状态管理

  • getAppState():读取应用级状态(appStore),返回 Immutable 状态序列化后的 JS 对象(test/lib/brave.js);
  • getWindowState():读取窗口级状态(windowStore,test/lib/brave.js)。

两个方法都通过devTools('electron').testData注入的测试钩子读取 store,因此必须由 Spectron 拉起应用后才可用。

站点管理(书签 / 历史 / 文件夹)

  • addSite:添加一条书签、文件夹或历史记录条目;
  • addSiteList:批量添加站点列表;
  • removeSite:移除站点。

当前源码中以更具体的命令呈现了同类能力:addBookmark(siteDetail)(等待书签条目写入后返回,test/lib/brave.js)、addHistorySite(siteDetail)(test/lib/brave.js)、addBookmarkFolder(siteDetail)(test/lib/brave.js)、removeBookmark/removeBookmarkFolder(test/lib/brave.js)等。

偏好设置

  • changeSetting(key, value):修改应用设置,并在返回前等待新值写入状态(test/lib/brave.js);
  • changeSiteSetting(hostPattern, key, value):修改针对某主机模式的站点级设置(test/lib/brave.js)。

文档特别强调:这些辅助方法中有很多并不会等待动作完成。在辅助方法被统一改造之前,测试必须自行确保异步操作已完成——例如等待标签数量增加,以证明 IPC 消息已被接收并处理。文档引用了一个曾因"有时动作已完成、有时未完成"而间歇性失败的案例,其修复方式正是等待getTabCount变化后再继续。

此外,brave.js还注册了大量文档未列全的等待型命令,例如waitForUrl、waitForTab、waitForElementCount、waitForDataFile、waitForInputText、waitForBookmarkEntry、waitForHistoryEntry、loadUrl、openBraveMenu、activateTabByIndex等,详见 test/lib/brave.js。不确定用法时,最直接的方式是在现有测试中搜索对应方法的调用示例。

调试测试:从命令行日志到浏览器内断点

开启命令级 verbose 日志

设置环境变量BRAVE_TEST_COMMAND_LOGS=1,即可让部分命令输出额外信息,用于定位失败原因:

BRAVE_TEST_COMMAND_LOGS=1 npm run test

日志开关的实现在 test/lib/brave.js:logVerboseEnabled同时受BRAVE_TEST_ALL_LOGS与BRAVE_TEST_COMMAND_LOGS控制,所有logVerbose(...)调用点(几乎每个自定义命令都带日志)都会在开关开启时打印。

官方文档给出了运行test/components/braveryPanelTest.js中 "blocks custom adblock resources in private tab" 测试时的真实输出片段(已截取关键行):

waitForUrl("chrome-extension://mnojpmjdmbbfmejpflffifhffcmidifd/about-newtab.html") waitForBrowserWindow() waitForDataFile("adblock") => undefined waitForDataFile("adblock") => {"etag":"\"215b010f2f5ff8b102896957564862d7\"","lastCheckDate":1477083465282,"lastCheckVersion":"2"} tabByIndex(0) tabHandles() => handles.length = 1; handles[0] = "CDwindow-e532c598-ab85-4114-9bef-8cfbfc14035e"; loadUrl("chrome-extension://mnojpmjdmbbfmejpflffifhffcmidifd/about-adblock.html") waitForTabCount(2) getTabCount() => 1 getTabCount() => 2 waitForUrl("http://localhost:23188/adblock.html") openBraveMenu() ✓ blocks custom adblock resources in private tab (4569ms)

可以看到:日志中每个命令都会打印"发起 → 结果",waitForDataFile的轮询过程、getTabCount从 1 到 2 的等待过程都一目了然,这正是排查竞态问题的利器。

使用 debug() 暂停浏览器

在测试中调用 webdriver 的debug()命令可暂停浏览器执行:

yield this.app.client.debug()

通常更省事的方式是把它追加到一串命令的末尾,例如.waitForUrl(url).debug()。暂停后,可以打开浏览器的 dev tools(或页面内容 dev tools)检查日志、console 与其他状态。注意要尽快操作,否则超时会导致测试失败(必要时可调大--timeout)。

获取浏览器 / renderer 进程日志

  • BRAVE_TEST_BROWSER_LOGS=1:测试结束(停止应用)时输出主进程日志;
  • BRAVE_TEST_RENDERER_LOGS=1:测试结束(停止应用)时输出 renderer 进程日志。

对应的实现在 test/lib/brave.js 的stopApp中:分别调用 Spectron 的getMainProcessLogs()与getRenderProcessLogs()并逐行打印。

应对 UI 测试的间歇性失败:官方策略清单

UI 测试极易写错并引入间歇性失败(intermittent failures)。正因如此,UI 测试运行更慢、更脆弱,单元测试始终是优先选择。若要降低 UI 测试的间歇性失败概率,文档给出的策略包括:

  • 永远显式:不要依赖隐式行为或时机巧合;
  • 绝不假设状态立即反映:例如加载 URL 后不要假设sites状态已新增条目,改用waitForSiteEntry等待;
  • 开启 verbose 模式(BRAVE_TEST_COMMAND_LOGS=1)获取更丰富的失败信息;
  • 优先新增 Webdriver IO 命令,尤其是等待型命令,并为它们添加日志;
  • 让自建命令尽量等待结果:例如"修改设置"同时等待新值反映到状态(changeSetting→waitForSettingValue即为此模式);
  • 警惕临时禁用的元素:点击可能发生在元素仍处于 disabled 状态时,需用等待类命令规避。

深入源码:测试基础设施是如何运转的

结合 test/lib/brave.js 可以还原一次 webdriver 测试的完整生命周期:

  1. 启动:startApp(test/lib/brave.js)设置环境变量NODE_ENV=test、CHROME_USER_DATA_DIR=<临时目录>、SPECTRON=true,以./node_modules/.bin/electron(Windows 下为node_modules/electron-prebuilt/dist/brave.exe)启动应用,并传入--enable-logging --v=1;
  2. 命令注册:beforeAll/beforeEach钩子调用addCommands()为this.app.client注册全部自定义命令,并通过chaiAsPromised.transferPromiseness让 chai 断言与 webdriverio 的 promise 链无缝衔接(test/lib/brave.js);
  3. 本地测试服务器:beforeAllServerSetup用 test/lib/server.js 在测试前起一个静态文件服务器,指向 test/fixtures 目录(包含adblock.html、autoplay.html、login1.html等大量测试页面);
  4. 执行与清理:stopApp在必要时输出进程日志,并按KEEP_BRAVE_USER_DATA_DIR决定是否删除临时用户数据目录(test/lib/brave.js)。

这套基础设施解释了文档中的诸多约定:测试必须等待(浏览器启动慢)、必须显式(状态更新异步)、必须自带服务器(避免外部依赖)。

测试目录结构速查

  • test/unit:单元测试(约 100+ 个文件,覆盖状态、工具函数、常量、组件逻辑);
  • test/lib:测试基础设施——brave.js(Spectron 封装与自定义命令)、server.js(本地测试服务器)、selectors.js(DOM 选择器)、coMocha.js(generator 支持)、userProfiles.js 等;
  • test/fixtures:本地测试页面与资源(HTML、图片、视频、PDF);
  • 组件/功能测试目录:navbar-components/、tab-components/、bookmark-components/、bravery-components/、misc-components/、contents/、about/、integration/、performance/、muon-native/;
  • 测试配置:test/mocha.opts。

整体来看,Brave browser-laptop 的测试体系遵循"单元测试优先、UI 测试显式等待、基础设施复用、日志驱动调试"的设计思路。对维护者而言,掌握本文的命令集与等待型辅助方法,即可在新增功能时快速写出稳定、可复现的测试。

  • 桌面应用

【免费下载链接】browser-laptop

[DEPRECATED] Please see https://github.com/brave/brave-browser for the current version of Brave

项目地址:https://gitcode.com/gh_mirrors/br/browser-laptop
点击查看免费下载

相关推荐

上一篇:Oh My Emacs包管理终极指南:使用el-get轻松管理Emacs插件
下一篇:性能调优revanced-patches:优化补丁的执行效率

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 21:24:57

大学生计算机二级C语言在线测试平台的设计与实现(需求文档)

论文&#xff08;设计&#xff09;基本要求&#xff1a;包括论文&#xff08;设计&#xff09;的基本内容、应完成的基本环节及各环节要求、学生应遵循的学术规范等一、基本内容本设计旨在为考计算机二级C语言的学生提供一个综合能力测试的平台。该平台将集成考试功能、评分系统…

作者头像 李华
网站建设 2026/9/27 21:21:36

WaLiOffice Excel 工具实战:sheet_generate 多表生成与 rust-xlsxwriter XLSX 渲染

文档教程后端 【免费下载链接】CodeGuide :books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总&#xff0c;旨在为大家提供一个清晰详细的学习教程&#xff0c;侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助&#xff0c;请给予支持(关注、…

作者头像 李华
网站建设 2026/9/27 21:20:23

不会Vue也能做全栈?Java后端实测飞算JavaAI

企业固定资产管理看起来像是录入设备、分配员工、定期盘点&#xff0c;真正开发时却要处理一整条业务链&#xff1a;资产领用后状态要同步&#xff0c;归还后要重新入库&#xff0c;维修记录需要关联具体资产&#xff0c;盘点结果还要区分正常、盘盈和盘亏。过去由Java后端独立…

作者头像 李华