SurfSense 的 Playwright E2E 与 CI/CD 集成实战:GitHub Actions、Docker、Sharding 与报告体系
2026/9/14 18:12:35 网站建设 项目流程

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.ymlsurfsense_web/playwright.config.tsdocker/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/checkout@v4 - uses: actions/setup-node@v4 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-artifact@v4 if: ${{ !cancelled() }} with: name: playwright-report path: playwright-report/ retention-days: 30

几个关键点的说明:

  • timeout-minutes: 60防止测试卡死无限占用 runner;
  • cache: "npm"setup-node自动缓存node_modules(pnpm 项目则改用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/checkout@v4 - uses: actions/setup-node@v4 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-artifact@v4 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/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: "npm" - name: Install dependencies run: npm ci - name: Download blob reports uses: actions/download-artifact@v4 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-artifact@v4 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-artifactpattern: 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/checkout@v4 - uses: actions/setup-node@v4 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: - CI=true - BASE_URL=http://app:3000 depends_on: - app app: build: ./app ports: - "3000:3000"

两个细节值得注意:

  • 报告与test-results(trace、截图、视频)通过 volume 挂载回宿主机,容器--rm删除后产物不丢;
  • BASE_URL=http://app:3000使用容器网络内的服务名而非localhostCI=true则触发 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 playwright

2.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_PROXY=http://127.0.0.1:1」三层防线,确保测试永远不会触发真实的第三方 API 调用;
  • 健康检查门控dbpg_isreadyredisredis-cli pingbackend用容器内 Python 请求/openapi.jsoncelery_workercelery inspect ping。工作流里docker compose up -d --build --wait --wait-timeout 300会阻塞到所有健康检查变绿,替代了脆弱的curl轮询脚本;
  • 临时存储:Postgres 数据目录用tmpfs挂载,每次 CI 运行都是干净数据库,无需清理 volume;
  • 构建缓存:后端镜像声明了cache_from/cache_to: type=gha(GitHub 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/cache@v5 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/cache@v5 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 CI=1 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 环境切换 Reporter

export 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 --shard=1/4 # Run shard 2 npx playwright test --shard=2/4

--shard=N/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: true(playwright.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-reports

merge-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-test@surfsense.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 dev(Turbopack 曾导致 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/cache@v4 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-deps

cache-hit输出驱动的三段式(命中则跳过安装 / 未命中全装 / 命中补装系统依赖)在 e2e-tests.yml 中有逐行对应的真实实现,差异仅在于 key 使用hashFiles('surfsense_web/pnpm-lock.yaml')且只装 chromium。

6.2 缓存 Node 依赖

npm 项目直接用setup-node内置缓存:

- uses: actions/setup-node@v4 with: node-version: 22 cache: "npm" - name: Install dependencies run: npm ci

pnpm 项目则缓存pnpm store path输出的 store 目录(2.4 节示例),配合--frozen-lockfile安装。此外 SurfSense 还缓存了surfsense_web/.next/cache(Next.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 @flaky

7.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 --project=smoke)随之成为可能。SurfSense 的 projects 划分则基于「setup → chromium」的依赖关系(playwright.config.ts):setup项目只匹配*.setup.ts(认证状态生成),chromium项目声明dependencies: ["setup"]并复用storageState,是 project 机制「先鉴权、再测试」的经典用法。

八、Best Practices 与 CI 优化配置

8.1 实践清单

PracticeBenefit
Usenpm ciDeterministic installs
Run headless in CIFaster, no display needed
Set retries in CI onlyHandle flakiness
Upload artifacts on failureDebug failures
Use sharding for large suitesFaster execution
Cache browsersFaster setup
Use blob reporter for shardsMerge reports correctly
Use tags for PR vs nightlyFast feedback + coverage
Exclude @flaky in CIStable pipeline

「只在 CI 设置 retries」是重点:本地重跑会掩盖真实缺陷,而 CI 偶发抖动的代价是流水线失败,两者权衡后retries: process.env.CI ? N : 0是通行做法。SurfSense 当前取值为retries: process.env.CI ? 1 : 0(playwright.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-reportalways()条件下上传(成功也要可回看),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.md;
  • CI 失败调试: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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询