@clipboard-health/playwright-toolkit 1.0.0

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.
@@ -0,0 +1,252 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MailpitRequestError = void 0;
4
+ exports.createMailpitClient = createMailpitClient;
5
+ exports.fetchMailpitValue = fetchMailpitValue;
6
+ exports.fetchMagicLinkFromMailpit = fetchMagicLinkFromMailpit;
7
+ exports.fetchEmailOtpCodeFromMailpit = fetchEmailOtpCodeFromMailpit;
8
+ exports.extractMagicLinkFromMailpitMessage = extractMagicLinkFromMailpitMessage;
9
+ exports.extractEmailOtpCodeFromMailpitMessage = extractEmailOtpCodeFromMailpitMessage;
10
+ exports.isTransientMailpitError = isTransientMailpitError;
11
+ const util_ts_1 = require("@clipboard-health/util-ts");
12
+ const retry_1 = require("./retry");
13
+ const setupRetry_1 = require("./setupRetry");
14
+ const DEFAULT_MAILPIT_BASE_URL = "https://mailpit.tools.cbh.rocks/api/v1";
15
+ const DEFAULT_TIMEOUT_MS = 60_000;
16
+ const DEFAULT_POLL_INTERVAL_MS = 1000;
17
+ const SENT_AFTER_TOLERANCE_MS = 30_000;
18
+ const MAX_MESSAGES_TO_FETCH = 3;
19
+ class MailpitRequestError extends Error {
20
+ cause;
21
+ isTransient;
22
+ status;
23
+ constructor(params) {
24
+ super(params.message, { cause: params.cause });
25
+ this.name = "MailpitRequestError";
26
+ this.cause = params.cause;
27
+ this.isTransient = params.isTransient ?? false;
28
+ this.status = params.status;
29
+ }
30
+ }
31
+ exports.MailpitRequestError = MailpitRequestError;
32
+ class MailpitValueNotFoundError extends Error {
33
+ }
34
+ function createMailpitClient(params) {
35
+ if (params.password.length === 0) {
36
+ throw new Error("Mailpit password must not be empty");
37
+ }
38
+ const fetchImplementation = params.fetchImplementation ?? fetch;
39
+ const baseUrl = params.baseUrl ?? DEFAULT_MAILPIT_BASE_URL;
40
+ const authorization = `Basic ${Buffer.from(`${params.username ?? "cbh"}:${params.password}`).toString("base64")}`;
41
+ return {
42
+ async searchMessages({ query }) {
43
+ const response = await requestMailpitJson({
44
+ fetchImplementation,
45
+ requestTimeoutMs: params.requestTimeoutMs,
46
+ url: addSearchQuery({ baseUrl, query }),
47
+ authorization,
48
+ });
49
+ if (!(0, util_ts_1.isRecord)(response) || !Array.isArray(response["messages"])) {
50
+ throw new MailpitRequestError({
51
+ message: "Mailpit search response is malformed",
52
+ });
53
+ }
54
+ return response["messages"].filter(isMailpitMessageSummary);
55
+ },
56
+ async getMessage({ messageId }) {
57
+ const response = await requestMailpitJson({
58
+ fetchImplementation,
59
+ requestTimeoutMs: params.requestTimeoutMs,
60
+ url: new URL(`message/${encodeURIComponent(messageId)}`, ensureTrailingSlash(baseUrl)).toString(),
61
+ authorization,
62
+ });
63
+ if (!isMailpitMessage(response)) {
64
+ throw new MailpitRequestError({
65
+ message: "Mailpit message response is malformed",
66
+ });
67
+ }
68
+ return response;
69
+ },
70
+ };
71
+ }
72
+ async function fetchMailpitValue(params) {
73
+ const excludedValues = new Set(params.excludedValues ?? []);
74
+ const valuesByMessageId = new Map();
75
+ const result = await (0, retry_1.runWithRetry)({
76
+ operationName: `wait for Mailpit ${params.valueLabel}`,
77
+ operation: async () => {
78
+ const messages = await params.client.searchMessages({
79
+ query: `to:${params.email}`,
80
+ });
81
+ const candidates = messages
82
+ .filter((message) => isMessageSentAfter({ message, sentAfter: params.sentAfter }))
83
+ .toSorted(compareMailpitMessagesNewestFirst)
84
+ .slice(0, MAX_MESSAGES_TO_FETCH);
85
+ for (const message of candidates) {
86
+ if (valuesByMessageId.has(message.ID)) {
87
+ const cachedValue = valuesByMessageId.get(message.ID);
88
+ if (cachedValue !== undefined && !excludedValues.has(cachedValue)) {
89
+ return { value: cachedValue, messageId: message.ID };
90
+ }
91
+ continue;
92
+ }
93
+ try {
94
+ // eslint-disable-next-line no-await-in-loop -- Candidates are checked newest-first.
95
+ const fullMessage = await params.client.getMessage({
96
+ messageId: message.ID,
97
+ });
98
+ const value = params.extractValue({ message: fullMessage });
99
+ valuesByMessageId.set(message.ID, value);
100
+ if (value !== undefined && !excludedValues.has(value)) {
101
+ return { value, messageId: message.ID };
102
+ }
103
+ }
104
+ catch (error) {
105
+ if (!isTransientMailpitError({ error })) {
106
+ throw error;
107
+ }
108
+ }
109
+ }
110
+ throw new MailpitValueNotFoundError(`No matching Mailpit ${params.valueLabel} is available yet`);
111
+ },
112
+ mode: {
113
+ kind: "poll",
114
+ timeoutMs: params.timeoutMs ?? DEFAULT_TIMEOUT_MS,
115
+ intervalsMs: [params.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS],
116
+ isTransient: ({ error }) => error instanceof MailpitValueNotFoundError || isTransientMailpitError({ error }),
117
+ },
118
+ sleepImplementation: params.sleepImplementation,
119
+ nowImplementation: params.nowImplementation,
120
+ });
121
+ return result.value;
122
+ }
123
+ async function fetchMagicLinkFromMailpit(params) {
124
+ return await fetchMailpitValue({
125
+ ...params,
126
+ excludedValues: params.excludeLinks,
127
+ extractValue: extractMagicLinkFromMailpitMessage,
128
+ valueLabel: "Cognito magic link",
129
+ });
130
+ }
131
+ async function fetchEmailOtpCodeFromMailpit(params) {
132
+ return await fetchMailpitValue({
133
+ ...params,
134
+ excludedValues: params.excludeCodes,
135
+ extractValue: extractEmailOtpCodeFromMailpitMessage,
136
+ valueLabel: "Cognito email OTP",
137
+ });
138
+ }
139
+ function extractMagicLinkFromMailpitMessage(params) {
140
+ for (const content of [params.message.Text, params.message.HTML]) {
141
+ const match = /https?:\/\/[^\s]+\/v2\/email-login-link\?[^\s<"')]*/i.exec(content);
142
+ if (match !== null) {
143
+ return match[0];
144
+ }
145
+ }
146
+ return undefined;
147
+ }
148
+ function extractEmailOtpCodeFromMailpitMessage(params) {
149
+ const textCode = extractEmailOtpCodeFromContent(params.message.Text);
150
+ if (textCode !== undefined) {
151
+ return textCode;
152
+ }
153
+ const htmlText = params.message.HTML.replaceAll(/<(style|script)[^>]*>[\s\S]*?<\/\1>/gi, "").replaceAll(/<[^>]+>/g, " ");
154
+ return extractEmailOtpCodeFromContent(htmlText);
155
+ }
156
+ function extractEmailOtpCodeFromContent(source) {
157
+ const eightDigits = /(?<!\d)\d{8}(?!\d)/.exec(source);
158
+ if (eightDigits !== null) {
159
+ return eightDigits[0];
160
+ }
161
+ const twoGroups = /(?<!\d)(\d{4})\s*(\d{4})(?!\d)/.exec(source);
162
+ return twoGroups === null ? undefined : `${twoGroups[1]}${twoGroups[2]}`;
163
+ }
164
+ function isTransientMailpitError(params) {
165
+ if (!(params.error instanceof MailpitRequestError)) {
166
+ return false;
167
+ }
168
+ const { status } = params.error;
169
+ return params.error.isTransient || status === 404 || (0, setupRetry_1.isRetryableHttpStatus)({ status });
170
+ }
171
+ async function requestMailpitJson(params) {
172
+ const abortController = new AbortController();
173
+ const timeout = setTimeout(() => {
174
+ abortController.abort();
175
+ }, params.requestTimeoutMs ?? 10_000);
176
+ try {
177
+ let response;
178
+ try {
179
+ response = await params.fetchImplementation(params.url, {
180
+ headers: { authorization: params.authorization },
181
+ signal: abortController.signal,
182
+ });
183
+ }
184
+ catch (error) {
185
+ throw new MailpitRequestError({
186
+ message: `Mailpit request failed: ${(0, util_ts_1.toErrorMessage)(error)}`,
187
+ cause: error,
188
+ isTransient: true,
189
+ });
190
+ }
191
+ if (!response.ok) {
192
+ throw new MailpitRequestError({
193
+ message: `Mailpit request failed with HTTP ${response.status}`,
194
+ status: response.status,
195
+ });
196
+ }
197
+ return await response.json();
198
+ }
199
+ finally {
200
+ clearTimeout(timeout);
201
+ }
202
+ }
203
+ function addSearchQuery(params) {
204
+ const url = new URL("search", ensureTrailingSlash(params.baseUrl));
205
+ url.searchParams.set("query", params.query);
206
+ return url.toString();
207
+ }
208
+ function ensureTrailingSlash(url) {
209
+ return url.endsWith("/") ? url : `${url}/`;
210
+ }
211
+ function isMessageSentAfter(params) {
212
+ if (params.sentAfter === undefined) {
213
+ return true;
214
+ }
215
+ const messageTimestamp = Date.parse(params.message.Created);
216
+ if (!Number.isFinite(messageTimestamp)) {
217
+ return true;
218
+ }
219
+ return messageTimestamp >= params.sentAfter.getTime() - SENT_AFTER_TOLERANCE_MS;
220
+ }
221
+ function compareMailpitMessagesNewestFirst(first, second) {
222
+ const firstTimestamp = Date.parse(first.Created);
223
+ const secondTimestamp = Date.parse(second.Created);
224
+ if (!Number.isFinite(firstTimestamp)) {
225
+ return Number.isFinite(secondTimestamp) ? 1 : 0;
226
+ }
227
+ if (!Number.isFinite(secondTimestamp)) {
228
+ return -1;
229
+ }
230
+ return secondTimestamp - firstTimestamp;
231
+ }
232
+ function isMailpitMessage(value) {
233
+ return ((0, util_ts_1.isRecord)(value) &&
234
+ hasMailpitMessageHeaders(value) &&
235
+ typeof value["Date"] === "string" &&
236
+ typeof value["Text"] === "string" &&
237
+ typeof value["HTML"] === "string");
238
+ }
239
+ function isMailpitMessageSummary(value) {
240
+ return (0, util_ts_1.isRecord)(value) && hasMailpitMessageHeaders(value) && typeof value["Created"] === "string";
241
+ }
242
+ function hasMailpitMessageHeaders(value) {
243
+ return (typeof value["ID"] === "string" &&
244
+ typeof value["Subject"] === "string" &&
245
+ isMailpitAddress(value["From"]) &&
246
+ Array.isArray(value["To"]) &&
247
+ value["To"].every(isMailpitAddress));
248
+ }
249
+ function isMailpitAddress(value) {
250
+ return ((0, util_ts_1.isRecord)(value) && typeof value["Address"] === "string" && typeof value["Name"] === "string");
251
+ }
252
+ //# sourceMappingURL=mailpit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mailpit.js","sourceRoot":"","sources":["../../../../../packages/playwright-toolkit/src/lib/mailpit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ export type RetryFailureReason = "attempts-exhausted" | "non-transient" | "timeout";
2
+ export interface RetryAttemptContext {
3
+ attemptNumber: number;
4
+ elapsedMs: number;
5
+ signal: AbortSignal;
6
+ }
7
+ export interface RetryFailureContext extends RetryAttemptContext {
8
+ error: unknown;
9
+ }
10
+ export interface ClassifiedRetryMode {
11
+ kind: "classified";
12
+ maxAttempts: number;
13
+ delayMs?: number | ((context: RetryFailureContext) => number) | undefined;
14
+ isTransient: (context: RetryFailureContext) => boolean;
15
+ }
16
+ export interface PollRetryMode {
17
+ kind: "poll";
18
+ timeoutMs: number;
19
+ intervalsMs?: readonly number[] | undefined;
20
+ isTransient?: ((context: RetryFailureContext) => boolean) | undefined;
21
+ }
22
+ export type RetryMode = ClassifiedRetryMode | PollRetryMode;
23
+ export interface RetrySuccess<T> {
24
+ value: T;
25
+ attempts: number;
26
+ }
27
+ export interface RunWithRetryParams<T> {
28
+ operationName: string;
29
+ operation: (context: RetryAttemptContext) => Promise<T>;
30
+ mode: RetryMode;
31
+ nowImplementation?: (() => number) | undefined;
32
+ sleepImplementation?: ((params: {
33
+ durationMs: number;
34
+ }) => Promise<void>) | undefined;
35
+ onFailedAttempt?: ((context: RetryFailureContext) => void | Promise<void>) | undefined;
36
+ }
37
+ interface RetryErrorParams {
38
+ operationName: string;
39
+ attempts: number;
40
+ elapsedMs: number;
41
+ reason: RetryFailureReason;
42
+ cause: unknown;
43
+ }
44
+ /**
45
+ * Rich terminal error for both retry modes.
46
+ *
47
+ * `reason` distinguishes deterministic bail-out, attempt exhaustion, and poll
48
+ * timeout while `cause` retains the last operation error.
49
+ */
50
+ export declare class RetryError extends Error {
51
+ readonly attempts: number;
52
+ readonly cause: unknown;
53
+ readonly elapsedMs: number;
54
+ readonly operationName: string;
55
+ readonly reason: RetryFailureReason;
56
+ constructor(params: RetryErrorParams);
57
+ }
58
+ /**
59
+ * Runs one bounded retry policy.
60
+ *
61
+ * Classified mode is for operations where the caller can positively identify
62
+ * transient failures. Poll mode is for idempotent readiness probes where every
63
+ * failure means "not ready yet" until the timeout expires.
64
+ */
65
+ export declare function runWithRetry<T>(params: RunWithRetryParams<T>): Promise<RetrySuccess<T>>;
66
+ export {};
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RetryError = void 0;
4
+ exports.runWithRetry = runWithRetry;
5
+ const util_ts_1 = require("@clipboard-health/util-ts");
6
+ class PollAttemptTimeoutError extends Error {
7
+ constructor() {
8
+ super("Poll attempt exceeded the remaining timeout budget");
9
+ this.name = "PollAttemptTimeoutError";
10
+ }
11
+ }
12
+ /**
13
+ * Rich terminal error for both retry modes.
14
+ *
15
+ * `reason` distinguishes deterministic bail-out, attempt exhaustion, and poll
16
+ * timeout while `cause` retains the last operation error.
17
+ */
18
+ class RetryError extends Error {
19
+ attempts;
20
+ cause;
21
+ elapsedMs;
22
+ operationName;
23
+ reason;
24
+ constructor(params) {
25
+ super(formatRetryErrorMessage(params), { cause: params.cause });
26
+ this.name = "RetryError";
27
+ this.attempts = params.attempts;
28
+ this.cause = params.cause;
29
+ this.elapsedMs = params.elapsedMs;
30
+ this.operationName = params.operationName;
31
+ this.reason = params.reason;
32
+ }
33
+ }
34
+ exports.RetryError = RetryError;
35
+ /**
36
+ * Runs one bounded retry policy.
37
+ *
38
+ * Classified mode is for operations where the caller can positively identify
39
+ * transient failures. Poll mode is for idempotent readiness probes where every
40
+ * failure means "not ready yet" until the timeout expires.
41
+ */
42
+ async function runWithRetry(params) {
43
+ validateRetryMode(params.mode);
44
+ const nowImplementation = params.nowImplementation ?? Date.now;
45
+ const sleepImplementation = params.sleepImplementation ?? sleep;
46
+ const startedAtMs = nowImplementation();
47
+ let attemptNumber = 0;
48
+ let lastError;
49
+ for (;;) {
50
+ const elapsedBeforeAttemptMs = getElapsedMs({
51
+ nowImplementation,
52
+ startedAtMs,
53
+ });
54
+ if (params.mode.kind === "poll" &&
55
+ attemptNumber > 0 &&
56
+ elapsedBeforeAttemptMs >= params.mode.timeoutMs) {
57
+ throw new RetryError({
58
+ operationName: params.operationName,
59
+ attempts: attemptNumber,
60
+ elapsedMs: elapsedBeforeAttemptMs,
61
+ reason: "timeout",
62
+ cause: lastError,
63
+ });
64
+ }
65
+ attemptNumber += 1;
66
+ const abortController = new AbortController();
67
+ try {
68
+ // eslint-disable-next-line no-await-in-loop -- Retry attempts are intentionally sequential.
69
+ const value = await runAttempt({
70
+ context: {
71
+ attemptNumber,
72
+ elapsedMs: elapsedBeforeAttemptMs,
73
+ signal: abortController.signal,
74
+ },
75
+ mode: params.mode,
76
+ operation: params.operation,
77
+ abortController,
78
+ });
79
+ return { value, attempts: attemptNumber };
80
+ }
81
+ catch (error) {
82
+ lastError = error;
83
+ const failureContext = {
84
+ attemptNumber,
85
+ elapsedMs: getElapsedMs({ nowImplementation, startedAtMs }),
86
+ error,
87
+ signal: abortController.signal,
88
+ };
89
+ try {
90
+ // eslint-disable-next-line no-await-in-loop -- Attempt reporting is intentionally sequential.
91
+ await runWithinRetryBudget({
92
+ abortController,
93
+ elapsedMs: failureContext.elapsedMs,
94
+ mode: params.mode,
95
+ promise: Promise.resolve(params.onFailedAttempt?.(failureContext)),
96
+ });
97
+ }
98
+ catch (hookError) {
99
+ if (hookError instanceof PollAttemptTimeoutError) {
100
+ throw createPollTimeoutError({
101
+ attempts: attemptNumber,
102
+ cause: error,
103
+ elapsedMs: getElapsedMs({ nowImplementation, startedAtMs }),
104
+ operationName: params.operationName,
105
+ });
106
+ }
107
+ throw hookError;
108
+ }
109
+ failureContext.elapsedMs = getElapsedMs({
110
+ nowImplementation,
111
+ startedAtMs,
112
+ });
113
+ const terminalReason = getTerminalReason({
114
+ context: failureContext,
115
+ mode: params.mode,
116
+ });
117
+ if (terminalReason !== undefined) {
118
+ throw new RetryError({
119
+ operationName: params.operationName,
120
+ attempts: attemptNumber,
121
+ elapsedMs: failureContext.elapsedMs,
122
+ reason: terminalReason,
123
+ cause: error,
124
+ });
125
+ }
126
+ const durationMs = getRetryDelayMs({
127
+ context: failureContext,
128
+ mode: params.mode,
129
+ });
130
+ try {
131
+ // eslint-disable-next-line no-await-in-loop -- Retry attempts are intentionally sequential.
132
+ await runWithinRetryBudget({
133
+ abortController,
134
+ elapsedMs: failureContext.elapsedMs,
135
+ mode: params.mode,
136
+ promise: sleepImplementation({ durationMs }),
137
+ });
138
+ }
139
+ catch (sleepError) {
140
+ if (sleepError instanceof PollAttemptTimeoutError) {
141
+ throw createPollTimeoutError({
142
+ attempts: attemptNumber,
143
+ cause: error,
144
+ elapsedMs: getElapsedMs({ nowImplementation, startedAtMs }),
145
+ operationName: params.operationName,
146
+ });
147
+ }
148
+ throw sleepError;
149
+ }
150
+ }
151
+ }
152
+ }
153
+ function validateRetryMode(mode) {
154
+ if (mode.kind === "classified") {
155
+ if (!Number.isInteger(mode.maxAttempts) || mode.maxAttempts < 1) {
156
+ throw new Error("Classified retry maxAttempts must be a positive integer");
157
+ }
158
+ if (typeof mode.delayMs === "number" && (!Number.isFinite(mode.delayMs) || mode.delayMs < 0)) {
159
+ throw new Error("Classified retry delayMs must be finite and non-negative");
160
+ }
161
+ return;
162
+ }
163
+ if (!Number.isFinite(mode.timeoutMs) || mode.timeoutMs <= 0) {
164
+ throw new Error("Poll retry timeoutMs must be positive");
165
+ }
166
+ if (mode.intervalsMs?.length === 0) {
167
+ throw new Error("Poll retry intervalsMs must not be empty");
168
+ }
169
+ if (mode.intervalsMs?.some((intervalMs) => !Number.isFinite(intervalMs) || intervalMs < 0) === true) {
170
+ throw new Error("Poll retry intervalsMs must be finite and non-negative");
171
+ }
172
+ }
173
+ function getTerminalReason(params) {
174
+ if (params.mode.kind === "classified") {
175
+ if (!params.mode.isTransient(params.context)) {
176
+ return "non-transient";
177
+ }
178
+ return params.context.attemptNumber >= params.mode.maxAttempts
179
+ ? "attempts-exhausted"
180
+ : undefined;
181
+ }
182
+ if (params.context.error instanceof PollAttemptTimeoutError) {
183
+ return "timeout";
184
+ }
185
+ if (params.mode.isTransient?.(params.context) === false) {
186
+ return "non-transient";
187
+ }
188
+ return params.context.elapsedMs >= params.mode.timeoutMs ? "timeout" : undefined;
189
+ }
190
+ async function runAttempt(params) {
191
+ const operationPromise = params.operation(params.context);
192
+ return await runWithinRetryBudget({
193
+ abortController: params.abortController,
194
+ elapsedMs: params.context.elapsedMs,
195
+ mode: params.mode,
196
+ promise: operationPromise,
197
+ });
198
+ }
199
+ async function runWithinRetryBudget(params) {
200
+ if (params.mode.kind === "classified") {
201
+ return await params.promise;
202
+ }
203
+ const remainingTimeoutMs = Math.max(params.mode.timeoutMs - params.elapsedMs, 0);
204
+ let timeout;
205
+ try {
206
+ return await Promise.race([
207
+ params.promise,
208
+ new Promise((_resolve, reject) => {
209
+ timeout = setTimeout(() => {
210
+ params.abortController.abort();
211
+ reject(new PollAttemptTimeoutError());
212
+ }, remainingTimeoutMs);
213
+ }),
214
+ ]);
215
+ }
216
+ finally {
217
+ if (timeout !== undefined) {
218
+ clearTimeout(timeout);
219
+ }
220
+ }
221
+ }
222
+ function getRetryDelayMs(params) {
223
+ let durationMs;
224
+ if (params.mode.kind === "classified") {
225
+ durationMs =
226
+ typeof params.mode.delayMs === "function"
227
+ ? params.mode.delayMs(params.context)
228
+ : (params.mode.delayMs ?? 0);
229
+ }
230
+ else {
231
+ const intervalsMs = params.mode.intervalsMs ?? [100, 250, 500, 1000];
232
+ const intervalIndex = Math.min(params.context.attemptNumber - 1, intervalsMs.length - 1);
233
+ const configuredDelayMs = intervalsMs[intervalIndex] ?? 0;
234
+ const remainingMs = Math.max(params.mode.timeoutMs - params.context.elapsedMs, 0);
235
+ durationMs = Math.min(configuredDelayMs, remainingMs);
236
+ }
237
+ if (!Number.isFinite(durationMs) || durationMs < 0) {
238
+ throw new Error("Retry delay must be finite and non-negative");
239
+ }
240
+ return durationMs;
241
+ }
242
+ function getElapsedMs(params) {
243
+ return Math.max(params.nowImplementation() - params.startedAtMs, 0);
244
+ }
245
+ async function sleep(params) {
246
+ await new Promise((resolve) => {
247
+ setTimeout(resolve, params.durationMs);
248
+ });
249
+ }
250
+ function formatRetryErrorMessage(params) {
251
+ return (`${params.operationName} failed after ${params.attempts} ` +
252
+ `${params.attempts === 1 ? "attempt" : "attempts"} ` +
253
+ `(${params.elapsedMs}ms, reason: ${params.reason}). ` +
254
+ `Last error: ${(0, util_ts_1.toErrorMessage)(params.cause)}`);
255
+ }
256
+ function createPollTimeoutError(params) {
257
+ return new RetryError({
258
+ ...params,
259
+ reason: "timeout",
260
+ });
261
+ }
262
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../../../../packages/playwright-toolkit/src/lib/retry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ export type SetupRetryClassification = "deterministic" | "identity-collision" | "transient";
2
+ export type SetupRetryDecision = "do-not-retry" | "regenerate-identity" | "retry-same-identity";
3
+ export interface SetupRetryResult {
4
+ classification: SetupRetryClassification;
5
+ retryDecision: SetupRetryDecision;
6
+ }
7
+ export interface SetupErrorContext {
8
+ error: unknown;
9
+ status: number | undefined;
10
+ }
11
+ export interface ClassifySetupRetryParams {
12
+ error: unknown;
13
+ isIdentityCollision?: (context: SetupErrorContext) => boolean;
14
+ isTransientError?: (context: SetupErrorContext) => boolean;
15
+ }
16
+ /**
17
+ * Classifies setup failures without conflating identity collisions and
18
+ * transient infrastructure errors.
19
+ *
20
+ * Collision retries must regenerate a fresh identity. Transient retries must
21
+ * repeat the same identity so they do not change the scenario under test.
22
+ */
23
+ export declare function classifySetupRetry(params: ClassifySetupRetryParams): SetupRetryResult;
24
+ export declare function isRetryableHttpStatus(params: {
25
+ status: number | undefined;
26
+ }): boolean;
27
+ export declare function getHttpErrorStatus(params: {
28
+ error: unknown;
29
+ }): number | undefined;