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