Convex 演示应用浏览器测试实战用 Puppeteer 驱动无头 Chromium 守护前端行为【免费下载链接】convex-backendThe open-source reactive database for app developers项目地址: https://gitcode.com/gh_mirrors/co/convex-backend导读本文基于 convex-backend 仓库中的npm-packages/demo_browser_tests目录完整讲解该开源项目如何用 Puppeteer 驱动无头 Chromium对演示应用demo apps的浏览器内行为做端到端回归测试。读者将掌握如何正确配置 Puppeteer 浏览器二进制含 linux-arm64 平台的坑、如何在 CI 中排查失败现场截图 HTML dump 事件日志、如何为 AuthKit 登录这类第三方页面编写抗抖动的重试逻辑以及如何复用同一套浏览器自动化代码走完 Dashboard 和公开管理 APIPlatform的 OAuth 认证流程。测试集概览测试什么、放在哪里demo_browser_tests是仓库里专门用于「浏览器内行为验证」的测试目录不直接测后端 API而是用 Puppeteer 拉起无头 Chromium真实打开演示应用页面模拟用户点击、输入、登录再断言页面状态。正如 README 所述其目的就是 make sure that the in-browser behavior of our demo apps actually works。从 package.json 可以看到完整的测试清单与运行脚本npm script对应源码验证目标test-tutorialtutorial.ts快速入门聊天 Demo随机用户名渲染、发消息后列表回显test-users-and-authusers-and-auth.tsAuth0 登录流程真实第三方认证test-sessionssessions.ts会话类 Demotest-relational-data-modelingrelational-data-modeling.ts关系型数据建模 Demotest-create-react-appcreate-react-app.tsCRA 脚手架 Demotest-dashboard-production-provisiondashboard-production-provision.tsDashboard 生产环境开通流程test-dashboard-redirects-brand-new/test-dashboard-redirects-last-viewed对应 redirects 脚本Dashboard 重定向行为test-platformplatform.ts经 tsc 编译后公开管理 API 全链路技术栈方面TypeScript Puppeteer^24.0.0 CommanderCLI 参数解析构建方式为tsc编译到dist/后由 node 执行运行所需账号密码、URL 端口等信息通过命令行参数或环境变量传入。依赖里还引用了同仓库的convex-dev/platformworkspace:*说明管理 API 测试与 npm-packages 内的 Platform 客户端包是同一套代码。获取浏览器PUPPETEER_EXECUTABLE_PATH 与 linux-arm64 的坑README 开篇就点明一个实战中非常容易踩的坑postinstall会为 Puppeteer 下载一个 Chrome除非环境变量PUPPETEER_EXECUTABLE_PATH已经指向现成的浏览器。CI 的 arm64 runner 之所以必须设置这个变量是因为 Chrome for Testing 官方并不发布 linux-arm64 构建——此时下载会静默拿到一个 x86-64 二进制在 arm64 机器上根本无法执行shell 会把它当脚本重新解释报出令人困惑的错误chrome: 1: Syntax error: ; unexpected这段逻辑的源码实现位于 install-browser.mjs核心决策非常简单const provided process.env.PUPPETEER_EXECUTABLE_PATH; if (!provided) { // 未设置变量走 Puppeteer 自带下载x64 平台可行 execFileSync(puppeteer, [browsers, install, chrome], { stdio: inherit, shell: true, }); } else if (existsSync(provided)) { console.log(Using the browser at PUPPETEER_EXECUTABLE_PATH${provided}); } else { // 变量指向了不存在的文件直接报错退出避免带病运行 console.error(...); process.exit(1); }关键设计点以变量是否存在作为唯一判据x64 的 AMI 不设置该变量Puppeteer 自带的下载流程可以正常工作arm64 的 AMI 则把 chromium 打进镜像并导出PUPPETEER_EXECUTABLE_PATHpuppeteer.launch()会直接读取该变量。一个变量同时覆盖两种环境逻辑简洁。变量存在但文件缺失时立即process.exit(1)不做降级下载的兜底因为 arm64 上降级下载得到的 x86-64 二进制依然无法运行宁可让安装步骤显式失败。linux-arm64 工作站上的做法README 明确给出自行安装一个 chromium 并指向它export PUPPETEER_EXECUTABLE_PATH/path/to/chromium这一点在 arm64 开发机、树莓派类设备或任何无 x64 二进制可用的环境里都适用。浏览器驱动的公共底座common.ts 的四个核心能力所有测试共享的运行时底座在 common.ts它提供了四个被反复复用的能力1. withBrowser生命周期与失败现场保全withBrowser(testFn)统一负责puppeteer.launch({ headless: true })、创建页面、执行测试函数、最后在finally中关闭页面与浏览器。它的精髓在失败分支一旦测试抛错会立刻记录当前 URL 与页面标题用于区分目标页面渲染为空与我们根本没到目标页面并同时落盘两类现场证据const screenshot path.join(outDir, ${prefix}.png); await page.screenshot({ path: screenshot, fullPage: true }); const html path.join(outDir, ${prefix}.html); fs.writeFileSync(html, await page.content());即全页截图fullPage 页面 HTML dump二者互补截图回答页面长什么样HTML 回答DOM 里到底有什么。这正是 README 中 Failures write a screenshot and a page HTML dump next to the logs 的实现出处。2. noteBrowserEvent让成功的重试也有迹可循export function noteBrowserEvent(message: string) { const outDir process.env.SCREENSHOT_DIR || .; const testName path.basename(process.argv[1] || unknown, .js); fs.appendFileSync( path.join(outDir, browser-events.log), ${new Date().toISOString()} ${testName} ${message}\n, ); }它把值得记录的浏览器事件追加写入browser-events.log默认目录取SCREENSHOT_DIR环境变量否则当前目录O_APPEND追加模式保证 xdist 并行 worker 交错写入时单行不被打断。注释里解释了为什么要这样做pytest 在 fd 层捕获了子进程的 stdout/stderr只在测试失败时才打印所以一个重试后成功的用例恰恰是最想统计、却最容易被吞掉的场景——事件日志绕开了控制台把这类信号写进 CI 归档的 artifactintegration.yml 会上传smoke/test_tempdir/*.log。3. assertDivWithContent面向内容的轮询断言export const assertDivWithContent async (page, selector, innerText) { await page.waitForFunction( (selector: string, innerText: string) { const divs [...document.querySelectorAll(selector)] as HTMLDivElement[]; return divs.some((div) div.innerText innerText); }, {}, selector, innerText, ); };注意其实现细节waitForFunction的第一个参数不是闭包所以被捕获的变量必须通过后面的参数显式传入源码注释明确提醒了这一点。它的语义是轮询直到页面上存在某个选择器命中、且innerText精确匹配的元素天然适合 React/Vite 这类异步渲染页面——不依赖固定等待而是等待 DOM 真正达成预期状态。4. sleep简单可靠的节流工具export const sleep (durationMs: number) new Promise((r) setTimeout(r, durationMs));用于重试间隔、观察窗口等场景。在 CI 里查明到底发生了什么README 专门用一节讲 CI 排障流程核心手段如下失败现场测试失败时截图*.png与页面 HTML dump*.html会写入日志旁的目录默认由SCREENSHOT_DIR控制见 common.ts 中outDir的取值逻辑命名格式为${testName}-failure-${Date.now()}。事件日志值得注意的浏览器事件会追加到browser-events.log最终全部落入该次运行的smoke logsartifact。Flake 量化手段直接 grep 事件日志即可统计两类关键事件grep login-form-recovered browser-events.log # 被重试吸收掉的空白页抖动次数 grep login-form-gave-up browser-events.log # 重试也救不回来的次数README 特别强调不能用控制台输出来做这件事因为 pytest 捕获了 stdout/stderr 且只对失败用例打印一个成功的重试会在控制台上不留任何痕迹——这正是noteBrowserEvent写入独立事件日志的根本原因。AuthKit 登录表单一个为第三方页面写的抗抖动登录器Dashboard 相关测试dashboardHelpers.ts集中体现了与第三方托管登录页AuthKit对抗的实战经验也是 README login-form-recovered / login-form-gave-up 两个事件名的来源。dashboardHelpers.ts 中值得细读的设计有三处1. 分级超时的重试策略const LOGIN_FORM_ATTEMPT_TIMEOUTS_MS [60000, 20000, 20000];首轮给足 60 秒因为第一次访问还要额外支付 Next.js 按需编译路由的开销重试面对的是已预热warm的服务器所以收紧到 20 秒。每次失败会先noteBrowserEvent(login-form-blank ...)最后一轮仍失败才记录login-form-gave-up并抛错——而中间任何一次成功后若attempt 0都会记录login-form-recovered after N blank render(s)把AuthKit 从未白屏与白屏了但我们救回来了这两类情况精确区分开。2. 用键盘 Enter 代替鼠标点击async function pressEnterOn(page: Page, selector: string) { const element await page.waitForSelector(selector, { visible: true }); await element!.focus(); await page.keyboard.press(Enter); }AuthKit 托管的 UI 存在一种布局对按钮自身包围盒中心做合成鼠标点击时命中测试最终落在html上点击被吞掉、表单永不提交。因此这里统一走聚焦 回车的键盘路径。3. 不等待跳转而是等待结果AuthKit 的登录表单是客户端提交登录成功前不会产生导航。所以waitForSignInOutcome用page.waitForFunction轮询三类终态window.location.host离开 AuthKit 主机登录成功、出现[data-typeerror]凭据被拒、出现 Skip for now 按钮无头浏览器无法创建 passkey需要跳过录入插页。loginToDashboard把这些串成完整流程访问 Dashboard → 输入邮箱回车 → 输入密码回车 → 轮询结果 → 处理 passkey 插页 → 校验 outcome。方法开头还会把默认超时统一提高到 60 秒以降低 CI 抖动。典型用例剖析基础 Demo 测试tutorial.tstutorial.ts 是最典型的聊天 Demo端到端验证可视为其他 Demo 测试的模板page.setDefaultTimeout(120_000); // CI 上 30s 默认导航超时不够用 page.setDefaultNavigationTimeout(120_000); await page.goto(http://127.0.0.1:${argv[2]}, { waitUntil: domcontentloaded }); await page.waitForSelector(.badge); // 等待用户徽章渲染 // 断言用户名形如 User xxx随机匿名用户 await assertDivWithContent(page, ul, ); // 初始无消息 await page.type(form input[placeholder], al pastor rocks); await page.click(form input[typesubmit]); await assertDivWithContent(page, span, al pastor rocks); // 消息回显流程清晰导航端口由argv[2]传入→ 等.badge渲染 → 校验匿名用户名前缀 → 断言消息列表为空 → 发消息 → 断言消息回显。注意选择器刻意使用placeholder、typesubmit这类结构特征而非元素 id与 README 的 Current Oddities 一节呼应。真实第三方认证users-and-auth.tsusers-and-auth.ts 演示了与 Auth0 的完整交互打开本地 Demo → 点击登录按钮 →waitForNavigation到 Auth0 登录页 → 填入测试账号邮箱密码 → 点击 Continue → 等待跳回 Demo → 断言页面出现 Logged in as jamieconvex.dev。README 明确说明这用的是 jamie 在 Auth0 上为这个测试应用专门创建的测试账号属于已知限制之一见下文怪癖与限制。Dashboard 测试README 只用一句话宣布 There are dashboard tests here too!源码层面由三类脚本体现生产环境开通dashboard-production-provision.ts重定向行为dashboard-redirects-brand-new.ts与dashboard-redirects-last-viewed.ts分别验证新用户/上次访问用户的 Dashboard 落地重定向公共底座上述脚本都通过 dashboardHelpers.ts 的loginToDashboard完成登录DASHBOARD_URL固定为http://localhost:6789。平台公开管理 API测试从 OAuth 到 Token 到 API 全链路README 的最后两节描述的是同一件事的两步先用 Dashboard 浏览器自动化辅助代码走一遍 OAuth 流程拿到团队 token再用这个 token 调公开管理 API。第一步OAuth 流程。oauth_flow.ts 是完整的 OAuth 授权码流程演练先loginToDashboard正常登录 Dashboard再导航到OAUTH_URL环境变量给出的授权 URL等待 Authorize 按钮并点击最后用waitForFunction等待重定向回到localhost:8080/callback。两个实战细节值得注意page.goto在登录重定向链仍在途时会抛net::ERR_ABORTED所以导航被包在 try/catch 里且对返回 4xx/5xx和抛错两种情况都做重试最多 2 次、间隔 3 秒用法约定node dist/oauth_flow.js username password同时要求设置OAUTH_URL。第二步获取团队 token。team_token.ts 复用loginToDashboard登录后直接访问${DASHBOARD_URL}/t/engineering-smoketests/settings/access-tokens页面自动化点击 Create Token、填写input#tokenName、提交表单、等待 token 出现在列表中再点 Show 从span::-p-text(eyJ)提取以eyJ开头的 JWT token 打印到 stdout供调用方捕获。第三步管理 API 冒烟。platform.ts 是纯 API 层的冒烟脚本通过 package.json 的test-platform运行它使用convex-dev/platform的createManagementClient/createDeploymentClient验证了公开管理 API 的完整生命周期通过GET /token_details解析团队 token 对应的teamId也支持-t显式指定GET /teams/{team_id}/projects列出项目POST /teams/{team_id}/create_project创建 dev 类型项目并从响应中取得deploymentName与deploymentUrlPOST /deployments/{deployment_name}/create_deploy_key创建部署密钥用部署客户端设置环境变量FOOBAR/update_environment_variables并回读校验/list_environment_variables测试 canonical URL 的自定义/回读/删除并断言删除后恢复默认值最后POST /projects/{project_id}/delete清理现场。整个脚本从建项目到删项目形成闭环且每个 API 响应都检查response.ok失败即抛错退出退出码非 0 会被上层判定失败。已知怪癖与限制README 的自述README 用专门一节坦承当前测试的两点不足这对接手维护该测试集的人是最重要的信息选择器脆弱测试用到的选择器有点怪异、间接依赖当前 Demo 代码的结构如 placeholder、按钮文本而非元素 id因此比较脆弱。仓库目前的取舍是与其为了测试往 Demo 代码里塞一堆 Demo 本身用不到的 id不如接受测试选择器的脆弱——这是刻意选择的权衡不是疏漏。真实第三方依赖users-and-auth使用 Auth0 做认证测试确实覆盖了它但用的是 jamie 在 Auth0 上为这个测试应用专门创建的测试账号。这意味着该用例依赖外部账号与第三方服务的可用性也解释了 dashboardHelpers 里大量重试逻辑的存在意义——第三方页面AuthKit/Auth0的行为不完全可控需要用轮询、重试、键盘事件与事件日志把抖动降到可量化、可追溯的程度。小结一套可复制的浏览器测试方法论从demo_browser_tests这个目录可以提炼出一套完整、可复制的浏览器端到端测试方法论环境以PUPPETEER_EXECUTABLE_PATH为开关一套安装逻辑同时覆盖 x64 与 arm64见 install-browser.mjs底座withBrowser统一管理生命周期并自动保全失败现场noteBrowserEvent让成功的重试在 CI 里也有据可查见 common.ts第三方对抗分级超时重试、键盘事件代替鼠标点击、等待结果而非等待跳转见 dashboardHelpers.ts断言面向内容的轮询断言而非固定 sleep兼顾稳定性与速度CI 排障smoke logsartifact 聚合截图、HTML dump 与browser-events.log用 grep 两个关键字即可量化 flake 吸收率。这套模式不仅服务于 convex-backend 的 Demo 验证对任何需要用无头浏览器守护前端真实行为的项目都有直接借鉴价值。【免费下载链接】convex-backendThe open-source reactive database for app developers项目地址: https://gitcode.com/gh_mirrors/co/convex-backend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
财务净现值计算公式:3个坑点让你面试必问变加分 财务净现值计算公式:3个坑点让你面试必问变加分 刚升级完财务系统,发现原来手算的NPV和代码跑出来的对不上,甚至直接报错。这种版本升级后 API… · 2026/9/23 9:20:32
CLAUDE.md 文件爆火背后:一份 Markdown 配置如何让 Claude Code 少走弯路 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/23 9:20:24
乌龟量化新手避坑:5招搞定版本升级与性能优化 乌龟量化新手避坑:5招搞定版本升级与性能优化 刚把旧代码跑起来,一升级库版本,满屏的 AttributeError 和 ImportError 是不是让你头皮发麻? 别慌,这不是你代码写得烂,是 乌龟量化 这类回测框架在迭代中为了… · 2026/9/23 10:16:13
现金宝安全吗?3个坑让代码崩盘,这份保姆级教程救急 现金宝安全吗?3个坑让代码崩盘,这份保姆级教程救急 代码从网上复制下来,本地一跑直接报错,日志里全是红字,看着就头大。这种“复制粘贴即死”的尴尬,相信每个后端老手都经历过。别急,今天这篇保姆级教程,咱们不整虚的,直接上手拆解“现金宝”这类金… · 2026/9/23 10:16:06
Oracle数据库编程实战:用TaoToken统一Key排查异常订单的配置与验证 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/23 10:15:47
Novip源码解析:新手避坑指南,3步搞定环境配置 Novip源码解析:新手避坑指南,3步搞定环境配置 刚毕业进嵌入式组,老板甩来个“novip”项目,说这玩意儿是内部封装的驱动接口,让你先跑通Demo。结果你打开GitHub,连README都没看懂,配置环境时编译器报了一堆“undefin… · 2026/9/23 10:15:47
SSM老项目实战:JSP银行叫号系统源码环境搭建与避坑指南 简介:这是一套面向Java Web初学者与课程设计需求的银行排队叫号系统完整项目,采用SSM框架搭配JSP技术实现,运行于JDK1.8与Tomcat7环境,数据库使用MySQL 5.7。项目涵盖取号、叫号、窗口管理与业务统计等典型银行场景模块࿰… · 2026/9/23 10:15:47
搞懂6589避坑指南:后端视角下的水利工程数据解析 搞懂6589避坑指南:后端视角下的水利工程数据解析 刚接手水利工程项目的后端开发,打开IDE满屏红色的StackTrace报错,看着那一串 NullPointerException 和 IndexOutOfBoundsException… · 2026/9/23 10:15:40
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29