@clipboard-health/playwright-toolkit 1.3.2 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Shared anti-flake primitives for Clipboard Health Playwright suites.
4
4
 
5
- The package owns retry policy, APM correlation, shared admin-token caching, deployed-asset checks, Mailpit polling, Cognito login diagnostics, and setup retry classification. Consuming repositories keep only configuration and domain-specific matching.
5
+ The package owns retry policy, APM correlation, shared admin-token caching, deployed-asset checks, JSON response lifetime capture, Mailpit polling, Cognito login diagnostics, and setup retry classification. Consuming repositories keep only configuration and domain-specific matching.
6
6
 
7
7
  ## Install
8
8
 
@@ -21,6 +21,7 @@ npm install --save-dev @clipboard-health/playwright-toolkit
21
21
  | Copy-pasted traceparent page fixture | `createTraceparentFixtures()` |
22
22
  | Admin token promise cache and file lock | `generateAdminAuthToken()` or `getOrCreateAdminAuthToken()` |
23
23
  | Deployed frontend/mobile asset loops | `verifyDeployedAssets()` and `waitForDeployedAssets()` |
24
+ | Deferred Playwright JSON response reads | `waitForParsedJsonResponse()` |
24
25
  | Mailpit search/fetch loops | `createMailpitClient()`, `fetchMagicLinkFromMailpit()`, `fetchEmailOtpCodeFromMailpit()` |
25
26
  | Cognito OTP redirect debugging | `fillOtpAndWaitForCognitoRedirect()` |
26
27
  | Setup HTTP and identity retry checks | `classifySetupRetry()` and `isRetryableHttpStatus()` |
@@ -87,6 +88,31 @@ operation: async ({ signal }) =>
87
88
  });
