@clipboard-health/playwright-toolkit
共享反花样原生作 Clippboard 健康游戏套房.
该套件拥有重试政策,APM相关,共享admin-token craching,部署-资产检查,JSON响应终生捕获,Mailpit投票,Cognitto登录诊断,并设置重试分类. 消耗寄存器只保留配置和特定域的匹配.
安装
npm install --save-dev @clipboard-health/playwright-toolkit
@playwright/test 是同级依赖。 该套件支持Playwright 1.50并更新.
API 地图
| 当地能力 | 包替换 |
|---|---|
retryWithBail | runWithRetry({ mode: { kind: "classified", ... } }) |
retryUntilPassOrTimeout | runWithRetry({ mode: { kind: "poll", ... } }) |
| 复制已复制的可追溯父页面固定 | createTraceparentFixtures() |
| 管理令牌承诺缓存和文件锁定 | generateAdminAuthToken() 或者说 getOrCreateAdminAuthToken() |
| 部署的前端/流动资产循环 | verifyDeployedAssets() 财务报告和审定财务报表 waitForDeployedAssets() |
| 被推迟的 Playwright JSON 回复为 | waitForParsedJsonResponse() |
| 邮箱搜索/拖网 | createMailpitClient(), (中文(简体) ), fetchMagicLinkFromMailpit(), (中文(简体) ), fetchEmailOtpCodeFromMailpit() |
| 认知OTP 重定向调试 | fillOtpAndWaitForCognitoRedirect() |
| 设置 HTTP 和身份重试检查 | classifySetupRetry() 财务报告和审定财务报表 isRetryableHttpStatus() |
重试合同
runWithRetry 是此软件包中唯一的复试抽象。 它有两种模式,因为复试和投票回答不同的问题.
分类重试
对通常应传递第一次尝试的操作使用分类重试. 打电话者必须提供 isTransient。不存在重复任意失败的默认情况.
import { runWithRetry } from "@clipboard-health/playwright-toolkit";
const result = await runWithRetry({
operationName: "create shift offer",
operation: async () => await createShiftOffer(),
mode: {
kind: "classified",
maxAttempts: 4,
delayMs: 2000,
isTransient: ({ error }) => isKnownCdcReadinessError(error),
},
});
const shiftOffer = result.value;
一项合法的分类重试符合Flaky-critic B1合同:
- 手术是安全的重复.
isTransient积极识别已知的瞬间状态.- 验证、许可和其他确定性失败返回
false。 。 。 . - 重试有一个固定的尝试预算.
- 过度抛出
RetryError尝试计数 时间过长 终极原因 以及最后原因.
不使用像每个 422 作为上游。 匹配可以在不更改请求的情况下解决的特定准备状态消息或服务条件.
投票直到通过
使用民意调查模式来进行"先入为出"的"先入为出"探测器,其中失败意为"尚未就绪"
const result = await runWithRetry({
operationName: "wait for worker profile",
operation: async () => await assertWorkerProfileReady(),
mode: {
kind: "poll",
timeoutMs: 90_000,
intervalsMs: [1000, 2000, 3000, 5000],
},
});
设定 mode.isTransient 当探测器也可以投出决定性的失败。 正在返回 false 立即停下来 reason: "non-transient"。 。 。 .
投票模式将每次尝试与剩余超时预算相竞争,并中止尝试 signal 当期限到期时。 将信号传递给网络呼叫, 因此超时工作被取消:
operation: async ({ signal }) =>
await fetch(readinessUrl, {
signal,
});
JSON 反应寿命
Playwright响应机构可以在文档导航后变得无法读取. 使用
waitForParsedJsonResponse 当一个页面动作同时等待 JSON 响应并交叉 a
导航边界。 帮助者分析每个候选人 waitForResponse 上游和
仅返回解析值,所以调用者不能不小心保留懒惰 Response 手柄.
Predicate-local解析缩小了反应-寿命窗口,但无法消除它:铬可以
丢弃基本的 CDP 资源同时 response.json() 还有待处理.
const workerResponsePromise = waitForParsedJsonResponse({
page,
timeoutMs: 30_000,
isCandidate: ({ response }) =>
response.ok() && new URL(response.url()).pathname === "/api/worker",
parseCandidate: ({ body }) => workerResponseSchema.parse(body),
});
await page.reload({ waitUntil: "domcontentloaded" });
const workerResponse = await workerResponsePromise;
返回时 undefined 从 parseCandidate 当 URL 级别候选人有效,但其正文不是时
测试需要的反应。 如果一个候选机构读取失败 准确的铬
Network.getResponseBody 编号 : No resource with given identifier found 签名,帮助者拒绝
只有那个候选人 并且继续留在 原来的有限服务员。 它不发布另一个
请求或导航,添加重试或睡眠,或扩展超时。 无效的 JSON, 解析和计划
出错, 近似匹配的协议出错, 页面或目标关闭, 并立即发布取消 .
如果外部服务员在分类损失后超时,错误将原超时保留为
其原因,只添加带损失计数、方法、状态和经编辑路径的带界限的诊断
模板。 保持存储器专用的URL,方法,资源类型,以及消费者的策略匹配.
每一次检测的痕量父母固定
一次扩展存储器已有的 Playwright 测试对象 :
import { test as base } from "@playwright/test";
import {
createTraceparentFixtures,
type TraceparentFixtures,
} from "@clipboard-health/playwright-toolkit";
export const test = base.extend<TraceparentFixtures>(createTraceparentFixtures());
自动固定器生成一个非零W3C traceparent,保留项目级别 extraHTTPHeaders,在测试机构运行前在浏览器上下文上安装合并头,并添加一个 traceparent 测试注释。 将已有信头传递到 installTraceparentForTest 如果自定义固定器手动安装了信头.
管理符
generateAdminAuthToken 运行 cbh auth gentoken user,重复只批准瞬态的 CLI 签名,从错误中编辑管理邮件,并在原子文件系统锁后缓存无记名符号.
const tokenEntry = await generateAdminAuthToken({
adminEmail: adminUser.email,
apiEnvironmentName: "staging",
cacheIdentity: {
namespace: "cbh-admin-frontend",
clientName: "admin-app",
issuer: "clipboard-health-staging",
tokenKind: "access",
},
clientName: "admin-app",
cacheDurationMs: 10 * 60 * 1000,
validationPolicy: "admin-session-v1",
validateToken: async ({ authToken, signal }) => {
const response = await fetch(`${adminApiUrl}/auth/session`, {
headers: { Authorization: authToken },
signal,
});
if (response.status === 401 || response.status === 403) {
return false;
}
if (!response.ok) {
throw new Error(`Admin token validation failed with HTTP ${response.status}`);
}
return true;
},
});
const adminAuthToken = tokenEntry.authToken;
缓存键包含环境,邮件的SHA-256摘要,以及可选的显式缓存身份;它从未包含邮件本身. 给每个仓库一个稳定 namespace,并在这些值有差异时,按符号类型、客户端、受众和发行人区分证书. generateAdminAuthToken 自动区分不同的 clientName 值即使当 cacheIdentity 省略.
JWT 缓存新鲜度以 cacheDurationMs 和标志 exp 索赔减去60秒安全故障。 不透明无记名标志继续使用 cacheDurationMs设置 jwtExpirationSafetySkewMs 当一个套房需要不同的skew.
何时 validateToken 提供,工具包在保存缓存锁的同时运行只读探测器,并在缓存条目中记录成功验证,所以等待Playwright工人共享一个探测器. 给探测器一个清晰的 validationPolicy 当验证合同变得更为严格或有其他变化时,就予以加价; 政策改变促使重新确认。 工具包默认在30秒后中止验证,范围被限制在锁的 stale 租赁下. 通过提供的 signal 到网络请求,并设定 validationTimeoutMs 当终点需要更短的最后期限时.
返回时 false 只有在消费者拒绝证书时,例如HTTP 401 或者说 403; 抛出 5xx 反应和其他验证基础设施故障。 一个被拒绝的缓存令牌被拆出,并更换一次给所有等待的工人和共享缓存文件系统的shards. 拒绝生成的令牌不会被缓存 。 探测器应核实相同的受众,并允许在不改变应用程序状态的情况下对套房需求进行分界.
缓存和锁定文件使用模式 0600。一个在生成前获取锁重读缓存的流程,该流程防止在 Playwright 工人、硬盘和共享的本地流程之间重复代币薄荷 cacheDirectory。单独的 CI 主机或装有独立文件系统的容器可能仍然可以独立地薄荷.
使用 getOrCreateAdminAuthToken 当寄存器需要不同的符号命令时 :
const tokenEntry = await getOrCreateAdminAuthToken({
adminEmail,
apiEnvironmentName,
cacheIdentity: {
namespace: "cbh-mobile-app",
audience: "monolith-api",
clientName: "mobile-app",
tokenKind: "access",
},
cacheDurationMs: 10 * 60 * 1000,
createToken: async () => await generateTokenWithRepositoryCli(),
});
在消费者拒绝一个标志后 把证书交给 forceRefresh。来电者报告同一被拒绝的证书分享一个替代者,包括另一名工人已经刷新缓存后抵达的来电者:
import { getOrCreateAdminAuthToken } from "@clipboard-health/playwright-toolkit";
const freshTokenEntry = await getOrCreateAdminAuthToken({
...tokenParams,
forceRefresh: {
rejectedAuthToken,
},
});
保留 forceRefresh: true 在没有被拒绝证书的情况下,故意无条件轮换.
或者,在API中明确使用相同的默认身份的生成符号无效 generateAdminAuthToken数字 :
import { invalidateGeneratedAdminAuthToken } from "@clipboard-health/playwright-toolkit";
await invalidateGeneratedAdminAuthToken({
adminEmail,
apiEnvironmentName,
clientName: "admin-app",
});
使用 invalidateAdminAuthToken 页:1 getOrCreateAdminAuthToken 条目,通过相同 cacheIdentity 用于创建条目.
仅在只读或以其他方式安全重复时自动重试请求. 该工具包不重放消费者请求,因为它无法确定一个失败的突变是否具有副作用.
使用 onCacheEvent 安全诊断 事件曝光 kind,缓存键和证书指纹,可选受众,和薄荷/过期/验证时间戳. 指纹区分相继的资质,而不披露符号,电子邮件,或原始缓存身份.
已部署资产
仓库包裹仍然发现资产,并决定哪些文件是运行时间或指纹。 该套件拥有请求货币,超时,缓存断接,内容类型检查,瞬态HTTP重试,尝试诊断,稳定窗口投票.
const report = await waitForDeployedAssets({
checks: localAssetManifest.map((asset) => ({
path: asset.path,
url: new URL(asset.path, deploymentBaseUrl).toString(),
method: asset.isFingerprintNamedJavaScript ? "GET" : "HEAD",
cacheMode: asset.isRuntimeAsset ? "cache-busted" : "normal",
expectedContentTypes: [asset.contentType],
})),
timeoutMs: 10 * 60 * 1000,
pollIntervalMs: 10_000,
stableWindowMs: 30_000,
});
HTTP 密码 408, (中文(简体) ), 425, (中文(简体) ), 429,以及 5xx 答复对于资产交付来说是短暂的。 内容类型不匹配和失败的自定义验证器是决定性的, 除非验证器返回 isTransient: true。 。 。 .
使用 validateResponse 用于特定存储库的检查,例如: build-info.json 承诺匹配。 验证器在调用收发包前不会消耗一个成功的响应机体,因此回调器可以直接读取.
邮箱
从寄存器配置创建客户端, 然后使用打入的计票器 :
const mailpit = createMailpitClient({
password: process.env.MAILPIT_PASSWORD ?? "",
});
const code = await fetchEmailOtpCodeFromMailpit({
client: mailpit,
email,
sentAfter: codeRequestedAt,
excludeCodes: [previousCode],
});
await page.getByLabel("Verification Code").fill(code.value);
粉选者搜索最新的第一, 容忍不完整的日期 搜索结果, 获取最多三个候选人 每一次探测, 404, (中文(简体) ), 408, (中文(简体) ), 429,以及 5xx。它们返回所取值和源消息ID.
超时错误和成功结果包括民意测验诊断. 这些区别瞬间
从信件中查找错误并计数缓存提取失败的跳过 。 第一个
整个等待中三处提取缺失 仅携带 SHA-256 消息ID散列, 内容
通道、合并文本/HTML字符计数桶、候选人取货年龄和检察官办公室形状.
形状分类遵守记录的四加四检察官办公室格式;它不选择
值或更改参数. other 表示内置的检察官办公室分析器确认的值为:
调用者的提取器没有选中 。 样本中没有原始信件字段或提取出的秘密.
该错误为Playwright报告保留了这个证据,但没有单独的日志钩.
Misses仍然被消息ID所缓存,默认等待仍然为60秒. 这些诊断 帮助分类未来的失败;它们不能确定历史消息为何失败.
认知检察官办公室和登录诊断
fillOtpAndWaitForCognitoRedirect 显示器 RespondToAuthChallenge 请求 SMS_OTP 财务报告和审定财务报表 EMAIL_OTP 等待预期的重定向.
await fillOtpAndWaitForCognitoRedirect({
page,
testInfo,
otp,
expectedUrl: /\/dashboard/,
});
在失败时,错误包括已消毒的Cognitto请求摘要,响应或请求失败细节,当前URL,以及可见的页面文本样本. 何时 testInfo 并附上经编辑的截图. sanitizeCognitoDiagnosticText 财务报告和审定财务报表 isCognitoOtpChallengeRequest 是对寄存器特定登录流量的公开.
设置重试分类
classifySetupRetry 将身份碰撞与短暂的基础设施故障区分开来:
const classification = classifySetupRetry({
error,
isIdentityCollision: ({ error: candidate }) => isWorkerCreationPhoneCollision(candidate),
});
其结果与片面批评C1合同相同:
| 分类 | 决定 | 身份行为 |
|---|---|---|
identity-collision | regenerate-identity | 在下次尝试前生成新的电话、电子邮件或ID . |
transient | retry-same-identity | 重复同样的请求和身份. |
deterministic | do-not-retry | 立即失败. |
isRetryableHttpStatus 返回时 true 仅限 408, (中文(简体) ), 429,以及 5xx。一个寄存器可以添加狭义 isTransientError 一个已知的非HTTP瞬间签名的上游.
移徙清单
- 将本地帮助导入替换为包导出 .
- 将寄存器特定URL,选择器,资产发现,以及错误信息匹配等移动到一薄包装中.
- 保持重试分类收窄. 记录为什么手术是安全的重复.
- 对于身份相撞,在重试操作中生成身份,这样每次相撞尝试都会得到一个新的值.
- 删除本地助手并将其边界测试移植到包装上.
- 运行消费存储器的 Playwright 单元测试和 E2E 设置检查.