@stablyai/playwright-base 0.1.6 → 0.1.7-next.10

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,112 @@
1
+ import * as _stablyai_internal_playwright_test from '@stablyai/internal-playwright-test';
2
+ import { Browser, BrowserContext, BrowserType, Locator, Page } from '@stablyai/internal-playwright-test';
3
+ import * as z4 from 'zod/v4/core';
4
+
5
+ interface LocatorDescribeOptions {
6
+ autoHeal?: boolean;
7
+ }
8
+ declare function augmentLocator<T extends Locator>(locator: T): T;
9
+ declare function augmentPage<T extends Page>(page: T): T;
10
+ declare function augmentBrowserContext<T extends BrowserContext>(context: T): T;
11
+ declare function augmentBrowser<T extends Browser>(browser: T): T;
12
+ declare function augmentBrowserType<TBrowser extends Browser>(browserType: BrowserType<TBrowser>): BrowserType<TBrowser>;
13
+
14
+ interface ExtractSchema extends z4.$ZodType {
15
+ safeParseAsync(data: unknown, params?: z4.ParseContext<z4.$ZodIssue>): Promise<z4.util.SafeParseResult<z4.output<this>>>;
16
+ }
17
+ type SchemaOutput<T extends ExtractSchema> = z4.output<T>;
18
+
19
+ type MatcherContext = {
20
+ isNot: boolean;
21
+ message?: () => string;
22
+ };
23
+ declare const stablyPlaywrightMatchers: {
24
+ readonly toMatchScreenshotPrompt: (this: MatcherContext, received: Page | Locator, condition: string, options?: ScreenshotPromptOptions) => Promise<{
25
+ pass: boolean;
26
+ message: () => string;
27
+ }>;
28
+ };
29
+
30
+ declare function setApiKey(apiKey: string): void;
31
+ declare function requireApiKey(): string;
32
+
33
+ type ScreenshotPromptOptions = _stablyai_internal_playwright_test.PageAssertionsToHaveScreenshotOptions;
34
+
35
+ interface Expect<T = Page> {
36
+ toMatchScreenshotPrompt(condition: string, options?: ScreenshotPromptOptions): Promise<void>;
37
+ }
38
+ declare module "@stablyai/internal-playwright-test" {
39
+ interface Locator {
40
+ /**
41
+ * Extracts information from this locator using Stably AI.
42
+ *
43
+ * Takes a screenshot of the locator and uses AI to extract information based on the
44
+ * provided prompt. When a schema is provided, the extracted data is validated and
45
+ * typed according to the schema.
46
+ *
47
+ * @param prompt - A natural language description of what information to extract
48
+ * @returns A string containing the extracted information
49
+ */
50
+ extract(prompt: string): Promise<string>;
51
+ /**
52
+ * Extracts information from this locator using Stably AI.
53
+ *
54
+ * Takes a screenshot of the locator and uses AI to extract information based on the
55
+ * provided prompt. The extracted data is validated and typed according to the schema.
56
+ *
57
+ * @param prompt - A natural language description of what information to extract
58
+ * @param options - Configuration object containing the Zod schema for validation
59
+ * @param options.schema - Zod schema to validate and type the extracted data
60
+ * @returns Typed data matching the provided schema
61
+ */
62
+ extract<T extends ExtractSchema>(prompt: string, options: {
63
+ schema: T;
64
+ }): Promise<SchemaOutput<T>>;
65
+ describe(description: string, options?: LocatorDescribeOptions): Locator;
66
+ }
67
+ interface Page {
68
+ /**
69
+ * Extracts information from this page using Stably AI.
70
+ *
71
+ * Takes a screenshot of the page and uses AI to extract information based on the
72
+ * provided prompt. When a schema is provided, the extracted data is validated and
73
+ * typed according to the schema.
74
+ *
75
+ * @param prompt - A natural language description of what information to extract
76
+ * @returns A string containing the extracted information
77
+ */
78
+ extract(prompt: string): Promise<string>;
79
+ /**
80
+ * Extracts information from this page using Stably AI.
81
+ *
82
+ * Takes a screenshot of the page and uses AI to extract information based on the
83
+ * provided prompt. The extracted data is validated and typed according to the schema.
84
+ *
85
+ * @param prompt - A natural language description of what information to extract
86
+ * @param options - Configuration object containing the Zod schema for validation
87
+ * @param options.schema - Zod schema to validate and type the extracted data
88
+ * @returns Typed data matching the provided schema
89
+ */
90
+ extract<T extends ExtractSchema>(prompt: string, options: {
91
+ schema: T;
92
+ }): Promise<SchemaOutput<T>>;
93
+ }
94
+ interface BrowserContext {
95
+ agent(prompt: string, options: {
96
+ page: Page;
97
+ maxCycles?: number;
98
+ }): Promise<{
99
+ success: boolean;
100
+ }>;
101
+ }
102
+ interface Browser {
103
+ agent(prompt: string, options: {
104
+ page: Page;
105
+ maxCycles?: number;
106
+ }): Promise<{
107
+ success: boolean;
108
+ }>;
109
+ }
110
+ }
111
+
112
+ export { type Expect, type ExtractSchema, type LocatorDescribeOptions, type SchemaOutput, type ScreenshotPromptOptions, augmentBrowser, augmentBrowserContext, augmentBrowserType, augmentLocator, augmentPage, requireApiKey, setApiKey, stablyPlaywrightMatchers };
package/dist/index.d.ts CHANGED
@@ -1,15 +1,38 @@
1
- import type { Page } from "@stablyai/internal-playwright-test";
2
- import type { LocatorDescribeOptions } from "./playwright-augment/augment";
3
- import type { ExtractSchema, SchemaOutput } from "./ai/extract";
4
- import { augmentBrowser, augmentBrowserContext, augmentBrowserType, augmentLocator, augmentPage } from "./playwright-augment/augment";
5
- import { stablyPlaywrightMatchers } from "./expect";
6
- import { requireApiKey } from "./runtime";
7
- export { setApiKey } from "./runtime";
8
- export type { LocatorDescribeOptions } from "./playwright-augment/augment";
9
- export type { ExtractSchema, SchemaOutput } from "./ai/extract";
10
- export type ScreenshotPromptOptions = import("@stablyai/internal-playwright-test").PageAssertionsToHaveScreenshotOptions;
11
- export { augmentBrowser, augmentBrowserContext, augmentBrowserType, augmentLocator, augmentPage, stablyPlaywrightMatchers, requireApiKey, };
12
- export interface Expect<T = Page> {
1
+ import * as _stablyai_internal_playwright_test from '@stablyai/internal-playwright-test';
2
+ import { Browser, BrowserContext, BrowserType, Locator, Page } from '@stablyai/internal-playwright-test';
3
+ import * as z4 from 'zod/v4/core';
4
+
5
+ interface LocatorDescribeOptions {
6
+ autoHeal?: boolean;
7
+ }
8
+ declare function augmentLocator<T extends Locator>(locator: T): T;
9
+ declare function augmentPage<T extends Page>(page: T): T;
10
+ declare function augmentBrowserContext<T extends BrowserContext>(context: T): T;
11
+ declare function augmentBrowser<T extends Browser>(browser: T): T;
12
+ declare function augmentBrowserType<TBrowser extends Browser>(browserType: BrowserType<TBrowser>): BrowserType<TBrowser>;
13
+
14
+ interface ExtractSchema extends z4.$ZodType {
15
+ safeParseAsync(data: unknown, params?: z4.ParseContext<z4.$ZodIssue>): Promise<z4.util.SafeParseResult<z4.output<this>>>;
16
+ }
17
+ type SchemaOutput<T extends ExtractSchema> = z4.output<T>;
18
+
19
+ type MatcherContext = {
20
+ isNot: boolean;
21
+ message?: () => string;
22
+ };
23
+ declare const stablyPlaywrightMatchers: {
24
+ readonly toMatchScreenshotPrompt: (this: MatcherContext, received: Page | Locator, condition: string, options?: ScreenshotPromptOptions) => Promise<{
25
+ pass: boolean;
26
+ message: () => string;
27
+ }>;
28
+ };
29
+
30
+ declare function setApiKey(apiKey: string): void;
31
+ declare function requireApiKey(): string;
32
+
33
+ type ScreenshotPromptOptions = _stablyai_internal_playwright_test.PageAssertionsToHaveScreenshotOptions;
34
+
35
+ interface Expect<T = Page> {
13
36
  toMatchScreenshotPrompt(condition: string, options?: ScreenshotPromptOptions): Promise<void>;
14
37
  }
15
38
  declare module "@stablyai/internal-playwright-test" {
@@ -85,3 +108,5 @@ declare module "@stablyai/internal-playwright-test" {
85
108
  }>;
86
109
  }
87
110
  }
