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.
Files changed (64) hide show
  1. package/README.md +208 -54
  2. package/dist/http.d.ts +1 -1
  3. package/dist/http.js +1 -1
  4. package/dist/prompts.d.ts +6 -0
  5. package/dist/prompts.js +39 -0
  6. package/dist/prompts.js.map +1 -0
  7. package/dist/server.d.ts +4 -3
  8. package/dist/server.js +63 -5
  9. package/dist/server.js.map +1 -1
  10. package/dist/tools/analyze-history.d.ts +48 -0
  11. package/dist/tools/analyze-history.js +132 -0
  12. package/dist/tools/analyze-history.js.map +1 -0
  13. package/dist/tools/compare-visual-state.d.ts +89 -17
  14. package/dist/tools/compare-visual-state.js +10 -3
  15. package/dist/tools/compare-visual-state.js.map +1 -1
  16. package/dist/tools/diagnose-flaky.js +5 -2
  17. package/dist/tools/diagnose-flaky.js.map +1 -1
  18. package/dist/tools/generate-e2e-test.d.ts +11 -0
  19. package/dist/tools/generate-e2e-test.js +50 -3
  20. package/dist/tools/generate-e2e-test.js.map +1 -1
  21. package/dist/tools/get-failure.d.ts +6 -1
  22. package/dist/tools/get-failure.js +17 -11
  23. package/dist/tools/get-failure.js.map +1 -1
  24. package/dist/tools/get-run-status.d.ts +28 -0
  25. package/dist/tools/get-run-status.js +75 -0
  26. package/dist/tools/get-run-status.js.map +1 -0
  27. package/dist/tools/inspect-page.d.ts +102 -14
  28. package/dist/tools/inspect-page.js +50 -10
  29. package/dist/tools/inspect-page.js.map +1 -1
  30. package/dist/tools/probe-script.d.ts +20 -0
  31. package/dist/tools/probe-script.js +641 -0
  32. package/dist/tools/probe-script.js.map +1 -0
  33. package/dist/tools/run-test.d.ts +5 -1
  34. package/dist/tools/run-test.js +30 -3
  35. package/dist/tools/run-test.js.map +1 -1
  36. package/dist/tools/shared.d.ts +117 -3
  37. package/dist/tools/shared.js +154 -285
  38. package/dist/tools/shared.js.map +1 -1
  39. package/dist/tools/suggest-fix.d.ts +89 -0
  40. package/dist/tools/suggest-fix.js +437 -0
  41. package/dist/tools/suggest-fix.js.map +1 -0
  42. package/dist/tools/validate-selector.d.ts +83 -6
  43. package/dist/tools/validate-selector.js +71 -32
  44. package/dist/tools/validate-selector.js.map +1 -1
  45. package/dist/types/index.d.ts +40 -0
  46. package/dist/utils/locator-expr.d.ts +38 -0
  47. package/dist/utils/locator-expr.js +280 -0
  48. package/dist/utils/locator-expr.js.map +1 -0
  49. package/dist/utils/playwright-runner.d.ts +9 -1
  50. package/dist/utils/playwright-runner.js +60 -4
  51. package/dist/utils/playwright-runner.js.map +1 -1
  52. package/dist/utils/report-parser.d.ts +3 -1
  53. package/dist/utils/report-parser.js +22 -0
  54. package/dist/utils/report-parser.js.map +1 -1
  55. package/dist/utils/run-history.d.ts +103 -0
  56. package/dist/utils/run-history.js +219 -0
  57. package/dist/utils/run-history.js.map +1 -0
  58. package/dist/utils/run-registry.d.ts +34 -0
  59. package/dist/utils/run-registry.js +86 -0
  60. package/dist/utils/run-registry.js.map +1 -0
  61. package/dist/utils/trace-reader.d.ts +23 -2
  62. package/dist/utils/trace-reader.js +86 -15
  63. package/dist/utils/trace-reader.js.map +1 -1
  64. package/package.json +2 -2
