Strapi e2e 测试实战:tests/app-template 测试应用模板的 Schema 设计与 API 定制

Strapi e2e 测试实战:tests/app-template 测试应用模板的 Schema 设计与 API 定制 Strapi e2e 测试实战tests/app-template 测试应用模板的 Schema 设计与 API 定制【免费下载链接】strapi Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer-first.项目地址: https://gitcode.com/GitHub_Trending/st/strapiStrapi 的 Playwright 端到端e2e测试依赖一个预置的“测试应用模板”它决定了每次生成的 test-app 长什么样有哪些内容类型、组件以及哪些专为测试服务的 API 端点。本文以官方文档 App Template 为主体完整拆解tests/app-template模板的内容 Schema、更新工作流与 API 定制并结合模板内真实源码服务、路由、控制器与 e2e 测试工具rate-limit.ts 等说明每项定制的设计动机与实现细节。读完本文你将掌握 Strapi 团队如何搭建一套“真实、可复现、可清理”的 e2e 测试底座并能独立完成模板的修改与数据同步。一、App Template 是什么在 e2e 体系中处于什么位置App Template 位于仓库根目录下的tests/app-template被 e2ePlaywright与其他测试类型共享。它为 e2e 测试提供定制化的应用骨架与工具端点使测试可以稳定运行预置了完整的内容类型Article、Author、Product、Match 等与组件meta.seo、match.player、page-blocks/*等内嵌了一批“测试专用”的 API 路由如开关登录限流供 Playwright 用例通过 HTTP 请求操控被测应用生成的 test-app 通过 run-e2e-tests 脚本 自动把 monorepo 内最新代码链接进去保证测的是当前开发版本。需要牢记的核心维护规则来自文档 Overview如果你修改了模板必须运行yarn test:e2e:clean删除已有 test-app 并重新生成否则新生成的测试应用不会拾取模板变更。此外tests/app-template/README.md 指出该模板同时被e2ePlaywright和cliJest两套测试共用由统一测试运行器 run-tests.js 自动使用。模板中甚至还有一个template/子目录存放供 CLI 测试生成应用使用的另一份精简 API 集合。修改模板后e2e 与 cli 两条测试线都会受到影响这正是“共享模板”带来的收益所有测试类型面对一致的应用结构。文档同时强调了一条设计原则模板应当真实realistic其组织方式应贴近真实用户用 Strapi 创建应用的样子。这也是为什么模板里的内容类型会包含 i18n、动态区Dynamic Zone、blocks、唯一约束、正则校验等“用户日常会用到的全部特性”——e2e 测试正是靠这套真实度来覆盖核心业务流。二、如何更新 App Template完整工作流文档给出的更新流程分“改模板”与“模板改完后同步数据”两步。所有命令均从 monorepo 根目录执行。2.1 修改模板本体运行yarn test:e2e:clean删除已有 test-app运行yarn test:e2e -c1 -- --ui只生成一个 test-app-c1把并发限制为 1--ui让 Playwright 以交互模式打开而不真正跑用例按 Data Transfer 文档“导入现有数据包”一节 的指引把现有数据集导入这个测试实例此时测试应用的服务端运行在1337 端口登录后台在后台的 Content-Type Builder 中做出你需要的 schema 变更把 test-app 中新生成的文件content-type JSON、路由、控制器、service 等复制回tests/app-template覆盖模板。2.2 模板更新后的数据同步模板变了数据库里的数据包data packet可能就不再匹配需要重新导出一份yarn test:e2e:clean删除旧 test-appyarn test:e2e -c1 -- --ui用新模板生成一个新测试应用按 Data Transfer 文档“导入现有数据包”一节 导入数据按 Data Transfer 文档“导出更新数据包”一节 导出更新后的数据包放回tests/e2e/data。Data Transfer 文档还特别警告如果你改动了任何内容 Schema包括新增内容类型务必同步更新 app-template否则 DTS 导入会因为找不到对应 schema 而失败。模板、schema、数据包三者必须保持一致。三、模板内置的内容 Schema 详解以下是模板中预置的核心内容类型。它们的完整 schema 定义含kind、info等元信息见 tests/app-template/src/api 下各内容类型的schema.json文件。3.1 Article集合类型{ // ... attributes: { title: { type: string }, content: { type: blocks }, authors: { type: relation, relation: manyToMany, target: api::author.author, inversedBy: articles } } // ... }典型的“博客文章”模型title字符串、content使用blocks类型Strapi v5 的富文本表示法authors与 Author 建立多对多关联inversedBy: articles指明反向关联字段。3.2 Author集合类型{ // ... attributes: { name: { type: string }, profile: { allowedTypes: [images, files, videos, audios], type: media, multiple: false }, articles: { type: relation, relation: manyToMany, target: api::article.article, mappedBy: authors } } // ... }profile是单值 media 属性通过allowedTypes限制可上传的媒体类别articles是 Article 侧authors的反向关联mappedBy: authors。Article ↔ Author 构成 e2e 测试中最基础的关系对被列表、表单、关联选择器等多类用例反复使用。3.3 Homepage单类型{ // ... attributes: { title: { type: string }, content: { type: blocks }, admin_user: { type: relation, relation: oneToOne, target: admin::user }, seo: { type: component, repeatable: false, component: meta.seo } } // ... }Homepage 是模板中的“主页”单类型Single Type。值得注意的是admin_user它是一条指向管理面板用户admin::user的一对一关联用于验证跨内容管理器/管理面板的关联能力seo则挂了一个不可重复的meta.seo组件。3.4 Product国际化的集合类型Product 是一个国际化internationalized的集合类型其字段级的 i18n 配置差异非常值得研究——这恰好覆盖了 i18n 的所有典型形态{ // ... attributes: { name: { pluginOptions: { i18n: { localized: true } }, type: string, required: true }, slug: { pluginOptions: { i18n: { localized: true } }, type: uid, targetField: name, required: true }, isAvailable: { pluginOptions: { i18n: { localized: false } }, type: boolean, default: true, required: true }, description: { pluginOptions: { i18n: { localized: true } }, type: blocks }, images: { type: media, multiple: true, required: false, allowedTypes: [images, files, videos, audios], pluginOptions: { i18n: { localized: false } } }, seo: { type: component, repeatable: false, pluginOptions: { i18n: { localized: true } }, component: meta.seo }, sku: { pluginOptions: { i18n: { localized: true } }, type: integer, unique: true }, variations: { type: component, repeatable: true, pluginOptions: { i18n: { localized: true } }, component: product.variations } } // ... }从字段配置中可以读出几个 e2e 测试点slug是uid类型targetField指向name——即 slug 由 name 派生且每个语言版本独立派生localized: trueisAvailable与images显式localized: false代表“全语言共享同一值”的字段与name/description/seo等localized: true字段形成对照正好覆盖本地化编辑器的两类交互sku带unique: true约束且参与本地化可用来测唯一性校验在 i18n 场景下的行为variations是可重复组件repeatable: true并挂product.variations组件测试组件增删排序等交互。3.5 Match集合类型正则约束 组件 动态区{ // ... attributes: { date: { type: date }, kit_man: { type: string }, opponent: { type: string, required: true, regex: ^(?!.*richmond).* }, lineup: { type: component, repeatable: true, component: match.player }, most_valuable_player: { type: component, repeatable: false, component: match.player }, sections: { type: dynamiczone, components: [match.player, product.variations] } } // ... }Match 是模板中“特性密度”最高的 schemaopponent用regex: ^(?!.*richmond).*设置了必填 正则校验负向前瞻禁止包含 richmond是验证前端校验提示的标准用例lineup可重复与most_valuable_player不可重复同时使用match.player组件覆盖组件重复/单实例两种形态sections是动态区Dynamic Zone只允许match.player和product.variations两个组件用于测试动态区的区块增删、排序与内容类型切换。3.6 Shop国际化的单类型动态区 最小数量约束Shop 是一个国际化单类型{ // ... attributes: { title: { pluginOptions: { i18n: { localized: true } }, type: string, required: true }, content: { pluginOptions: { i18n: { localized: true } }, type: dynamiczone, components: [ page-blocks.product-carousel, page-blocks.hero-image, page-blocks.content-and-image ], required: true, min: 2 }, seo: { type: component, repeatable: false, pluginOptions: { i18n: { localized: true } }, component: meta.seo } } // ... }它的content动态区白名单了三个page-blocks组件产品轮播、主视觉图、图文混排并且同时声明了required: true与min: 2——即每个语言版本的动态区至少要有 2 个区块。这是验证动态区“最小数量”约束的理想场景。完整的区块组件定义可在 tests/app-template/src/components/page-blocks 目录下查看。3.7 Upcoming Match单类型{ // ... attributes: { title: { type: string }, number_of_upcoming_matches: { type: integer }, next_match: { type: date } } // ... }最简单的单类型提供整数与日期字段作为单类型列表视图、发布状态等基础场景的测试对象。四、API 定制测试专用端点及其源码实现文档指出模板中有一组 API 定制位于tests/app-template/src/api/config文档原文写作template/src/api/config实际相对 monorepo 根目录的路径为 tests/app-template/src/api/config。这是一个没有 content-type、只有路由/控制器/service 的纯 API专门承载测试钩子。从 routes/config.js 可以看到 4 条路由全部为 POST 且auth: false测试运行时不需要登录态即可调用路由handler用途/config/ratelimit/enableconfig.rateLimitEnable开关登录限流中间件/config/permissions/pruneconfig.permissionsPrune清理数据库中残留权限/config/permissions/resync-super-adminconfig.permissionsResyncSuperAdminDTS 导入后重同步超级管理员权限/config/resettransfertokenconfig.resetTransferToken重置数据转移 token4.1 Rate Limit登录限流开关用法文档给出的 Playwright 助手代码与仓库中 tests/utils/rate-limit.ts 的实现一致async function toggleRateLimiting(page, enabled true) { await page.request.fetch(/api/config/ratelimit/enable, { method: POST, data: { value: enabled }, }); }它做了什么该端点用于启用或禁用 Strapi 的登录限流中间件。启用状态下每个用户的登录请求被限制为 5 分钟内最多 5 次。源码级印证控制器 config.js 中rateLimitEnable从请求体取出value委托给 service而 service config.js 的实现只有一行strapi.config.set(admin.rateLimit.enabled, !!value);即直接在运行时改写admin.rateLimit.enabled配置项注意!!value做了布尔归一化无需重启服务即可热切换限流行为。为什么需要它有些测试场景需要连续进行多次错误登录例如验证错误提示、锁定逻辑本身此时限流会干扰断言于是测试可以先调用toggleRateLimiting(page, false)关掉限流测完再打开。真实用例可见 tests/e2e/tests/admin/login.spec.ts其中在同一用例内先后调用toggleRateLimiting(page, true)与toggleRateLimiting(page, false)来分别验证“限流生效”与“限流关闭”两种行为。4.2 Admin Auto Open禁用后台自动开浏览器用法在测试应用插件的 bootstrap 阶段调用bootstrap({ strapi }) { strapi.service(api::config.config).adminAutoOpenEnable(false); },它做了什么启用或禁用 admin 面板的“首次启动自动打开浏览器”行为。对应 service 实现同样是运行时改配置strapi.config.set(admin.autoOpen, !!value);为什么需要它文档给出了很实际的理由本地跑 e2e 测试时如果autoOpen为 true每次 test-app 首次启动都会弹出一个浏览器窗口反复生成/重启测试应用时非常烦人。因此在测试应用实例的 bootstrap 阶段将其禁用让 Playwright 完全接管浏览器实例。4.3 其余两条测试端点除文档重点介绍的 Rate Limit 与 Admin Auto Open 外同一 API 还有两个配套端点均定义在 routes/config.js 并在 controllers/config.js 中实现/config/permissions/prune调用strapi.service(admin::permission)的cleanPermissionsInDatabase()清理数据库中残留的权限记录/config/permissions/resync-super-admin调用 resync-super-admin-after-import.js在DTS 导入之后同步 E2E 专用的 Content-Manager 配置并重置 Super Admin 权限——这是数据导入工作流见 Data Transfer 文档的收尾步骤/config/resettransfertoken调用 create-transfer-token.js 的createTestTransferToken为测试重置数据转移 token。五、小结模板、数据、测试三者的联动关系tests/app-template不是孤立存在的它处在一条清晰的联动链上模板tests/app-template定义 schema 与测试端点是 e2e 与 cli 两套测试的共同应用蓝本测试应用由 run-tests.js 运行器基于模板生成到test-apps/e2e/test-app-{n}并链接 monorepo 最新依赖数据包tests/e2e/data下的解包导出目录通过tests/utils/dts-import.ts的resetDatabaseAndImportDataFromPath在每个用例前重置数据库保证用例相互隔离任何一方模板 schema、数据包、测试代码发生变化都需要按第二节的流程clean→ 重新生成 → 导入 →必要时导出保持三者一致。如果你要为 Strapi 新增一个覆盖新特性如新的 i18n 形态、新的组件交互的 e2e 用例标准路径就是在 Content-Type Builder 中按 2.1 节流程把新 schema 落进模板按 Data Transfer 文档同步数据包再利用tests/app-template/src/api/config这类测试端点为用例提供所需的运行时控制能力。配套阅读e2e Setup 文档 覆盖 Playwright 运行参数与 CE/EE 环境变量Data Transfer 文档 覆盖数据包的导入导出细节。【免费下载链接】strapi Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer-first.项目地址: https://gitcode.com/GitHub_Trending/st/strapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考