playwright-e2e-mcp 0.1.4 → 0.2.0
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/README.md +208 -54
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/prompts.d.ts +6 -0
- package/dist/prompts.js +39 -0
- package/dist/prompts.js.map +1 -0
- package/dist/server.d.ts +4 -3
- package/dist/server.js +63 -5
- package/dist/server.js.map +1 -1
- package/dist/tools/analyze-history.d.ts +48 -0
- package/dist/tools/analyze-history.js +132 -0
- package/dist/tools/analyze-history.js.map +1 -0
- package/dist/tools/compare-visual-state.d.ts +89 -17
- package/dist/tools/compare-visual-state.js +10 -3
- package/dist/tools/compare-visual-state.js.map +1 -1
- package/dist/tools/diagnose-flaky.js +5 -2
- package/dist/tools/diagnose-flaky.js.map +1 -1
- package/dist/tools/generate-e2e-test.d.ts +11 -0
- package/dist/tools/generate-e2e-test.js +50 -3
- package/dist/tools/generate-e2e-test.js.map +1 -1
- package/dist/tools/get-failure.d.ts +6 -1
- package/dist/tools/get-failure.js +17 -11
- package/dist/tools/get-failure.js.map +1 -1
- package/dist/tools/get-run-status.d.ts +28 -0
- package/dist/tools/get-run-status.js +75 -0
- package/dist/tools/get-run-status.js.map +1 -0
- package/dist/tools/inspect-page.d.ts +102 -14
- package/dist/tools/inspect-page.js +50 -10
- package/dist/tools/inspect-page.js.map +1 -1
- package/dist/tools/probe-script.d.ts +20 -0
- package/dist/tools/probe-script.js +641 -0
- package/dist/tools/probe-script.js.map +1 -0
- package/dist/tools/run-test.d.ts +5 -1
- package/dist/tools/run-test.js +30 -3
- package/dist/tools/run-test.js.map +1 -1
- package/dist/tools/shared.d.ts +117 -3
- package/dist/tools/shared.js +154 -285
- package/dist/tools/shared.js.map +1 -1
- package/dist/tools/suggest-fix.d.ts +89 -0
- package/dist/tools/suggest-fix.js +437 -0
- package/dist/tools/suggest-fix.js.map +1 -0
- package/dist/tools/validate-selector.d.ts +83 -6
- package/dist/tools/validate-selector.js +71 -32
- package/dist/tools/validate-selector.js.map +1 -1
- package/dist/types/index.d.ts +40 -0
- package/dist/utils/locator-expr.d.ts +38 -0
- package/dist/utils/locator-expr.js +280 -0
- package/dist/utils/locator-expr.js.map +1 -0
- package/dist/utils/playwright-runner.d.ts +9 -1
- package/dist/utils/playwright-runner.js +60 -4
- package/dist/utils/playwright-runner.js.map +1 -1
- package/dist/utils/report-parser.d.ts +3 -1
- package/dist/utils/report-parser.js +22 -0
- package/dist/utils/report-parser.js.map +1 -1
- package/dist/utils/run-history.d.ts +103 -0
- package/dist/utils/run-history.js +219 -0
- package/dist/utils/run-history.js.map +1 -0
- package/dist/utils/run-registry.d.ts +34 -0
- package/dist/utils/run-registry.js +86 -0
- package/dist/utils/run-registry.js.map +1 -0
- package/dist/utils/trace-reader.d.ts +23 -2
- package/dist/utils/trace-reader.js +86 -15
- package/dist/utils/trace-reader.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,31 +1,108 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tool 5 — validate-selector: verify a
|
|
3
|
-
*
|
|
4
|
-
* matches look like
|
|
2
|
+
* Tool 5 — validate-selector: verify a selector or Playwright locator
|
|
3
|
+
* against a live page: does it parse, how many elements match (Playwright
|
|
4
|
+
* actions need exactly one), what do the matches look like, and which
|
|
5
|
+
* sturdier locator addresses the same element?
|
|
6
|
+
*
|
|
7
|
+
* Accepts CSS, every Playwright selector engine (text=, role=, xpath=, >>)
|
|
8
|
+
* and locator expressions such as getByRole('button', { name: 'Save' }).
|
|
9
|
+
* Expressions are parsed into a whitelisted call chain, never evaluated.
|
|
5
10
|
*/
|
|
6
11
|
import { z } from 'zod';
|
|
7
12
|
import type { ToolContext, ToolResponse } from '../types/index.js';
|
|
8
13
|
declare const validateSelectorInput: z.ZodObject<{
|
|
14
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
16
|
+
type: z.ZodEnum<{
|
|
17
|
+
fill: "fill";
|
|
18
|
+
goto: "goto";
|
|
19
|
+
click: "click";
|
|
20
|
+
dblclick: "dblclick";
|
|
21
|
+
hover: "hover";
|
|
22
|
+
press: "press";
|
|
23
|
+
check: "check";
|
|
24
|
+
uncheck: "uncheck";
|
|
25
|
+
select: "select";
|
|
26
|
+
wait: "wait";
|
|
27
|
+
}>;
|
|
28
|
+
locator: z.ZodOptional<z.ZodString>;
|
|
29
|
+
value: z.ZodOptional<z.ZodString>;
|
|
30
|
+
url: z.ZodOptional<z.ZodString>;
|
|
31
|
+
ms: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
}, z.core.$strip>>>;
|
|
33
|
+
viewport: z.ZodOptional<z.ZodObject<{
|
|
34
|
+
width: z.ZodNumber;
|
|
35
|
+
height: z.ZodNumber;
|
|
36
|
+
}, z.core.$strip>>;
|
|
37
|
+
storageState: z.ZodOptional<z.ZodString>;
|
|
38
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
9
39
|
url: z.ZodString;
|
|
10
40
|
selector: z.ZodString;
|
|
11
41
|
projectRoot: z.ZodOptional<z.ZodString>;
|
|
12
|
-
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
13
42
|
}, z.core.$strip>;
|
|
14
43
|
export type ValidateSelectorInput = z.infer<typeof validateSelectorInput>;
|
|
15
44
|
export declare const validateSelectorSchema: z.ZodObject<{
|
|
45
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
46
|
+
actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
47
|
+
type: z.ZodEnum<{
|
|
48
|
+
fill: "fill";
|
|
49
|
+
goto: "goto";
|
|
50
|
+
click: "click";
|
|
51
|
+
dblclick: "dblclick";
|
|
52
|
+
hover: "hover";
|
|
53
|
+
press: "press";
|
|
54
|
+
check: "check";
|
|
55
|
+
uncheck: "uncheck";
|
|
56
|
+
select: "select";
|
|
57
|
+
wait: "wait";
|
|
58
|
+
}>;
|
|
59
|
+
locator: z.ZodOptional<z.ZodString>;
|
|
60
|
+
value: z.ZodOptional<z.ZodString>;
|
|
61
|
+
url: z.ZodOptional<z.ZodString>;
|
|
62
|
+
ms: z.ZodOptional<z.ZodNumber>;
|
|
63
|
+
}, z.core.$strip>>>;
|
|
64
|
+
viewport: z.ZodOptional<z.ZodObject<{
|
|
65
|
+
width: z.ZodNumber;
|
|
66
|
+
height: z.ZodNumber;
|
|
67
|
+
}, z.core.$strip>>;
|
|
68
|
+
storageState: z.ZodOptional<z.ZodString>;
|
|
69
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
16
70
|
url: z.ZodString;
|
|
17
71
|
selector: z.ZodString;
|
|
18
72
|
projectRoot: z.ZodOptional<z.ZodString>;
|
|
19
|
-
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
20
73
|
}, z.core.$strip>;
|
|
21
74
|
export declare const validateSelectorTool: {
|
|
22
75
|
name: string;
|
|
23
76
|
description: string;
|
|
24
77
|
inputSchema: z.ZodObject<{
|
|
78
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
79
|
+
actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
80
|
+
type: z.ZodEnum<{
|
|
81
|
+
fill: "fill";
|
|
82
|
+
goto: "goto";
|
|
83
|
+
click: "click";
|
|
84
|
+
dblclick: "dblclick";
|
|
85
|
+
hover: "hover";
|
|
86
|
+
press: "press";
|
|
87
|
+
check: "check";
|
|
88
|
+
uncheck: "uncheck";
|
|
89
|
+
select: "select";
|
|
90
|
+
wait: "wait";
|
|
91
|
+
}>;
|
|
92
|
+
locator: z.ZodOptional<z.ZodString>;
|
|
93
|
+
value: z.ZodOptional<z.ZodString>;
|
|
94
|
+
url: z.ZodOptional<z.ZodString>;
|
|
95
|
+
ms: z.ZodOptional<z.ZodNumber>;
|
|
96
|
+
}, z.core.$strip>>>;
|
|
97
|
+
viewport: z.ZodOptional<z.ZodObject<{
|
|
98
|
+
width: z.ZodNumber;
|
|
99
|
+
height: z.ZodNumber;
|
|
100
|
+
}, z.core.$strip>>;
|
|
101
|
+
storageState: z.ZodOptional<z.ZodString>;
|
|
102
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
25
103
|
url: z.ZodString;
|
|
26
104
|
selector: z.ZodString;
|
|
27
105
|
projectRoot: z.ZodOptional<z.ZodString>;
|
|
28
|
-
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
29
106
|
}, z.core.$strip>;
|
|
30
107
|
handler: (args: ValidateSelectorInput, ctx: ToolContext) => Promise<ToolResponse>;
|
|
31
108
|
};
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tool 5 — validate-selector: verify a
|
|
3
|
-
*
|
|
4
|
-
* matches look like
|
|
2
|
+
* Tool 5 — validate-selector: verify a selector or Playwright locator
|
|
3
|
+
* against a live page: does it parse, how many elements match (Playwright
|
|
4
|
+
* actions need exactly one), what do the matches look like, and which
|
|
5
|
+
* sturdier locator addresses the same element?
|
|
6
|
+
*
|
|
7
|
+
* Accepts CSS, every Playwright selector engine (text=, role=, xpath=, >>)
|
|
8
|
+
* and locator expressions such as getByRole('button', { name: 'Save' }).
|
|
9
|
+
* Expressions are parsed into a whitelisted call chain, never evaluated.
|
|
5
10
|
*/
|
|
6
11
|
import { z } from 'zod';
|
|
7
|
-
import {
|
|
12
|
+
import { PlaywrightMcpError } from '../types/index.js';
|
|
13
|
+
import { assertHttpUrl, bestLocator, clipLines, describePageState, guard, loginHint, pageStateShape, resolvePageState, resolveProjectRoot, runBrowserScript, toolError, toolText, } from './shared.js';
|
|
8
14
|
import { assertUrlAllowed } from '../utils/url-policy.js';
|
|
15
|
+
import { formatLocator, isLocatorExpression, parseLocator } from '../utils/locator-expr.js';
|
|
9
16
|
const validateSelectorInput = z.object({
|
|
10
17
|
url: z.string().describe('Full URL of the live page to test against (http/https)'),
|
|
11
|
-
selector: z
|
|
18
|
+
selector: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1)
|
|
21
|
+
.describe("Selector or locator, e.g. getByRole('button', { name: 'Save' })"),
|
|
12
22
|
projectRoot: z
|
|
13
23
|
.string()
|
|
14
24
|
.optional()
|
|
15
|
-
.describe(
|
|
25
|
+
.describe('Project whose Playwright launches the browser (default: server root)'),
|
|
26
|
+
...pageStateShape,
|
|
16
27
|
timeoutMs: z
|
|
17
28
|
.number()
|
|
18
29
|
.int()
|
|
@@ -22,31 +33,49 @@ const validateSelectorInput = z.object({
|
|
|
22
33
|
.describe('Overall limit in ms (default 45000)'),
|
|
23
34
|
});
|
|
24
35
|
export const validateSelectorSchema = validateSelectorInput;
|
|
25
|
-
|
|
36
|
+
/** Brittle CSS: positional chains and long descendant paths break on any layout change. */
|
|
37
|
+
function isBrittle(selector, chain) {
|
|
38
|
+
if (chain.length !== 1 || chain[0].method !== 'locator')
|
|
39
|
+
return false;
|
|
40
|
+
return /:nth-(of-type|child)\(|(\s>\s.*){3,}/.test(selector) || /^\/\/|^xpath=/.test(selector);
|
|
41
|
+
}
|
|
42
|
+
function invalid(selector, reason) {
|
|
43
|
+
return toolText([
|
|
44
|
+
`## ❌ INVALID — \`${selector}\``,
|
|
45
|
+
'',
|
|
46
|
+
`**Parse error:** \`${reason}\``,
|
|
47
|
+
'',
|
|
48
|
+
"**How to fix:** pass valid CSS (`#save`, `[data-testid=\"save\"]`), a Playwright selector (`text=Save`, `role=button[name=\"Save\"]`) or a locator (`getByRole('button', { name: 'Save' })`). Common mistakes: unescaped `#`/`.` in ids, a stray `>`, unbalanced quotes.",
|
|
49
|
+
'',
|
|
50
|
+
"Use **inspect-page** with `view: 'locators'` to see verified locators for the live elements.",
|
|
51
|
+
].join('\n'));
|
|
52
|
+
}
|
|
26
53
|
export const validateSelectorTool = {
|
|
27
54
|
name: 'validate-selector',
|
|
28
|
-
description:
|
|
55
|
+
description: "Check a CSS selector, Playwright selector or locator (getByRole/getByTestId…, .first(), .filter()) on a live page: validity, match count (actions need exactly 1), sample matches and a sturdier verified locator for each. Accepts storageState/headers/actions.",
|
|
29
56
|
inputSchema: validateSelectorSchema,
|
|
30
57
|
handler: async (args, ctx) => guard('validate-selector', async () => {
|
|
31
58
|
const url = assertHttpUrl(args.url);
|
|
32
59
|
await assertUrlAllowed(url);
|
|
33
60
|
const selector = args.selector.trim();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
let chain;
|
|
62
|
+
try {
|
|
63
|
+
chain = parseLocator(selector);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
if (err instanceof PlaywrightMcpError)
|
|
67
|
+
return invalid(selector, err.message.replace(/^Cannot parse locator: /, ''));
|
|
68
|
+
throw err;
|
|
42
69
|
}
|
|
43
70
|
const timeoutMs = args.timeoutMs ?? 45_000;
|
|
44
71
|
const root = await resolveProjectRoot(args.projectRoot, ctx);
|
|
72
|
+
const state = await resolvePageState(args, root, url, ctx);
|
|
45
73
|
const outcome = await runBrowserScript({
|
|
74
|
+
...state,
|
|
46
75
|
mode: 'validate',
|
|
47
76
|
projectRoot: root,
|
|
48
77
|
url,
|
|
49
|
-
|
|
78
|
+
selectorChain: chain,
|
|
50
79
|
gotoTimeout: Math.max(5_000, timeoutMs - 5_000),
|
|
51
80
|
startedAt: Date.now(),
|
|
52
81
|
}, { timeoutMs, signal: ctx.signal });
|
|
@@ -54,25 +83,26 @@ export const validateSelectorTool = {
|
|
|
54
83
|
return toolError(outcome.kind ?? 'UNKNOWN', `Could not validate "${selector}" on ${url}: ${outcome.error ?? 'unknown error'}`, outcome.hint, outcome.stderrTail);
|
|
55
84
|
}
|
|
56
85
|
const data = outcome.data;
|
|
57
|
-
if (data.parseError)
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
'',
|
|
61
|
-
`**Parse error:** \`${data.parseError}\``,
|
|
62
|
-
'',
|
|
63
|
-
'**How to fix:** only CSS selectors are supported (`document.querySelectorAll` semantics). Common mistakes: unescaped `#`/`.` in ids/classes, stray `>`, or a Playwright engine prefix like `text=`.',
|
|
64
|
-
'',
|
|
65
|
-
'Use **inspect-page** to see the actual selectors of the live elements.',
|
|
66
|
-
].join('\n'));
|
|
67
|
-
}
|
|
86
|
+
if (data.parseError)
|
|
87
|
+
return invalid(selector, data.parseError);
|
|
88
|
+
const kind = isLocatorExpression(selector) ? 'locator' : 'selector';
|
|
68
89
|
const lines = [];
|
|
90
|
+
const stateLine = describePageState(state, root);
|
|
69
91
|
if (data.matchCount === 0) {
|
|
70
|
-
lines.push(`## ✅ VALID — 0 matches`, '', `\`${selector}\` is
|
|
71
|
-
|
|
92
|
+
lines.push(`## ✅ VALID — 0 matches`, '', `\`${selector}\` is a valid ${kind} but matched nothing on ${url}.`, '');
|
|
93
|
+
if (stateLine)
|
|
94
|
+
lines.push(`**Page state:** ${stateLine}`, '');
|
|
95
|
+
const login = loginHint(data.finalUrl, state);
|
|
96
|
+
if (login)
|
|
97
|
+
lines.push(`> 🔒 ${login}`, '');
|
|
98
|
+
lines.push("**How to fix:** confirm the element exists in this state (it may appear only after an interaction: pass `actions`). Run **inspect-page** with `view: 'locators'` on the same URL to list real, verified locators.");
|
|
72
99
|
return toolText(lines.join('\n'));
|
|
73
100
|
}
|
|
74
101
|
lines.push(`## ✅ VALID — ${data.matchCount} match${data.matchCount === 1 ? '' : 'es'}`, '');
|
|
75
|
-
lines.push(`**URL:** ${url}
|
|
102
|
+
lines.push(`**URL:** ${url}${kind === 'locator' ? ` | **Parsed as:** \`${formatLocator(chain)}\`` : ''}`);
|
|
103
|
+
if (stateLine)
|
|
104
|
+
lines.push(`**Page state:** ${stateLine}`);
|
|
105
|
+
lines.push('');
|
|
76
106
|
const matches = data.elements;
|
|
77
107
|
for (let i = 0; i < matches.length; i += 1) {
|
|
78
108
|
const element = matches[i];
|
|
@@ -80,10 +110,19 @@ export const validateSelectorTool = {
|
|
|
80
110
|
lines.push(`${i + 1}. \`${element.selector}\` — **${element.tag}** · ${element.visible ? 'visible' : 'hidden'} · ${box}`);
|
|
81
111
|
if (element.text)
|
|
82
112
|
lines.push(` text: "${clipLines(element.text, 1, 140)}"`);
|
|
113
|
+
const best = bestLocator(element);
|
|
114
|
+
if (best && best !== formatLocator(chain))
|
|
115
|
+
lines.push(` unique locator: \`${best}\``);
|
|
83
116
|
}
|
|
84
117
|
lines.push('');
|
|
85
118
|
if (data.matchCount > 1) {
|
|
86
|
-
lines.push(`> ${data.matchCount} elements match
|
|
119
|
+
lines.push(`> ⚠️ ${data.matchCount} elements match, so a Playwright action on it throws a strict mode violation. Use the unique locator of the element you mean (above), or add \`.first()\` / \`.filter({ hasText: '…' })\` deliberately.`, '');
|
|
120
|
+
}
|
|
121
|
+
else if (isBrittle(selector, chain)) {
|
|
122
|
+
const best = bestLocator(matches[0]);
|
|
123
|
+
if (best && !best.startsWith('locator(')) {
|
|
124
|
+
lines.push(`> 💡 \`${selector}\` depends on page structure and breaks on layout changes. Prefer \`${best}\`, which targets the same element.`, '');
|
|
125
|
+
}
|
|
87
126
|
}
|
|
88
127
|
const hidden = matches.filter((element) => !element.visible).length;
|
|
89
128
|
if (hidden > 0 && hidden === matches.length) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-selector.js","sourceRoot":"","sources":["../../src/tools/validate-selector.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"validate-selector.js","sourceRoot":"","sources":["../../src/tools/validate-selector.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -64,6 +64,11 @@ export interface ToolContext {
|
|
|
64
64
|
* or run project code, and callers may not pick another project root.
|
|
65
65
|
*/
|
|
66
66
|
restricted?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Report progress to the client (MCP notifications/progress), when it
|
|
69
|
+
* asked for it with a progress token.
|
|
70
|
+
*/
|
|
71
|
+
progress?: (progress: number, total?: number, message?: string) => void;
|
|
67
72
|
}
|
|
68
73
|
/**
|
|
69
74
|
* MCP tool return value (content blocks + optional structured payload).
|
|
@@ -180,6 +185,8 @@ export interface RunTestOptions {
|
|
|
180
185
|
/** Playwright --grep expression. */
|
|
181
186
|
grep?: string;
|
|
182
187
|
browser?: BrowserName;
|
|
188
|
+
/** Exact Playwright project name (e.g. the project a failure ran in); wins over `browser`. */
|
|
189
|
+
project?: string;
|
|
183
190
|
headed?: boolean;
|
|
184
191
|
/** Hard wall-clock limit for the whole run; process group is killed. */
|
|
185
192
|
timeoutMs?: number;
|
|
@@ -202,6 +209,16 @@ export interface RunTestOptions {
|
|
|
202
209
|
lastFailed?: boolean;
|
|
203
210
|
/** Abort when the MCP client disconnects. */
|
|
204
211
|
signal?: AbortSignal;
|
|
212
|
+
/** Called with Playwright's line-reporter progress while the run is going. */
|
|
213
|
+
onProgress?: (progress: RunProgress) => void;
|
|
214
|
+
}
|
|
215
|
+
/** Live progress of a run, parsed from the line reporter. */
|
|
216
|
+
export interface RunProgress {
|
|
217
|
+
done: number;
|
|
218
|
+
total?: number;
|
|
219
|
+
failed: number;
|
|
220
|
+
/** Title of the most recently finished test. */
|
|
221
|
+
last?: string;
|
|
205
222
|
}
|
|
206
223
|
export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'flaky' | 'unknown';
|
|
207
224
|
export type FailureKind = 'assertion' | 'timeout' | 'browser-crash' | 'syntax' | 'config' | 'server-unreachable' | 'unknown';
|
|
@@ -226,8 +243,22 @@ export interface TestFailure {
|
|
|
226
243
|
/** Test console output (stdout) captured for this attempt. */
|
|
227
244
|
stdout?: string;
|
|
228
245
|
}
|
|
246
|
+
/** One test's final outcome in a run (every test, not only failures). */
|
|
247
|
+
export interface TestOutcome {
|
|
248
|
+
file: string;
|
|
249
|
+
line?: number;
|
|
250
|
+
title: string;
|
|
251
|
+
project?: string;
|
|
252
|
+
status: TestStatus;
|
|
253
|
+
durationMs?: number;
|
|
254
|
+
failureKind?: FailureKind;
|
|
255
|
+
/** First line of the error, for failed/flaky tests. */
|
|
256
|
+
message?: string;
|
|
257
|
+
}
|
|
229
258
|
export interface RunTestResult {
|
|
230
259
|
ok: boolean;
|
|
260
|
+
/** Final outcome of every test in the run (feeds run history). */
|
|
261
|
+
outcomes?: TestOutcome[];
|
|
231
262
|
exitCode: number | null;
|
|
232
263
|
signalName: string | null;
|
|
233
264
|
durationMs: number;
|
|
@@ -300,6 +331,15 @@ export interface ElementInfo {
|
|
|
300
331
|
width: number;
|
|
301
332
|
height: number;
|
|
302
333
|
};
|
|
334
|
+
/** Accessible name, when the element has a role. */
|
|
335
|
+
name?: string;
|
|
336
|
+
/**
|
|
337
|
+
* Playwright locators proven (in the same page) to resolve to exactly this
|
|
338
|
+
* element, best first: role + name, test id, label, placeholder, text, CSS.
|
|
339
|
+
*/
|
|
340
|
+
locators?: import('../utils/locator-expr.js').LocatorCall[][];
|
|
341
|
+
/** heal mode: how closely the element matches the broken locator. */
|
|
342
|
+
score?: number;
|
|
303
343
|
}
|
|
304
344
|
export interface ConsoleMessageInfo {
|
|
305
345
|
type: string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe parsing of Playwright locator expressions.
|
|
3
|
+
*
|
|
4
|
+
* Agents and tests speak in locators — `getByRole('button', { name: 'Save' })`,
|
|
5
|
+
* `getByTestId('cart')`, `locator('#cta').first()` — not just CSS. This module
|
|
6
|
+
* turns such an expression into a plain JSON chain of whitelisted method calls
|
|
7
|
+
* that the browser child script replays against a real page. Nothing is ever
|
|
8
|
+
* evaluated as code: the parser accepts only string, number, boolean, regex
|
|
9
|
+
* and flat object literals, and only the methods listed in LOCATOR_METHODS.
|
|
10
|
+
*
|
|
11
|
+
* A bare string that is not a call chain (`#cta`, `text=Buy`, `role=button`)
|
|
12
|
+
* becomes `locator(<string>)`, so every Playwright selector engine works too.
|
|
13
|
+
*/
|
|
14
|
+
/** A JSON-safe regex literal (RegExp does not survive JSON.stringify). */
|
|
15
|
+
export interface RegexValue {
|
|
16
|
+
$regex: string;
|
|
17
|
+
flags: string;
|
|
18
|
+
}
|
|
19
|
+
export type LocatorArg = string | number | boolean | RegexValue | {
|
|
20
|
+
[key: string]: LocatorArg;
|
|
21
|
+
};
|
|
22
|
+
export interface LocatorCall {
|
|
23
|
+
method: LocatorMethod;
|
|
24
|
+
args: LocatorArg[];
|
|
25
|
+
}
|
|
26
|
+
export declare const LOCATOR_METHODS: readonly ["getByRole", "getByText", "getByLabel", "getByPlaceholder", "getByAltText", "getByTitle", "getByTestId", "locator", "first", "last", "nth", "filter"];
|
|
27
|
+
export type LocatorMethod = (typeof LOCATOR_METHODS)[number];
|
|
28
|
+
/** True when the input looks like a call chain rather than a selector string. */
|
|
29
|
+
export declare function isLocatorExpression(input: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Parse a locator expression or selector into a whitelisted call chain.
|
|
32
|
+
* Throws INVALID_PATH with a usable hint on anything else.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseLocator(input: string): LocatorCall[];
|
|
35
|
+
/** Single-quoted JS string literal. */
|
|
36
|
+
export declare function quote(text: string): string;
|
|
37
|
+
/** Render a chain back to source, e.g. `getByRole('button', { name: 'Save' })`. */
|
|
38
|
+
export declare function formatLocator(calls: LocatorCall[]): string;
|