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