@cosmicdrift/kumiko-测试
久美子应用的测试模板. 只测试:绝不从生产代码中导入.
import { seedTenant, setupAppTestStack } from "@cosmicdrift/kumiko-dev-server/testing";
const stack = await setupAppTestStack([myFeature]);
const tenant = await seedTenant(stack, { users: 2 }); // light: no rows, own id/key/users
const persisted = await seedTenant(stack, { persist: true }); // real rows via the dispatcher
await tenant.api.writeOk("my-feature:write:thing:create", { title: "x" });
preload/{temporal,ci-log,scrub-env,schema-env-defaults,env,real,dom}: bunfig 键preload条目.scrub-env删除PROVIDER_ENV_KEYS(单位违约);schema-env-defaults填补JWT_SECRET编辑KUMIKO_SECRETS_MASTER_KEY_*配对 : 框架本身的嵌入式方案要求的(单元, DOM, 集成, 真实的—— 从来不是服务终点;env添加schema-env-defaults加减 本地主机服务默认值( 整合) ;real拒绝运行没有KUMIKO_REAL_PROVIDERS=1并保存钥匙;dom登记幸福 (窗口/文件/HTMLELENTE),同时保留 Bun的本地取取/请求/答复,并清理 (单位:千美元)@testing-library/react卸载, Radix DOM/风格的漏出,window.location在每次测试之后.kumiko-testing bunfig [--dom] [--coverage]: 写bunfig.toml, (中文(简体) ),bunfig.integration.toml财务报告和审定财务报表bunfig.real.toml(单位:千美元)bunfig.dom.toml) (中文(简体) ).--dom添加preload/dom(快乐度+测试-图书馆/反应清理);@happy-dom/global-registrator财务报告和审定财务报表@testing-library/react(这需要@testing-library/dom)是可选的同伴依赖关系,在使用时安装它们--dom。 。 。 .kumiko-testing integration [--parallel N] [--timings <file>]: 运行*.integration.test.ts15s的预算.--parallel只有当你要求它,然后与--no-isolate数字 : 包子 1. 4.0的隐含--isolate每个文件都会泄露本地记忆,直到CI工人被OOM杀死.
E2E级(./e2e, (中文(简体) ), ./e2e/seed-route(中文(简体) )
@playwright/test 是可选的同伴依赖关系;只有 ./e2e 子路径导入它。 服务器侧
(单位:千美元)./e2e/seed-route从来没有.
// e2e/server.ts: mount the seed routes next to the app
runDevApp({ features, auth: { admin }, extraRoutes: createE2eSeedRoutes() });
// playwright.config.ts
export default defineAppE2eConfig({ port: 4321 });
// e2e/flow.spec.ts
import { expect, test } from "@cosmicdrift/kumiko-testing/e2e";
test("member sees the note", async ({ seedTenant, page }) => {
const tenant = await seedTenant({ users: 1, with: [seedNotes] });
await tenant.loginAs(page, tenant.members[0]!);
});
defineAppE2eConfig({ port, env?, locale?, projects?, serverEntry?, testDir?, testMatch?, isolatedSpecs? }):平行、重复、超时和 工人(KUMIKO_E2E_WORKERS覆盖)由模板固定;项目不能设置.isolatedSpecs: ["**/composer.spec.ts"]移动改变全应用程序状态的光谱(目录条目,全局) 将每个项目的一个孤立的双(设置)chromium-isolated, (中文(简体) ),<name>-isolated)),其运行于所有平行 (b) 由项目本身的一位工人实施的项目use. 有自己的项目testMatch被拒绝。 不能这样 与--shard; 细节docs/guides/testing-standard.md。 。 。 .KUMIKO_E2E_REMOTE_BASE_URL=https://staging.example.com运行相同的规格对已运行 实例: 没有启动网络服务器baseURL是该 URL(只有https或https) 。 添加KUMIKO_E2E_STORAGE_STATE=<file>记录的会话playwright codegen --save-storage· ; 超时、取景口和 DPR 保留模板。 种子路线在这样的目标上不存在,所以使用 用于仅需要现有会话的规格。 追踪、截图和录像都停了 因为一个微量的拉链能保存目标的会话饼干和数据.csrfFetch处理__Host-前缀的 csrf cookie a https目标集.- 种子路径仅以
KUMIKO_TEST_SEED=1外边NODE_ENV=production,与 每次运行KUMIKO_TEST_SEED_TOKEN输入x-kumiko-test-seed头曰.defineAppE2eConfig设置所有三个. - 模板还设定
KUMIKO_DEV_STYLESHEET_WATCH=0,因此dev-server构建应用程序的CSS一次并 从不启动尾风--watch过程——该监视器否则会处理每件 Playwright 文物 写入test-results/作为重建的触发器. - 每次运行都有自己的Meilisearch索引前缀
e2e_<uuid>_(单位:千美元)KUMIKO_MEILI_INDEX_PREFIX)和 模板的globalTeardown完全放下那些指数。 拥有自己撕裂的配置可以保存 清理为globalTeardown: [base.globalTeardown, "./e2e/teardown.ts"]。一个被杀死的运行会留下它的索引 后面 后面那个. - App 角色 :
createE2eSeedRoutes({ extraRoles: ["TenantMember"] })让我们tenant.addUser(["TenantMember"])种子他们. tenant.addUser(["SystemAdmin"])种子 SysAdmin 屏幕的平台操作员 :SystemAdmin土地作为土地 全球用户角色,用户作为租户加入Member除非您同时通过租户角色 (单位:千美元)["TenantAdmin", "SystemAdmin"]) (中文(简体) ). 上面的种子门是环绕此地的唯一边界.- 用于测试数据的应用种子器 种子用户无法创建 :
createE2eSeedRoutes({ extraSeeders: { checks: async (ctx, tenantId, body) => … } }),从流中调用为await tenant.seed("checks", { days: 30 })。播种者会得到body作为unknown(用zod来验证)parse页:1ZodError改为400)和actx谁write编号 :query作为租户的系统用户运行 (SystemAdmin),绑定在那个租户身上,所以目标处理者必须承认SystemAdmin(数据不正常) 处理器像回溯历史一样,需要一个SystemAdmin-只处理器. 没有原始的 DB 访问 . 仅接受同一服务器种子租入路线所播种的租户;未知的渗出地名为404个. - A 类
SeedPart接收{ tenant }和与进程相悖seedTenant还有HTTP租户. - 已有自定义服务器条目( 邮件传输、 KMS、 启动种子, ...) , 也可以运行在 prod 中 ?
点
serverEntry而不是重复e2e/server.ts,只载着种子路线 当种子条件维持时:...(isE2eSeedingEnabled() ? createE2eSeedRoutes() : [])它本身extraRoutes——然后从不登记路线,而不仅仅是按要求404.isE2eSeedingEnabled从@cosmicdrift/kumiko-testing/e2e/seed-route。 。 。 . - 测试的 App-wide 默认值(例如已完成的入职)属于
SeedPart转至seedTenant({ with: [defaults] })从来没有过postSave钩上"tenant"实体,自此 钩子也点燃每个真正的签注.
截图./e2e(中文(简体) )
种子身份是每个运行独有的(admin-<tenantId>@…) (中文(简体) ). 为了显示一个可以显示的地址
流量在租户存在后对映射进行记录,跑者用文字和形式值替换
在每次抓取之前(在每个主题和视图变化之后),种子数据保持不变:
presentIdentities([{ from: tenant.admin.email, to: "[email protected]" }]) 从情景固定装置,或
captureScreenshot(page, name, { presentIdentities: [...] }) 内线.
runScreenshots编号 :runMatrix 进行同样的测试 test 上面,所以一个情景的 flow
也获得种子租户的固定物:
{ name: "dispatch", flow: async (page, { seedTenant }) => {
const tenant = await seedTenant({ users: 1 });
const [member] = tenant.members;
if (!member) throw new Error("seedTenant returned no member");
await tenant.loginAs(page, member);
await page.goto("/dispatch");
},
beforeCapture: async (page) => {
await page.addStyleTag({ content: ".live-clock { visibility: hidden }" });
},
}
beforeCapture?: (page) => Promise<void> 在设置了取景口和页面后运行,
就在截图前. runMatrix 调用它一次每个主题 × 视图端 对于相同的情景,所以
它必须是一能的——隐藏或掩盖一个元素,而不是像点击按钮那样一发一发的动作,
它只会在第一次捕捉 和每一次超时.
runMatrix 也支持通过 Playwright 项目以 Viewport id 命名的真实设备仿真
(单位:千美元)desktop, (中文(简体) ), tablet, (中文(简体) ), mobile与 use.isMobile: true数字 :
projects: [
{ name: "desktop", use: { ...devices["Desktop Chrome"] } },
{ name: "tablet", use: { ...devices["iPad Pro 11"] } }, // portrait, WebKit
{ name: "mobile", use: { ...devices["iPhone 13"] } }, // portrait, WebKit
];
一个设备工程精确抓取 <name>.png 设备的原生尺寸——没有 setViewportSize, (中文(简体) ),
将毁灭一切。 任何其它工程( 桌面通过) 仍然循环剩余部分
视图端口通过 setViewportSize,跳过 IDs 一个已经覆盖的设备工程。 没有设备
工程, 无更改 : 桌面、 平板电脑和移动设备全部通过 setViewportSize 和以前一样.
SCREENSHOT_VIEWPORTS 仍然过滤两者 — 一个过滤出设备工程的测试被跳过而非运行
空无相. CI 需要帮助 bunx playwright install webkit 以运行使用 WebKit 设备的设备项目.