@@ -1,31 +1,108 @@
1
1
  /**
2
- * Tool 5 — validate-selector: verify a CSS selector against a live page:
3
- * is it syntactically valid, how many elements match, and what do the
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 CSS selector against a live page:
3
- * is it syntactically valid, how many elements match, and what do the
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 { assertHttpUrl, clipLines, guard, resolveProjectRoot, runBrowserScript, toolError, toolText, } from './shared.js';
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.string().min(1).describe('CSS selector to validate'),
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("Project whose Playwright install launches the browser; defaults to the server working directory"),
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
- const ENGINE_RE = /^(text|xpath|id|css|pierce|nth|visible|has-text)=|>>|:has-text\(|:text\(/i;
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: 'Check a CSS selector against a live page: syntax validity, match count, and a sample of matched elements (tag, visibility, box, text). Also flags Playwright-only selector engines (text=, xpath=, >>, :has-text()) that are not valid CSS.',
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
- if (ENGINE_RE.test(selector)) {
35
- return toolText([
36
- `## ⚠️ \`${selector}\` is not a CSS selector`,
37
- '',
38
- 'It uses a **Playwright selector engine** (`text=`, `xpath=`, `>>`, `:has-text()` …). Those are valid *inside Playwright tests* but cannot be validated as CSS here, and Playwright CSS syntax is otherwise a subset of what the browser accepts.',
39
- '',
40
- '**How to fix:** rewrite it as plain CSS (e.g. `text=Login` → `[role="button"]:has-text("Login")` is still Playwright-only — prefer `[data-testid="login-button"]`), then re-validate.',
41
- ].join('\n'));
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
- selector,
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 toolText([
59
- `## ❌ INVALID — \`${selector}\``,
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 syntactically valid but matched nothing on ${url}.`, '');
71
- lines.push('**How to fix:** confirm the element exists at this URL (app may render after an interaction). Run **inspect-page** with the same URL to list real selectors, or add `waitFor` there.');
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. If you need exactly one, scope it: \`#id ${selector}\` or use the unique selector shown above.`, '');
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":"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"}
1
+ {"version":3,"file":"validate-selector.js","sourceRoot":"","sources":["../../src/tools/validate-selector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EACL,aAAa,EACb,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,KAAK,EACL,SAAS,EACT,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,SAAS,EACT,QAAQ,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG5F,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wDAAwD,CAAC;IAClF,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,iEAAiE,CAAC;IAC9E,WAAW,EAAE,CAAC;SACX,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,sEAAsE,CAAC;IACnF,GAAG,cAAc;IACjB,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,KAAK,CAAC;SACV,GAAG,CAAC,OAAO,CAAC;SACZ,QAAQ,EAAE;SACV,QAAQ,CAAC,qCAAqC,CAAC;CACnD,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,sBAAsB,GAAG,qBAAqB,CAAC;AAE5D,2FAA2F;AAC3F,SAAS,SAAS,CAAC,QAAgB,EAAE,KAAoB;IACvD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtE,OAAO,sCAAsC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACjG,CAAC;AAED,SAAS,OAAO,CAAC,QAAgB,EAAE,MAAc;IAC/C,OAAO,QAAQ,CACb;QACE,oBAAoB,QAAQ,IAAI;QAChC,EAAE;QACF,sBAAsB,MAAM,IAAI;QAChC,EAAE;QACF,0QAA0Q;QAC1Q,EAAE;QACF,8FAA8F;KAC/F,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,mBAAmB;IACzB,WAAW,EACT,mQAAmQ;IACrQ,WAAW,EAAE,sBAAsB;IACnC,OAAO,EAAE,KAAK,EAAE,IAA2B,EAAE,GAAgB,EAAyB,EAAE,CACtF,KAAK,CAAC,mBAAmB,EAAE,KAAK,IAAI,EAAE;QACpC,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAEtC,IAAI,KAAoB,CAAC;QACzB,IAAI,CAAC;YACH,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,kBAAkB;gBAAE,OAAO,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,yBAAyB,EAAE,EAAE,CAAC,CAAC,CAAC;YACpH,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QAC7D,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAE3D,MAAM,OAAO,GAAG,MAAM,gBAAgB,CACpC;YACE,GAAG,KAAK;YACR,IAAI,EAAE,UAAU;YAChB,WAAW,EAAE,IAAI;YACjB,GAAG;YACH,aAAa,EAAE,KAAK;YACpB,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,CAAC;YAC/C,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,EACD,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAClC,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;YAChB,OAAO,SAAS,CACd,OAAO,CAAC,IAAI,IAAI,SAAS,EACzB,uBAAuB,QAAQ,QAAQ,GAAG,KAAK,OAAO,CAAC,KAAK,IAAI,eAAe,EAAE,EACjF,OAAO,CAAC,IAAI,EACZ,OAAO,CAAC,UAAU,CACnB,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,OAAO,CAAC,IAAK,CAAC;QAC3B,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAE/D,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC;QACpE,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACjD,IAAI,IAAI,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;YAC1B,KAAK,CAAC,IAAI,CAAC,wBAAwB,EAAE,EAAE,EAAE,KAAK,QAAQ,iBAAiB,IAAI,2BAA2B,GAAG,GAAG,EAAE,EAAE,CAAC,CAAC;YAClH,IAAI,SAAS;gBAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;YAC9D,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAC9C,IAAI,KAAK;gBAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YAC3C,KAAK,CAAC,IAAI,CACR,mNAAmN,CACpN,CAAC;YACF,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,gBAAgB,IAAI,CAAC,UAAU,SAAS,IAAI,CAAC,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAC5F,KAAK,CAAC,IAAI,CAAC,YAAY,GAAG,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,yBAAyB,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5G,IAAI,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,SAAS,EAAE,CAAC,CAAC;QAC1D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3C,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,QAAQ,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;YACzH,KAAK,CAAC,IAAI,CACR,GAAG,CAAC,GAAG,CAAC,OAAO,OAAO,CAAC,QAAQ,UAAU,OAAO,CAAC,GAAG,QAAQ,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,MAAM,GAAG,EAAE,CAC9G,CAAC;YACF,IAAI,OAAO,CAAC,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,aAAa,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9E,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;YAClC,IAAI,IAAI,IAAI,IAAI,KAAK,aAAa,CAAC,KAAK,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,wBAAwB,IAAI,IAAI,CAAC,CAAC;QAC1F,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CACR,QAAQ,IAAI,CAAC,UAAU,yMAAyM,EAChO,EAAE,CACH,CAAC;QACJ,CAAC;aAAM,IAAI,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YACrC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBACzC,KAAK,CAAC,IAAI,CAAC,UAAU,QAAQ,uEAAuE,IAAI,qCAAqC,EAAE,EAAE,CAAC,CAAC;YACrJ,CAAC;QACH,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;QACpE,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;YAC5C,KAAK,CAAC,IAAI,CAAC,wIAAwI,EAAE,EAAE,CAAC,CAAC;QAC3J,CAAC;QACD,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC;CACL,CAAC"}
@@ -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;