@stablyai/playwright-base 0.1.10 → 0.2.1
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 +479 -361
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +16 -13
- package/dist/index.d.ts +16 -13
- package/dist/index.mjs +479 -361
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,9 @@ __export(index_exports, {
|
|
|
41
41
|
});
|
|
42
42
|
module.exports = __toCommonJS(index_exports);
|
|
43
43
|
|
|
44
|
+
// src/expect.ts
|
|
45
|
+
var import_internal_playwright_test = require("@stablyai/internal-playwright-test");
|
|
46
|
+
|
|
44
47
|
// src/runtime.ts
|
|
45
48
|
var configuredApiKey = process.env.STABLY_API_KEY;
|
|
46
49
|
function setApiKey(apiKey) {
|
|
@@ -67,108 +70,65 @@ var isObject = (value) => {
|
|
|
67
70
|
// src/ai/metadata.ts
|
|
68
71
|
var SDK_METADATA_HEADERS = {
|
|
69
72
|
"X-Client-Name": "stably-playwright-sdk-js",
|
|
70
|
-
"X-Client-Version": "0.1
|
|
73
|
+
"X-Client-Version": "0.2.1"
|
|
71
74
|
};
|
|
72
75
|
|
|
73
|
-
// src/ai/
|
|
74
|
-
var
|
|
75
|
-
var
|
|
76
|
-
try {
|
|
77
|
-
return require("zod/v4/core");
|
|
78
|
-
} catch {
|
|
79
|
-
return void 0;
|
|
80
|
-
}
|
|
81
|
-
})();
|
|
82
|
-
var isExtractionResponse = (value) => {
|
|
76
|
+
// src/ai/verify-prompt.ts
|
|
77
|
+
var PROMPT_ASSERTION_ENDPOINT = "https://api.stably.ai/internal/v1/assert";
|
|
78
|
+
var parseSuccessResponse = (value) => {
|
|
83
79
|
if (!isObject(value)) {
|
|
84
|
-
|
|
80
|
+
throw new Error("Verify prompt returned unexpected response shape");
|
|
85
81
|
}
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
const { reason, success } = value;
|
|
83
|
+
if (typeof success !== "boolean") {
|
|
84
|
+
throw new Error("Verify prompt returned unexpected response shape");
|
|
88
85
|
}
|
|
89
|
-
|
|
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";
|
|
86
|
+
if (reason !== void 0 && typeof reason !== "string") {
|
|
87
|
+
throw new Error("Verify prompt returned unexpected response shape");
|
|
99
88
|
}
|
|
89
|
+
return {
|
|
90
|
+
reason,
|
|
91
|
+
success
|
|
92
|
+
};
|
|
100
93
|
};
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
throw new ExtractValidationError("Validation failed", result.error.issues);
|
|
94
|
+
var parseErrorResponse = (value) => {
|
|
95
|
+
if (!isObject(value)) {
|
|
96
|
+
return void 0;
|
|
105
97
|
}
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
98
|
+
const { error } = value;
|
|
99
|
+
return typeof error !== "string" ? void 0 : { error };
|
|
100
|
+
};
|
|
101
|
+
async function verifyPrompt({
|
|
109
102
|
prompt,
|
|
110
|
-
|
|
111
|
-
schema
|
|
103
|
+
screenshot
|
|
112
104
|
}) {
|
|
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
105
|
const apiKey = requireApiKey();
|
|
122
106
|
const form = new FormData();
|
|
123
107
|
form.append("prompt", prompt);
|
|
124
|
-
|
|
125
|
-
form.append("jsonSchema", JSON.stringify(jsonSchema));
|
|
126
|
-
}
|
|
127
|
-
const pngBuffer = await pageOrLocator.screenshot({ type: "png" });
|
|
128
|
-
const u8 = Uint8Array.from(pngBuffer);
|
|
108
|
+
const u8 = Uint8Array.from(screenshot);
|
|
129
109
|
const blob = new Blob([u8], { type: "image/png" });
|
|
130
110
|
form.append("image", blob, "screenshot.png");
|
|
131
|
-
const response = await fetch(
|
|
132
|
-
|
|
111
|
+
const response = await fetch(PROMPT_ASSERTION_ENDPOINT, {
|
|
112
|
+
body: form,
|
|
133
113
|
headers: {
|
|
134
114
|
...SDK_METADATA_HEADERS,
|
|
135
115
|
Authorization: `Bearer ${apiKey}`
|
|
136
116
|
},
|
|
137
|
-
|
|
117
|
+
method: "POST"
|
|
138
118
|
});
|
|
139
|
-
const
|
|
119
|
+
const parsed = await response.json().catch(() => void 0);
|
|
140
120
|
if (response.ok) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
const { value } = raw;
|
|
148
|
-
return schema ? await validateWithSchema(schema, value) : typeof value === "string" ? value : JSON.stringify(value);
|
|
121
|
+
const { reason, success } = parseSuccessResponse(parsed);
|
|
122
|
+
return {
|
|
123
|
+
pass: success,
|
|
124
|
+
reason
|
|
125
|
+
};
|
|
149
126
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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;
|
|
127
|
+
const err = parseErrorResponse(parsed);
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Verify prompt failed (${response.status})${err ? `: ${err.error}` : ""}`
|
|
130
|
+
);
|
|
166
131
|
}
|
|
167
|
-
var createLocatorExtract = (locator) => createExtract(locator);
|
|
168
|
-
var createPageExtract = (page) => createExtract(page);
|
|
169
|
-
|
|
170
|
-
// src/playwright-augment/methods/agent.ts
|
|
171
|
-
var import_internal_playwright_test = require("@stablyai/internal-playwright-test");
|
|
172
132
|
|
|
173
133
|
// src/playwright-type-predicates.ts
|
|
174
134
|
function isPage(candidate) {
|
|
@@ -178,6 +138,9 @@ function isLocator(candidate) {
|
|
|
178
138
|
return typeof candidate === "object" && candidate !== null && typeof candidate.screenshot === "function" && typeof candidate.nth === "function";
|
|
179
139
|
}
|
|
180
140
|
|
|
141
|
+
// src/image-compare.ts
|
|
142
|
+
var jpeg = __toESM(require("jpeg-js"));
|
|
143
|
+
|
|
181
144
|
// ../../node_modules/.pnpm/pixelmatch@7.1.0/node_modules/pixelmatch/index.js
|
|
182
145
|
function pixelmatch(img1, img2, output, width, height, options = {}) {
|
|
183
146
|
const {
|
|
@@ -336,7 +299,6 @@ function drawGrayPixel(img, i, alpha, output) {
|
|
|
336
299
|
|
|
337
300
|
// src/image-compare.ts
|
|
338
301
|
var import_pngjs = require("pngjs");
|
|
339
|
-
var jpeg = __toESM(require("jpeg-js"));
|
|
340
302
|
var isPng = (buffer) => {
|
|
341
303
|
return buffer.length >= 8 && buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10;
|
|
342
304
|
};
|
|
@@ -346,14 +308,14 @@ var isJpeg = (buffer) => {
|
|
|
346
308
|
var decodeImage = (buffer) => {
|
|
347
309
|
if (isPng(buffer)) {
|
|
348
310
|
const png2 = import_pngjs.PNG.sync.read(buffer);
|
|
349
|
-
return { data: png2.data,
|
|
311
|
+
return { data: png2.data, height: png2.height, width: png2.width };
|
|
350
312
|
}
|
|
351
313
|
if (isJpeg(buffer)) {
|
|
352
314
|
const img = jpeg.decode(buffer, { maxMemoryUsageInMB: 1024 });
|
|
353
|
-
return { data: img.data,
|
|
315
|
+
return { data: img.data, height: img.height, width: img.width };
|
|
354
316
|
}
|
|
355
317
|
const png = import_pngjs.PNG.sync.read(buffer);
|
|
356
|
-
return { data: png.data,
|
|
318
|
+
return { data: png.data, height: png.height, width: png.width };
|
|
357
319
|
};
|
|
358
320
|
var imagesAreSimilar = ({
|
|
359
321
|
image1,
|
|
@@ -405,7 +367,9 @@ async function takeStableScreenshot(target, options) {
|
|
|
405
367
|
return await target.screenshot(options);
|
|
406
368
|
};
|
|
407
369
|
while (true) {
|
|
408
|
-
if (Date.now() >= deadline)
|
|
370
|
+
if (Date.now() >= deadline) {
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
409
373
|
const delay = pollIntervals.length ? pollIntervals.shift() : 1e3;
|
|
410
374
|
if (delay) {
|
|
411
375
|
await page.waitForTimeout(delay);
|
|
@@ -424,49 +388,279 @@ async function takeStableScreenshot(target, options) {
|
|
|
424
388
|
return actual ?? await safeScreenshot();
|
|
425
389
|
}
|
|
426
390
|
|
|
391
|
+
// src/expect.ts
|
|
392
|
+
function createFailureMessage({
|
|
393
|
+
condition,
|
|
394
|
+
didPass,
|
|
395
|
+
isNot,
|
|
396
|
+
reason,
|
|
397
|
+
targetType
|
|
398
|
+
}) {
|
|
399
|
+
const expectation = isNot ? "not to satisfy" : "to satisfy";
|
|
400
|
+
const result = didPass ? "it did" : "it did not";
|
|
401
|
+
let message = `Expected ${targetType} ${expectation} ${JSON.stringify(condition)}, but ${result}.`;
|
|
402
|
+
if (reason) {
|
|
403
|
+
message += `
|
|
404
|
+
|
|
405
|
+
Reason: ${reason}`;
|
|
406
|
+
}
|
|
407
|
+
return message;
|
|
408
|
+
}
|
|
409
|
+
var stablyPlaywrightMatchers = {
|
|
410
|
+
async toMatchScreenshotPrompt(received, condition, options) {
|
|
411
|
+
const target = isPage(received) ? received : isLocator(received) ? received : void 0;
|
|
412
|
+
if (!target) {
|
|
413
|
+
throw new Error(
|
|
414
|
+
"toMatchScreenshotPrompt only supports Playwright Page and Locator instances."
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
const targetType = isPage(target) ? "page" : "locator";
|
|
418
|
+
const screenshot = await takeStableScreenshot(target, options);
|
|
419
|
+
const verifyResult = await verifyPrompt({ prompt: condition, screenshot });
|
|
420
|
+
const testInfo = import_internal_playwright_test.test.info();
|
|
421
|
+
testInfo.attachments.push({
|
|
422
|
+
body: Buffer.from(
|
|
423
|
+
JSON.stringify(
|
|
424
|
+
{
|
|
425
|
+
pass: verifyResult.pass,
|
|
426
|
+
prompt: condition,
|
|
427
|
+
reasoning: verifyResult.reason
|
|
428
|
+
},
|
|
429
|
+
null,
|
|
430
|
+
2
|
|
431
|
+
),
|
|
432
|
+
"utf-8"
|
|
433
|
+
),
|
|
434
|
+
contentType: "application/json",
|
|
435
|
+
name: "toMatchScreenshotPrompt-reasoning"
|
|
436
|
+
});
|
|
437
|
+
return {
|
|
438
|
+
message: () => createFailureMessage({
|
|
439
|
+
condition,
|
|
440
|
+
didPass: verifyResult.pass,
|
|
441
|
+
isNot: this.isNot,
|
|
442
|
+
reason: verifyResult.reason,
|
|
443
|
+
targetType
|
|
444
|
+
}),
|
|
445
|
+
name: "toMatchScreenshotPrompt",
|
|
446
|
+
pass: verifyResult.pass
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// src/playwright-augment/methods/agent.ts
|
|
452
|
+
var import_internal_playwright_test2 = require("@stablyai/internal-playwright-test");
|
|
453
|
+
|
|
454
|
+
// src/utils/truncate.ts
|
|
455
|
+
var truncate = (inp, length) => inp.length <= length || inp.length <= 3 ? inp : `${inp.slice(0, length - 3)}...`;
|
|
456
|
+
|
|
427
457
|
// src/playwright-augment/methods/agent/construct-payload.ts
|
|
428
458
|
function constructAgentPayload({
|
|
429
|
-
|
|
430
|
-
|
|
459
|
+
activePage,
|
|
460
|
+
additionalContext,
|
|
431
461
|
isError,
|
|
462
|
+
message,
|
|
463
|
+
model,
|
|
432
464
|
screenshot,
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
additionalContext
|
|
465
|
+
sessionId,
|
|
466
|
+
tabManager
|
|
436
467
|
}) {
|
|
437
468
|
const form = new FormData();
|
|
438
|
-
|
|
439
|
-
|
|
469
|
+
const viewportSize = activePage.viewportSize();
|
|
470
|
+
const tabs = Array.from(tabManager.entries()).map(([page, alias]) => ({
|
|
471
|
+
alias,
|
|
472
|
+
url: page.url()
|
|
473
|
+
}));
|
|
474
|
+
const activePageAlias = tabManager.get(activePage);
|
|
475
|
+
const metadata = {
|
|
476
|
+
allPages: tabs,
|
|
477
|
+
message,
|
|
478
|
+
sessionId
|
|
479
|
+
};
|
|
480
|
+
if (model) {
|
|
481
|
+
metadata.model = model;
|
|
482
|
+
}
|
|
440
483
|
if (isError) {
|
|
441
|
-
|
|
484
|
+
metadata.isError = isError;
|
|
442
485
|
}
|
|
443
486
|
if (additionalContext) {
|
|
444
|
-
|
|
487
|
+
metadata.additionalContext = additionalContext;
|
|
445
488
|
}
|
|
446
|
-
const viewportSize = activePage.viewportSize();
|
|
447
489
|
if (viewportSize) {
|
|
448
|
-
|
|
490
|
+
metadata.pageDimensions = viewportSize;
|
|
449
491
|
}
|
|
492
|
+
if (activePageAlias) {
|
|
493
|
+
metadata.activePageAlias = activePageAlias;
|
|
494
|
+
}
|
|
495
|
+
const metadataBlob = new Blob([JSON.stringify(metadata)], {
|
|
496
|
+
type: "application/json"
|
|
497
|
+
});
|
|
498
|
+
form.append("metadata", metadataBlob, "metadata.json");
|
|
450
499
|
const screenshotBytes = Uint8Array.from(screenshot);
|
|
451
500
|
const screenshotBlob = new Blob([screenshotBytes], { type: "image/png" });
|
|
452
501
|
form.append("screenshot", screenshotBlob, "screenshot.png");
|
|
453
|
-
const tabs = Array.from(tabManager.entries()).map(([page, alias]) => ({
|
|
454
|
-
alias,
|
|
455
|
-
url: page.url()
|
|
456
|
-
}));
|
|
457
|
-
form.append("all_pages", JSON.stringify(tabs));
|
|
458
|
-
const activePageAlias = tabManager.get(activePage);
|
|
459
|
-
if (activePageAlias) {
|
|
460
|
-
form.append("active_page_alias", activePageAlias);
|
|
461
|
-
}
|
|
462
502
|
return form;
|
|
463
503
|
}
|
|
464
504
|
|
|
505
|
+
// src/playwright-augment/methods/agent/exec-response.ts
|
|
506
|
+
var DEFAULT_AGENT_WAIT_MS = 3e3;
|
|
507
|
+
async function execResponse({
|
|
508
|
+
activePage: initialActivePage,
|
|
509
|
+
agentResponse,
|
|
510
|
+
browserContext,
|
|
511
|
+
tabManager
|
|
512
|
+
}) {
|
|
513
|
+
let activePage = initialActivePage;
|
|
514
|
+
try {
|
|
515
|
+
switch (agentResponse.action) {
|
|
516
|
+
case "key": {
|
|
517
|
+
const { text } = agentResponse;
|
|
518
|
+
if (text) {
|
|
519
|
+
await activePage.keyboard.press(text);
|
|
520
|
+
return { activePage, result: { message: `pressed "${text}"` } };
|
|
521
|
+
}
|
|
522
|
+
return { activePage, result: { message: "pressed key" } };
|
|
523
|
+
}
|
|
524
|
+
case "type": {
|
|
525
|
+
const { text } = agentResponse;
|
|
526
|
+
await activePage.keyboard.type(text);
|
|
527
|
+
return { activePage, result: { message: `typed "${text}"` } };
|
|
528
|
+
}
|
|
529
|
+
case "mouse_move": {
|
|
530
|
+
const [x, y] = agentResponse.coordinate;
|
|
531
|
+
await activePage.mouse.move(x, y);
|
|
532
|
+
return { activePage, result: { message: `mouse moved completed` } };
|
|
533
|
+
}
|
|
534
|
+
case "left_click": {
|
|
535
|
+
const [x, y] = agentResponse.coordinate;
|
|
536
|
+
await activePage.mouse.click(x, y);
|
|
537
|
+
return { activePage, result: { message: `left click completed` } };
|
|
538
|
+
}
|
|
539
|
+
case "right_click": {
|
|
540
|
+
const [x, y] = agentResponse.coordinate;
|
|
541
|
+
await activePage.mouse.click(x, y, { button: "right" });
|
|
542
|
+
return { activePage, result: { message: `right click completed` } };
|
|
543
|
+
}
|
|
544
|
+
case "double_click": {
|
|
545
|
+
const [x, y] = agentResponse.coordinate;
|
|
546
|
+
await activePage.mouse.dblclick(x, y);
|
|
547
|
+
return { activePage, result: { message: `double click completed` } };
|
|
548
|
+
}
|
|
549
|
+
case "triple_click": {
|
|
550
|
+
const [x, y] = agentResponse.coordinate;
|
|
551
|
+
await activePage.mouse.click(x, y, { clickCount: 3 });
|
|
552
|
+
return { activePage, result: { message: `triple click completed` } };
|
|
553
|
+
}
|
|
554
|
+
case "left_click_drag": {
|
|
555
|
+
const [startX, startY] = agentResponse.start_coordinate;
|
|
556
|
+
const [endX, endY] = agentResponse.coordinate;
|
|
557
|
+
await activePage.mouse.move(startX, startY);
|
|
558
|
+
await activePage.mouse.down();
|
|
559
|
+
await activePage.mouse.move(endX, endY);
|
|
560
|
+
await activePage.mouse.up();
|
|
561
|
+
return { activePage, result: { message: `drag completed` } };
|
|
562
|
+
}
|
|
563
|
+
case "screenshot": {
|
|
564
|
+
await takeStableScreenshot(activePage);
|
|
565
|
+
return { activePage, result: { message: "captured screenshot" } };
|
|
566
|
+
}
|
|
567
|
+
case "wait": {
|
|
568
|
+
const waitMs = agentResponse.milliseconds ?? DEFAULT_AGENT_WAIT_MS;
|
|
569
|
+
await activePage.waitForTimeout(waitMs);
|
|
570
|
+
return { activePage, result: { message: `waited ${waitMs}ms` } };
|
|
571
|
+
}
|
|
572
|
+
case "navigate_to_url": {
|
|
573
|
+
await activePage.goto(agentResponse.url);
|
|
574
|
+
return {
|
|
575
|
+
activePage,
|
|
576
|
+
result: { message: `navigated to "${agentResponse.url}"` }
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
case "new_tab_url": {
|
|
580
|
+
const newPage = await browserContext.newPage();
|
|
581
|
+
await newPage.goto(agentResponse.url);
|
|
582
|
+
await newPage.waitForLoadState("domcontentloaded");
|
|
583
|
+
activePage = newPage;
|
|
584
|
+
return { activePage, result: { message: "opened new tab" } };
|
|
585
|
+
}
|
|
586
|
+
case "switch_tab": {
|
|
587
|
+
const entry = Array.from(tabManager.entries()).find(
|
|
588
|
+
([, alias]) => alias === agentResponse.tab_alias
|
|
589
|
+
);
|
|
590
|
+
const page = entry?.[0];
|
|
591
|
+
if (!page) {
|
|
592
|
+
throw new Error(
|
|
593
|
+
`Tab with alias ${agentResponse.tab_alias} not found`
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
await page.bringToFront();
|
|
597
|
+
activePage = page;
|
|
598
|
+
return {
|
|
599
|
+
activePage,
|
|
600
|
+
result: { message: `switched to "${agentResponse.tab_alias}"` }
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
case "scroll": {
|
|
604
|
+
const [x, y] = agentResponse.coordinate;
|
|
605
|
+
await activePage.mouse.move(x, y);
|
|
606
|
+
let deltaX = 0;
|
|
607
|
+
let deltaY = 0;
|
|
608
|
+
switch (agentResponse.scroll_direction) {
|
|
609
|
+
case "up":
|
|
610
|
+
deltaY = -agentResponse.scroll_amount;
|
|
611
|
+
break;
|
|
612
|
+
case "down":
|
|
613
|
+
deltaY = agentResponse.scroll_amount;
|
|
614
|
+
break;
|
|
615
|
+
case "left":
|
|
616
|
+
deltaX = -agentResponse.scroll_amount;
|
|
617
|
+
break;
|
|
618
|
+
case "right":
|
|
619
|
+
deltaX = agentResponse.scroll_amount;
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
await activePage.mouse.wheel(deltaX, deltaY);
|
|
623
|
+
return {
|
|
624
|
+
activePage,
|
|
625
|
+
result: { message: `scrolled ${agentResponse.scroll_direction}` }
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
case "navigate_back": {
|
|
629
|
+
const res = await activePage.goBack();
|
|
630
|
+
if (!res) {
|
|
631
|
+
throw new Error("navigate_back failed: no history entry");
|
|
632
|
+
}
|
|
633
|
+
return { activePage, result: { message: "navigated back" } };
|
|
634
|
+
}
|
|
635
|
+
case "aria_snapshot": {
|
|
636
|
+
const ariaSnapshot = await activePage._snapshotForAI();
|
|
637
|
+
return {
|
|
638
|
+
activePage,
|
|
639
|
+
result: { message: `ARIA Snapshot:
|
|
640
|
+
${ariaSnapshot}` }
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
case "terminate_test": {
|
|
644
|
+
const { reason, success } = agentResponse;
|
|
645
|
+
return {
|
|
646
|
+
activePage,
|
|
647
|
+
finalSuccess: success,
|
|
648
|
+
result: { message: reason, shouldTerminate: true }
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
} catch (error) {
|
|
653
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
654
|
+
return { activePage, result: { isError: true, message } };
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
465
658
|
// src/playwright-augment/methods/agent.ts
|
|
466
|
-
var AGENT_PATH = "internal/
|
|
659
|
+
var AGENT_PATH = "internal/v3/agent";
|
|
467
660
|
var STABLY_API_URL = process.env.STABLY_API_URL || "https://api.stably.ai";
|
|
468
661
|
var AGENT_ENDPOINT = new URL(AGENT_PATH, STABLY_API_URL).toString();
|
|
469
662
|
function createAgentStub() {
|
|
663
|
+
let thoughtsIndex = 0;
|
|
470
664
|
return async (prompt, options) => {
|
|
471
665
|
const apiKey = requireApiKey();
|
|
472
666
|
const maxCycles = options.maxCycles ?? 30;
|
|
@@ -477,8 +671,8 @@ function createAgentStub() {
|
|
|
477
671
|
});
|
|
478
672
|
let activePage = options.page;
|
|
479
673
|
let agentMessage = {
|
|
480
|
-
message: prompt,
|
|
481
674
|
isError: false,
|
|
675
|
+
message: prompt,
|
|
482
676
|
shouldTerminate: false
|
|
483
677
|
};
|
|
484
678
|
let finalSuccess;
|
|
@@ -496,165 +690,81 @@ function createAgentStub() {
|
|
|
496
690
|
newPageOpenedMsg = `opened new tab ${alias} (${page.url()})`;
|
|
497
691
|
};
|
|
498
692
|
browserContext.on("page", onNewPage);
|
|
499
|
-
return await
|
|
693
|
+
return await import_internal_playwright_test2.test.step(`[Agent] ${prompt}`, async () => {
|
|
500
694
|
try {
|
|
501
695
|
for (let i = 0; i < maxCycles; i++) {
|
|
502
696
|
if (agentMessage.shouldTerminate) {
|
|
503
697
|
break;
|
|
504
698
|
}
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
return { message: "pressed key" };
|
|
540
|
-
}
|
|
541
|
-
case "type": {
|
|
542
|
-
const { text } = agentResponse;
|
|
543
|
-
await activePage.keyboard.type(text);
|
|
544
|
-
return { message: `typed "${text}"` };
|
|
545
|
-
}
|
|
546
|
-
case "mouse_move": {
|
|
547
|
-
const [x, y] = agentResponse.coordinate;
|
|
548
|
-
await activePage.mouse.move(x, y);
|
|
549
|
-
return { message: `mouse moved to [${x}, ${y}]` };
|
|
550
|
-
}
|
|
551
|
-
case "left_click": {
|
|
552
|
-
const [x, y] = agentResponse.coordinate;
|
|
553
|
-
await activePage.mouse.click(x, y);
|
|
554
|
-
return { message: `left click at [${x}, ${y}]` };
|
|
555
|
-
}
|
|
556
|
-
case "right_click": {
|
|
557
|
-
const [x, y] = agentResponse.coordinate;
|
|
558
|
-
await activePage.mouse.click(x, y, { button: "right" });
|
|
559
|
-
return { message: `right click at [${x}, ${y}]` };
|
|
560
|
-
}
|
|
561
|
-
case "double_click": {
|
|
562
|
-
const [x, y] = agentResponse.coordinate;
|
|
563
|
-
await activePage.mouse.dblclick(x, y);
|
|
564
|
-
return { message: `double click at [${x}, ${y}]` };
|
|
565
|
-
}
|
|
566
|
-
case "triple_click": {
|
|
567
|
-
const [x, y] = agentResponse.coordinate;
|
|
568
|
-
await activePage.mouse.click(x, y, { clickCount: 3 });
|
|
569
|
-
return { message: `triple click at [${x}, ${y}]` };
|
|
570
|
-
}
|
|
571
|
-
case "left_click_drag": {
|
|
572
|
-
const [startX, startY] = agentResponse.start_coordinate;
|
|
573
|
-
const [endX, endY] = agentResponse.coordinate;
|
|
574
|
-
await activePage.mouse.move(startX, startY);
|
|
575
|
-
await activePage.mouse.down();
|
|
576
|
-
await activePage.mouse.move(endX, endY);
|
|
577
|
-
await activePage.mouse.up();
|
|
578
|
-
return {
|
|
579
|
-
message: `dragged from [${startX}, ${startY}] to [${endX}, ${endY}]`
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
case "screenshot": {
|
|
583
|
-
await takeStableScreenshot(activePage);
|
|
584
|
-
return { message: "captured screenshot" };
|
|
585
|
-
}
|
|
586
|
-
case "wait": {
|
|
587
|
-
const waitMs = agentResponse.milliseconds ?? 3e3;
|
|
588
|
-
await activePage.waitForTimeout(waitMs);
|
|
589
|
-
return { message: `waited ${waitMs}ms` };
|
|
590
|
-
}
|
|
591
|
-
case "navigate_to_url": {
|
|
592
|
-
await activePage.goto(agentResponse.url);
|
|
593
|
-
return { message: `navigated to "${agentResponse.url}"` };
|
|
594
|
-
}
|
|
595
|
-
case "new_tab_url": {
|
|
596
|
-
const newPage = await browserContext.newPage();
|
|
597
|
-
await newPage.goto(agentResponse.url);
|
|
598
|
-
await newPage.waitForLoadState("domcontentloaded");
|
|
599
|
-
return { message: "opened new tab" };
|
|
600
|
-
}
|
|
601
|
-
case "switch_tab": {
|
|
602
|
-
const entry = Array.from(tabManager.entries()).find(
|
|
603
|
-
([, alias]) => alias === agentResponse.tab_alias
|
|
604
|
-
);
|
|
605
|
-
const page = entry?.[0];
|
|
606
|
-
if (!page) {
|
|
607
|
-
throw new Error(
|
|
608
|
-
`Tab with alias ${agentResponse.tab_alias} not found`
|
|
609
|
-
);
|
|
610
|
-
}
|
|
611
|
-
await page.bringToFront();
|
|
612
|
-
activePage = page;
|
|
613
|
-
return {
|
|
614
|
-
message: `switched to "${agentResponse.tab_alias}"`
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
case "scroll": {
|
|
618
|
-
const [x, y] = agentResponse.coordinate;
|
|
619
|
-
await activePage.mouse.move(x, y);
|
|
620
|
-
let deltaX = 0;
|
|
621
|
-
let deltaY = 0;
|
|
622
|
-
switch (agentResponse.scroll_direction) {
|
|
623
|
-
case "up":
|
|
624
|
-
deltaY = -agentResponse.scroll_amount;
|
|
625
|
-
break;
|
|
626
|
-
case "down":
|
|
627
|
-
deltaY = agentResponse.scroll_amount;
|
|
628
|
-
break;
|
|
629
|
-
case "left":
|
|
630
|
-
deltaX = -agentResponse.scroll_amount;
|
|
631
|
-
break;
|
|
632
|
-
case "right":
|
|
633
|
-
deltaX = agentResponse.scroll_amount;
|
|
634
|
-
break;
|
|
635
|
-
}
|
|
636
|
-
await activePage.mouse.wheel(deltaX, deltaY);
|
|
637
|
-
return {
|
|
638
|
-
message: `scrolled ${agentResponse.scroll_direction}`
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
case "navigate_back": {
|
|
642
|
-
const res = await activePage.goBack();
|
|
643
|
-
if (!res)
|
|
644
|
-
throw new Error("navigate_back failed: no history entry");
|
|
645
|
-
return { message: "navigated back" };
|
|
646
|
-
}
|
|
647
|
-
case "terminate_test": {
|
|
648
|
-
const { success, reason } = agentResponse;
|
|
649
|
-
finalSuccess = success;
|
|
650
|
-
return { message: reason, shouldTerminate: true };
|
|
651
|
-
}
|
|
699
|
+
const agentResponses = await import_internal_playwright_test2.test.step(`[Thinking ${thoughtsIndex + 1}]`, async (stepInfo) => {
|
|
700
|
+
const screenshot = await takeStableScreenshot(activePage);
|
|
701
|
+
const response = await fetch(AGENT_ENDPOINT, {
|
|
702
|
+
body: constructAgentPayload({
|
|
703
|
+
activePage,
|
|
704
|
+
additionalContext: newPageOpenedMsg ? { newPageMessage: newPageOpenedMsg } : void 0,
|
|
705
|
+
isError: agentMessage.isError,
|
|
706
|
+
message: agentMessage.message,
|
|
707
|
+
model: options.model,
|
|
708
|
+
screenshot,
|
|
709
|
+
sessionId,
|
|
710
|
+
tabManager
|
|
711
|
+
}),
|
|
712
|
+
headers: {
|
|
713
|
+
...SDK_METADATA_HEADERS,
|
|
714
|
+
Authorization: `Bearer ${apiKey}`
|
|
715
|
+
},
|
|
716
|
+
method: "POST"
|
|
717
|
+
});
|
|
718
|
+
newPageOpenedMsg = void 0;
|
|
719
|
+
const responseJson = await response.json();
|
|
720
|
+
if (!response.ok) {
|
|
721
|
+
throw new Error(
|
|
722
|
+
`Agent call failed: ${JSON.stringify(responseJson)}`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
const reasoningTexts = responseJson.map((r) => r.content).filter((content) => content !== void 0);
|
|
726
|
+
const reasoningText = reasoningTexts.join("\n\n");
|
|
727
|
+
const truncatedReasoningText = truncate(reasoningText, 120);
|
|
728
|
+
await stepInfo.attach(
|
|
729
|
+
`[Thinking ${thoughtsIndex + 1}] ${truncatedReasoningText}`,
|
|
730
|
+
{
|
|
731
|
+
body: reasoningText,
|
|
732
|
+
contentType: "text/plain"
|
|
652
733
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
734
|
+
);
|
|
735
|
+
thoughtsIndex++;
|
|
736
|
+
return responseJson;
|
|
737
|
+
});
|
|
738
|
+
let combinedMessages = [];
|
|
739
|
+
let aggregatedIsError = false;
|
|
740
|
+
let aggregatedShouldTerminate = false;
|
|
741
|
+
for (const agentResponse of agentResponses) {
|
|
742
|
+
const {
|
|
743
|
+
activePage: newActivePage,
|
|
744
|
+
finalSuccess: maybeFinal,
|
|
745
|
+
result
|
|
746
|
+
} = await execResponse({
|
|
747
|
+
activePage,
|
|
748
|
+
agentResponse,
|
|
749
|
+
browserContext,
|
|
750
|
+
tabManager
|
|
751
|
+
});
|
|
752
|
+
activePage = newActivePage;
|
|
753
|
+
combinedMessages.push(result.message);
|
|
754
|
+
finalSuccess = maybeFinal ?? finalSuccess;
|
|
755
|
+
if (result.isError) {
|
|
756
|
+
aggregatedIsError = true;
|
|
757
|
+
}
|
|
758
|
+
if (result.shouldTerminate) {
|
|
759
|
+
aggregatedShouldTerminate = true;
|
|
760
|
+
break;
|
|
656
761
|
}
|
|
657
|
-
}
|
|
762
|
+
}
|
|
763
|
+
agentMessage = {
|
|
764
|
+
isError: aggregatedIsError,
|
|
765
|
+
message: combinedMessages.join("\n"),
|
|
766
|
+
shouldTerminate: aggregatedShouldTerminate
|
|
767
|
+
};
|
|
658
768
|
}
|
|
659
769
|
} finally {
|
|
660
770
|
browserContext.off("page", onNewPage);
|
|
@@ -664,6 +774,106 @@ function createAgentStub() {
|
|
|
664
774
|
};
|
|
665
775
|
}
|
|
666
776
|
|
|
777
|
+
// src/ai/extract.ts
|
|
778
|
+
var EXTRACT_PATH = "internal/v2/extract";
|
|
779
|
+
var STABLY_API_URL2 = process.env.STABLY_API_URL || "https://api.stably.ai";
|
|
780
|
+
var EXTRACT_ENDPOINT = new URL(EXTRACT_PATH, STABLY_API_URL2).toString();
|
|
781
|
+
var zodV4 = (() => {
|
|
782
|
+
try {
|
|
783
|
+
return require("zod/v4/core");
|
|
784
|
+
} catch {
|
|
785
|
+
return void 0;
|
|
786
|
+
}
|
|
787
|
+
})();
|
|
788
|
+
var isExtractionResponse = (value) => {
|
|
789
|
+
if (!isObject(value)) {
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
if (value.success === true) {
|
|
793
|
+
return "value" in value;
|
|
794
|
+
}
|
|
795
|
+
return value.success === false && typeof value.error === "string";
|
|
796
|
+
};
|
|
797
|
+
var isErrorResponse = (value) => {
|
|
798
|
+
return isObject(value) && typeof value.error === "string";
|
|
799
|
+
};
|
|
800
|
+
var ExtractValidationError = class extends Error {
|
|
801
|
+
constructor(message, issues) {
|
|
802
|
+
super(message);
|
|
803
|
+
this.issues = issues;
|
|
804
|
+
this.name = "ExtractValidationError";
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
async function validateWithSchema(schema, value) {
|
|
808
|
+
const result = await schema.safeParseAsync(value);
|
|
809
|
+
if (!result.success) {
|
|
810
|
+
throw new ExtractValidationError("Validation failed", result.error.issues);
|
|
811
|
+
}
|
|
812
|
+
return result.data;
|
|
813
|
+
}
|
|
814
|
+
async function extract({
|
|
815
|
+
pageOrLocator,
|
|
816
|
+
prompt,
|
|
817
|
+
schema
|
|
818
|
+
}) {
|
|
819
|
+
if (schema && !zodV4) {
|
|
820
|
+
throw new Error(
|
|
821
|
+
"Schema support requires installing zod@4. Please add it to enable schemas."
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
const jsonSchema = schema && zodV4 ? zodV4?.toJSONSchema(
|
|
825
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
826
|
+
schema
|
|
827
|
+
) : void 0;
|
|
828
|
+
const apiKey = requireApiKey();
|
|
829
|
+
const form = new FormData();
|
|
830
|
+
form.append("prompt", prompt);
|
|
831
|
+
if (jsonSchema) {
|
|
832
|
+
form.append("jsonSchema", JSON.stringify(jsonSchema));
|
|
833
|
+
}
|
|
834
|
+
const pngBuffer = await pageOrLocator.screenshot({ type: "png" });
|
|
835
|
+
const u8 = Uint8Array.from(pngBuffer);
|
|
836
|
+
const blob = new Blob([u8], { type: "image/png" });
|
|
837
|
+
form.append("image", blob, "screenshot.png");
|
|
838
|
+
const response = await fetch(EXTRACT_ENDPOINT, {
|
|
839
|
+
body: form,
|
|
840
|
+
headers: {
|
|
841
|
+
...SDK_METADATA_HEADERS,
|
|
842
|
+
Authorization: `Bearer ${apiKey}`
|
|
843
|
+
},
|
|
844
|
+
method: "POST"
|
|
845
|
+
});
|
|
846
|
+
const raw = await response.json().catch(() => void 0);
|
|
847
|
+
if (response.ok) {
|
|
848
|
+
if (!isExtractionResponse(raw)) {
|
|
849
|
+
throw new Error("Extract returned unexpected response shape");
|
|
850
|
+
}
|
|
851
|
+
if (!raw.success) {
|
|
852
|
+
throw new Error(`Extract failed: ${raw.error}`);
|
|
853
|
+
}
|
|
854
|
+
const { value } = raw;
|
|
855
|
+
return schema ? await validateWithSchema(schema, value) : typeof value === "string" ? value : JSON.stringify(value);
|
|
856
|
+
}
|
|
857
|
+
throw new Error(isErrorResponse(raw) ? raw.error : "Extract failed");
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// src/playwright-augment/methods/extract.ts
|
|
861
|
+
function createExtract(pageOrLocator) {
|
|
862
|
+
const impl = async (prompt, options) => {
|
|
863
|
+
if (options?.schema) {
|
|
864
|
+
return extract({
|
|
865
|
+
pageOrLocator,
|
|
866
|
+
prompt,
|
|
867
|
+
schema: options.schema
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
return extract({ pageOrLocator, prompt });
|
|
871
|
+
};
|
|
872
|
+
return impl;
|
|
873
|
+
}
|
|
874
|
+
var createLocatorExtract = (locator) => createExtract(locator);
|
|
875
|
+
var createPageExtract = (page) => createExtract(page);
|
|
876
|
+
|
|
667
877
|
// src/playwright-augment/augment.ts
|
|
668
878
|
var LOCATOR_PATCHED = Symbol.for("stably.playwright.locatorPatched");
|
|
669
879
|
var LOCATOR_DESCRIBE_WRAPPED = Symbol.for(
|
|
@@ -675,9 +885,9 @@ var BROWSER_PATCHED = Symbol.for("stably.playwright.browserPatched");
|
|
|
675
885
|
var BROWSER_TYPE_PATCHED = Symbol.for("stably.playwright.browserTypePatched");
|
|
676
886
|
function defineHiddenProperty(target, key, value) {
|
|
677
887
|
Object.defineProperty(target, key, {
|
|
678
|
-
value,
|
|
679
|
-
enumerable: false,
|
|
680
888
|
configurable: true,
|
|
889
|
+
enumerable: false,
|
|
890
|
+
value,
|
|
681
891
|
writable: true
|
|
682
892
|
});
|
|
683
893
|
}
|
|
@@ -724,6 +934,7 @@ function augmentBrowserContext(context) {
|
|
|
724
934
|
if (originalPages) {
|
|
725
935
|
context.pages = () => originalPages().map(
|
|
726
936
|
(page) => augmentPage(page)
|
|
937
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
727
938
|
);
|
|
728
939
|
}
|
|
729
940
|
if (!context.agent) {
|
|
@@ -749,6 +960,7 @@ function augmentBrowser(browser) {
|
|
|
749
960
|
const originalContexts = browser.contexts.bind(browser);
|
|
750
961
|
browser.contexts = () => originalContexts().map(
|
|
751
962
|
(context) => augmentBrowserContext(context)
|
|
963
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
752
964
|
);
|
|
753
965
|
if (!browser.agent) {
|
|
754
966
|
defineHiddenProperty(browser, "agent", createAgentStub());
|
|
@@ -772,14 +984,19 @@ function augmentBrowserType(browserType) {
|
|
|
772
984
|
return augmentBrowser(browser);
|
|
773
985
|
};
|
|
774
986
|
}
|
|
775
|
-
const originalConnectOverCDP = browserType.connectOverCDP?.bind(
|
|
987
|
+
const originalConnectOverCDP = browserType.connectOverCDP?.bind(
|
|
988
|
+
browserType
|
|
989
|
+
);
|
|
776
990
|
if (originalConnectOverCDP) {
|
|
777
991
|
browserType.connectOverCDP = async (...args) => {
|
|
778
992
|
const browser = await originalConnectOverCDP(...args);
|
|
779
993
|
return augmentBrowser(browser);
|
|
780
994
|
};
|
|
781
995
|
}
|
|
782
|
-
const originalLaunchPersistentContext =
|
|
996
|
+
const originalLaunchPersistentContext = (
|
|
997
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
998
|
+
browserType.launchPersistentContext?.bind(browserType)
|
|
999
|
+
);
|
|
783
1000
|
if (originalLaunchPersistentContext) {
|
|
784
1001
|
browserType.launchPersistentContext = async (...args) => {
|
|
785
1002
|
const context = await originalLaunchPersistentContext(...args);
|
|
@@ -789,105 +1006,6 @@ function augmentBrowserType(browserType) {
|
|
|
789
1006
|
defineHiddenProperty(browserType, BROWSER_TYPE_PATCHED, true);
|
|
790
1007
|
return browserType;
|
|
791
1008
|
}
|
|
792
|
-
|
|
793
|
-
// src/ai/verify-prompt.ts
|
|
794
|
-
var PROMPT_ASSERTION_ENDPOINT = "https://api.stably.ai/internal/v1/assert";
|
|
795
|
-
var parseSuccessResponse = (value) => {
|
|
796
|
-
if (!isObject(value)) {
|
|
797
|
-
throw new Error("Verify prompt returned unexpected response shape");
|
|
798
|
-
}
|
|
799
|
-
const { success, reason } = value;
|
|
800
|
-
if (typeof success !== "boolean") {
|
|
801
|
-
throw new Error("Verify prompt returned unexpected response shape");
|
|
802
|
-
}
|
|
803
|
-
if (reason !== void 0 && typeof reason !== "string") {
|
|
804
|
-
throw new Error("Verify prompt returned unexpected response shape");
|
|
805
|
-
}
|
|
806
|
-
return {
|
|
807
|
-
success,
|
|
808
|
-
reason
|
|
809
|
-
};
|
|
810
|
-
};
|
|
811
|
-
var parseErrorResponse = (value) => {
|
|
812
|
-
if (!isObject(value)) {
|
|
813
|
-
return void 0;
|
|
814
|
-
}
|
|
815
|
-
const { error } = value;
|
|
816
|
-
return typeof error !== "string" ? void 0 : { error };
|
|
817
|
-
};
|
|
818
|
-
async function verifyPrompt({
|
|
819
|
-
prompt,
|
|
820
|
-
screenshot
|
|
821
|
-
}) {
|
|
822
|
-
const apiKey = requireApiKey();
|
|
823
|
-
const form = new FormData();
|
|
824
|
-
form.append("prompt", prompt);
|
|
825
|
-
const u8 = Uint8Array.from(screenshot);
|
|
826
|
-
const blob = new Blob([u8], { type: "image/png" });
|
|
827
|
-
form.append("image", blob, "screenshot.png");
|
|
828
|
-
const response = await fetch(PROMPT_ASSERTION_ENDPOINT, {
|
|
829
|
-
method: "POST",
|
|
830
|
-
headers: {
|
|
831
|
-
...SDK_METADATA_HEADERS,
|
|
832
|
-
Authorization: `Bearer ${apiKey}`
|
|
833
|
-
},
|
|
834
|
-
body: form
|
|
835
|
-
});
|
|
836
|
-
const parsed = await response.json().catch(() => void 0);
|
|
837
|
-
if (response.ok) {
|
|
838
|
-
const { success, reason } = parseSuccessResponse(parsed);
|
|
839
|
-
return {
|
|
840
|
-
pass: success,
|
|
841
|
-
reason
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
const err = parseErrorResponse(parsed);
|
|
845
|
-
throw new Error(
|
|
846
|
-
`Verify prompt failed (${response.status})${err ? `: ${err.error}` : ""}`
|
|
847
|
-
);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
// src/expect.ts
|
|
851
|
-
function createFailureMessage({
|
|
852
|
-
targetType,
|
|
853
|
-
condition,
|
|
854
|
-
didPass,
|
|
855
|
-
isNot,
|
|
856
|
-
reason
|
|
857
|
-
}) {
|
|
858
|
-
const expectation = isNot ? "not to satisfy" : "to satisfy";
|
|
859
|
-
const result = didPass ? "it did" : "it did not";
|
|
860
|
-
let message = `Expected ${targetType} ${expectation} ${JSON.stringify(condition)}, but ${result}.`;
|
|
861
|
-
if (reason) {
|
|
862
|
-
message += `
|
|
863
|
-
|
|
864
|
-
Reason: ${reason}`;
|
|
865
|
-
}
|
|
866
|
-
return message;
|
|
867
|
-
}
|
|
868
|
-
var stablyPlaywrightMatchers = {
|
|
869
|
-
async toMatchScreenshotPrompt(received, condition, options) {
|
|
870
|
-
const target = isPage(received) ? received : isLocator(received) ? received : void 0;
|
|
871
|
-
if (!target) {
|
|
872
|
-
throw new Error(
|
|
873
|
-
"toMatchScreenshotPrompt only supports Playwright Page and Locator instances."
|
|
874
|
-
);
|
|
875
|
-
}
|
|
876
|
-
const targetType = isPage(target) ? "page" : "locator";
|
|
877
|
-
const screenshot = await takeStableScreenshot(target, options);
|
|
878
|
-
const verifyResult = await verifyPrompt({ prompt: condition, screenshot });
|
|
879
|
-
return {
|
|
880
|
-
pass: verifyResult.pass,
|
|
881
|
-
message: () => createFailureMessage({
|
|
882
|
-
targetType,
|
|
883
|
-
condition,
|
|
884
|
-
didPass: verifyResult.pass,
|
|
885
|
-
reason: verifyResult.reason,
|
|
886
|
-
isNot: this.isNot
|
|
887
|
-
})
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
};
|
|
891
1009
|
// Annotate the CommonJS export names for ESM import in node:
|
|
892
1010
|
0 && (module.exports = {
|
|
893
1011
|
augmentBrowser,
|