@stablyai/playwright-base 0.1.1 → 0.1.2

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.
@@ -1,9 +1,45 @@
1
- import * as z4 from "zod/v4/core";
2
- import { z } from "zod";
3
- import { requireApiKey } from "../runtime";
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.extract = extract;
37
+ const z4 = __importStar(require("zod/v4/core"));
38
+ const zod_1 = require("zod");
39
+ const runtime_1 = require("../runtime");
4
40
  const EXTRACT_ENDPOINT = "https://api.stably.ai/internal/v1/extract";
5
- const zSuccess = z.object({ value: z.unknown() });
6
- const zError = z.object({ error: z.string() });
41
+ const zSuccess = zod_1.z.object({ value: zod_1.z.unknown() });
42
+ const zError = zod_1.z.object({ error: zod_1.z.string() });
7
43
  class ExtractValidationError extends Error {
8
44
  issues;
9
45
  constructor(message, issues) {
@@ -19,9 +55,9 @@ async function validateWithSchema(schema, value) {
19
55
  }
20
56
  return result.data;
21
57
  }
22
- export async function extract({ prompt, pageOrLocator, schema, }) {
58
+ async function extract({ prompt, pageOrLocator, schema, }) {
23
59
  const jsonSchema = schema ? z4.toJSONSchema(schema) : undefined;
24
- const apiKey = requireApiKey();
60
+ const apiKey = (0, runtime_1.requireApiKey)();
25
61
  const form = new FormData();
26
62
  form.append("prompt", prompt);
27
63
  if (jsonSchema) {
@@ -1,15 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyPrompt = verifyPrompt;
1
4
  const PROMPT_ASSERTION_ENDPOINT = "https://api.stably.ai/internal/v1/assert";
2
- import { z } from "zod";
3
- import { requireApiKey } from "../runtime";
4
- const zSuccess = z.object({
5
- success: z.boolean(),
6
- reason: z.string().optional(),
5
+ const zod_1 = require("zod");
6
+ const runtime_1 = require("../runtime");
7
+ const zSuccess = zod_1.z.object({
8
+ success: zod_1.z.boolean(),
9
+ reason: zod_1.z.string().optional(),
7
10
  });
8
- const zError = z.object({
9
- error: z.string(),
11
+ const zError = zod_1.z.object({
12
+ error: zod_1.z.string(),
10
13
  });
11
- export async function verifyPrompt({ prompt, screenshot, }) {
12
- const apiKey = requireApiKey();
14
+ async function verifyPrompt({ prompt, screenshot, }) {
15
+ const apiKey = (0, runtime_1.requireApiKey)();
13
16
  const form = new FormData();
14
17
  form.append("prompt", prompt);
15
18
  const u8 = Uint8Array.from(screenshot);
package/dist/expect.js CHANGED
@@ -1,6 +1,9 @@
1
- import { isLocator, isPage } from "./playwright-type-predicates";
2
- import { verifyPrompt } from "./ai/verify-prompt";
3
- import { takeStableScreenshot } from "./screenshot";
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stablyPlaywrightMatchers = void 0;
4
+ const playwright_type_predicates_1 = require("./playwright-type-predicates");
5
+ const verify_prompt_1 = require("./ai/verify-prompt");
6
+ const screenshot_1 = require("./screenshot");
4
7
  function createFailureMessage({ targetType, condition, didPass, isNot, reason, }) {
5
8
  const expectation = isNot ? "not to satisfy" : "to satisfy";
6
9
  const result = didPass ? "it did" : "it did not";
@@ -10,21 +13,21 @@ function createFailureMessage({ targetType, condition, didPass, isNot, reason, }
10
13
  }
11
14
  return message;
12
15
  }
13
- export const stablyPlaywrightMatchers = {
16
+ exports.stablyPlaywrightMatchers = {
14
17
  async toMatchScreenshotPrompt(received, condition, options) {
15
- const target = isPage(received)
18
+ const target = (0, playwright_type_predicates_1.isPage)(received)
16
19
  ? received
17
- : isLocator(received)
20
+ : (0, playwright_type_predicates_1.isLocator)(received)
18
21
  ? received
19
22
  : undefined;
20
23
  if (!target) {
21
24
  // Should never happen
22
25
  throw new Error("toMatchScreenshotPrompt only supports Playwright Page and Locator instances.");
23
26
  }
24
- const targetType = isPage(target) ? "page" : "locator";
27
+ const targetType = (0, playwright_type_predicates_1.isPage)(target) ? "page" : "locator";
25
28
  // Wait for two consecutive identical screenshots before sending to AI
26
- const screenshot = await takeStableScreenshot(target, options);
27
- const verifyResult = await verifyPrompt({ prompt: condition, screenshot });
29
+ const screenshot = await (0, screenshot_1.takeStableScreenshot)(target, options);
30
+ const verifyResult = await (0, verify_prompt_1.verifyPrompt)({ prompt: condition, screenshot });
28
31
  return {
29
32
  pass: verifyResult.pass,
30
33
  message: () => createFailureMessage({
@@ -1,6 +1,45 @@
1
- import pixelmatch from "pixelmatch";
2
- import { PNG } from "pngjs";
3
- import * as jpeg from "jpeg-js";
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.imagesAreSimilar = void 0;
40
+ const pixelmatch_1 = __importDefault(require("pixelmatch"));
41
+ const pngjs_1 = require("pngjs");
42
+ const jpeg = __importStar(require("jpeg-js"));
4
43
  const isPng = (buffer) => {
5
44
  return (buffer.length >= 8 &&
6
45
  buffer[0] === 0x89 &&
@@ -17,7 +56,7 @@ const isJpeg = (buffer) => {
17
56
  };
18
57
  const decodeImage = (buffer) => {
19
58
  if (isPng(buffer)) {
20
- const png = PNG.sync.read(buffer);
59
+ const png = pngjs_1.PNG.sync.read(buffer);
21
60
  return { data: png.data, width: png.width, height: png.height };
22
61
  }
23
62
  if (isJpeg(buffer)) {
@@ -25,10 +64,10 @@ const decodeImage = (buffer) => {
25
64
  return { data: img.data, width: img.width, height: img.height };
26
65
  }
27
66
  // Default to PNG decode; if it fails upstream, treat as different sizes
28
- const png = PNG.sync.read(buffer);
67
+ const png = pngjs_1.PNG.sync.read(buffer);
29
68
  return { data: png.data, width: png.width, height: png.height };
30
69
  };
31
- export const imagesAreSimilar = ({ image1, image2, threshold, }) => {
70
+ const imagesAreSimilar = ({ image1, image2, threshold, }) => {
32
71
  const decodedImage1 = decodeImage(image1);
33
72
  const decodedImage2 = decodeImage(image2);
34
73
  if (decodedImage1.width !== decodedImage2.width ||
@@ -36,6 +75,7 @@ export const imagesAreSimilar = ({ image1, image2, threshold, }) => {
36
75
  return false;
37
76
  }
38
77
  const diffRgbaData = new Uint8Array(decodedImage1.width * decodedImage1.height * 4);
39
- const numDiffPixels = pixelmatch(decodedImage1.data, decodedImage2.data, diffRgbaData, decodedImage1.width, decodedImage1.height, { threshold });
78
+ const numDiffPixels = (0, pixelmatch_1.default)(decodedImage1.data, decodedImage2.data, diffRgbaData, decodedImage1.width, decodedImage1.height, { threshold });
40
79
  return numDiffPixels === 0;
41
80
  };
81
+ exports.imagesAreSimilar = imagesAreSimilar;
package/dist/index.js CHANGED
@@ -1,5 +1,15 @@
1
- import { augmentBrowser, augmentBrowserContext, augmentBrowserType, augmentLocator, augmentPage, } from "./playwright-augment/augment";
2
- import { stablyPlaywrightMatchers } from "./expect";
3
- import { requireApiKey } from "./runtime";
4
- export { setApiKey } from "./runtime";
5
- export { augmentBrowser, augmentBrowserContext, augmentBrowserType, augmentLocator, augmentPage, stablyPlaywrightMatchers, requireApiKey, };
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireApiKey = exports.stablyPlaywrightMatchers = exports.augmentPage = exports.augmentLocator = exports.augmentBrowserType = exports.augmentBrowserContext = exports.augmentBrowser = exports.setApiKey = void 0;
4
+ const augment_1 = require("./playwright-augment/augment");
5
+ Object.defineProperty(exports, "augmentBrowser", { enumerable: true, get: function () { return augment_1.augmentBrowser; } });
6
+ Object.defineProperty(exports, "augmentBrowserContext", { enumerable: true, get: function () { return augment_1.augmentBrowserContext; } });
7
+ Object.defineProperty(exports, "augmentBrowserType", { enumerable: true, get: function () { return augment_1.augmentBrowserType; } });
8
+ Object.defineProperty(exports, "augmentLocator", { enumerable: true, get: function () { return augment_1.augmentLocator; } });
9
+ Object.defineProperty(exports, "augmentPage", { enumerable: true, get: function () { return augment_1.augmentPage; } });
10
+ const expect_1 = require("./expect");
11
+ Object.defineProperty(exports, "stablyPlaywrightMatchers", { enumerable: true, get: function () { return expect_1.stablyPlaywrightMatchers; } });
12
+ const runtime_1 = require("./runtime");
13
+ Object.defineProperty(exports, "requireApiKey", { enumerable: true, get: function () { return runtime_1.requireApiKey; } });
14
+ var runtime_2 = require("./runtime");
15
+ Object.defineProperty(exports, "setApiKey", { enumerable: true, get: function () { return runtime_2.setApiKey; } });
@@ -1,5 +1,12 @@
1
- import { createLocatorExtract, createPageExtract } from "./methods/extract";
2
- import { createAgentStub } from "./methods/agent";
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.augmentLocator = augmentLocator;
4
+ exports.augmentPage = augmentPage;
5
+ exports.augmentBrowserContext = augmentBrowserContext;
6
+ exports.augmentBrowser = augmentBrowser;
7
+ exports.augmentBrowserType = augmentBrowserType;
8
+ const extract_1 = require("./methods/extract");
9
+ const agent_1 = require("./methods/agent");
3
10
  const LOCATOR_PATCHED = Symbol.for("stably.playwright.locatorPatched");
4
11
  const LOCATOR_DESCRIBE_WRAPPED = Symbol.for("stably.playwright.locatorDescribeWrapped");
5
12
  const PAGE_PATCHED = Symbol.for("stably.playwright.pagePatched");
@@ -14,11 +21,11 @@ function defineHiddenProperty(target, key, value) {
14
21
  writable: true,
15
22
  });
16
23
  }
17
- export function augmentLocator(locator) {
24
+ function augmentLocator(locator) {
18
25
  if (locator[LOCATOR_PATCHED]) {
19
26
  return locator;
20
27
  }
21
- defineHiddenProperty(locator, "extract", createLocatorExtract(locator));
28
+ defineHiddenProperty(locator, "extract", (0, extract_1.createLocatorExtract)(locator));
22
29
  const markerTarget = locator;
23
30
  if (typeof locator.describe === "function" &&
24
31
  !markerTarget[LOCATOR_DESCRIBE_WRAPPED]) {
@@ -33,7 +40,7 @@ export function augmentLocator(locator) {
33
40
  defineHiddenProperty(locator, LOCATOR_PATCHED, true);
34
41
  return locator;
35
42
  }
36
- export function augmentPage(page) {
43
+ function augmentPage(page) {
37
44
  if (page[PAGE_PATCHED]) {
38
45
  return page;
39
46
  }
@@ -42,11 +49,11 @@ export function augmentPage(page) {
42
49
  const locator = originalLocator(...args);
43
50
  return augmentLocator(locator);
44
51
  });
45
- defineHiddenProperty(page, "extract", createPageExtract(page));
52
+ defineHiddenProperty(page, "extract", (0, extract_1.createPageExtract)(page));
46
53
  defineHiddenProperty(page, PAGE_PATCHED, true);
47
54
  return page;
48
55
  }
49
- export function augmentBrowserContext(context) {
56
+ function augmentBrowserContext(context) {
50
57
  if (context[CONTEXT_PATCHED]) {
51
58
  return context;
52
59
  }
@@ -60,12 +67,12 @@ export function augmentBrowserContext(context) {
60
67
  context.pages = (() => originalPages().map((page) => augmentPage(page)));
61
68
  }
62
69
  if (!context.agent) {
63
- defineHiddenProperty(context, "agent", createAgentStub());
70
+ defineHiddenProperty(context, "agent", (0, agent_1.createAgentStub)());
64
71
  }
65
72
  defineHiddenProperty(context, CONTEXT_PATCHED, true);
66
73
  return context;
67
74
  }
68
- export function augmentBrowser(browser) {
75
+ function augmentBrowser(browser) {
69
76
  if (browser[BROWSER_PATCHED]) {
70
77
  return browser;
71
78
  }
@@ -82,12 +89,12 @@ export function augmentBrowser(browser) {
82
89
  const originalContexts = browser.contexts.bind(browser);
83
90
  browser.contexts = (() => originalContexts().map((context) => augmentBrowserContext(context)));
84
91
  if (!browser.agent) {
85
- defineHiddenProperty(browser, "agent", createAgentStub());
92
+ defineHiddenProperty(browser, "agent", (0, agent_1.createAgentStub)());
86
93
  }
87
94
  defineHiddenProperty(browser, BROWSER_PATCHED, true);
88
95
  return browser;
89
96
  }
90
- export function augmentBrowserType(browserType) {
97
+ function augmentBrowserType(browserType) {
91
98
  if (browserType[BROWSER_TYPE_PATCHED]) {
92
99
  return browserType;
93
100
  }
@@ -1,7 +1,10 @@
1
- import { requireApiKey } from "../../runtime";
2
- export function createAgentStub() {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAgentStub = createAgentStub;
4
+ const runtime_1 = require("../../runtime");
5
+ function createAgentStub() {
3
6
  return async (prompt, options) => {
4
- requireApiKey();
7
+ (0, runtime_1.requireApiKey)();
5
8
  void prompt;
6
9
  void options;
7
10
  return { success: true };
@@ -1,16 +1,21 @@
1
- import { extract, } from "../../ai/extract";
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPageExtract = exports.createLocatorExtract = void 0;
4
+ const extract_1 = require("../../ai/extract");
2
5
  function createExtract(pageOrLocator) {
3
6
  const impl = (async (prompt, options) => {
4
7
  if (options?.schema) {
5
- return extract({
8
+ return (0, extract_1.extract)({
6
9
  prompt,
7
10
  schema: options.schema,
8
11
  pageOrLocator,
9
12
  });
10
13
  }
11
- return extract({ prompt, pageOrLocator });
14
+ return (0, extract_1.extract)({ prompt, pageOrLocator });
12
15
  });
13
16
  return impl;
14
17
  }
15
- export const createLocatorExtract = (locator) => createExtract(locator);
16
- export const createPageExtract = (page) => createExtract(page);
18
+ const createLocatorExtract = (locator) => createExtract(locator);
19
+ exports.createLocatorExtract = createLocatorExtract;
20
+ const createPageExtract = (page) => createExtract(page);
21
+ exports.createPageExtract = createPageExtract;
@@ -1,10 +1,14 @@
1
- export function isPage(candidate) {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPage = isPage;
4
+ exports.isLocator = isLocator;
5
+ function isPage(candidate) {
2
6
  return (typeof candidate === "object" &&
3
7
  candidate !== null &&
4
8
  typeof candidate.screenshot === "function" &&
5
9
  typeof candidate.goto === "function");
6
10
  }
7
- export function isLocator(candidate) {
11
+ function isLocator(candidate) {
8
12
  return (typeof candidate === "object" &&
9
13
  candidate !== null &&
10
14
  typeof candidate.screenshot === "function" &&
package/dist/runtime.js CHANGED
@@ -1,11 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setApiKey = setApiKey;
4
+ exports.getApiKey = getApiKey;
5
+ exports.requireApiKey = requireApiKey;
1
6
  let configuredApiKey = process.env.STABLY_API_KEY;
2
- export function setApiKey(apiKey) {
7
+ function setApiKey(apiKey) {
3
8
  configuredApiKey = apiKey;
4
9
  }
5
- export function getApiKey() {
10
+ function getApiKey() {
6
11
  return configuredApiKey;
7
12
  }
8
- export function requireApiKey() {
13
+ function requireApiKey() {
9
14
  const apiKey = getApiKey();
10
15
  if (!apiKey) {
11
16
  throw new Error("Missing Stably API key. Call setApiKey(apiKey) or set the STABLY_API_KEY environment variable.");
@@ -1,7 +1,10 @@
1
- import { isPage } from "./playwright-type-predicates";
2
- import { imagesAreSimilar } from "./image-compare";
3
- export async function takeStableScreenshot(target, options) {
4
- const page = isPage(target) ? target : target.page();
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.takeStableScreenshot = takeStableScreenshot;
4
+ const playwright_type_predicates_1 = require("./playwright-type-predicates");
5
+ const image_compare_1 = require("./image-compare");
6
+ async function takeStableScreenshot(target, options) {
7
+ const page = (0, playwright_type_predicates_1.isPage)(target) ? target : target.page();
5
8
  // Use a small budget for stabilization within the overall assertion timeout.
6
9
  // We allocate up to 25% of the total timeout (bounded between 300ms and 2000ms).
7
10
  const totalTimeout = options?.timeout ?? 5000;
@@ -25,7 +28,7 @@ export async function takeStableScreenshot(target, options) {
25
28
  if (!isFirstIteration &&
26
29
  actual &&
27
30
  previous &&
28
- imagesAreSimilar({
31
+ (0, image_compare_1.imagesAreSimilar)({
29
32
  image1: previous,
30
33
  image2: actual,
31
34
  threshold: options?.threshold ?? 0.02,
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@stablyai/playwright-base",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Shared augmentation runtime for Stably Playwright wrappers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
7
10
  "engines": {
8
11
  "node": ">=18"
9
12
  },
package/src/ai/extract.ts DELETED
@@ -1,97 +0,0 @@
1
- import type { Locator, Page } from "@stablyai/internal-playwright-test";
2
- import * as z4 from "zod/v4/core";
3
- import { z } from "zod";
4
- import { requireApiKey } from "../runtime";
5
-
6
- export type ExtractSchema = z4.$ZodType;
7
-
8
- export type SchemaOutput<T extends ExtractSchema> = z4.output<T>;
9
-
10
- type ExtractIssue = z4.$ZodIssue;
11
-
12
- const EXTRACT_ENDPOINT = "https://api.stably.ai/internal/v1/extract";
13
-
14
- const zSuccess = z.object({ value: z.unknown() });
15
- const zError = z.object({ error: z.string() });
16
-
17
- class ExtractValidationError extends Error {
18
- constructor(
19
- message: string,
20
- readonly issues: ReadonlyArray<ExtractIssue>,
21
- ) {
22
- super(message);
23
- this.name = "ExtractValidationError";
24
- }
25
- }
26
-
27
- async function validateWithSchema<T extends ExtractSchema>(
28
- schema: T,
29
- value: unknown,
30
- ): Promise<SchemaOutput<T>> {
31
- const result = await z4.safeParseAsync(schema, value);
32
- if (!result.success) {
33
- throw new ExtractValidationError("Validation failed", result.error.issues);
34
- }
35
-
36
- return result.data;
37
- }
38
-
39
- type ExtractSubject = Page | Locator;
40
-
41
- type BaseExtractArgs = {
42
- prompt: string;
43
- pageOrLocator: ExtractSubject;
44
- };
45
-
46
- type ExtractArgsWithSchema<T extends ExtractSchema> = BaseExtractArgs & {
47
- schema: T;
48
- };
49
-
50
- export async function extract(args: BaseExtractArgs): Promise<string>;
51
- export async function extract<T extends ExtractSchema>(
52
- args: ExtractArgsWithSchema<T>,
53
- ): Promise<SchemaOutput<T>>;
54
- export async function extract<T extends ExtractSchema>({
55
- prompt,
56
- pageOrLocator,
57
- schema,
58
- }: BaseExtractArgs & { schema?: T }): Promise<string | SchemaOutput<T>> {
59
- const jsonSchema = schema ? z4.toJSONSchema(schema) : undefined;
60
-
61
- const apiKey = requireApiKey();
62
-
63
- const form = new FormData();
64
- form.append("prompt", prompt);
65
- if (jsonSchema) {
66
- form.append("jsonSchema", JSON.stringify(jsonSchema));
67
- }
68
-
69
- const pngBuffer = await pageOrLocator.screenshot({ type: "png" }); // Buffer
70
- const u8 = Uint8Array.from(pngBuffer); // strips Buffer type → plain Uint8Array
71
- const blob = new Blob([u8], { type: "image/png" });
72
- form.append("image", blob, "screenshot.png");
73
-
74
- const response = await fetch(EXTRACT_ENDPOINT, {
75
- method: "POST",
76
- headers: {
77
- Authorization: `Bearer ${apiKey}`,
78
- },
79
- body: form,
80
- });
81
-
82
- const parsed = await response.json().catch(() => undefined as unknown);
83
-
84
- if (response.ok) {
85
- const { value } = zSuccess.parse(parsed);
86
- return schema
87
- ? await validateWithSchema(schema, value)
88
- : typeof value === "string"
89
- ? value
90
- : JSON.stringify(value);
91
- }
92
-
93
- const err = zError.safeParse(parsed);
94
- throw new Error(
95
- `Extract failed (${response.status})${err.success ? `: ${err.data.error}` : ""}`,
96
- );
97
- }
@@ -1,57 +0,0 @@
1
- const PROMPT_ASSERTION_ENDPOINT = "https://api.stably.ai/internal/v1/assert";
2
-
3
- import { z } from "zod";
4
- import { requireApiKey } from "../runtime";
5
-
6
- const zSuccess = z.object({
7
- success: z.boolean(),
8
- reason: z.string().optional(),
9
- });
10
-
11
- const zError = z.object({
12
- error: z.string(),
13
- });
14
-
15
- export async function verifyPrompt({
16
- prompt,
17
- screenshot,
18
- }: {
19
- prompt: string;
20
- screenshot: Uint8Array;
21
- }): Promise<{
22
- pass: boolean;
23
- reason?: string;
24
- }> {
25
- const apiKey = requireApiKey();
26
-
27
- const form = new FormData();
28
- form.append("prompt", prompt);
29
- const u8 = Uint8Array.from(screenshot);
30
- const blob = new Blob([u8], { type: "image/png" });
31
- form.append("image", blob, "screenshot.png");
32
-
33
- const response = await fetch(PROMPT_ASSERTION_ENDPOINT, {
34
- method: "POST",
35
- headers: {
36
- Authorization: `Bearer ${apiKey}`,
37
- },
38
- body: form,
39
- });
40
-
41
- const parsed = await response.json().catch(() => undefined as unknown);
42
-
43
- if (response.ok) {
44
- const { success, reason } = zSuccess.parse(parsed);
45
- return {
46
- pass: success,
47
- reason,
48
- };
49
- }
50
-
51
- const err = zError.safeParse(parsed);
52
- throw new Error(
53
- `Verify prompt failed (${response.status})${
54
- err.success ? `: ${err.data.error}` : ""
55
- }`,
56
- );
57
- }
package/src/expect.ts DELETED
@@ -1,77 +0,0 @@
1
- import type { Locator, Page } from "@stablyai/internal-playwright-test";
2
- import type { ScreenshotPromptOptions } from "./index";
3
-
4
- import { isLocator, isPage } from "./playwright-type-predicates";
5
-
6
- import { verifyPrompt } from "./ai/verify-prompt";
7
- import { takeStableScreenshot } from "./screenshot";
8
-
9
- type VerifyTargetType = "page" | "locator";
10
-
11
- type MatcherContext = {
12
- isNot: boolean;
13
- message?: () => string;
14
- };
15
-
16
- function createFailureMessage({
17
- targetType,
18
- condition,
19
- didPass,
20
- isNot,
21
- reason,
22
- }: {
23
- targetType: VerifyTargetType;
24
- condition: string;
25
- didPass: boolean;
26
- isNot: boolean;
27
- reason?: string;
28
- }): string {
29
- const expectation = isNot ? "not to satisfy" : "to satisfy";
30
- const result = didPass ? "it did" : "it did not";
31
-
32
- let message = `Expected ${targetType} ${expectation} ${JSON.stringify(condition)}, but ${result}.`;
33
- if (reason) {
34
- message += `\n\nReason: ${reason}`;
35
- }
36
-
37
- return message;
38
- }
39
-
40
- export const stablyPlaywrightMatchers = {
41
- async toMatchScreenshotPrompt(
42
- this: MatcherContext,
43
- received: Page | Locator,
44
- condition: string,
45
- options?: ScreenshotPromptOptions,
46
- ) {
47
- const target = isPage(received)
48
- ? received
49
- : isLocator(received)
50
- ? received
51
- : undefined;
52
- if (!target) {
53
- // Should never happen
54
- throw new Error(
55
- "toMatchScreenshotPrompt only supports Playwright Page and Locator instances.",
56
- );
57
- }
58
- const targetType: VerifyTargetType = isPage(target) ? "page" : "locator";
59
-
60
- // Wait for two consecutive identical screenshots before sending to AI
61
- const screenshot = await takeStableScreenshot(target, options);
62
-
63
- const verifyResult = await verifyPrompt({ prompt: condition, screenshot });
64
-
65
- return {
66
- pass: verifyResult.pass,
67
- message: () =>
68
- createFailureMessage({
69
- targetType,
70
- condition,
71
- didPass: verifyResult.pass,
72
- reason: verifyResult.reason,
73
- isNot: this.isNot,
74
- }),
75
- };
76
- },
77
- } as const;
@@ -1,69 +0,0 @@
1
- import pixelmatch from "pixelmatch";
2
- import { PNG } from "pngjs";
3
- import * as jpeg from "jpeg-js";
4
-
5
- const isPng = (buffer: Buffer): boolean => {
6
- return (
7
- buffer.length >= 8 &&
8
- buffer[0] === 0x89 &&
9
- buffer[1] === 0x50 &&
10
- buffer[2] === 0x4e &&
11
- buffer[3] === 0x47 &&
12
- buffer[4] === 0x0d &&
13
- buffer[5] === 0x0a &&
14
- buffer[6] === 0x1a &&
15
- buffer[7] === 0x0a
16
- );
17
- };
18
-
19
- const isJpeg = (buffer: Buffer): boolean => {
20
- return buffer.length >= 2 && buffer[0] === 0xff && buffer[1] === 0xd8;
21
- };
22
-
23
- const decodeImage = (
24
- buffer: Buffer,
25
- ): { data: Uint8Array; width: number; height: number } => {
26
- if (isPng(buffer)) {
27
- const png = PNG.sync.read(buffer);
28
- return { data: png.data, width: png.width, height: png.height };
29
- }
30
- if (isJpeg(buffer)) {
31
- const img = jpeg.decode(buffer, { maxMemoryUsageInMB: 1024 });
32
- return { data: img.data, width: img.width, height: img.height };
33
- }
34
- // Default to PNG decode; if it fails upstream, treat as different sizes
35
- const png = PNG.sync.read(buffer);
36
- return { data: png.data, width: png.width, height: png.height };
37
- };
38
-
39
- export const imagesAreSimilar = ({
40
- image1,
41
- image2,
42
- threshold,
43
- }: {
44
- image1: Buffer;
45
- image2: Buffer;
46
- threshold: number;
47
- }): boolean => {
48
- const decodedImage1 = decodeImage(image1);
49
- const decodedImage2 = decodeImage(image2);
50
- if (
51
- decodedImage1.width !== decodedImage2.width ||
52
- decodedImage1.height !== decodedImage2.height
53
- ) {
54
- return false;
55
- }
56
- const diffRgbaData = new Uint8Array(
57
- decodedImage1.width * decodedImage1.height * 4,
58
- );
59
- const numDiffPixels = pixelmatch(
60
- decodedImage1.data,
61
- decodedImage2.data,
62
- diffRgbaData,
63
- decodedImage1.width,
64
- decodedImage1.height,
65
- { threshold },
66
- );
67
-
68
- return numDiffPixels === 0;
69
- };
package/src/index.ts DELETED
@@ -1,111 +0,0 @@
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
-
5
- import {
6
- augmentBrowser,
7
- augmentBrowserContext,
8
- augmentBrowserType,
9
- augmentLocator,
10
- augmentPage,
11
- } from "./playwright-augment/augment";
12
- import { stablyPlaywrightMatchers } from "./expect";
13
- import { requireApiKey } from "./runtime";
14
-
15
- export { setApiKey } from "./runtime";
16
-
17
- export type { LocatorDescribeOptions } from "./playwright-augment/augment";
18
- export type { ExtractSchema, SchemaOutput } from "./ai/extract";
19
- export type ScreenshotPromptOptions =
20
- import("@stablyai/internal-playwright-test").PageAssertionsToHaveScreenshotOptions;
21
- export {
22
- augmentBrowser,
23
- augmentBrowserContext,
24
- augmentBrowserType,
25
- augmentLocator,
26
- augmentPage,
27
- stablyPlaywrightMatchers,
28
- requireApiKey,
29
- };
30
-
31
- export interface Expect<T = Page> {
32
- toMatchScreenshotPrompt(
33
- condition: string,
34
- options?: ScreenshotPromptOptions,
35
- ): Promise<void>;
36
- }
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>(
63
- prompt: string,
64
- options: { schema: T },
65
- ): Promise<SchemaOutput<T>>;
66
- describe(description: string, options?: LocatorDescribeOptions): Locator;
67
- }
68
-
69
- interface Page {
70
- /**
71
- * Extracts information from this page using Stably AI.
72
- *
73
- * Takes a screenshot of the page and uses AI to extract information based on the
74
- * provided prompt. When a schema is provided, the extracted data is validated and
75
- * typed according to the schema.
76
- *
77
- * @param prompt - A natural language description of what information to extract
78
- * @returns A string containing the extracted information
79
- */
80
- extract(prompt: string): Promise<string>;
81
- /**
82
- * Extracts information from this page using Stably AI.
83
- *
84
- * Takes a screenshot of the page and uses AI to extract information based on the
85
- * provided prompt. The extracted data is validated and typed according to the schema.
86
- *
87
- * @param prompt - A natural language description of what information to extract
88
- * @param options - Configuration object containing the Zod schema for validation
89
- * @param options.schema - Zod schema to validate and type the extracted data
90
- * @returns Typed data matching the provided schema
91
- */
92
- extract<T extends ExtractSchema>(
93
- prompt: string,
94
- options: { schema: T },
95
- ): Promise<SchemaOutput<T>>;
96
- }
97
-
98
- interface BrowserContext {
99
- agent(
100
- prompt: string,
101
- options: { page: Page; maxCycles?: number },
102
- ): Promise<{ success: boolean }>;
103
- }
104
-
105
- interface Browser {
106
- agent(
107
- prompt: string,
108
- options: { page: Page; maxCycles?: number },
109
- ): Promise<{ success: boolean }>;
110
- }
111
- }
@@ -1,207 +0,0 @@
1
- import type {
2
- Browser,
3
- BrowserContext,
4
- BrowserType,
5
- Locator,
6
- Page,
7
- } from "@stablyai/internal-playwright-test";
8
-
9
- import { createLocatorExtract, createPageExtract } from "./methods/extract";
10
- import { createAgentStub } from "./methods/agent";
11
-
12
- export interface LocatorDescribeOptions {
13
- autoHeal?: boolean;
14
- }
15
-
16
- const LOCATOR_PATCHED = Symbol.for("stably.playwright.locatorPatched");
17
- const LOCATOR_DESCRIBE_WRAPPED = Symbol.for(
18
- "stably.playwright.locatorDescribeWrapped",
19
- );
20
- const PAGE_PATCHED = Symbol.for("stably.playwright.pagePatched");
21
- const CONTEXT_PATCHED = Symbol.for("stably.playwright.contextPatched");
22
- const BROWSER_PATCHED = Symbol.for("stably.playwright.browserPatched");
23
- const BROWSER_TYPE_PATCHED = Symbol.for("stably.playwright.browserTypePatched");
24
-
25
- function defineHiddenProperty<T, K extends PropertyKey>(
26
- target: T,
27
- key: K,
28
- value: unknown,
29
- ): void {
30
- Object.defineProperty(target as unknown as object, key, {
31
- value,
32
- enumerable: false,
33
- configurable: true,
34
- writable: true,
35
- });
36
- }
37
-
38
- export function augmentLocator<T extends Locator>(locator: T): T {
39
- if (
40
- (locator as unknown as { [LOCATOR_PATCHED]?: boolean })[LOCATOR_PATCHED]
41
- ) {
42
- return locator;
43
- }
44
-
45
- defineHiddenProperty(locator, "extract", createLocatorExtract(locator));
46
-
47
- const markerTarget = locator as unknown as Record<PropertyKey, unknown>;
48
-
49
- if (
50
- typeof locator.describe === "function" &&
51
- !markerTarget[LOCATOR_DESCRIBE_WRAPPED]
52
- ) {
53
- const originalDescribe = locator.describe.bind(locator);
54
- locator.describe = ((
55
- description: string,
56
- options?: LocatorDescribeOptions,
57
- ) => {
58
- void options;
59
- const result = originalDescribe(description);
60
- return result ? augmentLocator(result as Locator) : result;
61
- }) as Locator["describe"];
62
-
63
- defineHiddenProperty(locator, LOCATOR_DESCRIBE_WRAPPED, true);
64
- }
65
-
66
- defineHiddenProperty(locator, LOCATOR_PATCHED, true);
67
-
68
- return locator;
69
- }
70
-
71
- export function augmentPage<T extends Page>(page: T): T {
72
- if ((page as unknown as { [PAGE_PATCHED]?: boolean })[PAGE_PATCHED]) {
73
- return page;
74
- }
75
-
76
- const originalLocator = page.locator.bind(page);
77
- page.locator = ((...args: Parameters<Page["locator"]>) => {
78
- const locator = originalLocator(...args);
79
- return augmentLocator(locator);
80
- }) as Page["locator"];
81
-
82
- defineHiddenProperty(page, "extract", createPageExtract(page));
83
- defineHiddenProperty(page, PAGE_PATCHED, true);
84
-
85
- return page;
86
- }
87
-
88
- export function augmentBrowserContext<T extends BrowserContext>(context: T): T {
89
- if (
90
- (context as unknown as { [CONTEXT_PATCHED]?: boolean })[CONTEXT_PATCHED]
91
- ) {
92
- return context;
93
- }
94
-
95
- const originalNewPage = context.newPage.bind(context);
96
- context.newPage = (async (...args: Parameters<BrowserContext["newPage"]>) => {
97
- const page = await originalNewPage(...args);
98
- return augmentPage(page);
99
- }) as BrowserContext["newPage"];
100
-
101
- const originalPages = context.pages?.bind(context);
102
- if (originalPages) {
103
- context.pages = (() =>
104
- originalPages().map((page) =>
105
- augmentPage(page),
106
- )) as BrowserContext["pages"];
107
- }
108
-
109
- if (!(context as unknown as { agent?: unknown }).agent) {
110
- defineHiddenProperty(context, "agent", createAgentStub());
111
- }
112
-
113
- defineHiddenProperty(context, CONTEXT_PATCHED, true);
114
-
115
- return context;
116
- }
117
-
118
- export function augmentBrowser<T extends Browser>(browser: T): T {
119
- if (
120
- (browser as unknown as { [BROWSER_PATCHED]?: boolean })[BROWSER_PATCHED]
121
- ) {
122
- return browser;
123
- }
124
-
125
- const originalNewContext = browser.newContext.bind(browser);
126
- browser.newContext = (async (...args: Parameters<Browser["newContext"]>) => {
127
- const context = await originalNewContext(...args);
128
- return augmentBrowserContext(context);
129
- }) as Browser["newContext"];
130
-
131
- const originalNewPage = browser.newPage.bind(browser);
132
- browser.newPage = (async (...args: Parameters<Browser["newPage"]>) => {
133
- const page = await originalNewPage(...args);
134
- return augmentPage(page);
135
- }) as Browser["newPage"];
136
-
137
- const originalContexts = browser.contexts.bind(browser);
138
- browser.contexts = (() =>
139
- originalContexts().map((context) =>
140
- augmentBrowserContext(context),
141
- )) as Browser["contexts"];
142
-
143
- if (!(browser as unknown as { agent?: unknown }).agent) {
144
- defineHiddenProperty(browser, "agent", createAgentStub());
145
- }
146
-
147
- defineHiddenProperty(browser, BROWSER_PATCHED, true);
148
-
149
- return browser;
150
- }
151
-
152
- export function augmentBrowserType<TBrowser extends Browser>(
153
- browserType: BrowserType<TBrowser>,
154
- ): BrowserType<TBrowser> {
155
- if (
156
- (browserType as unknown as { [BROWSER_TYPE_PATCHED]?: boolean })[
157
- BROWSER_TYPE_PATCHED
158
- ]
159
- ) {
160
- return browserType;
161
- }
162
-
163
- const originalLaunch = browserType.launch.bind(browserType);
164
- browserType.launch = (async (
165
- ...args: Parameters<BrowserType<TBrowser>["launch"]>
166
- ) => {
167
- const browser = await originalLaunch(...args);
168
- return augmentBrowser(browser);
169
- }) as BrowserType<TBrowser>["launch"];
170
-
171
- const originalConnect = browserType.connect?.bind(browserType);
172
- if (originalConnect) {
173
- browserType.connect = (async (
174
- ...args: Parameters<NonNullable<BrowserType<TBrowser>["connect"]>>
175
- ) => {
176
- const browser = await originalConnect(...args);
177
- return augmentBrowser(browser);
178
- }) as NonNullable<BrowserType<TBrowser>["connect"]>;
179
- }
180
-
181
- const originalConnectOverCDP = browserType.connectOverCDP?.bind(browserType);
182
- if (originalConnectOverCDP) {
183
- browserType.connectOverCDP = (async (
184
- ...args: Parameters<NonNullable<BrowserType<TBrowser>["connectOverCDP"]>>
185
- ) => {
186
- const browser = await originalConnectOverCDP(...args);
187
- return augmentBrowser(browser);
188
- }) as NonNullable<BrowserType<TBrowser>["connectOverCDP"]>;
189
- }
190
-
191
- const originalLaunchPersistentContext =
192
- browserType.launchPersistentContext?.bind(browserType);
193
- if (originalLaunchPersistentContext) {
194
- browserType.launchPersistentContext = (async (
195
- ...args: Parameters<
196
- NonNullable<BrowserType<TBrowser>["launchPersistentContext"]>
197
- >
198
- ) => {
199
- const context = await originalLaunchPersistentContext(...args);
200
- return augmentBrowserContext(context);
201
- }) as NonNullable<BrowserType<TBrowser>["launchPersistentContext"]>;
202
- }
203
-
204
- defineHiddenProperty(browserType, BROWSER_TYPE_PATCHED, true);
205
-
206
- return browserType;
207
- }
@@ -1,19 +0,0 @@
1
- import type { Page } from "@stablyai/internal-playwright-test";
2
- import { requireApiKey } from "../../runtime";
3
-
4
- type AgentOptions = {
5
- page: Page;
6
- maxCycles?: number;
7
- };
8
-
9
- export function createAgentStub(): (
10
- prompt: string,
11
- options: AgentOptions,
12
- ) => Promise<{ success: boolean }> {
13
- return async (prompt: string, options: AgentOptions) => {
14
- requireApiKey();
15
- void prompt;
16
- void options;
17
- return { success: true };
18
- };
19
- }
@@ -1,48 +0,0 @@
1
- import type { Locator, Page } from "@stablyai/internal-playwright-test";
2
- import {
3
- type ExtractSchema,
4
- type SchemaOutput,
5
- extract,
6
- } from "../../ai/extract";
7
-
8
- type ExtractOptions<T extends ExtractSchema> = {
9
- schema: T;
10
- };
11
-
12
- type ExtractMethod = {
13
- (prompt: string): Promise<string>;
14
- <T extends ExtractSchema>(
15
- prompt: string,
16
- options: ExtractOptions<T>,
17
- ): Promise<SchemaOutput<T>>;
18
- };
19
-
20
- type LocatorExtract = ExtractMethod;
21
- type PageExtract = ExtractMethod;
22
-
23
- type ExtractSubject = Locator | Page;
24
-
25
- function createExtract(pageOrLocator: ExtractSubject): ExtractMethod {
26
- const impl = (async (
27
- prompt: string,
28
- options?: ExtractOptions<ExtractSchema>,
29
- ) => {
30
- if (options?.schema) {
31
- return extract({
32
- prompt,
33
- schema: options.schema,
34
- pageOrLocator,
35
- });
36
- }
37
-
38
- return extract({ prompt, pageOrLocator });
39
- }) as ExtractMethod;
40
-
41
- return impl;
42
- }
43
-
44
- export const createLocatorExtract = (locator: Locator): LocatorExtract =>
45
- createExtract(locator);
46
-
47
- export const createPageExtract = (page: Page): PageExtract =>
48
- createExtract(page);
@@ -1,19 +0,0 @@
1
- import { Page, Locator } from "@stablyai/internal-playwright-test";
2
-
3
- export function isPage(candidate: unknown): candidate is Page {
4
- return (
5
- typeof candidate === "object" &&
6
- candidate !== null &&
7
- typeof (candidate as Page).screenshot === "function" &&
8
- typeof (candidate as Page).goto === "function"
9
- );
10
- }
11
-
12
- export function isLocator(candidate: unknown): candidate is Locator {
13
- return (
14
- typeof candidate === "object" &&
15
- candidate !== null &&
16
- typeof (candidate as Locator).screenshot === "function" &&
17
- typeof (candidate as Locator).nth === "function"
18
- );
19
- }
package/src/runtime.ts DELETED
@@ -1,19 +0,0 @@
1
- let configuredApiKey: string | undefined = process.env.STABLY_API_KEY;
2
-
3
- export function setApiKey(apiKey: string): void {
4
- configuredApiKey = apiKey;
5
- }
6
-
7
- export function getApiKey(): string | undefined {
8
- return configuredApiKey;
9
- }
10
-
11
- export function requireApiKey(): string {
12
- const apiKey = getApiKey();
13
- if (!apiKey) {
14
- throw new Error(
15
- "Missing Stably API key. Call setApiKey(apiKey) or set the STABLY_API_KEY environment variable.",
16
- );
17
- }
18
- return apiKey;
19
- }
package/src/screenshot.ts DELETED
@@ -1,52 +0,0 @@
1
- import type { Locator, Page } from "@stablyai/internal-playwright-test";
2
- import type { ScreenshotPromptOptions } from "./index";
3
- import { isPage } from "./playwright-type-predicates";
4
- import { imagesAreSimilar } from "./image-compare";
5
-
6
- export async function takeStableScreenshot(
7
- target: Page | Locator,
8
- options?: ScreenshotPromptOptions,
9
- ): Promise<Buffer> {
10
- const page = isPage(target) ? target : target.page();
11
-
12
- // Use a small budget for stabilization within the overall assertion timeout.
13
- // We allocate up to 25% of the total timeout (bounded between 300ms and 2000ms).
14
- const totalTimeout =
15
- (options as { timeout?: number } | undefined)?.timeout ?? 5000;
16
- // Budget is 25% of the total timeout
17
- const stabilizationBudgetMs = Math.floor(totalTimeout * 0.25);
18
- const stabilityBudgetMs = Math.min(
19
- 2000,
20
- Math.max(300, stabilizationBudgetMs),
21
- );
22
- const deadline = Date.now() + stabilityBudgetMs;
23
-
24
- let actual: Buffer | undefined;
25
- let previous: Buffer | undefined;
26
- const pollIntervals = [0, 100, 250, 500];
27
- let isFirstIteration = true;
28
-
29
- while (true) {
30
- if (Date.now() >= deadline) break;
31
- const delay = pollIntervals.length ? pollIntervals.shift()! : 1000;
32
- if (delay) {
33
- await page.waitForTimeout(delay);
34
- }
35
- previous = actual;
36
- actual = await target.screenshot(options);
37
- if (
38
- !isFirstIteration &&
39
- actual &&
40
- previous &&
41
- imagesAreSimilar({
42
- image1: previous,
43
- image2: actual,
44
- threshold: options?.threshold ?? 0.02,
45
- })
46
- ) {
47
- return actual;
48
- }
49
- isFirstIteration = false;
50
- }
51
- return actual ?? (await target.screenshot(options));
52
- }
@@ -1,6 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "noEmit": false
5
- }
6
- }
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "extends": "@tsconfig/node18/tsconfig.json",
3
- "compilerOptions": {
4
- "declaration": true,
5
- "outDir": "./dist",
6
- "rootDir": "src",
7
- "forceConsistentCasingInFileNames": true,
8
- "resolveJsonModule": true,
9
- "noEmit": true,
10
- "moduleResolution": "bundler",
11
- "module": "ESNext",
12
- "baseUrl": ".",
13
- "paths": {
14
- "~/*": ["src/*"]
15
- }
16
- },
17
- "include": ["src/**/*"],
18
- "exclude": ["node_modules", "dist"]
19
- }