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