
SurfSense 的 Playwright E2E 与 CI/CD 集成实战GitHub Actions、Docker、Sharding 与报告体系【免费下载链接】SurfSenseOpen-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP server. Join our Discord: https://discord.gg/ejRNvftDp9项目地址: https://gitcode.com/GitHub_Trending/su/SurfSense本文为 SurfSense 的 E2E 测试基础设施指南系统讲解如何将 Playwright 接入 GitHub Actions 与 Docker 环境覆盖分片sharding、多 Reporter 配置、环境变量与 Secrets 管理、浏览器/依赖缓存与基于标签的测试过滤。读完之后你既能掌握一套可复制到任何项目的 Playwright CI/CD 标准方案也能从 SurfSense 仓库的真实工作流.github/workflows/e2e-tests.yml、surfsense_web/playwright.config.ts、docker/docker-compose.e2e.yml看到这套方案在「Next.js FastAPI Celery Postgres Redis」全栈上的落地细节。一、GitHub Actions 工作流Playwright 官方推荐的 CI 接入方式是 GitHub Actions。SurfSense 仓库中的 E2E 测试工作流 就是在该模式基础上、针对全栈 E2E 场景做了扩展的实例。1.1 基础工作流最小可用的工作流包含检出代码、安装 Node 依赖、安装 Playwright 浏览器、运行测试、上传 HTML 报告。标准形态如下# .github/workflows/playwright.yml name: Playwright Tests on: push: branches: [main] pull_request: branches: [main] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 22 cache: npm - name: Install dependencies run: npm ci - name: Install Playwright browsers run: npx playwright install --with-deps - name: Run Playwright tests run: npx playwright test - uses: actions/upload-artifactv4 if: ${{ !cancelled() }} with: name: playwright-report path: playwright-report/ retention-days: 30几个关键点的说明timeout-minutes: 60防止测试卡死无限占用 runnercache: npm让setup-node自动缓存node_modulespnpm 项目则改用pnpm/action-setup store 缓存见 2.4 节npx playwright install --with-deps会同时安装浏览器二进制与系统依赖库最后的upload-artifact使用if: ${{ !cancelled() }}确保被手动取消的运行也能保留报告用于排查。1.2 带 Sharding 的工作流测试套件变大后可将用例切分为多个 shard 并行执行每个 shard 独立输出 blob 报告最后由merge-reports作业合并name: Playwright Tests on: push: branches: [main] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest strategy: fail-fast: false matrix: shardIndex: [1, 2, 3, 4] shardTotal: [4] steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 22 cache: npm - name: Install dependencies run: npm ci - name: Install Playwright browsers run: npx playwright install --with-deps - name: Run Playwright tests run: npx playwright test --shard${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - name: Upload blob report if: ${{ !cancelled() }} uses: actions/upload-artifactv4 with: name: blob-report-${{ matrix.shardIndex }} path: blob-report retention-days: 1 merge-reports: if: ${{ !cancelled() }} needs: [test] runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 22 cache: npm - name: Install dependencies run: npm ci - name: Download blob reports uses: actions/download-artifactv4 with: path: all-blob-reports pattern: blob-report-* merge-multiple: true - name: Merge reports run: npx playwright merge-reports --reporter html ./all-blob-reports - name: Upload HTML report uses: actions/upload-artifactv4 with: name: html-report path: playwright-report retention-days: 14要点fail-fast: false让一个 shard 失败不取消其他 shard保证能拿到完整的失败清单shard 上传的 blob 报告retention-days: 1即可中间产物合并后的 HTML 报告保留更久merge-reports作业通过needs: [test]等待全部 shard 完成后用download-artifact的pattern: blob-report-*merge-multiple: true把所有 shard 的 blob 归拢到一个目录再合并。1.3 容器化运行如果不想在 runner 上安装系统依赖可以直接在 Playwright 官方镜像中运行镜像版本应与package.json中的playwright/test版本匹配jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest container: # Use latest or more appropriate playwright version (match package.json) image: mcr.microsoft.com/playwright:v1.40.0-jammy steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 22 cache: npm - name: Install dependencies run: npm ci - name: Run tests run: npx playwright test env: HOME: /root注意HOME: /root这一行容器内 Playwright 把浏览器装到$HOME/.cache/ms-playwright而 GitHub Actions 的 workspace 与缓存目录路径默认假设与裸 runner 一致显式设置HOME可避免浏览器路径解析问题。二、Docker 方案2.1 Dockerfile官方镜像自带浏览器与系统依赖是 E2E 容器的首选基座FROM mcr.microsoft.com/playwright:v1.40.0-jammy WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . CMD [npx, playwright, test]先把 lockfile 与包清单单独 COPY 并安装利用 Docker 层缓存让「依赖未变时不重装」成立是标准的多层优化。2.2 Docker Compose测试容器与被测应用同栈当 E2E 需要访问本地起的应用服务时可以让测试容器与应用容器组成一个 compose 栈# docker-compose.yml version: 3.8 services: playwright: build: . volumes: - ./playwright-report:/app/playwright-report - ./test-results:/app/test-results environment: - CItrue - BASE_URLhttp://app:3000 depends_on: - app app: build: ./app ports: - 3000:3000两个细节值得注意报告与test-resultstrace、截图、视频通过 volume 挂载回宿主机容器--rm删除后产物不丢BASE_URLhttp://app:3000使用容器网络内的服务名而非localhostCItrue则触发 Playwright 的 CI 模式headless、禁用交互等。2.3 运行命令# Build and run docker build -t playwright-tests . docker run --rm -v $(pwd)/playwright-report:/app/playwright-report playwright-tests # With docker-compose docker-compose run --rm playwright2.4 SurfSense 的实际做法Docker 负责后端栈Playwright 跑在宿主机SurfSense 的 E2E 场景比单容器更复杂被测对象是「Next.js 前端 FastAPI 后端 Celery worker Postgres Redis」的全链路。仓库的 E2E 工作流 采取的策略是用 Docker Compose 起一个隔离的hermetic后端栈Playwright 本体跑在宿主机上。后端栈定义在 docker/docker-compose.e2e.yml其核心设计包括网络级断网db/redis/celery_worker只挂在internal: true的 bridge 网络上容器在 L3 层面就无法访问外网backend额外挂一个普通ingress桥接网仅供宿主机访问:8000。这与 1.3 节的单容器方案不同——这里用「网络隔离 哨兵密钥COMPOSIO_API_KEY: e2e-deny-real-call-sentinel等HTTPS_PROXYhttp://127.0.0.1:1」三层防线确保测试永远不会触发真实的第三方 API 调用健康检查门控db用pg_isready、redis用redis-cli ping、backend用容器内 Python 请求/openapi.json、celery_worker用celery inspect ping。工作流里docker compose up -d --build --wait --wait-timeout 300会阻塞到所有健康检查变绿替代了脆弱的curl轮询脚本临时存储Postgres 数据目录用tmpfs挂载每次 CI 运行都是干净数据库无需清理 volume构建缓存后端镜像声明了cache_from/cache_to: typeghaGitHub Actions 构建层缓存跨运行复用~9 GB量级的多阶段构建结果ephemeral 数据库名DATABASE_URL指向独立的surfsense_e2e库与开发环境隔离。工作流中与缓存相关的步骤与原文档 Caching 一节逐行对应只是把npm换成了 pnpm 生态- name: Get pnpm store directory id: pnpm-cache run: echo STORE_PATH$(pnpm store path --silent) $GITHUB_OUTPUT - name: Cache pnpm store uses: actions/cachev5 with: path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} key: pnpm-${{ runner.os }}-${{ hashFiles(surfsense_web/pnpm-lock.yaml) }} restore-keys: pnpm-${{ runner.os }}- - name: Cache Playwright browsers id: playwright-cache uses: actions/cachev5 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles(surfsense_web/pnpm-lock.yaml) }} - name: Install Playwright browsers if: steps.playwright-cache.outputs.cache-hit ! true working-directory: surfsense_web run: pnpm exec playwright install --with-deps chromium - name: Install Playwright system deps (cache hit) if: steps.playwright-cache.outputs.cache-hit true working-directory: surfsense_web run: pnpm exec playwright install-deps chromium可以注意到两点实际工程细节一是只安装chromium单浏览器配置里只定义了 chromium 项目比--with-deps全量安装更快二是浏览器缓存命中时仍要跑install-deps因为系统库依赖无法用文件哈希精确缓存而浏览器二进制可以。前端则由 Playwright 的webServer字段托管见 5.3 节CI 下执行pnpm build pnpm start起生产模式服务与线上构建完全一致本地开发则用pnpm exec next dev快速迭代。surfsense_web/tests/README.md 中给出了与 CI 完全对齐的本地复现路径docker compose -f docker/docker-compose.e2e.yml up -d --build --wait起后端栈、注册测试用户然后在surfsense_web/下执行pnpm test:e2e:prod该脚本即 package.json 中的cross-env CI1 playwright test。三、Reporting 报告体系3.1 多 Reporter 组合配置Playwright 允许同时启用多个 reporter各司其职// playwright.config.ts export default defineConfig({ reporter: [ // Always generate [html, { outputFolder: playwright-report }], // Console output [list], // CI-friendly [github], // GitHub Actions annotations // JUnit for CI integration [junit, { outputFile: results.xml }], // JSON for custom processing [json, { outputFile: results.json }], // Blob for merging shards [blob, { outputDir: blob-report }], ], });各 reporter 的职责划分html供人看配合 artifact 上传后浏览器打开list提供实时控制台输出github把失败信息以 annotation 形式直接标在 PR 的 diff 上junit/json供外部 CI 系统集成或自定义脚本处理blob是 shard 合并的中间格式人类不可读只喂给merge-reports。3.2 按 CI 环境切换 Reporterexport default defineConfig({ reporter: process.env.CI ? [[github], [blob], [html]] : [[list], [html]], });process.env.CI在 GitHub Actions、GitLab CI 等主流平台上都会被自动设置为真值因此无需额外配置即可区分「本地调试」与「流水线」。SurfSense 的 playwright.config.ts 采用的是同一模式的变体并额外通过open参数控制 HTML 报告的打开行为reporter: process.env.CI ? [[html, { open: never }], [github], [list]] : [[html, { open: on-failure }], [list]],即本地运行失败时自动打开 HTML 报告页方便排查CI 上永远不尝试打开headless 环境也没有图形界面。四、Sharding 分片执行4.1 命令行分片# Split into 4 shards, run shard 1 npx playwright test --shard1/4 # Run shard 2 npx playwright test --shard2/4--shardN/M表示「M 个 shard 中的第 N 个」。Playwright 按测试文件维度做确定性切分保证同一套用例在不同 shard 数下都能完整覆盖且不重复。4.2 配置侧配合// playwright.config.ts export default defineConfig({ // Evenly distribute tests across shards fullyParallel: true, // For blob reporter to merge later reporter: process.env.CI ? [[blob]] : [[html]], });fullyParallel: true让文件间并行调度更均衡配合分片可获得更均匀的耗时分布。SurfSense 的配置同样设置了fullyParallel: trueplaywright.config.ts但当前 CI 采用workers: 1串行执行全栈 journey 测试对数据库状态强敏感串行换取确定性分片能力在套件规模扩大后再开启——这也是原文档「Use sharding for large suites」一行的实践注脚。4.3 合并分片报告# After all shards complete, merge blob reports npx playwright merge-reports --reporter html ./all-blob-reportsmerge-reports是独立命令不需要再跑测试读取各 shard 产出的blob-report/目录并重新生成任意格式的报告如 1.2 节工作流中的--reporter html。五、Environment 环境管理5.1 按 NODE_ENV 加载 .env 文件// playwright.config.ts import { defineConfig } from playwright/test; import dotenv from dotenv; // Load env file based on environment dotenv.config({ path: .env.${process.env.NODE_ENV || development} }); export default defineConfig({ use: { baseURL: process.env.BASE_URL || http://localhost:3000, }, });5.2 多环境矩阵同一份用例通过矩阵变量指向不同环境staging / production# .github/workflows/playwright.yml jobs: test: strategy: matrix: environment: [staging, production] steps: - name: Run tests run: npx playwright test env: BASE_URL: ${{ matrix.environment staging https://staging.example.com || https://example.com }} TEST_USER: ${{ secrets[format(TEST_USER_{0}, matrix.environment)] }}5.3 Secrets 管理与 Playwright webServer 注入测试账号等敏感值放在仓库 Secrets 中仅通过env:注入运行步骤不写入配置或测试代码# GitHub Actions secrets - name: Run tests run: npx playwright test env: TEST_EMAIL: ${{ secrets.TEST_EMAIL }} TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}// tests use environment variables test(login, async ({ page }) { await page.getByLabel(Email).fill(process.env.TEST_EMAIL!); await page.getByLabel(Password).fill(process.env.TEST_PASSWORD!); });SurfSense 对「环境值如何穿透到被测应用」这一问题给出了更完整的样板playwright.config.ts 在模块顶层集中解析并回写环境然后透传给webServer启动的 Next.js 进程const PORT process.env.PORT || 3000; const BACKEND_PORT process.env.BACKEND_PORT || 8000; const baseURL process.env.PLAYWRIGHT_BASE_URL || http://localhost:${PORT}; process.env.PLAYWRIGHT_TEST_EMAIL ?? e2e-testsurfsense.net; process.env.PLAYWRIGHT_TEST_PASSWORD ?? E2eTestPassword123!; process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL ?? backendURL; process.env.SURFSENSE_BACKEND_INTERNAL_URL ?? backendURL; process.env.AUTH_TYPE ?? LOCAL; webServer: { // Local stays on webpack dev (Turbopack caused stale-lock panics in E2E). command: process.env.CI ? pnpm build pnpm start : pnpm exec next dev, url: http://localhost:${PORT}, reuseExistingServer: !process.env.CI, timeout: process.env.CI ? 300_000 : 180_000, stdout: pipe, stderr: pipe, env: { /* 透传后端地址等变量给 Next.js */ }, }值得注意的设计取舍??提供默认值使本地「零环境变量」即可运行覆盖时只需显式导出reuseExistingServer: !process.env.CI本地复用已存在的 dev server 加速迭代CI 上强制新起避免陈旧进程导致的不确定结果注释里记录了「本地保留 webpack devTurbopack 曾导致 stale-lock panic」这类经验性决策这是生产级 E2E 配置中很典型的知识沉淀测试账号在 CI 工作流中由后端/auth/register接口真实注册e2e-tests.yml 的「Register E2E test user」步骤并在注册后清空 Redis 中surfsense:auth_rate_limit:*限流计数保证登录链路每次都从干净状态开始。测试登录会话的复用则交给auth.setup.ts写入playwright/.auth/user.json供chromium项目通过storageState加载见 auth.setup.ts 与配置中的projects依赖关系。六、Caching 缓存策略6.1 缓存 Playwright 浏览器浏览器二进制是 CI 里最大的可缓存资产数百 MB用 lockfile 哈希作为 key- name: Cache Playwright browsers uses: actions/cachev4 id: playwright-cache with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles(package-lock.json) }} - name: Install Playwright browsers if: steps.playwright-cache.outputs.cache-hit ! true run: npx playwright install --with-deps - name: Install system deps only if: steps.playwright-cache.outputs.cache-hit true run: npx playwright install-depscache-hit输出驱动的三段式命中则跳过安装 / 未命中全装 / 命中补装系统依赖在 e2e-tests.yml 中有逐行对应的真实实现差异仅在于 key 使用hashFiles(surfsense_web/pnpm-lock.yaml)且只装 chromium。6.2 缓存 Node 依赖npm 项目直接用setup-node内置缓存- uses: actions/setup-nodev4 with: node-version: 22 cache: npm - name: Install dependencies run: npm cipnpm 项目则缓存pnpm store path输出的 store 目录2.4 节示例配合--frozen-lockfile安装。此外 SurfSense 还缓存了surfsense_web/.next/cacheNext.js 构建缓存key 含github.sha并配多级restore-keys回退进一步压缩 CI 时间。七、Tag-Based 测试过滤7.1 在 CI 中按标签运行# Run smoke tests on PR - name: Run smoke tests run: npx playwright test --grep smoke # Run full regression nightly - name: Run regression run: npx playwright test --grep regression # Exclude flaky tests - name: Run stable tests run: npx playwright test --grep-invert flaky7.2 PR 快反馈 vs 夜间全量# .github/workflows/pr.yml - Fast feedback - name: Run critical tests run: npx playwright test --grep smoke|critical # .github/workflows/nightly.yml - Full coverage - name: Run all tests run: npx playwright test --grep-invert flaky这是典型的「快慢分层」策略PR 上只跑冒烟级用例换反馈速度夜间定时任务承担全量回归与 flaky 暴露。7.3 在配置中做标签过滤// playwright.config.ts export default defineConfig({ grep: process.env.CI ? /smoke|critical/ : undefined, grepInvert: process.env.CI ? /flaky/ : undefined, });7.4 按 Project 划分标签// playwright.config.ts export default defineConfig({ projects: [ { name: smoke, grep: /smoke/, }, { name: regression, grepInvert: /smoke/, }, ], });projects级别的grep/grepInvert可以把同一批文件组织成两个逻辑套件pnpm test:e2e smoke之类的按项目运行方式playwright test --projectsmoke随之成为可能。SurfSense 的 projects 划分则基于「setup → chromium」的依赖关系playwright.config.tssetup项目只匹配*.setup.ts认证状态生成chromium项目声明dependencies: [setup]并复用storageState是 project 机制「先鉴权、再测试」的经典用法。八、Best Practices 与 CI 优化配置8.1 实践清单PracticeBenefitUsenpm ciDeterministic installsRun headless in CIFaster, no display neededSet retries in CI onlyHandle flakinessUpload artifacts on failureDebug failuresUse sharding for large suitesFaster executionCache browsersFaster setupUse blob reporter for shardsMerge reports correctlyUse tags for PR vs nightlyFast feedback coverageExclude flaky in CIStable pipeline「只在 CI 设置 retries」是重点本地重跑会掩盖真实缺陷而 CI 偶发抖动的代价是流水线失败两者权衡后retries: process.env.CI ? N : 0是通行做法。SurfSense 当前取值为retries: process.env.CI ? 1 : 0playwright.config.ts并配合trace: on-first-retry——只在首次重试失败时保留 trace兼顾诊断能力与产物体积。8.2 CI 优化后的完整配置参考// playwright.config.ts - CI optimized export default defineConfig({ testDir: ./tests, fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: process.env.CI ? [[github], [blob], [html]] : [[list], [html]], use: { baseURL: process.env.BASE_URL || http://localhost:3000, trace: on-first-retry, screenshot: only-on-failure, video: on-first-retry, }, });逐项解析 CI 相关开关forbidOnly: !!process.env.CI防止test.only被误提交后在 CI 上静默跳过大量用例——这是 E2E 流水线最常见的「假绿」来源screenshot: only-on-failure、video/trace按需录制失败产物自动进入test-results/工作流再用actions/upload-artifact上传。SurfSense 的 CI 把video直接设为off容器化后端 确定性 API 驱动使视频价值有限但保留 trace 并在失败时上传test-results/作为playwright-tracesartifact失败诊断不止前端e2e-tests.yml 在failure() || cancelled()时还会 dump 各 compose 服务的日志到compose-logs/并作为 artifact 上传覆盖「测试失败到底是前端、后端还是 worker 的问题」的归因需求。8.3 失败产物与 artifact 生命周期SurfSense 工作流对产物做了分级保留HTML 报告与 trace 保留 14 天retention-days: 14后端栈日志保留 7 天playwright-report在always()条件下上传成功也要可回看traces 仅在failure()时上传。配合concurrency: cancel-in-progress: true同一 ref 的新推送取消旧运行可以既保证最新代码的反馈速度又不丢失被取消运行的报告。九、延伸参考围绕本文的 CI/CD 主题仓库内的 Playwright 技能文档还提供了配套深读材料测试标签test-tags.md —— 标签定义与--grep过滤的完整模式性能优化performance.md —— workers/sharding 与并行化的进阶调优GitHub Actions 细节github-actions.md 与 docker.md、parallel-sharding.mdCI 失败调试debugging.md —— trace viewer 用法与常见失败归因。项目侧的落地入口则是 surfsense_web/tests/README.md本地/CI 两种运行方式、surfsense_web/playwright.config.ts唯一事实来源的 E2E 配置与 docker/docker-compose.e2e.yml隔离后端栈定义三者与本文各节一一对应。【免费下载链接】SurfSenseOpen-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP server. Join our Discord: https://discord.gg/ejRNvftDp9项目地址: https://gitcode.com/GitHub_Trending/su/SurfSense创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考