ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Chainlit 项目 E2E 测试并行化研究报告

Chainlit 项目 E2E 测试并行化研究报告 Chainlit 项目 E2E 测试并行化研究报告【免费下载链接】chainlitBuild Conversational AI in minutes ⚡️项目地址: https://gitcode.com/GitHub_Trending/ch/chainlitoutput_articleChainlit E2E 测试并行化改造指南从严格串行到多分片执行导读本文基于 Chainlit 仓库中的研究文档 e2e-parallel-execution.md系统梳理该仓库 E2E 测试当前严格串行的架构根因、Cypress 并行化能力边界以及两条可行的并行化改造路径。文中所有结论均结合当前仓库中的 cypress.config.ts、run.ts、config.py、e2e-tests.yaml 等真实源码交叉验证——事实上研究文档提出的Strategy ACI 矩阵分片方案已在当前仓库落地。读完本文你将掌握 E2E 测试并行化的瓶颈分析思路、分片方案选型依据以及 Chainlit 仓库的具体落地形态。当前架构严格串行及其四个结构性根因研究文档明确指出在当前架构下E2E 测试无法并行运行。这不是配置开关的问题而是由四个相互叠加的结构性设计共同决定的。1. 单一硬编码端口8000每个测试后端都启动在端口8000上。该端口定义在 backend/chainlit/config.pyDEFAULT_HOST 127.0.0.1 DEFAULT_PORT 8000它被RunSettings模型作为默认值消费config.pyclass RunSettings(BaseModel): module_name: Optional[str] None host: str DEFAULT_HOST port: int DEFAULT_PORT ...而启动测试后端的 cypress/support/run.ts 在调用uv run chainlit run时并未传递--port参数因此每个后端都绑定到默认端口const command uv; const args [ --project, CHAILIT_DIR, run, chainlit, run, entryPointPath, -h, --ci ];同样的硬编码也出现在 cypress.config.ts 中并被用作baseUrlexport const CHAINLIT_APP_PORT 8000; // ... baseUrl: http://127.0.0.1:${CHAINLIT_APP_PORT},2. 每个 spec 文件的 kill → 启动 → 测试 → kill 生命周期cypress.config.ts 通过 Cypress 的setupNodeEvents注册了严格的进程编排on(before:spec, async (spec) { await killChainlit(); await runChainlit(spec); }); on(after:spec, async () { await killChainlit(); });其中killChainlit()是基于端口的进程终止cypress.config.tsasync function killChainlit() { await fkill(:${CHAINLIT_APP_PORT}, { force: true, silent: true }); }这里使用fkill(:8000)按端口号杀死进程。研究文档特别指出其风险如果同时存在两个测试运行器其中一个会把另一个的后端进程杀掉。这正是串行架构的典型特征——进程生命周期与端口强耦合天然排斥并发。此外配置中还注册了SIGTERM/SIGINT/SIGHUP/SIGBREAK信号处理cypress.config.ts确保测试进程退出时也能兜底清理 Chainlit 后端。3. 每个 spec 拥有独立的 Chainlit 应用仓库的 E2E 测试采用一测试一应用的组织方式每个测试目录cypress/e2e/test_name/下都包含main.py—— 该测试专用的 Chainlit 应用.chainlit/config.toml—— 该测试的独立配置。run.ts 通过spec.absolute定位测试目录并以环境变量的方式把目录注入 Chainlitconst testDir spec ? dirname(spec.absolute) : SAMPLE_DIR; const entryPointFileName spec ? spec.name.startsWith(async) ? main_async.py : spec.name.startsWith(sync) ? main_sync.py : main.py : hello.py; const entryPointPath join(testDir, entryPointFileName);启动时的环境变量注入run.tsconst options: SpawnOptionsWithoutStdio { env: { ...process.env, CHAINLIT_APP_ROOT: testDir } };由于每个测试的main.py是完全不同的后端应用服务端无法复用必须在 spec 之间重启。4. Cypress 按 spec 串行执行标准的cypress run在单个浏览器进程中一次处理一个 spec 文件。仓库的 npm 脚本也印证了这一点package.jsontest:e2e: cypress run研究文档指出Cypress Cloud 提供跨 CI 机器的并行能力但当时项目并未启用——脚本中没有--parallel或--record标志。特例测试中途重启data_layer并非所有测试都遵循每 spec 一次生命周期。data_layerspec 会在单个测试内部调用cy.task(restartChainlit, Cypress.spec)来杀进程并重新拉起后端用于验证线程thread在服务端重启后的持久化行为cypress/e2e/data_layer/spec.cy.tsit(Verifies thread continuation after server restart and new thread creation, () { cy.task(restartChainlit, Cypress.spec).then(() { cy.section(Before server restart); // ... 登录、开始对话、验证反馈与线程队列 }); cy.task(restartChainlit, Cypress.spec).then(() { cy.section(After server restart); verifyContinueThread(); // ... }); });对应的restartChainlittask 实现在 cypress.config.ts同样依赖killChainlit() → runChainlit(spec)的端口级编排restartChainlit(spec: Cypress.Spec) { return new Promise((resolve) { killChainlit().then(() { runChainlit(spec).then(() { setTimeout(() resolve(null), 1000); }); }); }); }测试依赖的线程数据以thread_history.pickle文件形式落盘在测试目录见 cypress/e2e/data_layer/main.pyspec 通过cleanupThreadHistory在用例前后清理该文件spec.cy.ts。这类共享文件系统的副作用是并行化时必须隔离的隐藏依赖。Cypress 的并行化能力边界内置--parallel依赖 Cypress CloudCypress并不免费提供开箱即用的本地并行。官方用法是cypress run --record --parallel关键事实--parallel必须与--record搭配使用后者会把结果上报到 Cypress Cloud付费服务Cypress Cloud 扮演编排者角色它基于历史运行时长用负载均衡策略把 spec 文件动态分配给可用的 CI 机器因此 Cypress 官方并行是跨机器的多机并行而非本地的多进程并行。Cypress 与 Playwright 的并行能力对比特性CypressPlaywright本地并行不支持内置--workers4CI 分片通过 Cypress Cloud付费内置--shard1/4编排方式基于历史时长的动态分配手动均分免费的 Cypress 并行替代方案工具说明手动 CI matrix 分片用 GitHub Actions matrix 任务配合--spec把 spec 文件拆分到多台机器sorry-cypress开源自托管的 Cypress Cloud 替代品支持--parallelcypress-split通过SPLIT/SPLIT_INDEX环境变量在 CI 机器间拆分 spec 的插件currents.dev提供免费额度的 Cypress Cloud 替代服务注以上外部工具仅作能力概述具体接入方式请以各工具官方文档为准。当前仓库实际选用的是cypress-split见下文落地现状。并行化改造的瓶颈与对策研究文档将并行化的障碍与解决方案整理如下瓶颈解决方案硬编码端口8000为每个 worker 分配唯一端口如8000 workerIndex向chainlit run传入--port并动态配置baseUrl基于端口的进程终止fkill(:8000)改为基于 PID 的进程管理——保存spawn()返回的子进程 PID 并直接 kill单一 Cypress 浏览器进程使用 Cypress Cloud--parallel、sorry-cypress、cypress-split或在 CI matrix 任务间分片共享文件系统.chainlit/目录目录本身已按测试隔离但像data_layer写入的thread_history.pickle等临时文件必须保持每个 worker 隔离Strategy ACI Matrix 分片最简单把约 50 个 spec 文件按 N 组拆分到独立的 GitHub Actions runner 上在 workflow 中增加 matrix 维度为每个 runner 分配一组 spec每个 runner 仍可使用端口8000因为机器彼此独立无需修改 Cypress 配置或run.ts——只需用--spec传入子集。这条路径的吸引力在于跨机器天然隔离端口与进程改动面最小。Strategy B同机并行工作量更大让CHAINLIT_APP_PORT动态化——按 worker 从环境变量读取用 PID 追踪spawn返回的child.pid替换fkill(:port)用 cypress-split 或 sorry-cypress 把 spec 分发到多个 Cypress 进程每个进程拥有各自的baseUrl每个 Cypress 进程通过独立的CYPRESS_BASE_URL指向自己专属的后端端口。同机并行的难点在于前两步必须同时解决端口冲突与端口级杀进程两个耦合点改造面大、回归风险高。当前仓库的落地现状Strategy A cypress-split研究文档之后仓库实际推进到了实施阶段。证据在 .github/workflows/e2e-tests.yamljobs: prepare: runs-on: ubuntu-slim outputs: indexes: ${{ steps.shard-indexes.outputs.indexes }} steps: - id: shard-indexes name: Compute shard indexes run: | json$(jq -nc --argjson n ${{ inputs.e2e_parallel_shards }} [range(1; $n 1)]) echo indexes$json $GITHUB_OUTPUT e2e-tests: needs: prepare strategy: matrix: os: [ubuntu-latest, windows-latest] containers: ${{ fromJSON(needs.prepare.outputs.indexes) }}工作流通过e2e_parallel_shards输入默认5动态生成分片索引在ubuntu-latest与windows-latest两个 OS 上各起 5 个并行分片。运行测试时注入 cypress-split 需要的环境变量env: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} SPLIT: ${{ inputs.e2e_parallel_shards }} SPLIT_INDEX1: ${{ matrix.containers }} run: pnpm test:e2e这正是Strategy ACI matrix 分片 cypress-splitspec 自动切分的组合形态每个 runner 是独立机器端口8000冲突天然不存在cypress-split在测试运行前把全部 spec 按SPLIT/SPLIT_INDEX动态分配给各分片无需手工维护--spec子集Cypress 配置侧已接入插件cypress.config.tsimport cypressSplit from cypress-split; // ... cypressSplit(on, config);对应依赖在 package.json 中声明cypress-split: ^1.24.31。并行分片下的配套工程Cypress 二进制缓存并行分片会放大依赖安装成本5 个分片 × 2 个 OS 都要装 Cypress因此 e2e-tests.yaml 使用CYPRESS_CACHE_FOLDER把 Linux / Windows 默认不同的 Cypress 缓存目录统一为仓库工作区下的单一路径配合actions/cache跨分片复用env: # Single path for actions/cache on Linux Windows (default Cypress dirs differ by OS). CYPRESS_CACHE_FOLDER: ${{ github.workspace }}/.cypress-cache steps: - name: Cache Cypress binary uses: actions/cachev5 with: path: .cypress-cache key: cypress-${{ runner.os }}-${{ hashFiles(pnpm-lock.yaml) }}这与另一份研究文档 ci-cache-optimization.md 中用actions/cache替换 cypress action、统一跨 OS 缓存路径的 P1 建议一脉相承。前端与 Python 依赖的缓存则分别由 .github/actions/pnpm-node-install/action.yamlactions/setup-nodev6.3.0cache: pnpm与 .github/actions/uv-python-install/action.yamlastral-sh/setup-uvv8.0.0enable-cache: true提供与 CI 顶层编排 ci.yaml 配合。结论与选型建议回顾整条改造路径可以提炼出三点可迁移的经验先识别串行架构的结构性耦合。Chainlit 仓库的串行并非单一原因而是端口、进程生命周期、应用隔离、Cypress 执行模型四个因素叠加。逐一拆解后才能评估并行化的真实成本。跨机器分片是低风险首选。Strategy A 以机器隔离规避了端口与 PID 管理两大难题仅引入--spec/cypress-split 的切分逻辑是投入产出比最高的起步方案——当前仓库正是沿此路径落地并以CYPRESS_CACHE_FOLDER统一了多分片下的二进制缓存。同机并行需要更深的工程改造。若未来需要在单机上压缩时间如本地开发或小型 runner就必须同时落地动态端口、PID 级进程管理、多 Cypress 进程与独立baseUrl并额外隔离thread_history.pickle这类共享文件系统副作用。研究文档 e2e-parallel-execution.md 的完整分析含 Cypress/Playwright 对比与免费替代工具矩阵是理解上述改造的原始依据感兴趣的读者可继续对照 cypress.config.ts、cypress/support/run.ts 与 .github/workflows/e2e-tests.yaml 追踪从研究到实施的完整演进。/output_article【免费下载链接】chainlitBuild Conversational AI in minutes ⚡️项目地址: https://gitcode.com/GitHub_Trending/ch/chainlit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表