- 测试
- 测试智能体
- 人工智能
- 浏览器控制
- CLI
【免费下载链接】e2e
Next generation e2e testing framework for web and mobile apps.
@e2e-dev/web是 e2e 端到端测试框架的浏览器引擎包,基于 Playwright 构建,负责为e2e的运行器驱动所有 web 目标。本文以 packages/web/CHANGELOG.md 为骨架,系统梳理该包从 0.2.0 到当前 0.13.0 的完整演进脉络:Playwright 依赖策略的两次反转、webfixture 改名为browser、引擎契约(engine contract)的多次重塑、以及语义观察与定位器在封闭 Shadow DOM、display: contents、可见性等边缘场景上的持续对齐 Playwright 语义,并结合 packages/web/package.json、packages/web/src/engine.ts 等源码给出可验证的实现证据。
一、包的定位与当前状态(0.13.0)
e2e核心包不附带任何引擎:每个 target 必须显式声明驱动它的引擎,@e2e-dev/web就是浏览器这一侧的引擎,它实现公开的e2e/engine契约,与设备引擎(@e2e-dev/mobile)挂在同一个接缝上、互无特权。当前 packages/web/package.json 的关键事实与 CHANGELOG 逐条吻合:
- 包名
@e2e-dev/web,版本0.13.0,许可证 Apache-2.0(CHANGELOG 0.11.0-canary-20260921154506 记录了从 MIT 到 Apache-2.0 的重新授权); "dependencies": { "playwright-core": "1.63.0" }—— 精确 pin 住的playwright-core,对应 0.12.0 的破坏性变更;"peerDependencies": { "e2e": ">=0.18.0 <1" }—— 对应 0.13.0 中“要求e2e0.18.0 及以上”的补丁;"engines": { "node": "^22.22.3 || >=24.8.0" }—— 对应 0.13.0 的 Node 版本破坏性变更;- CLI 二进制
e2e-web("bin": { "e2e-web": "./dist/bin.js" }),由 packages/web/src/bin.ts 转发到cli.ts,用于安装本包 pin 住的 Playwright 对应版本的浏览器。
packages/web/README.md 给出了最小配置形态,可视为 CHANGELOG 各条变更收敛后的“现状快照”:
import type { E2EConfig } from 'e2e'; import { web } from '@e2e-dev/web'; export default { targets: [{ name: 'web', engine: web(), app: { url: 'http://localhost:3000' } }], } satisfies E2EConfig;target 声明被测应用(app.url必填),引擎只负责驱动它。浏览器选项包括browser(chromium/firefox/webkit,默认chromium,或一个租用托管浏览器的BrowserProvider)、viewport(默认 1280x720)、testIdAttribute(默认data-testid)、userAgent、locale与timezoneId、initScripts、connect(CDP 附着远程浏览器)等。
二、0.13.0:Node 版本抬升与 TypeScript 加载器自研
这是当前版本中最重要的两条 Breaking 变更(Minor Changes)。
1. Node.js 版本下限抬升。e2e及所有@e2e-dev包要求 Node.js 22.22.3+(22 系)或 24.8.0+;22.12–22.22.2、整个 23 系、24.0–24.7 均不再支持,CLI 会在这些版本上拒绝启动并指名应升级到的版本。根源是一个 Node.js 上游缺陷:CommonJS 文件导入的模块经由新加载器的module.registerHooks去 require TypeScript 时失败,该问题在 22.22.3 与 24.8.0 中修复。packages/web/package.json 的engines字段即为该结论的直接证据。
2. 用基于 oxc 的自研加载器替代 tsx。e2e 现在用自己的加载器编译 TypeScript,不再依赖 tsx。动机很具体:tsx 带入 esbuild,其 postinstall 脚本会让 pnpm 11+ 上的每次pnpm install失败于ERR_PNPM_IGNORED_BUILDS(除非项目显式批准构建);自研加载器使“e2e 安装的包中没有任何一个运行安装脚本”成为可能。行为上保持兼容的部分:
- 配置与测试照旧按
package.json声明的模块体系加载(ESM);./x.js、./x解析到x.ts,./x.jsx解析到x.tsx; - 支持目录 index 导入、tsconfig 的
paths与baseUrl、workspace 包直接导出.ts源码、.tsx/.jsx中的 JSX、enums、namespaces、experimentalDecorators与带 source map 的堆栈; - 不带
with { type: 'json' }的 JSON 导入仍可加载; - 文件名中含
%、#或空格的文件不再以 "URI malformed" 失败或丢失源码位置; - 每个文件读其最近的
tsconfig.json,而非工作目录下的那份。
行为收紧的部分(升级时需要自查的清单):
| 变更 | 说明 |
|---|---|
.cts中的import语句 | 不再被编译成require调用,需要import x = require()写法 |
标准装饰器 / auto-accessors /.cts中的 ESMimport与值export | 无experimentalDecorators时被拒绝,错误指名所在行;import type、export type、import x = require()、export =仍然可用 |
| Bun / Deno 运行时 | CLI 直接拒绝(bun --bun等),要求安装后在 Node.js 上运行 |
未用import type导入但保留在输出中的类型 | 加载失败并指名该 import(典型场景是emitDecoratorMetadata下被装饰成员的类型) |
CommonJS 文件 require 的.ts | 按 ESM 运行(与其他.ts一致),因此用module.exports写的文件需要改用.cts扩展名 |
| JavaScript 文件中的 import | 按 Node.js 规则解析,不再套用 TypeScript 的扩展名规则或 tsconfigpaths |
另外,已安装包内部的 TypeScript 编译不再读取项目自身的tsconfig.json。
3.screen.swipe新增duration。screen.swipe({ from, to, duration })为路径式滑动增加了可选的duration(毫秒),控制手势耗时——这是 0.13.0 唯一面向测试代码的新 API。
三、0.13.0 的补丁变更:健壮性细节
0.13.0 的 Patch Changes 每一条都对应一类真实的失败模式:
- 无头运行不再重复下载完整 Chromium:headless shell 已安装时跳过整包下载;安装过程不再顺带删除缓存中的其他浏览器。设置
PLAYWRIGHT_SKIP_BROWSER_GC=0可恢复旧的清理行为。这条在源码中有直接对应:packages/web/src/install.ts 定义了headlessShellInstalled()(检查chromium_headless_shell-<revision>目录下的INSTALLATION_COMPLETE标记与可执行文件)和isBrowserInstalled()(无头 chromium 检查 headless shell,其余检查整包路径)。 browser.evaluate接受字符串表达式:browser.evaluate('document.title')返回标题,不再抛EVALUATE_FAILED: ... is not a function;若字符串求值结果本身是函数(如'() => document.title'),仍会带着参数被调用。- 定位器二次匹配的重解析:动作即将执行时定位器恰好出现第二个匹配(典型是页面转场中旧页新页短暂并存)不再报
ENGINE_FAILURE(Playwright strict mode 违例);定位器重新解析一次,两个匹配依旧以LOCATOR_AMBIGUOUS失败,重复项消失则动作照常执行。拖拽在 press 之后 drop 目标出现第二个匹配时以ACTION_FAILED失败且不重试。 - 无响应页面不再占满测试超时:脚本永不让出主线程的页面上,tap / locate / 导航会在动作或导航超时处以
ACTION_FAILED失败(即使 trace 正在录制);此类被打断的 tap/击键可能已到达页面,重放时永不重复。 - 表格语义:无 scope 的表头现在能从相邻数据单元格推断,语义观察与原生行表头对齐。
- Peer 版本要求:需要
e2e0.18.0 及以上;旧版 runner 上引擎加载失败或有头运行无法启动浏览器。
四、0.12.0:playwright-core 精确 pin 与 Playwright 依赖策略的第二反转
0.12.0 的 Breaking 变更改写了 Playwright 的引入方式:@e2e-dev/web依赖精确版本pin 的playwright-core,而不再对playwright做 peer 声明。项目自己不再安装 Playwright,引擎永远运行其受测版本;如果应用自身的测试依赖 Playwright,则保留那份独立副本——两份只有在版本相同时才共享浏览器缓存。迁移命令:
npm uninstall playwrightCI 中改用包自带命令安装浏览器,它运行的是引擎 pin 的那份 Playwright:npx @e2e-dev/web install chromium --with-deps(pnpm 下为pnpm exec e2e-web install chromium --with-deps),取代旧的npx playwright install chromium --with-deps。查看 trace 则用npx playwright-core@1.63.0 show-trace <file>(pnpm 项目用pnpm dlx)。e2e init也不再向新项目添加playwright。
这一条必须放进历史脉络里看,因为它反转了 0.10.0 的决定:0.10.0 时playwright从依赖改为 peer dependency(>=1.63.0 <2),理由是避免引擎再拉第二份 Playwright、第二份浏览器缓存;0.12.0 则改为引擎自己 pinplaywright-core,让“引擎运行的 Playwright 版本”完全由包本身决定。两次变更合起来解释了为什么今天 packages/web/package.json 里既没有playwrightpeer、也没有playwright依赖,只有一个精确的playwright-core@1.63.0。
0.12.0 的 Minor 还新增了两组引擎能力:
- Init scripts(对齐 Playwright 的
addInitScript):web({ initScripts })对每个测试生效,browser.addInitScript(script, arg?)针对单个页面。脚本可以是源码字符串、{ path }或函数。源码侧的类型WebInitScript由 packages/web/src/init-scripts.ts 提供并校验(packages/web/src/engine.ts 的web()工厂调用validateInitScripts)。 locale/timezoneId:为每次 attempt 的 context 设置语言与时区,效果等同 Playwright 的use.locale/use.timezoneId——影响navigator.language、Intl、Accept-Language头与本地Date。Intl拒绝的 tag 在配置加载时即以INVALID_CONFIG报错;headers中与locale并存的accept-language条目同样报错;持久化远程 context(connect.reconnectEndpoint或scope: 'attempt'的 provider)下这两个选项直接INVALID_CONFIG。packages/web/src/engine.ts 中validateLocale/validateTimezoneId与CREATION_OPTION_KEYS的冲突检查(“持久 context 下这些选项要求新建 context”)正是这条规则的落地。
0.12.0 的 Patch 变更值得单独展开,因为它们把一批接口语义精确对齐到了 Playwright:
check()/uncheck()放宽:点击后控件被移除或页面离开(如单选框被选中视图替换)时动作视为成功并记录、可重放;替换物不被读取,需要就另行断言。取消勾选已选单选框、点击未改变状态的情况以NOT_ACTIONABLE失败(此前是ENGINE_FAILURE)。locator.waitFor状态收紧:新增attached与detached,其余任何状态或选项键都以INVALID_ARGUMENT拒绝——此前所有非visible状态都被静默当作hidden,waitFor({ state: 'attached' })与拼错的状态在“节点不存在”时都会一次性通过。同时toHaveProperty对路径上的原始值透过包装对象读取(对齐 Jest),expect(browser).toHaveURL支持ignoreCase。- 导航白名单化:
app.open、browser.goto与 agent 的navigate动词只放行http:、https:与精确的about:blank,其余 scheme(chrome:、blob:、about:srcdoc等)一律POLICY_DENIED;view-source:file:///...这类包装 scheme 不再能加载本地文件;browser.setCookies对about:blankcookie URL 同样POLICY_DENIED。 - 带引号的 name 不再破坏定位器:
getByRole('button', { name: "Yes, I'm sure" })在链式(.first()、nth、filter)之后不再抛InvalidSelectorError;getByRole/getByText/getByLabel/getByPlaceholder/getByTestId中u/v标志且含引号或>>的 RegExp 同理。 - route 决策对齐 Playwright:
route.continue()发往网络而非交给前一个 route,并携带配置的headers;新增route.fallback()保留旧的链式行为;continue接受url(按goto同一规则校验)、method、headers、postData,fulfill接受path(相对项目根)与contentType;决策不接受的选项(如abort(errorCode)、拼错键)是INVALID_ARGUMENT而非静默忽略。browser.waitForResponse在响应头到达即解决(慢 body 不再导致超时误判),text()/json()等待 body 至动作超时;相对url的setCookies落在 base URL 上而非ENGINE_FAILURE。 - 可见性按渲染结果判定:
aria-hidden节点只要被绘制就是可见的(装饰性 spinner 上的toBeHidden()会等它消失);content-visibility: hidden下的内容不可见;无布局盒的节点boundingBox()返回null;checkbox/radio 的inputValue()/toHaveValue无论勾选状态都读其 value(默认on);可访问名会读嵌入控件的值——<button>Flash the screen <input value="3"> times</button>读作 "Flash the screen 3 times"。
五、0.11.x:browserfixture 改名、web()纯净化与包名迁移
0.11.0(含两个 canary 发布)是一次 API 面的大重整,其成果构成了今天 packages/web/src/index.ts 导出面的基础。
1. fixture 从web改为browser(Breaking)。测试写法变为test('...', async ({ screen, browser }) => { await browser.goto('/') })、expect(browser).toHaveURL('/')、requires: ['browser']。类型随之改名:Web变Browser,WebExpectation变BrowserExpectation;而web()工厂与网络/选项类型(WebRoute、WebResponse、WebOptions)保留原名。遗留的requires: ['web']会在所有 web 目标上跳过测试——改名即可。源码印证:packages/web/src/index.ts 中export const test = base.extend<{ browser: Browser }>(),并从./browser.ts导出Browser、BrowserExpectation、FrameScreen、WebRoute等类型。
2.web()只驱动浏览器(Breaking)。被测应用改为 target 侧声明:web({ url, environment, identity, command, readyUrl })成为targets: [{ engine: web(), app: { url, ... } }],web({ services })连同其他旧选项全部变为未知键INVALID_CONFIG。web()target 必须有app.url,并拒绝设备侧字段(bundleId、appPath、launchArguments、permissions);带app.url的 target 即为browsertarget,可被requires: ['browser']匹配。packages/web/src/engine.ts 中的NATIVE_APP_KEYS = ['bundleId', 'appPath', 'launchArguments', 'permissions']常量与rejectUnknownKeys('web()', options, WEB_OPTION_KEYS)调用正是这条契约的实现;对已废弃键还保留了显式报错:web({ allowedOrigins })与web({ video })(已更名screencast)会抛出指名迁移方向的INVALID_CONFIG。
3. 顶层describe/ 钩子导出。describe、beforeEach、afterEach、beforeAll、afterAll成为e2e的顶层导出,与test.describe、test.*钩子是同一批函数;@e2e-dev/web在test旁同样导出describe与四个钩子(用其browserfixture 做类型标注),使一个测试文件只从一个 import 注册。packages/web/src/index.ts 末尾的export const { describe, beforeEach, ... } = test;即此设计。
4. 0.11.0 的其他能力与修复(按 CHANGELOG 原意归纳):
web({ basicAuth: { username, password } })支持password: secrets.get(name):引擎在每次 attempt 启动时解析(provider 可轮换),runner 将值从报告、日志、agent 观察与 Playwright trace 中脱敏;Authorization头的 base64user:password派生形式也被注册脱敏,页面回显请求头时报告呈现Basic <secret:name>。保护仅限文本:截图、trace 录屏帧与模型像素保持原样。web({ video: { size, quality } })更名web({ screencast: { size, quality } })(WebVideoOptions变WebScreencastOptions)——它描述页面录屏参数;“哪些 attempt 录制”仍由配置或 target 上的video模式决定。BrowserProvider.record(lease, context):provider 可录制整个窗口(含标签与地址栏)作为 attempt 视频,替代页面 screencast;不能录制的 provider 不实现record。- 超时等待可操作性的动作报告 Playwright 标题行与调用日志里最后一个阻挡者(如
<div>// before app: { url: 'http://localhost:3000' }, targets: [{ name: 'web', platform: 'web', engine: web() }], // after targets: [{ name: 'web', platform: 'web', engine: web({ url: 'http://localhost:3000' }) }],注意这条演进与 0.11.0 的“
web()只驱动浏览器”正好又反转了一次:0.5.0 把app字段移入引擎选项,0.11.0 再把它移回 target 侧的app块——引擎最终只保留纯粹的浏览器驱动选项。阅读跨版本代码时必须以所在版本的 CHANGELOG 为准。其他早期里程碑:0.3.0引入 backend
prepare钩子(首跑浏览器下载移入其中,不再计入launchTimeout、不再每 worker 一次,进度以notice事件流式打印)与稳定节点 id(元素首次被观察时盖章、存活期间保持不变,executor 可 diff 两次观察;折叠的<select>列出至多 60 个选项;表格读作row/cell/columnheader节点)。0.6.0将 Node 下限提至 22.12,新增toBeFocused、toHaveAttribute匹配器。0.7.0引擎在 handle 上声明平台、新增视频录制(artifacts: ['video']或--video,每页一段video/video-part<n>.webm)、aria-hidden文本不再进入控件名与 label。0.8.0web()接受headers与basicAuth(受保护应用——Vercel 预览、ngrok 拦截页、HTTP Basic 认证的 staging 主机——在包括agent.act在内的所有入口可达)。0.9.0观察能力两大扩展:封闭 shadow root(context init script 在页面脚本前包装Element.prototype.attachShadow记录每个 closed root,页内读取器将其与 open root 同等遍历;e2e-closed=<css>注册选择器引擎保证密码字段遮罩生效)与display: contents元素(其子节点自行决定可见性,修复了单页结账表单整表单从观察树消失的问题)。0.10.0Playwright 改 peer dependency(>=1.63.0 <2)、tapAt(视口坐标点击,供 agent 的tap_at在模型指出的点落空时使用)、同 origin iframe 内节点改用顶层视口坐标。此外 0.11.0-canary 阶段还包含:
web({ browser })接受BrowserProvider(worker/attempt两种作用域、租约与释放、替换浏览器)、Role枚举扩充至tablist/grid/treeitem等 20 余个合成角色(getByRole('img')作为image别名)、web.frameLocator返回FrameScreen(frame 作用域的Screen,保留locator()与嵌套frameLocator()两个逃生舱)、键盘能力(keyboard.type/keyboard.press,无焦点目标时NOT_ACTIONABLE)、裸点指针动作(完整POINTER_ACTION_KINDS)、connect.reconnectEndpointCDP 传输恢复(持久浏览器跨断连保留页面与 context、校验 CDP 身份、已派发操作永不重复)、allowedOrigins全量移除(导航与 secret 填充不再按 origin 门控;credentials变{ username, password })、testIdAttribute下沉为web()选项等。七、当前实现的源码印证
CHANGELOG 描述的机制在源码中都有对应落点,便于读者按图索骥验证:
web()工厂与选项校验:packages/web/src/engine.ts 中web(options)依次执行:废弃键显式报错(allowedOrigins、video)→rejectUnknownKeys('web()', options, WEB_OPTION_KEYS)→BrowserProvider与connect互斥检查 →connect仅接受cdpEndpoint/reconnectEndpoint→ CDP 附着仅限 chromium →validateHeaders/validateBasicAuth/validateUserAgent/validateLocale/validateTimezoneId/validateInitScripts/validateScreencast→ 持久 context 模式(reconnectEndpoint或scope: 'attempt')下拒绝创建期选项。随后以defineEngine({ name: 'web', spiVersion: 1, platform: 'web', ... })注册完整生命周期钩子(prepare/finish/init/startAttempt/settleAttempt/endAttempt/dispose/observe/locate/perform)——其中settleAttempt钩子正是 0.11.0-canary 中“route/dialog 处理器失败不再被吞”修复引入的。- 浏览器安装 CLI:packages/web/src/bin.ts 转发到
cli.ts;packages/web/src/install.ts 用createRequire解析 pin 住的playwright-core/package.json并以其cli.js执行安装/下载,转发 SIGINT/SIGTERM 使取消的 CI 任务不留孤儿下载。 - 公共导出面:packages/web/src/index.ts 导出
web、surfaceOf、test(base.extend<{ browser: Browser }>())、describe与四个钩子,以及Browser、FrameScreen、WebRoute、WebScreencastOptions、WebInitScript、BrowserProvider系列类型——与 README 的“一个 import 注册全部”描述一致。 - 实现规模:packages/web/src 下约 35 个模块共 7500 余行,其中
surface.ts(1080 行,Playwright 驱动的主体)、browser.ts(794 行,browserfixture)、provider.ts(654 行,托管浏览器租约)、support.ts(490 行)、locators.ts(414 行)体量最大,对应 CHANGELOG 中观察、定位、网络与 provider 四大主题;测试位于 packages/web/tests(unit、integration、conformance、crosscheck四组)。
八、如何阅读这份 CHANGELOG:升级检查清单
这份 CHANGELOG 的信息密度很高,且 0.x 版本下 breaking 变更以 Minor 版本号发布。升级
@e2e-dev/web时建议按以下顺序核对:- 核对运行环境:
engines.node(当前^22.22.3 || >=24.8.0)与e2epeer(当前>=0.18.0 <1),二者不满足时 CLI 会拒绝启动;确认项目内.cts文件不含 ESM 值导入、module.exports风格的 CommonJS 文件改用.cts扩展名。 - 核对配置键:
web()与app块的未知键现在全部报错。已知废弃键:web({ allowedOrigins })、web({ video })(→screencast)、web({ url })等旧 app 字段(→ target 的app块)、顶层app配置键、screen.testIdAttribute(→web({ testIdAttribute }))、requires: ['web'](→'browser')。 - 核对依赖:移除自行安装的
playwright(0.12.0 起);import 路径使用@e2e-dev/web(0.11.0-canary 起),runner 包名为e2e(0.11.0-canary 起)。 - 核对行为语义:若测试依赖被修正的旧行为,重点检查可见性判定(
aria-hidden但被绘制的节点现在可见)、waitFor状态(拼错状态不再静默通过)、route 决策(continue语义变化)、getAttribute(原型键不再泄漏)、URL/文本模式(谓词函数被INVALID_ARGUMENT拒绝)、check()/uncheck()(点击后控件消失现在算成功)。 - 利用错误码定位:变更中大量引入
INVALID_CONFIG、INVALID_ARGUMENT、INVALID_LOCATOR、POLICY_DENIED、NOT_ACTIONABLE、LOCATOR_AMBIGUOUS、COLLECTION_ERROR等分类错误码,它们比泛化的ENGINE_FAILURE更能指向上游原因——这本身就是这个包近两个版本的主线之一:把“静默兜底”换成“指名报错”。
从源码结构看,这个包正沿着“与 Playwright 语义逐项对齐、错误分类逐项细化、契约校验逐项下沉到配置加载时”的路线收敛;CHANGELOG 中每一条修复几乎都引用了 Playwright 的对应行为作为对齐基准,阅读时把每条与 Playwright 文档对照,能最快理解其动机与边界。
赞- 测试
- 测试智能体
- 人工智能
- 浏览器控制
- CLI
点击查看免费下载【免费下载链接】e2e
Next generation e2e testing framework for web and mobile apps.
项目地址:https://gitcode.com/GitHub_Trending/e2e6/e2e相关推荐
e2e 框架的 @e2e-dev/mobile 变更日志精读:移动端测试引擎从 0.1.0 到 0.10.0 的 API 演进与设计动机
e2e 框架的 @e2e dev/mobile 变更日志精读:移动端测试引擎从 0.1.0 到 0.10.0 的 API 演进与设计动机 @e2e dev/mo
测试测试智能体人工智能浏览器控制CLI从 CHANGELOG 看 HashiCorp Packer 1.16.x 版本演进:新特性、破坏性变更与源码级解读
从 CHANGELOG 看 HashiCorp Packer 1.16.x 版本演进:新特性、破坏性变更与源码级解读 本文以仓库根目录 CHANGELOG.md
云原生DevOps运维Gin 版本演进全解:从 CHANGELOG 看 v0.2b 到 v1.12.0 的功能、性能与破坏性变更
Gin 版本演进全解:从 CHANGELOG 看 v0.2b 到 v1.12.0 的功能、性能与破坏性变更 Gin 的 CHANGELOG.md https:/
后端Web框架
上一篇:幻兽帕鲁存档编辑终极指南:3分钟掌握游戏数据自由下一篇:终极指南:5个步骤轻松配置Jellyfin MetaShark插件获取中文电影元数据
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考