ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

tldraw dotcom E2E 场景测试规范:从冒烟测试走向多角色用户场景

tldraw dotcom E2E 场景测试规范:从冒烟测试走向多角色用户场景 tldraw dotcom E2E 场景测试规范:从冒烟测试走向多角色用户场景【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. Worlds best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw本篇介绍 tldraw 生产应用(dotcom)的 E2E 测试规范。tldraw 正在把 dotcom 的 Playwright 测试从孤立的 UI 冒烟检查逐步迁移为贴近真实用户流程的场景测试(scenario tests),覆盖共享链接、工作区协作、访客权限等端到端行为。读完后你将掌握:如何按仓库约定编写*.scenario.spec.ts场景测试、如何使用多角色 actor fixture 与场景命名空间做数据隔离、如何选择合适的就绪等待与断言方式,以及如何正确运行/调试各套测试命令。背景:为什么从冒烟测试迁移到用户场景规范文档 README 开宗明义:dotcom 的测试正在向用户场景靠拢,而不是孤立的 UI 冒烟测试。新增的协同(dotcom)覆盖应当优先使用fixtures/scenario-test.ts中的场景 fixture。动机在于:共享、协作、成员权限这类行为需要多个用户同时操作、互相观察才能验证,单窗口、单用户的冒烟断言无法证明协作语义是否正确。仓库中的现状与规范一致:apps/dotcom/client/e2e/tests 下既有新式场景测试(auth.scenario.spec.ts、sharing-live.scenario.spec.ts、legacy-routes.scenario.spec.ts、workspaces-feature-coverage.scenario.spec.ts等),也保留着位于tests/smoke子目录的旧式冒烟套件。测试项目划分:chromium-scenarios 与 legacy chromium两套套件由 playwright.config.ts 中的文件名匹配规则区分:const scenarioTestMatch /.*\.scenario\.spec\.ts/ // Legacy smoke specs live in e2e/tests/smoke and are intentionally separate from the default // scenario runner. See e2e/README.md. const smokeTestMatch /tests\/smoke\/.*\.spec\.ts/项目配置对应两个 runner:chromium-scenarios:匹配*.scenario.spec.ts,并显式开启fullyParallel: true(见 playwright.config.ts)。这是规范要求的场景文件跑在chromium-scenarios项目下、全并行的落地点;chromium:仅匹配tests/smoke下的旧式 spec,使用基于重置(reset-based)的流程,有意与默认 runner 分开,并且不在 CI 上运行。规范明确要求:随着后续工作解锁,应把tests/smoke中的覆盖逐步迁移到场景测试中;两个项目都依赖global-setup项目。该 setup(global.setup.ts)调用clerk/testing/playwright的clerkSetup(),为测试准备 Clerk 测试账号环境。配置文件还有几个与场景测试直接相关的全局设置:CI 下retries: 2、forbidOnly: true;webServer在 CI 中用VITE_PREVIEW1 yarn dev-app启动完整 dotcom 开发栈(process-compose 拉起 postgres → migrate → zero-cache → workers → client,见 apps/dotcom/process-compose.yaml),本地则复用已运行的yarn preview-app;globalTeardown仅在 CI 生效,用于测试后拆除容器和端口。运行命令速查命令定义在 apps/dotcom/client/package.json 的scripts中,仓库根目录 package.json 也提供了转发入口:命令(仓库根目录)等价命令(dotcom workspace)说明yarn e2e-dotcomyarn workspace dotcom e2e常规本地/CI 套件,实际只运行chromium-scenarios项目;执行前会rm -rf e2e/.auth清除已存储的登录态—yarn workspace dotcom e2e-scenarios只运行场景套件,但不清除已存储的 auth(增量调试更快)yarn e2e-dotcom-smokeyarn workspace dotcom e2e-smoke运行保留的 legacy 冒烟套件(chromium项目,tests/smoke下的 spec),本地专用,CI 不跑yarn e2e-dotcom-allyarn workspace dotcom e2e-all同时运行新旧两个项目,用于对比新旧覆盖;同样先清 authworkspace 脚本的实际实现示例(节选自 apps/dotcom/client/package.json):e2e: rm -rf e2e/.auth NODE_OPTIONS--no-strip-types yarn playwright test --projectchromium-scenarios, e2e-scenarios: NODE_OPTIONS--no-strip-types yarn playwright test --projectchromium-scenarios, e2e-smoke: rm -rf e2e/.auth NODE_OPTIONS--no-strip-types yarn playwright test --projectchromium, e2e-all: rm -rf e2e/.auth NODE_OPTIONS--no-strip-types yarn playwright test --projectchromium --projectchromium-scenarios此外还有配套脚本:e2e-debug/e2e-all-debug(Playwright Inspector 调试)、e2e-ui/e2e-all-ui(UI 模式)、e2e-x10系列(通过--repeat-each10重复 10 遍,用于暴露并发下的偶发失败)。注意e2e与e2e-all都会先删掉e2e/.auth目录——该目录存放 actor 的登录态(storageState)文件,删除后首次运行会重新登录(见下文账号池)。场景 fixture:命名 actor 与场景命名空间场景 fixture 定义在 fixtures/scenario-test.ts,它扩展 Playwright 的base测试,暴露的 fixture 与规范逐条对应:命名 actor:owner、member、visitor三个 fixture。每个 actor 是一个DotcomActor实例,封装了独立的BrowserContext、Page,以及一组页面对象(Sidebar、Editor、HomePage、ShareMenu、DeleteFileDialog、ErrorPage、ImportHelper、SignInDialog、WorkspaceInviteDialog,见 scenario-test.ts)。owner与member是已登录账号,visitor是未登录的无痕上下文;数据按场景命名空间隔离:scenario.name(label)返回${this.id} ${label},其中id由getScenarioId生成——取测试完整标题的 slug(截断到 24 字符) 全标题的 sha1 前 6 位哈希,再拼上 CI 运行号GITHUB_RUN_ID、并行 worker 下标、repeatEachIndex与重试次数(见 scenario-test.ts)。因此规范强调文件和/工作区要用scenario.name命名:不同运行、不同并行 worker 之间数据天然互不冲突;上下文生命周期归 fixture 所有:actorsfixture 在测试结束后自动调用actors.closeAll()关闭所有 actor 上下文,owner/member/visitor这三个便捷 fixture 也各自包在testUse中,测试代码不需要手动清理浏览器上下文;账号与 worker 绑定:每个并行 worker 有自己的场景用户池下标。getScenarioUserIndex(parallelIndex)返回4 parallelIndex(SCENARIO_USER_POOL_START 4),超出NUMBER_OF_USERS(见 consts.ts,共 8 个 Clerk 测试账号)时会直接抛错提示增加测试账号或降低 worker 数。这正是规范中两条约定在源码中的体现:场景 actor 使用比 legacychromium项目更靠后的 Clerk 测试账号——SCENARIO_USER_POOL_START 4让两个项目即使在同一命令里一起跑(e2e-all),也各自使用不相交的账号;场景测试中不要重置整个数据库或共享用户——setupAndCleanup自动 fixture 只调用Database.reset(),而 fixtures/Database.ts 的reset()仅清理本 worker 绑定的两个 Clerk 测试账号(通过调用 worker 提供的测试接口POST /api/app/__test__/user/:id/prepare-for-test),而非清空全库。工作区/文件则靠scenario.name的命名空间天然隔离。一个典型测试的开头大致如下(按 fixture 约定编写):import { test, expect } from ../fixtures/scenario-test test(owner 与 member 在同一文件中协作, async ({ owner, member, scenario }) { const file await scenario.createSharedFile(owner, edit) await member.goto(file.sharedUrl) await scenario.createRectangle(owner) // 在已打开的窗口之间做实时断言,而不是 reload await member.expectCollaboratorCount(2) })场景 fixture 的常用搭建 API规范要求用scenario对象的方法完成常见前置搭建,而不是在每个测试里手写一遍 UI 流程。从 DotcomScenario 类 可以确认全部方法签名:scenario.createPersonalFile(actor, fileName?):确保侧边栏打开、切回主工作区(如可用),新建文档并断言其激活,返回{ fileName, url };scenario.createSharedFile(actor, linkType?, fileName?):先建个人文件,再通过共享菜单设置链接类型(edit|view)并复制共享链接,返回附带sharedUrl与linkType的结果;scenario.createGuestEditFile(owner, guest, fileName?)/scenario.createGuestViewFile(owner, guest, fileName?):owner 建共享文件后,让 guest actor 直接打开共享 URL——这是已登录访客以共享链接身份编辑/查看场景的标准入口;scenario.createPublishedFile(actor, fileName?):创建文件并发布,返回publishedUrl;scenario.importFileFromUrl(actor, url?):通过ImportHelpermock 远程 URL 并导航,验证/f/路由加载完成后返回文件名与 URL;scenario.downloadFileFromSidebar(actor, fileName):悬停侧边栏文件项、点 Download 菜单项,捕获 Playwright 的Download事件;scenario.setSharedLinkType(actor, linkType):在邀请页签切换共享开关,支持edit、view、no-access(关闭共享开关),并通过下拉框断言最终标签为 Editor/Viewer;scenario.createWorkspaceWithMember({ owner, member, workspaceName?, fileName? }):owner 侧通过应用内 mutator 建工作区与文件(等待 server 确认,见 scenario-test.ts),复制工作区邀请链接,member 打开邀请链接接受,并断言侧边栏出现工作区与文件;注意工作区名会按MAX_WORKSPACE_NAME_LENGTH截断,与生产 mutator 的钳制行为保持一致;scenario.createWorkspaceWithRemovedMember(...):在前者基础上,通过工作区设置菜单把 member 移除,用于验证成员被移除后访问受限的行为;scenario.createLegacyRouteFixture(actor):规范单独点名的遗留路由(/r、/ro、/v、/s及 history 路由)搭建方法。它向后端的POST /api/app/__test__/legacy-room调试接口发请求(带 30 次重试),创建命名空间化的、仅调试用的 worker 数据,而不是依赖共享的类生产 fixture,最后返回room、readonly、legacyReadonly、snapshot、history、historySnapshot六个可直接访问的 URL。数据准备的边界:直接写库只在必要时规范对直连数据库的用法划了明确边界:仅当前置条件通过 UI 做成本高或根本做不到时(例如开启某个工作区 feature flag),才允许直接做数据库搭建;被测行为本身必须走 UI 操作完成。从源码结构看这一边界是有支撑的:fixture 里确实保留了数据库直连能力——Database类用 Kysely pg 连接本地 Postgres(连接串指向127.0.0.1:6432,见 fixtures/Database.ts),提供getUserIdByEmail等查询能力;createPendingWorkspaceInvite也需要它按 email 查 member 的用户 ID。同时createLegacyRouteFixture走的是 worker 暴露的__test__调试端点,而非手工插库——用测试专用接口创建最小调试数据与用 UI 驱动被测行为的组合,正是这条约定的具体形态。同理,actor 的登录态也通过文件缓存:每个并行 worker 首次运行时,ensureStorageState(scenario-test.ts)会真实登录huppyclerk_testNtldraw.com/suppyclerk_testNtldraw.com并把 storageState 写入e2e/.auth/目录(文件名规则见 fixtures/helpers.ts),后续 worker 直接复用。这就是规范中稳定的 Clerk 测试账号被 worker 复用的实现。就绪等待:用 actor 助手替代 sleep规范要求通过 actor 助手和 page objects 等待就绪,避免新增 sleep(除非产品本身有意的等待)。actor.goto(url)的默认行为(scenario-test.ts)是调用waitForAppReady(),它串行等待五个条件:async waitForAppReady() { await this.homePage.isLoaded() await this.waitForEditorReady() await this.waitForAuthLoaded() await this.waitForAppStoreHydrated() await this.waitForFileRoomConnected() await this.waitForVisitorAccessMetadata() }各项含义(均通过page.waitForFunction轮询页面内window.app/window.editor状态):waitForAuthLoaded:登录态已加载。签名用户要求app.getUser()存在;未登录则要求页面上出现登录按钮或侧边栏开关等 DOM 锚点,并区分 app 路由(/、/f/)与遗留只读路由(/ro/、/v/);waitForAppStoreHydrated:仅对签名 actor 有意义,等待app.getUserFileStates()返回数组,即应用级 store 已从远端水合;waitForEditorReady:editor 已挂载并拥有当前页(editor.getCurrentPageId()),遗留只读路由下则退化为画布已挂载 路由前缀匹配;waitForFileRoomConnected:editor store 的连接状态为undefined或synced-remote,即文件房间(sync room)已连上远端;waitForVisitorAccessMetadata:以editor.getIsReadonly()能返回布尔值作为探针,证明挂载的 editor 已经拿到了当前访问模式(对未登录访客与已登录 guest 均适用);另有waitForSessionClosed(timeout 20000):轮询editor.getCollaborators().length直到为 0,用于断言对方已离线/会话已关闭。规范建议:当测试需要证明某个特定状态迁移时,应使用更窄的单一助手(waitForAuthLoaded、waitForAppStoreHydrated、waitForEditorReady、waitForFileRoomConnected、waitForVisitorAccessMetadata、waitForSessionClosed),而不是每次都用全量的waitForAppReady。断言风格:优先跨窗口实时断言规范在断言上给了两条原则:优先在已经打开的多个窗口之间做实时断言(live assertions)。reload 型检查对验证持久性仍然有用,但不应是协作、权限、成员行为的唯一证据。fixture 为此提供了配套工具:getCollaboratorCount()/expectCollaboratorCount(count, timeout)与getIsReadonly()/expectReadonly(readonly, timeout)(scenario-test.ts)都用expect.poll轮询真实 editor 状态——例如 owner 在一个窗口画矩形、member 在另一个已打开的窗口轮询协作者数量或形状,不需要各自刷新页面;旧式辅助expectBeforeAndAfterReload(fixtures/helpers.ts)仍然保留:它在 reload 前后各执行一次断言,中间留了 100ms 让乐观更新传播到服务端。这是reload 作为持久性证据的规范工具,但它不携带登录上下文切换,适合单窗口场景。Legacy 冒烟套件与迁移策略规范把 legacy 套件的定位说得非常明确:tests/smoke下的 spec 仍然使用基于重置的chromium项目,与默认 runner有意分离;不在 CI 上运行,避免拖慢主流程;迁移方向是:把tests/smoke的覆盖逐个改写成场景测试(as the follow-ups below are unblocked)。对实际维护者的含义是:新增覆盖永远写到*.scenario.spec.ts;只有当某条冒烟覆盖暂时无法在场景框架下表达(通常卡在 auth 边界,见下节)时,才留在tests/smoke,并把它视为待迁移项而非稳定资产。新旧对比可用yarn e2e-dotcom-all同时跑两个项目。Auth 后续迁移清单文档最后列出仍由 issue #9185 跟踪的 auth 边缘场景,并明确要求在具备 Clerk 层 mock 或无需逐测试重置共享账号的稳定账号状态之前,不要把它们放进场景项目。迁移分组有四项,值得作为哪些场景测试写法不成熟的参照:法务同意与 analytics 同意(auth legal acceptance and analytics consent):当前依赖对路由层 Clerk 响应的 patch;应替换为稳定的 Clerk 测试账号状态,或在 Clerk client 边界做 mock;验证与重发行为(verification and resend):Clerk 网络故障、畸形响应、冷却(cooldown)行为、验证码输入行为,需要在不依赖共享已登录用户清理的前提下隔离测试;OAuth 流程:需要一个可演练 Google 登录与法务同意的 Clerk/OAuth 边界,且不触发外部重定向;Auth 响应边缘情况:完整会话、不一致的missing_fieldspayload、缺少 email-code factor 数据等,需要等到能在路由层之下 mock Clerk 响应后才可纳入。小结:编写一个新场景测试的检查清单综合规范与源码,一个新*.scenario.spec.ts应满足:文件名以.scenario.spec.ts结尾,从 fixtures/scenario-test.ts 导入test/expect;使用owner/member/visitor命名 actor,文件与工作区一律用scenario.name(label)命名;前置搭建优先用scenario.createSharedFile/createGuestEditFile/createWorkspaceWithMember/createLegacyRouteFixture等现成方法;不重置全库、不重置共享用户;直连数据库只用于UI 做不了或成本过高的前置条件(如工作区 flag);就绪等待走actor.goto/ 细粒度waitFor*助手,不写裸sleep;协作、权限、成员行为优先跨窗口实时断言,expectBeforeAndAfterReload只作为持久性的补充证据;用yarn workspace dotcom e2e-scenarios验证(不清 auth),交付前用yarn e2e-dotcom走完整 CI 等价路径。【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. Worlds best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进