88
89
  ```
89
90
 
91
+ ## JSON response lifetime
92
+
93
+ Playwright response bodies can become unreadable after a document navigation. Use
94
+ `waitForParsedJsonResponse` when a page action both waits for a JSON response and crosses a
95
+ navigation boundary. The helper parses each candidate inside the `waitForResponse` predicate and
96
+ returns only the parsed value, so callers cannot accidentally retain a lazy `Response` handle.
97
+
98
+ ```typescript
99
+ const workerResponsePromise = waitForParsedJsonResponse({
100
+ page,
101
+ timeoutMs: 30_000,
102
+ isCandidate: ({ response }) =>
103
+ response.ok() && new URL(response.url()).pathname === "/api/worker",
104
+ parseCandidate: ({ body }) => workerResponseSchema.parse(body),
105
+ });
106
+
107
+ await page.reload({ waitUntil: "domcontentloaded" });
108
+
109
+ const workerResponse = await workerResponsePromise;
110
+ ```
111
+
112
+ Return `undefined` from `parseCandidate` when the URL-level candidate is valid but its body is not
113
+ the response the test needs. Parsing errors propagate immediately. Keep repository-specific URL,
114
+ method, resource-type, and schema matching in the consumer.
115
+
90
116
  ## Per-test traceparent fixture
91
117
 
92
118
  Extend the repository's existing Playwright test object once:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clipboard-health/playwright-toolkit",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Shared anti-flake primitives for Clipboard Health Playwright suites.",
5
5
  "keywords": [
6
6
  "cognito",
package/src/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./lib/adminAuthToken";
2
2
  export * from "./lib/cognitoDiagnostics";
3
3
  export * from "./lib/deployedAssets";
4
+ export * from "./lib/jsonResponse";
4
5
  export * from "./lib/mailpit";
5
6
  export * from "./lib/retry";
6
7
  export * from "./lib/setupRetry";
package/src/index.js CHANGED
@@ -4,6 +4,7 @@ const tslib_1 = require("tslib");
4
4
  tslib_1.__exportStar(require("./lib/adminAuthToken"), exports);
5
5
  tslib_1.__exportStar(require("./lib/cognitoDiagnostics"), exports);
6
6
  tslib_1.__exportStar(require("./lib/deployedAssets"), exports);
7
+ tslib_1.__exportStar(require("./lib/jsonResponse"), exports);
7
8
  tslib_1.__exportStar(require("./lib/mailpit"), exports);
8
9
  tslib_1.__exportStar(require("./lib/retry"), exports);
9
10
  tslib_1.__exportStar(require("./lib/setupRetry"), exports);
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/playwright-toolkit/src/index.ts"],"names":[],"mappings":";;;AAAA,+DAAqC;AACrC,mEAAyC;AACzC,+DAAqC;AACrC,wDAA8B;AAC9B,sDAA4B;AAC5B,2DAAiC;AACjC,4DAAkC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/playwright-toolkit/src/index.ts"],"names":[],"mappings":";;;AAAA,+DAAqC;AACrC,mEAAyC;AACzC,+DAAqC;AACrC,6DAAmC;AACnC,wDAA8B;AAC9B,sDAA4B;AAC5B,2DAAiC;AACjC,4DAAkC"}
@@ -0,0 +1,18 @@
1
+ import type { Page, Response } from "@playwright/test";
2
+ export interface WaitForParsedJsonResponseParams<T> {
3
+ isCandidate: (params: {
4
+ response: Response;
5
+ }) => boolean;
6
+ page: Page;
7
+ parseCandidate: (params: {
8
+ body: unknown;
9
+ response: Response;
10
+ }) => T | undefined;
11
+ timeoutMs: number;
12
+ }
13
+ /**
14
+ * Waits for a matching JSON response and parses its body while Playwright's
15
+ * document-scoped response resource is still readable. Return `undefined`
16
+ * from `parseCandidate` to keep waiting for another candidate response.
17
+ */
18
+ export declare function waitForParsedJsonResponse<T>(params: WaitForParsedJsonResponseParams<T>): Promise<T>;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.waitForParsedJsonResponse = waitForParsedJsonResponse;
4
+ const util_ts_1 = require("@clipboard-health/util-ts");
5
+ /**
6
+ * Waits for a matching JSON response and parses its body while Playwright's
7
+ * document-scoped response resource is still readable. Return `undefined`
8
+ * from `parseCandidate` to keep waiting for another candidate response.
9
+ */
10
+ async function waitForParsedJsonResponse(params) {
11
+ const { isCandidate, page, parseCandidate, timeoutMs } = params;
12
+ const parsedCandidates = new WeakMap();
13
+ const response = await page.waitForResponse(async (candidateResponse) => {
14
+ if (!isCandidate({ response: candidateResponse })) {
15
+ return false;
16
+ }
17
+ const parsedCandidate = parseCandidate({
18
+ body: await candidateResponse.json(),
19
+ response: candidateResponse,
20
+ });
21
+ // JSON null is a valid parsed value; only undefined means "keep waiting".
22
+ if (parsedCandidate === undefined) {
23
+ return false;
24
+ }
25
+ parsedCandidates.set(candidateResponse, { value: parsedCandidate });
26
+ return true;
27
+ }, { timeout: timeoutMs });
28
+ const parsedResponse = parsedCandidates.get(response);
29
+ if (!(0, util_ts_1.isDefined)(parsedResponse)) {
30
+ throw new Error("Expected the matched JSON response body to be captured while it was readable.");
31
+ }
32
+ return parsedResponse.value;
33
+ }
34
+ //# sourceMappingURL=jsonResponse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonResponse.js","sourceRoot":"","sources":["../../../../../packages/playwright-toolkit/src/lib/jsonResponse.ts"],"names":[],"mappings":";;;AAAA,uDAAsD;AActD;;;;GAIG;AACI,KAAK,oCACV,MAA0C;IAE1C,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IAChE,MAAM,gBAAgB,GAAG,IAAI,OAAO,EAAgC,CAAC;IAErE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CACzC,KAAK,EAAE,iBAAiB,EAAE,EAAE;QAC1B,IAAI,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;YAClD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,eAAe,GAAG,cAAc,CAAC;YACrC,IAAI,EAAE,MAAM,iBAAiB,CAAC,IAAI,EAAE;YACpC,QAAQ,EAAE,iBAAiB;SAC5B,CAAC,CAAC;QACH,0EAA0E;QAC1E,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;YAClC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,gBAAgB,CAAC,GAAG,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC;IACd,CAAC,EACD,EAAE,OAAO,EAAE,SAAS,EAAE,CACvB,CAAC;IACF,MAAM,cAAc,GAAG,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAEtD,IAAI,CAAC,IAAA,mBAAS,EAAC,cAAc,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,+EAA+E,CAChF,CAAC;IACJ,CAAC;IAED,OAAO,cAAc,CAAC,KAAK,CAAC;AAC9B,CAAC"}