111
+
112
+ export { type Expect, type ExtractSchema, type LocatorDescribeOptions, type SchemaOutput, type ScreenshotPromptOptions, augmentBrowser, augmentBrowserContext, augmentBrowserType, augmentLocator, augmentPage, requireApiKey, setApiKey, stablyPlaywrightMatchers };
package/dist/index.mjs ADDED
@@ -0,0 +1,467 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/runtime.ts
9
+ var configuredApiKey = process.env.STABLY_API_KEY;
10
+ function setApiKey(apiKey) {
11
+ configuredApiKey = apiKey;
12
+ }
13
+ function getApiKey() {
14
+ return configuredApiKey;
15
+ }
16
+ function requireApiKey() {
17
+ const apiKey = getApiKey();
18
+ if (!apiKey) {
19
+ throw new Error(
20
+ "Missing Stably API key. Call setApiKey(apiKey) or set the STABLY_API_KEY environment variable."
21
+ );
22
+ }
23
+ return apiKey;
24
+ }
25
+
26
+ // src/type-predicate/is-object.ts
27
+ var isObject = (value) => {
28
+ return typeof value === "object" && value !== null;
29
+ };
30
+
31
+ // src/ai/metadata.ts
32
+ var SDK_METADATA_HEADERS = {
33
+ "X-Client-Name": "stably-playwright-sdk-js",
34
+ "X-Client-Version": "0.1.7-next.10"
35
+ };
36
+
37
+ // src/ai/extract.ts
38
+ var EXTRACT_ENDPOINT = "https://api.stably.ai/internal/v2/extract";
39
+ var zodV4 = (() => {
40
+ try {
41
+ return __require("zod/v4/core");
42
+ } catch {
43
+ return void 0;
44
+ }
45
+ })();
46
+ var isExtractionResponse = (value) => {
47
+ if (!isObject(value)) {
48
+ return false;
49
+ }
50
+ if (value.success === true) {
51
+ return "value" in value;
52
+ }
53
+ return value.success === false && typeof value.error === "string";
54
+ };
55
+ var isErrorResponse = (value) => {
56
+ return isObject(value) && typeof value.error === "string";
57
+ };
58
+ var ExtractValidationError = class extends Error {
59
+ constructor(message, issues) {
60
+ super(message);
61
+ this.issues = issues;
62
+ this.name = "ExtractValidationError";
63
+ }
64
+ };
65
+ async function validateWithSchema(schema, value) {
66
+ const result = await schema.safeParseAsync(value);
67
+ if (!result.success) {
68
+ throw new ExtractValidationError("Validation failed", result.error.issues);
69
+ }
70
+ return result.data;
71
+ }
72
+ async function extract({
73
+ prompt,
74
+ pageOrLocator,
75
+ schema
76
+ }) {
77
+ if (schema && !zodV4) {
78
+ throw new Error(
79
+ "Schema support requires installing zod@4. Please add it to enable schemas."
80
+ );
81
+ }
82
+ const jsonSchema = schema && zodV4 ? zodV4?.toJSONSchema(
83
+ schema
84
+ ) : void 0;
85
+ const apiKey = requireApiKey();
86
+ const form = new FormData();
87
+ form.append("prompt", prompt);
88
+ if (jsonSchema) {
89
+ form.append("jsonSchema", JSON.stringify(jsonSchema));
90
+ }
91
+ const pngBuffer = await pageOrLocator.screenshot({ type: "png" });
92
+ const u8 = Uint8Array.from(pngBuffer);
93
+ const blob = new Blob([u8], { type: "image/png" });
94
+ form.append("image", blob, "screenshot.png");
95
+ const response = await fetch(EXTRACT_ENDPOINT, {
96
+ method: "POST",
97
+ headers: {
98
+ ...SDK_METADATA_HEADERS,
99
+ Authorization: `Bearer ${apiKey}`
100
+ },
101
+ body: form
102
+ });
103
+ const raw = await response.json().catch(() => void 0);
104
+ if (response.ok) {
105
+ if (!isExtractionResponse(raw)) {
106
+ throw new Error("Extract returned unexpected response shape");
107
+ }
108
+ if (raw.success === false) {
109
+ return raw.error;
110
+ }
111
+ const { value } = raw;
112
+ return schema ? await validateWithSchema(schema, value) : typeof value === "string" ? value : JSON.stringify(value);
113
+ }
114
+ throw new Error(isErrorResponse(raw) ? raw.error : "Extract failed");
115
+ }
116
+
117
+ // src/playwright-augment/methods/extract.ts
118
+ function createExtract(pageOrLocator) {
119
+ const impl = async (prompt, options) => {
120
+ if (options?.schema) {
121
+ return extract({
122
+ prompt,
123
+ schema: options.schema,
124
+ pageOrLocator
125
+ });
126
+ }
127
+ return extract({ prompt, pageOrLocator });
128
+ };
129
+ return impl;
130
+ }
131
+ var createLocatorExtract = (locator) => createExtract(locator);
132
+ var createPageExtract = (page) => createExtract(page);
133
+
134
+ // src/playwright-augment/methods/agent.ts
135
+ function createAgentStub() {
136
+ return async (prompt, options) => {
137
+ requireApiKey();
138
+ void prompt;
139
+ void options;
140
+ return { success: true };
141
+ };
142
+ }
143
+
144
+ // src/playwright-augment/augment.ts
145
+ var LOCATOR_PATCHED = Symbol.for("stably.playwright.locatorPatched");
146
+ var LOCATOR_DESCRIBE_WRAPPED = Symbol.for(
147
+ "stably.playwright.locatorDescribeWrapped"
148
+ );
149
+ var PAGE_PATCHED = Symbol.for("stably.playwright.pagePatched");
150
+ var CONTEXT_PATCHED = Symbol.for("stably.playwright.contextPatched");
151
+ var BROWSER_PATCHED = Symbol.for("stably.playwright.browserPatched");
152
+ var BROWSER_TYPE_PATCHED = Symbol.for("stably.playwright.browserTypePatched");
153
+ function defineHiddenProperty(target, key, value) {
154
+ Object.defineProperty(target, key, {
155
+ value,
156
+ enumerable: false,
157
+ configurable: true,
158
+ writable: true
159
+ });
160
+ }
161
+ function augmentLocator(locator) {
162
+ if (locator[LOCATOR_PATCHED]) {
163
+ return locator;
164
+ }
165
+ defineHiddenProperty(locator, "extract", createLocatorExtract(locator));
166
+ const markerTarget = locator;
167
+ if (typeof locator.describe === "function" && !markerTarget[LOCATOR_DESCRIBE_WRAPPED]) {
168
+ const originalDescribe = locator.describe.bind(locator);
169
+ locator.describe = (description, options) => {
170
+ void options;
171
+ const result = originalDescribe(description);
172
+ return result ? augmentLocator(result) : result;
173
+ };
174
+ defineHiddenProperty(locator, LOCATOR_DESCRIBE_WRAPPED, true);
175
+ }
176
+ defineHiddenProperty(locator, LOCATOR_PATCHED, true);
177
+ return locator;
178
+ }
179
+ function augmentPage(page) {
180
+ if (page[PAGE_PATCHED]) {
181
+ return page;
182
+ }
183
+ const originalLocator = page.locator.bind(page);
184
+ page.locator = (...args) => {
185
+ const locator = originalLocator(...args);
186
+ return augmentLocator(locator);
187
+ };
188
+ defineHiddenProperty(page, "extract", createPageExtract(page));
189
+ defineHiddenProperty(page, PAGE_PATCHED, true);
190
+ return page;
191
+ }
192
+ function augmentBrowserContext(context) {
193
+ if (context[CONTEXT_PATCHED]) {
194
+ return context;
195
+ }
196
+ const originalNewPage = context.newPage.bind(context);
197
+ context.newPage = async (...args) => {
198
+ const page = await originalNewPage(...args);
199
+ return augmentPage(page);
200
+ };
201
+ const originalPages = context.pages?.bind(context);
202
+ if (originalPages) {
203
+ context.pages = () => originalPages().map(
204
+ (page) => augmentPage(page)
205
+ );
206
+ }
207
+ if (!context.agent) {
208
+ defineHiddenProperty(context, "agent", createAgentStub());
209
+ }
210
+ defineHiddenProperty(context, CONTEXT_PATCHED, true);
211
+ return context;
212
+ }
213
+ function augmentBrowser(browser) {
214
+ if (browser[BROWSER_PATCHED]) {
215
+ return browser;
216
+ }
217
+ const originalNewContext = browser.newContext.bind(browser);
218
+ browser.newContext = async (...args) => {
219
+ const context = await originalNewContext(...args);
220
+ return augmentBrowserContext(context);
221
+ };
222
+ const originalNewPage = browser.newPage.bind(browser);
223
+ browser.newPage = async (...args) => {
224
+ const page = await originalNewPage(...args);
225
+ return augmentPage(page);
226
+ };
227
+ const originalContexts = browser.contexts.bind(browser);
228
+ browser.contexts = () => originalContexts().map(
229
+ (context) => augmentBrowserContext(context)
230
+ );
231
+ if (!browser.agent) {
232
+ defineHiddenProperty(browser, "agent", createAgentStub());
233
+ }
234
+ defineHiddenProperty(browser, BROWSER_PATCHED, true);
235
+ return browser;
236
+ }
237
+ function augmentBrowserType(browserType) {
238
+ if (browserType[BROWSER_TYPE_PATCHED]) {
239
+ return browserType;
240
+ }
241
+ const originalLaunch = browserType.launch.bind(browserType);
242
+ browserType.launch = async (...args) => {
243
+ const browser = await originalLaunch(...args);
244
+ return augmentBrowser(browser);
245
+ };
246
+ const originalConnect = browserType.connect?.bind(browserType);
247
+ if (originalConnect) {
248
+ browserType.connect = async (...args) => {
249
+ const browser = await originalConnect(...args);
250
+ return augmentBrowser(browser);
251
+ };
252
+ }
253
+ const originalConnectOverCDP = browserType.connectOverCDP?.bind(browserType);
254
+ if (originalConnectOverCDP) {
255
+ browserType.connectOverCDP = async (...args) => {
256
+ const browser = await originalConnectOverCDP(...args);
257
+ return augmentBrowser(browser);
258
+ };
259
+ }
260
+ const originalLaunchPersistentContext = browserType.launchPersistentContext?.bind(browserType);
261
+ if (originalLaunchPersistentContext) {
262
+ browserType.launchPersistentContext = async (...args) => {
263
+ const context = await originalLaunchPersistentContext(...args);
264
+ return augmentBrowserContext(context);
265
+ };
266
+ }
267
+ defineHiddenProperty(browserType, BROWSER_TYPE_PATCHED, true);
268
+ return browserType;
269
+ }
270
+
271
+ // src/playwright-type-predicates.ts
272
+ function isPage(candidate) {
273
+ return typeof candidate === "object" && candidate !== null && typeof candidate.screenshot === "function" && typeof candidate.goto === "function";
274
+ }
275
+ function isLocator(candidate) {
276
+ return typeof candidate === "object" && candidate !== null && typeof candidate.screenshot === "function" && typeof candidate.nth === "function";
277
+ }
278
+
279
+ // src/ai/verify-prompt.ts
280
+ var PROMPT_ASSERTION_ENDPOINT = "https://api.stably.ai/internal/v1/assert";
281
+ var parseSuccessResponse = (value) => {
282
+ if (!isObject(value)) {
283
+ throw new Error("Verify prompt returned unexpected response shape");
284
+ }
285
+ const { success, reason } = value;
286
+ if (typeof success !== "boolean") {
287
+ throw new Error("Verify prompt returned unexpected response shape");
288
+ }
289
+ if (reason !== void 0 && typeof reason !== "string") {
290
+ throw new Error("Verify prompt returned unexpected response shape");
291
+ }
292
+ return {
293
+ success,
294
+ reason
295
+ };
296
+ };
297
+ var parseErrorResponse = (value) => {
298
+ if (!isObject(value)) {
299
+ return void 0;
300
+ }
301
+ const { error } = value;
302
+ return typeof error !== "string" ? void 0 : { error };
303
+ };
304
+ async function verifyPrompt({
305
+ prompt,
306
+ screenshot
307
+ }) {
308
+ const apiKey = requireApiKey();
309
+ const form = new FormData();
310
+ form.append("prompt", prompt);
311
+ const u8 = Uint8Array.from(screenshot);
312
+ const blob = new Blob([u8], { type: "image/png" });
313
+ form.append("image", blob, "screenshot.png");
314
+ const response = await fetch(PROMPT_ASSERTION_ENDPOINT, {
315
+ method: "POST",
316
+ headers: {
317
+ ...SDK_METADATA_HEADERS,
318
+ Authorization: `Bearer ${apiKey}`
319
+ },
320
+ body: form
321
+ });
322
+ const parsed = await response.json().catch(() => void 0);
323
+ if (response.ok) {
324
+ const { success, reason } = parseSuccessResponse(parsed);
325
+ return {
326
+ pass: success,
327
+ reason
328
+ };
329
+ }
330
+ const err = parseErrorResponse(parsed);
331
+ throw new Error(
332
+ `Verify prompt failed (${response.status})${err ? `: ${err.error}` : ""}`
333
+ );
334
+ }
335
+
336
+ // src/image-compare.ts
337
+ import pixelmatch from "pixelmatch";
338
+ import { PNG } from "pngjs";
339
+ import * as jpeg from "jpeg-js";
340
+ var isPng = (buffer) => {
341
+ return buffer.length >= 8 && buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10;
342
+ };
343
+ var isJpeg = (buffer) => {
344
+ return buffer.length >= 2 && buffer[0] === 255 && buffer[1] === 216;
345
+ };
346
+ var decodeImage = (buffer) => {
347
+ if (isPng(buffer)) {
348
+ const png2 = PNG.sync.read(buffer);
349
+ return { data: png2.data, width: png2.width, height: png2.height };
350
+ }
351
+ if (isJpeg(buffer)) {
352
+ const img = jpeg.decode(buffer, { maxMemoryUsageInMB: 1024 });
353
+ return { data: img.data, width: img.width, height: img.height };
354
+ }
355
+ const png = PNG.sync.read(buffer);
356
+ return { data: png.data, width: png.width, height: png.height };
357
+ };
358
+ var imagesAreSimilar = ({
359
+ image1,
360
+ image2,
361
+ threshold
362
+ }) => {
363
+ const decodedImage1 = decodeImage(image1);
364
+ const decodedImage2 = decodeImage(image2);
365
+ if (decodedImage1.width !== decodedImage2.width || decodedImage1.height !== decodedImage2.height) {
366
+ return false;
367
+ }
368
+ const diffRgbaData = new Uint8Array(
369
+ decodedImage1.width * decodedImage1.height * 4
370
+ );
371
+ const numDiffPixels = pixelmatch(
372
+ decodedImage1.data,
373
+ decodedImage2.data,
374
+ diffRgbaData,
375
+ decodedImage1.width,
376
+ decodedImage1.height,
377
+ { threshold }
378
+ );
379
+ return numDiffPixels === 0;
380
+ };
381
+
382
+ // src/screenshot.ts
383
+ async function takeStableScreenshot(target, options) {
384
+ const page = isPage(target) ? target : target.page();
385
+ const totalTimeout = options?.timeout ?? 5e3;
386
+ const stabilizationBudgetMs = Math.floor(totalTimeout * 0.25);
387
+ const stabilityBudgetMs = Math.min(
388
+ 2e3,
389
+ Math.max(300, stabilizationBudgetMs)
390
+ );
391
+ const deadline = Date.now() + stabilityBudgetMs;
392
+ let actual;
393
+ let previous;
394
+ const pollIntervals = [0, 100, 250, 500];
395
+ let isFirstIteration = true;
396
+ while (true) {
397
+ if (Date.now() >= deadline) break;
398
+ const delay = pollIntervals.length ? pollIntervals.shift() : 1e3;
399
+ if (delay) {
400
+ await page.waitForTimeout(delay);
401
+ }
402
+ previous = actual;
403
+ actual = await target.screenshot(options);
404
+ if (!isFirstIteration && actual && previous && imagesAreSimilar({
405
+ image1: previous,
406
+ image2: actual,
407
+ threshold: options?.threshold ?? 0.02
408
+ })) {
409
+ return actual;
410
+ }
411
+ isFirstIteration = false;
412
+ }
413
+ return actual ?? await target.screenshot(options);
414
+ }
415
+
416
+ // src/expect.ts
417
+ function createFailureMessage({
418
+ targetType,
419
+ condition,
420
+ didPass,
421
+ isNot,
422
+ reason
423
+ }) {
424
+ const expectation = isNot ? "not to satisfy" : "to satisfy";
425
+ const result = didPass ? "it did" : "it did not";
426
+ let message = `Expected ${targetType} ${expectation} ${JSON.stringify(condition)}, but ${result}.`;
427
+ if (reason) {
428
+ message += `
429
+
430
+ Reason: ${reason}`;
431
+ }
432
+ return message;
433
+ }
434
+ var stablyPlaywrightMatchers = {
435
+ async toMatchScreenshotPrompt(received, condition, options) {
436
+ const target = isPage(received) ? received : isLocator(received) ? received : void 0;
437
+ if (!target) {
438
+ throw new Error(
439
+ "toMatchScreenshotPrompt only supports Playwright Page and Locator instances."
440
+ );
441
+ }
442
+ const targetType = isPage(target) ? "page" : "locator";
443
+ const screenshot = await takeStableScreenshot(target, options);
444
+ const verifyResult = await verifyPrompt({ prompt: condition, screenshot });
445
+ return {
446
+ pass: verifyResult.pass,
447
+ message: () => createFailureMessage({
448
+ targetType,
449
+ condition,
450
+ didPass: verifyResult.pass,
451
+ reason: verifyResult.reason,
452
+ isNot: this.isNot
453
+ })
454
+ };
455
+ }
456
+ };
457
+ export {
458
+ augmentBrowser,
459
+ augmentBrowserContext,
460
+ augmentBrowserType,
461
+ augmentLocator,
462
+ augmentPage,
463
+ requireApiKey,
464
+ setApiKey,
465
+ stablyPlaywrightMatchers
466
+ };
467
+ //# sourceMappingURL=index.mjs.map