- 网页爬虫
- 后端
- AI 应用
【免费下载链接】firecrawl
The web data API to search, scrape, and interact at scale. 🔥
导读
本文聚焦 Firecrawl 开源仓库中apps/api的日常开发流程,完整讲解仓库根目录 AGENTS.md 所定义的"先写端到端测试、再用pnpm harness启动整套服务验证、最后交给 CI"的标准工作流。你将掌握 Firecrawl 后端代码修改的完整闭环:如何编写带环境门控的 snips 测试、pnpm harness底层究竟启动了哪些服务与容器、以及如何只跑相关测试而非整条耗时测试套件。
一、仓库结构:一个 monorepo,两类资产
AGENTS.md开篇就点明了本仓库的定位:Firecrawl 是一个 Web 抓取 API(web scraper API),当前目录是一个 monorepo(多包单体仓库),并给出了最核心的两类资产划分:
apps/api:真正的 API 与 worker 代码(Express 服务、爬虫调度、队列 worker、提取 worker 等全部在此);apps/*-sdk:各类语言 SDK,例如js-sdk、python-sdk、go-sdk、rust-sdk、java-sdk、php-sdk、ruby-sdk、dot-net-sdk、elixir-sdk等。
这一划分意味着:对 API 的行为做任何改动,都会直接辐射到所有 SDK 与调用方。因此 AGENTS.md 强调,修改 API 时必须遵循一套固定的测试驱动步骤,而不是直接pnpm start或盲目提交。
二、修改 API 的四步标准工作流
AGENTS.md明确给出了修改 API 时应遵循的四个步骤,这也是整个文档的骨架:
- 编写端到端测试(若尚不存在),断言你的"胜利条件"(win conditions);
- 编写代码实现胜利条件;
- 用
pnpm harness jest ...运行测试; - 推送到分支、打开 PR,让 CI 验证胜利条件。
下文逐一展开,并结合仓库源码说明每一步的底层机制。
三、第一步:先写 E2E 测试(snips)
3.1 为什么 E2E 优先于单元测试
AGENTS.md的原文立场非常明确:E2E 测试(在 API 中被称为snips)总是优先于单元测试。从仓库结构可以印证这一点:apps/api/src/__tests__/snips/目录下存放了大量按 API 版本与功能维度组织的 E2E 测试,包括v0/、v1/、v2/三个版本目录,以及threat-protection、index-cache、wikipedia-url-parser、metadata-concat等专项测试文件。
与snips并列的还有e2e_noAuth、e2e_withAuth、e2e_full_withAuth、e2e_v1_withAuth等目录,它们同样是对真实 API 端点的端到端验证。整个apps/api/src/__tests__/之下,单元测试(*.test.ts且位于lib/等目录内)占比很小,绝大多数验证都发生在真实 HTTP 调用链路上。
3.2 测试覆盖要求:happy path + failure path
AGENTS.md对测试覆盖提出了最低要求:
- 1 条 happy path(主路径):如果存在多条代码路径显著不同的 happy path,鼓励多写;
- 1 条及以上 failure path(失败路径):验证异常分支与错误处理。
以apps/api/src/__tests__/snips/v1/scrape.test.ts为典型例子,其中既包含成功抓取的路径,也包含itIf(...)门控下的各类边界与异常分支;v1/deep-research.test.ts、v1/extract.test.ts等同样遵循"主路径 + 失败路径"的组织方式。
3.3 超时规范:必须使用scrapeTimeout
AGENTS.md特别强调:在 API 测试中,始终使用./lib中的scrapeTimeout来设置抓取超时。这里的./lib指的就是 apps/api/src/tests/snips/lib.ts,其中定义:
// Due to the limited resources of the CI runner, we need to set a longer timeout // for the many many scrape tests export const scrapeTimeout = 90000; export const indexCooldown = 30000;从源码注释看,scrapeTimeout取 90 秒是为了照顾 CI runner 资源有限、大量抓取测试并发执行时的真实耗时;indexCooldown则用于索引类测试的冷却等待。测试中不要各自硬编码超时,应统一引用该常量,保证 CI 与本地行为一致。
3.4 门控条件:按依赖能力裁剪测试
AGENTS.md指出,这些测试会在多种配置组合下运行,因此必须以如下方式对测试做门控(gating):
- 需要fire-engine(Firecrawl 的浏览器抓取引擎)时:
!process.env.TEST_SUITE_SELF_HOSTED,即仅在非自托管(云托管/生产)测试环境运行; - 需要AI能力时:
!process.env.TEST_SUITE_SELF_HOSTED || process.env.OPENAI_API_KEY || process.env.OLLAMA_BASE_URL,即非自托管环境、或自托管但配置了 OpenAI Key / Ollama 本地模型时运行。
在 apps/api/src/tests/snips/lib.ts 中,这些条件被封装成了一组可直接使用的辅助函数与布尔量:
export const TEST_SELF_HOST = !!config.TEST_SUITE_SELF_HOSTED; export const TEST_PRODUCTION = !TEST_SELF_HOST; export const HAS_AI = !!(config.OPENAI_API_KEY || config.OLLAMA_BASE_URL); export const HAS_FIRE_ENGINE = !!config.FIRE_ENGINE_BETA_URL; export const HAS_PLAYWRIGHT = !!config.PLAYWRIGHT_MICROSERVICE_URL; export const HAS_SEARCH = TEST_PRODUCTION || !!config.SEARXNG_ENDPOINT; export const describeIf = (cond: boolean) => (cond ? describe : describe.skip); export const concurrentIf = (cond: boolean) => (cond ? it.concurrent : it.skip); export const testIf = (cond: boolean) => (cond ? test : test.skip); export const itIf = (cond: boolean) => (cond ? it : it.skip);这意味着AGENTS.md中那两行环境变量门控,在源码里已经内化为TEST_PRODUCTION、HAS_AI、HAS_FIRE_ENGINE等常量,配合describeIf/itIf使用。例如 apps/api/src/tests/snips/v1/deep-research.test.ts 中:
describeIf(TEST_PRODUCTION || HAS_AI)("Deep Research", () => { ... });只有在"非自托管"或"自托管但配置了 AI 提供方"时才执行;不具备条件时整个 describe 块被describe.skip跳过,测试套件在无 AI 的本地环境下依然可完整跑通。
3.5 相关环境变量一览
这些门控变量最终都来自 apps/api/src/config.ts 的 zod schema,常用项如下:
| 环境变量 | 默认值 | 说明 |
|---|---|---|
TEST_SUITE_SELF_HOSTED | 无(可选) | 自托管测试模式开关 |
TEST_SUITE_WEBSITE | http://127.0.0.1:4321 | 测试站点地址(本地对应test-site) |
TEST_API_URL | http://127.0.0.1:3002 | 被测 API 地址 |
OPENAI_API_KEY | 无 | 启用 AI 类测试(OpenAI 提供方) |
OLLAMA_BASE_URL | 无 | 启用 AI 类测试(本地 Ollama) |
FIRE_ENGINE_BETA_URL | 无 | 启用 fire-engine 相关测试 |
SEARXNG_ENDPOINT | 无 | 启用搜索类测试(自托管时) |
另外lib.ts中还有一条值得注意的规则:当TEST_SUITE_WEBSITE是本地地址且处于自托管模式时,会自动开启config.ALLOW_LOCAL_WEBHOOKS;而在生产测试模式下禁止使用本地测试站点地址,避免误打线上环境。
四、第二步:编写代码实现胜利条件
在测试先行并定义好胜利条件后,第二步就是实现这些条件。由于前面已经用describeIf/itIf把测试框定在了真实 API 链路上,这一步的代码改动会自然落到 apps/api/src/controllers(v0/v1/v2 三套控制器)、apps/api/src/lib(核心库逻辑)与 apps/api/src/scraper(抓取实现)等目录中。AGENTS.md建议在构建 TODO 列表时始终把这四步记在心里,即每个任务都应当以"测试 + 实现 + 验证 + 提交"为单位闭环推进。
五、第三步:用pnpm harness运行测试
5.1 harness 是什么
AGENTS.md明确指出:pnpm harness是一条帮你把 API server 和 workers 拉起来用于跑测试的命令,不要手动pnpm start。在 apps/api/package.json 中,它的定义是:
"harness": "tsx src/harness.ts"也就是说pnpm harness <command...>实际执行的是tsx src/harness.ts <command...>,入口在 apps/api/src/harness.ts。
从 harness 源码可以确认它的能力远不止"启动一个进程":
- 依赖安装与构建:默认会执行
pnpm install、pnpm build(API),以及go mod tidy并编译sharedLibs/go-html-to-md为 c-shared 库(用于 HTML 转 Markdown); - 启动 API 与各类 worker:API(
api)、队列 worker(worker)、提取 worker(extract-worker)、NUQ_WORKER_COUNT个 nuq-worker(按NUQ_BACKEND选择nuq-worker或nuq-fdb-worker)、nuq-prefetch-worker、nuq-reconciler-worker,以及在启用 DB 认证时启动index-worker; - 容器编排:本地运行时自动用 Docker/Podman 拉起NUQ PostgreSQL 容器(
firecrawl-nuq-postgres)、NUQ RabbitMQ 容器(firecrawl-nuq-rabbitmq,rabbitmq:3-management)以及 FDB 后端所需的FoundationDB 容器(foundationdb/foundationdb:7.3.63),并在退出时优雅停掉这些容器; - 就绪等待:通过端口探测(
waitForPort)等待 API 在config.PORT(默认本地 3002 附近)就绪后再执行测试命令。
因此pnpm harness实际是一条"一键拉起完整本地微服务环境"的命令,而非简单的进程启动器。
5.2 三种启动模式
harness 支持三种特殊启动参数(见printUsage):
| 参数 | 行为 |
|---|---|
pnpm harness --start | 开发模式:使用tsc-watch监听 TypeScript 编译,编译成功后自动拉起服务,代码变更触发重新编译与服务重启 |
pnpm harness --start-built | 跳过依赖安装与构建,直接以已构建产物启动服务 |
pnpm harness <command...> | 生产模式:安装依赖 → 构建 → 拉起服务 → 等待 API 就绪 → 执行你传入的命令(如pnpm test:snips) |
AGENTS.md中推荐的正是第三种用法:pnpm harness jest ...;而仓库 CI 使用的实际命令为pnpm harness pnpm test:snips(见 .github/workflows/test-server.yml)。注意pnpm test:snips在package.json中定义为仅运行src/__tests__/snips/v1与src/__tests__/snips/v2两个目录下的用例。
5.3 本地只跑相关测试,全量交给 CI
AGENTS.md特别强调:完整测试套件耗时很长,本地应只执行相关测试,让 CI 去跑全量。结合 package.json 的脚本,这一点有非常具体的落地方式:
# 仅跑 snips 中的 v1 / v2 用例(注意先启动 harness) pnpm harness pnpm test:snips # 也可以只针对某个具体测试文件 pnpm harness pnpm exec vitest run src/__tests__/snips/v1/scrape.test.ts此外 package.json 还提供了按鉴权维度切分的全量脚本:pnpm test(排除e2e_noAuth)、pnpm test:local-no-auth(排除e2e_withAuth)、pnpm test:full(排除两套 auth E2E)。在改动只涉及某个 controller 或 lib 模块时,配合这些粒度选择即可显著缩短本地反馈循环。
5.4 运行与停止的工程细节
harness 还处理了很多容易踩坑的工程细节,值得开发者在阅读日志时留意:
- 输出按进程着色分组:
api(绿色)、worker(蓝色)、extract(品红)、nuq(青色)、index/go(黄色)等,便于在多进程日志中快速定位; - 优雅停机:收到 SIGINT/SIGTERM 时按序终止所有子进程,并停掉由它创建的 PostgreSQL / RabbitMQ / FoundationDB 容器;
- 容器运行时自动探测:优先使用 Docker,其次 Podman,两者都不可用时抛出明确错误,提示安装或手动设置
NUQ_DATABASE_URL/NUQ_RABBITMQ_URL/FDB_CLUSTER_FILE; - 尊重显式配置:如果环境里已经设置了
NUQ_DATABASE_URL、NUQ_RABBITMQ_URL或FDB_CLUSTER_FILE,harness 会跳过对应容器的创建,直接沿用外部连接。
六、第四步:推送分支、开 PR、让 CI 验证
完成本地验证后,工作流的收尾是:推送到分支 → 打开 Pull Request → 由 CI 验证胜利条件。CI 的主测试工作流位于 .github/workflows/test-server.yml,其中核心测试步骤正是:
pnpm harness pnpm test:snips(working-directory: apps/api,并通过npm_config_ignore_scripts: 'true'避免重复编译被缓存的 native 库。)
从该工作流的矩阵与注释可以推断,CI 会在多种配置组合下验证你的改动:
- 抓取引擎维度:是否启用 fire-engine(浏览器抓取);
- 代理维度:是否启用代理;
- 搜索维度:是否启用 searxng;
- AI 维度:是否配置 OpenAI / Ollama;
- 队列后端维度:
postgres与fdb两种 NuQ 队列后端(工作流会分别启动 Docker Postgres 或 FoundationDB 容器)。
这正是AGENTS.md要求"用环境变量门控测试"的根本原因:同一份 snips 测试必须能在这些矩阵组合中各自裁剪出可执行的子集,而不会因缺少某类服务而大面积失败。CI 还会生成 JUnit 报告并发布测试汇总,方便在 PR 上快速定位失败用例。
七、实践要点与常见误区小结
结合AGENTS.md与源码,最后汇总几条实操要点:
- 不要手动
pnpm start跑测试:那是开发服务器入口,测试必须通过pnpm harness拉起完整服务栈(API + worker + 各类 NuQ worker + 容器依赖),否则会因缺少队列/数据库/抓取服务而得到错误结论; - 先写测试再写代码:以
snipsE2E 定义胜利条件,至少覆盖 1 条 happy path 与 1 条失败路径; - 统一用
scrapeTimeout:从apps/api/src/__tests__/snips/lib.ts引入(90 秒),不要自行硬编码超时; - 善用门控辅助函数:
describeIf/itIf/testIf结合TEST_PRODUCTION、HAS_AI、HAS_FIRE_ENGINE等常量,让同一套测试在不同 CI 矩阵下自动裁剪; - 本地聚焦、CI 全量:完整套件耗时长,本地用
pnpm harness pnpm test:snips或定向 vitest 命令验证相关用例即可,全量验证交给 PR 上的 CI; - 留意 harness 的容器管理:本地首次运行会自动构建并启动 PostgreSQL / RabbitMQ / FoundationDB 容器,请确保 Docker 或 Podman 可用;若已有外部依赖,则通过
NUQ_DATABASE_URL、NUQ_RABBITMQ_URL、FDB_CLUSTER_FILE环境变量跳过容器创建。
按照这套流程,每次 API 改动都能在本地获得接近生产环境的验证反馈,并通过 CI 矩阵覆盖到多种运行配置,从而保证 Firecrawl 这条"搜索 → 抓取 → 交互"的链路在版本演进中持续可靠。
- 网页爬虫
- 后端
- AI 应用
【免费下载链接】firecrawl
The web data API to search, scrape, and interact at scale. 🔥
相关推荐
Firecrawl 贡献者开发指南:E2E 测试驱动、harness 本地调试与 knip 检查全流程
Firecrawl 贡献者开发指南:E2E 测试驱动、harness 本地调试与 knip 检查全流程 Firecrawl 是一个面向大规模搜索、抓取与交互场景
网页爬虫后端AI 应用AutoGPT PR 端到端手动测试技能:基于 Docker Compose、agent-browser 与 API 证据链的 E2E 测试工作流
AutoGPT PR 端到端手动测试技能:基于 Docker Compose、agent browser 与 API 证据链的 E2E 测试工作流 AutoGP
人工智能AI Agent自主智能体Agent 工作流工作流自动化后端前端RisingWave 连接器开发指南:基于 RiseDev 的一体化开发环境与 E2E 测试工作流
RisingWave 连接器开发指南:基于 RiseDev 的一体化开发环境与 E2E 测试工作流 RisingWave 支持大量外部连接器(Source 与
数据库流处理后端数据工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考