playwright-e2e-mcp 0.6.1 → 0.7.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 +40 -8
- package/dist/cli/ci.js +3 -0
- package/dist/cli/ci.js.map +1 -1
- package/dist/cli/guard.d.ts +11 -0
- package/dist/cli/guard.js +80 -0
- package/dist/cli/guard.js.map +1 -0
- package/dist/cli/upload.d.ts +14 -0
- package/dist/cli/upload.js +81 -0
- package/dist/cli/upload.js.map +1 -0
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +44 -6
- package/dist/server.js.map +1 -1
- package/dist/tools/analyze-history.d.ts +15 -0
- package/dist/tools/analyze-history.js +44 -9
- package/dist/tools/analyze-history.js.map +1 -1
- package/dist/tools/check-test-change.d.ts +44 -0
- package/dist/tools/check-test-change.js +132 -0
- package/dist/tools/check-test-change.js.map +1 -0
- package/dist/tools/get-run-status.js +8 -2
- package/dist/tools/get-run-status.js.map +1 -1
- package/dist/tools/probe-script.js +93 -1
- package/dist/tools/probe-script.js.map +1 -1
- package/dist/tools/quarantine.d.ts +61 -0
- package/dist/tools/quarantine.js +133 -0
- package/dist/tools/quarantine.js.map +1 -0
- package/dist/tools/run-test.d.ts +10 -1
- package/dist/tools/run-test.js +36 -5
- package/dist/tools/run-test.js.map +1 -1
- package/dist/tools/save-login.d.ts +142 -0
- package/dist/tools/save-login.js +264 -0
- package/dist/tools/save-login.js.map +1 -0
- package/dist/tools/shared.d.ts +24 -1
- package/dist/tools/shared.js +1 -1
- package/dist/tools/shared.js.map +1 -1
- package/dist/tools/structured.d.ts +8 -1
- package/dist/tools/structured.js +8 -1
- package/dist/tools/structured.js.map +1 -1
- package/dist/utils/pro-client.d.ts +29 -0
- package/dist/utils/pro-client.js +114 -0
- package/dist/utils/pro-client.js.map +1 -0
- package/dist/utils/quarantine.d.ts +64 -0
- package/dist/utils/quarantine.js +106 -0
- package/dist/utils/quarantine.js.map +1 -0
- package/dist/utils/test-change-guard.d.ts +69 -0
- package/dist/utils/test-change-guard.js +391 -0
- package/dist/utils/test-change-guard.js.map +1 -0
- package/dist/utils/totp.d.ts +26 -0
- package/dist/utils/totp.js +84 -0
- package/dist/utils/totp.js.map +1 -0
- package/package.json +1 -1
- package/skill/playwright-e2e-mcp/SKILL.md +41 -7
- package/skill/playwright-e2e-mcp/references/setup.md +7 -1
- package/skill/playwright-e2e-mcp/references/tools.md +41 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heal guard: compare a test file before and after a fix and catch the
|
|
3
|
+
* changes that make a failing test pass by testing less — an assertion
|
|
4
|
+
* deleted, a test skipped or removed, a matcher swapped for a weaker one,
|
|
5
|
+
* a hard wait, a forced click, an error swallowed, a timeout or retry
|
|
6
|
+
* count blown up.
|
|
7
|
+
*
|
|
8
|
+
* AI healers (Playwright's Healer agent, ours, any agent editing specs)
|
|
9
|
+
* are good at turning red tests green; this is the reviewer that checks
|
|
10
|
+
* they did it honestly. The scan is lexical: strings and comments are
|
|
11
|
+
* masked so their words do not count, then each test(...) call is matched
|
|
12
|
+
* by its describe path and title across the two versions.
|
|
13
|
+
*/
|
|
14
|
+
/** Matchers that check a specific value or state. */
|
|
15
|
+
const STRONG = new Set([
|
|
16
|
+
'toHaveText',
|
|
17
|
+
'toHaveValue',
|
|
18
|
+
'toHaveValues',
|
|
19
|
+
'toHaveURL',
|
|
20
|
+
'toHaveTitle',
|
|
21
|
+
'toEqual',
|
|
22
|
+
'toStrictEqual',
|
|
23
|
+
'toBe',
|
|
24
|
+
'toHaveCount',
|
|
25
|
+
'toHaveAttribute',
|
|
26
|
+
'toHaveClass',
|
|
27
|
+
'toHaveCSS',
|
|
28
|
+
'toHaveId',
|
|
29
|
+
'toHaveJSProperty',
|
|
30
|
+
'toHaveLength',
|
|
31
|
+
'toHaveProperty',
|
|
32
|
+
'toBeVisible',
|
|
33
|
+
'toBeHidden',
|
|
34
|
+
'toBeChecked',
|
|
35
|
+
'toBeEnabled',
|
|
36
|
+
'toBeDisabled',
|
|
37
|
+
'toBeEditable',
|
|
38
|
+
'toBeFocused',
|
|
39
|
+
'toBeEmpty',
|
|
40
|
+
'toMatch',
|
|
41
|
+
'toMatchObject',
|
|
42
|
+
'toMatchAriaSnapshot',
|
|
43
|
+
'toHaveScreenshot',
|
|
44
|
+
'toMatchSnapshot',
|
|
45
|
+
'toHaveAccessibleName',
|
|
46
|
+
'toHaveAccessibleDescription',
|
|
47
|
+
'toHaveRole',
|
|
48
|
+
'toBeInViewport',
|
|
49
|
+
'toContain',
|
|
50
|
+
'toContainEqual',
|
|
51
|
+
'toThrow',
|
|
52
|
+
'toBeOK',
|
|
53
|
+
]);
|
|
54
|
+
/** Matchers that pass for almost any value or state. */
|
|
55
|
+
const WEAK = new Set([
|
|
56
|
+
'toBeTruthy',
|
|
57
|
+
'toBeFalsy',
|
|
58
|
+
'toBeDefined',
|
|
59
|
+
'toBeUndefined',
|
|
60
|
+
'toBeNull',
|
|
61
|
+
'toBeAttached',
|
|
62
|
+
'toContainText',
|
|
63
|
+
'toBeGreaterThan',
|
|
64
|
+
'toBeGreaterThanOrEqual',
|
|
65
|
+
'toBeLessThan',
|
|
66
|
+
'toBeLessThanOrEqual',
|
|
67
|
+
'toBeInstanceOf',
|
|
68
|
+
]);
|
|
69
|
+
/**
|
|
70
|
+
* Replace the inside of strings, template literals and comments with
|
|
71
|
+
* spaces (newlines kept), so positions and line numbers stay the same
|
|
72
|
+
* and no word inside them looks like code.
|
|
73
|
+
*/
|
|
74
|
+
export function maskCode(source) {
|
|
75
|
+
const out = source.split('');
|
|
76
|
+
let i = 0;
|
|
77
|
+
const blank = (from, to) => {
|
|
78
|
+
for (let k = from; k < to; k += 1)
|
|
79
|
+
if (out[k] !== '\n')
|
|
80
|
+
out[k] = ' ';
|
|
81
|
+
};
|
|
82
|
+
while (i < source.length) {
|
|
83
|
+
const c = source[i];
|
|
84
|
+
const next = source[i + 1];
|
|
85
|
+
if (c === '/' && next === '/') {
|
|
86
|
+
const end = source.indexOf('\n', i);
|
|
87
|
+
const stop = end === -1 ? source.length : end;
|
|
88
|
+
blank(i, stop);
|
|
89
|
+
i = stop;
|
|
90
|
+
}
|
|
91
|
+
else if (c === '/' && next === '*') {
|
|
92
|
+
const end = source.indexOf('*/', i + 2);
|
|
93
|
+
const stop = end === -1 ? source.length : end + 2;
|
|
94
|
+
blank(i, stop);
|
|
95
|
+
i = stop;
|
|
96
|
+
}
|
|
97
|
+
else if (c === '"' || c === "'" || c === '`') {
|
|
98
|
+
let j = i + 1;
|
|
99
|
+
while (j < source.length && source[j] !== c) {
|
|
100
|
+
if (source[j] === '\\')
|
|
101
|
+
j += 1;
|
|
102
|
+
else if (c !== '`' && source[j] === '\n')
|
|
103
|
+
break;
|
|
104
|
+
j += 1;
|
|
105
|
+
}
|
|
106
|
+
blank(i + 1, Math.min(j, source.length));
|
|
107
|
+
i = j + 1;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
i += 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return out.join('');
|
|
114
|
+
}
|
|
115
|
+
/** Index of the parenthesis closing the one at `open` in masked code, or -1. */
|
|
116
|
+
function closingParen(masked, open) {
|
|
117
|
+
let depth = 0;
|
|
118
|
+
for (let i = open; i < masked.length; i += 1) {
|
|
119
|
+
const c = masked[i];
|
|
120
|
+
if (c === '(' || c === '{' || c === '[')
|
|
121
|
+
depth += 1;
|
|
122
|
+
else if (c === ')' || c === '}' || c === ']') {
|
|
123
|
+
depth -= 1;
|
|
124
|
+
if (depth === 0)
|
|
125
|
+
return c === ')' ? i : -1;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return -1;
|
|
129
|
+
}
|
|
130
|
+
/** The string literal starting at `at` in the original source (after whitespace), or undefined. */
|
|
131
|
+
function stringArgument(source, at) {
|
|
132
|
+
let i = at;
|
|
133
|
+
while (i < source.length && /\s/.test(source[i]))
|
|
134
|
+
i += 1;
|
|
135
|
+
const quote = source[i];
|
|
136
|
+
if (quote !== '"' && quote !== "'" && quote !== '`')
|
|
137
|
+
return undefined;
|
|
138
|
+
let j = i + 1;
|
|
139
|
+
let text = '';
|
|
140
|
+
while (j < source.length && source[j] !== quote) {
|
|
141
|
+
if (source[j] === '\\' && j + 1 < source.length) {
|
|
142
|
+
text += source[j + 1];
|
|
143
|
+
j += 2;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
text += source[j];
|
|
147
|
+
j += 1;
|
|
148
|
+
}
|
|
149
|
+
return text;
|
|
150
|
+
}
|
|
151
|
+
function lineOf(source, index) {
|
|
152
|
+
let line = 1;
|
|
153
|
+
for (let i = 0; i < index && i < source.length; i += 1)
|
|
154
|
+
if (source[i] === '\n')
|
|
155
|
+
line += 1;
|
|
156
|
+
return line;
|
|
157
|
+
}
|
|
158
|
+
function count(text, re) {
|
|
159
|
+
return (text.match(re) ?? []).length;
|
|
160
|
+
}
|
|
161
|
+
function maxNumber(text, re) {
|
|
162
|
+
let max = 0;
|
|
163
|
+
for (const match of text.matchAll(re)) {
|
|
164
|
+
const value = Number(match[match.length - 1].replace(/_/g, ''));
|
|
165
|
+
if (Number.isFinite(value) && value > max)
|
|
166
|
+
max = value;
|
|
167
|
+
}
|
|
168
|
+
return max;
|
|
169
|
+
}
|
|
170
|
+
/** Find every test(...) and describe(...) with a string title, and measure each test. */
|
|
171
|
+
export function shapeOf(source) {
|
|
172
|
+
const masked = maskCode(source);
|
|
173
|
+
const describes = [];
|
|
174
|
+
const tests = [];
|
|
175
|
+
const re = /\b(test|it)((?:\.describe)?)(?:\.(only|skip|fixme|fail|slow|serial|parallel))?\s*\(/g;
|
|
176
|
+
const describeRe = /\b(?:test\.)?describe(?:\.(only|skip|fixme|serial|parallel))?\s*\(/g;
|
|
177
|
+
for (const match of masked.matchAll(describeRe)) {
|
|
178
|
+
const open = match.index + match[0].length - 1;
|
|
179
|
+
const title = stringArgument(source, open + 1);
|
|
180
|
+
const end = closingParen(masked, open);
|
|
181
|
+
if (title !== undefined && end > 0)
|
|
182
|
+
describes.push({ title, start: match.index, end, modifier: match[1] });
|
|
183
|
+
}
|
|
184
|
+
for (const match of masked.matchAll(re)) {
|
|
185
|
+
if (match[2])
|
|
186
|
+
continue; // test.describe(...) is handled above
|
|
187
|
+
const before = masked.slice(Math.max(0, match.index - 1), match.index);
|
|
188
|
+
if (before === '.')
|
|
189
|
+
continue; // something.test(...)
|
|
190
|
+
const open = match.index + match[0].length - 1;
|
|
191
|
+
const title = stringArgument(source, open + 1);
|
|
192
|
+
if (title === undefined)
|
|
193
|
+
continue; // test.skip(condition) inside a body
|
|
194
|
+
const end = closingParen(masked, open);
|
|
195
|
+
if (end < 0)
|
|
196
|
+
continue;
|
|
197
|
+
tests.push({ title, start: match.index, end, modifier: match[3] });
|
|
198
|
+
}
|
|
199
|
+
const shapes = new Map();
|
|
200
|
+
for (const block of tests) {
|
|
201
|
+
const path = describes
|
|
202
|
+
.filter((d) => d.start < block.start && d.end > block.end)
|
|
203
|
+
.sort((a, b) => a.start - b.start)
|
|
204
|
+
.map((d) => d.title);
|
|
205
|
+
let key = [...path, block.title].join(' › ');
|
|
206
|
+
while (shapes.has(key))
|
|
207
|
+
key += ' (again)';
|
|
208
|
+
const body = masked.slice(block.start, block.end + 1);
|
|
209
|
+
const matchers = [...body.matchAll(/\.(to[A-Z]\w*)\s*\(/g)].map((m) => m[1]);
|
|
210
|
+
shapes.set(key, {
|
|
211
|
+
key,
|
|
212
|
+
title: block.title,
|
|
213
|
+
line: lineOf(source, block.start),
|
|
214
|
+
modifier: block.modifier,
|
|
215
|
+
assertions: count(body, /\bexpect(?:\.soft|\.poll)?\s*\(/g),
|
|
216
|
+
soft: count(body, /\bexpect\.soft\s*\(/g),
|
|
217
|
+
conditionalSkips: count(body, /\btest\.(?:skip|fixme)\s*\(/g),
|
|
218
|
+
hardWaits: count(body, /\.waitForTimeout\s*\(/g),
|
|
219
|
+
forced: count(body, /\bforce\s*:\s*true\b/g),
|
|
220
|
+
swallowed: count(body, /\.catch\s*\(\s*(?:\(\s*\w*\s*\)|\w+)\s*=>\s*(?:\{\s*\}|undefined|null|void 0|false|true)\s*\)/g),
|
|
221
|
+
tries: count(body, /\btry\s*\{/g),
|
|
222
|
+
maxTimeout: maxNumber(body, /(?:\btimeout\s*:\s*|\bsetTimeout\s*\(\s*)([\d_]+)/g),
|
|
223
|
+
strong: matchers.filter((m) => STRONG.has(m)).length,
|
|
224
|
+
weak: matchers.filter((m) => WEAK.has(m)).length,
|
|
225
|
+
body: body.replace(/\s+/g, ' '),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
tests: shapes,
|
|
230
|
+
only: tests.filter((t) => t.modifier === 'only').length + describes.filter((d) => d.modifier === 'only').length,
|
|
231
|
+
describeSkips: describes.filter((d) => d.modifier === 'skip' || d.modifier === 'fixme').length,
|
|
232
|
+
maxRetries: maxNumber(masked, /\bretries\s*:\s*([\d_]+)/g),
|
|
233
|
+
maxDiffAllowance: Math.max(maxNumber(masked, /\bmaxDiffPixels\s*:\s*([\d_]+)/g), maxNumber(masked, /\bmaxDiffPixelRatio\s*:\s*(0?\.\d+|\d+)/g) * 1_000_000, maxNumber(masked, /\bthreshold\s*:\s*(0?\.\d+|\d+)/g) * 1_000_000),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/** Compare two versions of one file. */
|
|
237
|
+
export function guardFileChange(file, beforeSource, afterSource) {
|
|
238
|
+
const before = shapeOf(beforeSource);
|
|
239
|
+
const findings = [];
|
|
240
|
+
if (afterSource === undefined) {
|
|
241
|
+
if (before.tests.size > 0) {
|
|
242
|
+
findings.push({
|
|
243
|
+
severity: 'block',
|
|
244
|
+
rule: 'file-deleted',
|
|
245
|
+
message: `The file was deleted with its ${before.tests.size} test${before.tests.size === 1 ? '' : 's'}.`,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return { file, findings, testsBefore: before.tests.size, testsAfter: 0 };
|
|
249
|
+
}
|
|
250
|
+
const after = shapeOf(afterSource);
|
|
251
|
+
const removed = [...before.tests.values()].filter((t) => !after.tests.has(t.key));
|
|
252
|
+
const added = [...after.tests.values()].filter((t) => !before.tests.has(t.key));
|
|
253
|
+
const renamed = new Map();
|
|
254
|
+
for (const old of removed) {
|
|
255
|
+
const twin = added.find((t) => ![...renamed.values()].includes(t) && t.body.replace(/^\S+\s*\(\s*/, '') === old.body.replace(/^\S+\s*\(\s*/, ''));
|
|
256
|
+
const similar = twin ??
|
|
257
|
+
added.find((t) => ![...renamed.values()].includes(t) &&
|
|
258
|
+
t.assertions >= old.assertions &&
|
|
259
|
+
t.strong >= old.strong &&
|
|
260
|
+
Math.abs(t.body.length - old.body.length) < Math.max(80, old.body.length * 0.2));
|
|
261
|
+
if (similar && removed.length === added.length)
|
|
262
|
+
renamed.set(old.key, similar);
|
|
263
|
+
}
|
|
264
|
+
for (const old of removed) {
|
|
265
|
+
const now = renamed.get(old.key);
|
|
266
|
+
if (now) {
|
|
267
|
+
compareTest(old, now, findings);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
findings.push({ severity: 'block', rule: 'test-removed', test: old.key, message: `Test removed (it had ${old.assertions} assertion${old.assertions === 1 ? '' : 's'}).` });
|
|
271
|
+
}
|
|
272
|
+
for (const [key, old] of before.tests) {
|
|
273
|
+
const now = after.tests.get(key);
|
|
274
|
+
if (now)
|
|
275
|
+
compareTest(old, now, findings);
|
|
276
|
+
}
|
|
277
|
+
if (after.only > before.only) {
|
|
278
|
+
findings.push({ severity: 'block', rule: 'only-added', message: '`.only` was added: every other test in the run is silently skipped.' });
|
|
279
|
+
}
|
|
280
|
+
if (after.describeSkips > before.describeSkips) {
|
|
281
|
+
findings.push({ severity: 'block', rule: 'skip-added', message: 'A whole describe block was marked skip or fixme.' });
|
|
282
|
+
}
|
|
283
|
+
if (after.maxRetries > before.maxRetries) {
|
|
284
|
+
findings.push({
|
|
285
|
+
severity: 'warn',
|
|
286
|
+
rule: 'retries-raised',
|
|
287
|
+
message: `Retries raised from ${before.maxRetries} to ${after.maxRetries}: a flaky test passing on retry is still flaky.`,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
if (after.maxDiffAllowance > before.maxDiffAllowance && before.maxDiffAllowance >= 0 && after.maxDiffAllowance > 0) {
|
|
291
|
+
findings.push({
|
|
292
|
+
severity: 'warn',
|
|
293
|
+
rule: 'screenshot-tolerance-raised',
|
|
294
|
+
message: 'A screenshot comparison now tolerates more differences (maxDiffPixels, maxDiffPixelRatio or threshold went up).',
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return { file, findings, testsBefore: before.tests.size, testsAfter: after.tests.size };
|
|
298
|
+
}
|
|
299
|
+
function compareTest(old, now, findings) {
|
|
300
|
+
const where = { test: now.key === old.key ? now.key : `${old.key} → ${now.key}`, line: now.line };
|
|
301
|
+
if ((now.modifier === 'skip' || now.modifier === 'fixme') && old.modifier !== now.modifier) {
|
|
302
|
+
findings.push({ severity: 'block', rule: 'skip-added', ...where, message: `Test marked \`${now.modifier}\`: it no longer runs.` });
|
|
303
|
+
}
|
|
304
|
+
if (now.modifier === 'fail' && old.modifier !== 'fail') {
|
|
305
|
+
findings.push({ severity: 'block', rule: 'skip-added', ...where, message: 'Test marked `fail`: it now passes when it fails.' });
|
|
306
|
+
}
|
|
307
|
+
if (now.conditionalSkips > old.conditionalSkips) {
|
|
308
|
+
findings.push({ severity: 'block', rule: 'skip-added', ...where, message: 'A `test.skip()` / `test.fixme()` call was added inside the test.' });
|
|
309
|
+
}
|
|
310
|
+
if (now.assertions < old.assertions) {
|
|
311
|
+
findings.push({
|
|
312
|
+
severity: 'block',
|
|
313
|
+
rule: 'assertions-removed',
|
|
314
|
+
...where,
|
|
315
|
+
message: `Assertions went from ${old.assertions} to ${now.assertions}.`,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
else if (now.soft > old.soft) {
|
|
319
|
+
findings.push({
|
|
320
|
+
severity: 'warn',
|
|
321
|
+
rule: 'assertion-softened',
|
|
322
|
+
...where,
|
|
323
|
+
message: 'A hard `expect` became `expect.soft`: the test keeps going after it fails.',
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
if (now.strong < old.strong && now.weak > old.weak) {
|
|
327
|
+
findings.push({
|
|
328
|
+
severity: 'warn',
|
|
329
|
+
rule: 'weaker-matcher',
|
|
330
|
+
...where,
|
|
331
|
+
message: 'A specific matcher (toHaveText, toBeVisible, toEqual…) was swapped for a looser one (toBeTruthy, toBeAttached, toContainText…).',
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
if (now.hardWaits > old.hardWaits) {
|
|
335
|
+
findings.push({ severity: 'warn', rule: 'hard-wait-added', ...where, message: '`waitForTimeout` was added: it hides the race instead of waiting for the condition.' });
|
|
336
|
+
}
|
|
337
|
+
if (now.forced > old.forced) {
|
|
338
|
+
findings.push({ severity: 'warn', rule: 'force-added', ...where, message: '`force: true` was added: the click skips the checks that catch a covered or disabled element.' });
|
|
339
|
+
}
|
|
340
|
+
if (now.swallowed > old.swallowed || now.tries > old.tries) {
|
|
341
|
+
findings.push({ severity: 'warn', rule: 'error-swallowed', ...where, message: 'A `try` block or empty `.catch()` was added: a failing step can now pass silently.' });
|
|
342
|
+
}
|
|
343
|
+
if (now.maxTimeout > Math.max(old.maxTimeout * 2, 30_000)) {
|
|
344
|
+
findings.push({
|
|
345
|
+
severity: 'warn',
|
|
346
|
+
rule: 'timeout-raised',
|
|
347
|
+
...where,
|
|
348
|
+
message: `A timeout went up to ${now.maxTimeout} ms${old.maxTimeout ? ` (was ${old.maxTimeout})` : ''}: waiting longer rarely fixes the cause.`,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
export function guardVerdict(results) {
|
|
353
|
+
const all = results.flatMap((r) => r.findings);
|
|
354
|
+
if (all.some((f) => f.severity === 'block'))
|
|
355
|
+
return 'BLOCK';
|
|
356
|
+
if (all.length > 0)
|
|
357
|
+
return 'WARN';
|
|
358
|
+
return 'OK';
|
|
359
|
+
}
|
|
360
|
+
/** Spec files and Playwright configs, the files the guard reads. */
|
|
361
|
+
export function isGuardedFile(file) {
|
|
362
|
+
return /\.(spec|test)\.[cm]?[jt]sx?$/.test(file) || /(^|\/)playwright\.config\.[cm]?[jt]s$/.test(file) || /(^|\/)(e2e|tests?)\/.*\.[cm]?[jt]sx?$/.test(file);
|
|
363
|
+
}
|
|
364
|
+
/** Markdown report shared by the tool and the CLI. */
|
|
365
|
+
export function renderGuardReport(results, scope) {
|
|
366
|
+
const verdict = guardVerdict(results);
|
|
367
|
+
const findings = results.flatMap((r) => r.findings.map((f) => ({ ...f, file: r.file })));
|
|
368
|
+
const blocks = findings.filter((f) => f.severity === 'block').length;
|
|
369
|
+
const warns = findings.length - blocks;
|
|
370
|
+
const lines = [];
|
|
371
|
+
if (verdict === 'BLOCK')
|
|
372
|
+
lines.push(`## 🛑 BLOCK — ${blocks} change${blocks === 1 ? '' : 's'} make tests check less`);
|
|
373
|
+
else if (verdict === 'WARN')
|
|
374
|
+
lines.push(`## ⚠️ WARN — ${warns} change${warns === 1 ? '' : 's'} to review`);
|
|
375
|
+
else
|
|
376
|
+
lines.push('## ✅ OK — no test was weakened');
|
|
377
|
+
lines.push('', `**Compared:** ${scope} · ${results.length} file${results.length === 1 ? '' : 's'}`, '');
|
|
378
|
+
if (findings.length === 0) {
|
|
379
|
+
lines.push('Assertions, skips, waits, timeouts and retries are the same or stricter in every changed test.');
|
|
380
|
+
return lines.join('\n');
|
|
381
|
+
}
|
|
382
|
+
for (const f of findings) {
|
|
383
|
+
const at = f.line ? `${f.file}:${f.line}` : f.file;
|
|
384
|
+
lines.push(`- ${f.severity === 'block' ? '🛑' : '⚠️'} \`${at}\`${f.test ? ` · ${f.test}` : ''} — ${f.message}`);
|
|
385
|
+
}
|
|
386
|
+
lines.push('', verdict === 'BLOCK'
|
|
387
|
+
? '**What to do:** a fix that makes a test pass by checking less hides the bug it was written to catch. Restore the assertion (or the test) and fix the locator, the wait or the app instead. If removing it is intended, say so in the PR and rerun the guard with that file excluded.'
|
|
388
|
+
: '**What to do:** each of these can be right, but each can also hide a real failure. Check the reason before merging.');
|
|
389
|
+
return lines.join('\n');
|
|
390
|
+
}
|
|
391
|
+
//# sourceMappingURL=test-change-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-change-guard.js","sourceRoot":"","sources":["../../src/utils/test-change-guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time-based one-time passwords (RFC 6238), so a login that asks for an
|
|
3
|
+
* authenticator code can be automated from the shared secret alone.
|
|
4
|
+
*
|
|
5
|
+
* The secret is either the base32 string an authenticator app shows under
|
|
6
|
+
* "can't scan the code?" or the whole otpauth:// URI from the QR code.
|
|
7
|
+
* The browser child carries its own copy of generateTotp (it is plain
|
|
8
|
+
* JavaScript run with the project's Node); the tests check both against
|
|
9
|
+
* the RFC vectors.
|
|
10
|
+
*/
|
|
11
|
+
export type TotpAlgorithm = 'SHA1' | 'SHA256' | 'SHA512';
|
|
12
|
+
export interface TotpSettings {
|
|
13
|
+
/** Raw secret bytes. */
|
|
14
|
+
key: Buffer;
|
|
15
|
+
digits: number;
|
|
16
|
+
period: number;
|
|
17
|
+
algorithm: TotpAlgorithm;
|
|
18
|
+
}
|
|
19
|
+
/** Decode RFC 4648 base32, ignoring case, spaces, dashes and padding. */
|
|
20
|
+
export declare function base32Decode(input: string): Buffer;
|
|
21
|
+
/** Read a base32 secret or an otpauth://totp/… URI into settings. */
|
|
22
|
+
export declare function parseTotpSecret(raw: string): TotpSettings;
|
|
23
|
+
/** The code for a moment (default now). */
|
|
24
|
+
export declare function generateTotp(settings: TotpSettings, atMs?: number): string;
|
|
25
|
+
/** Seconds until the current code expires. */
|
|
26
|
+
export declare function totpSecondsLeft(settings: TotpSettings, atMs?: number): number;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time-based one-time passwords (RFC 6238), so a login that asks for an
|
|
3
|
+
* authenticator code can be automated from the shared secret alone.
|
|
4
|
+
*
|
|
5
|
+
* The secret is either the base32 string an authenticator app shows under
|
|
6
|
+
* "can't scan the code?" or the whole otpauth:// URI from the QR code.
|
|
7
|
+
* The browser child carries its own copy of generateTotp (it is plain
|
|
8
|
+
* JavaScript run with the project's Node); the tests check both against
|
|
9
|
+
* the RFC vectors.
|
|
10
|
+
*/
|
|
11
|
+
import { createHmac } from 'node:crypto';
|
|
12
|
+
import { PlaywrightMcpError } from '../types/index.js';
|
|
13
|
+
const BASE32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
14
|
+
/** Decode RFC 4648 base32, ignoring case, spaces, dashes and padding. */
|
|
15
|
+
export function base32Decode(input) {
|
|
16
|
+
const clean = input.toUpperCase().replace(/[\s=-]/g, '');
|
|
17
|
+
if (clean.length === 0 || /[^A-Z2-7]/.test(clean)) {
|
|
18
|
+
throw new PlaywrightMcpError('The TOTP secret is not base32', 'INVALID_INPUT', {
|
|
19
|
+
hint: 'Use the letters-and-digits key the authenticator setup page shows (A–Z, 2–7), or the whole otpauth:// URI.',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const bytes = [];
|
|
23
|
+
let bits = 0;
|
|
24
|
+
let value = 0;
|
|
25
|
+
for (const char of clean) {
|
|
26
|
+
value = (value << 5) | BASE32.indexOf(char);
|
|
27
|
+
bits += 5;
|
|
28
|
+
if (bits >= 8) {
|
|
29
|
+
bytes.push((value >>> (bits - 8)) & 0xff);
|
|
30
|
+
bits -= 8;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return Buffer.from(bytes);
|
|
34
|
+
}
|
|
35
|
+
/** Read a base32 secret or an otpauth://totp/… URI into settings. */
|
|
36
|
+
export function parseTotpSecret(raw) {
|
|
37
|
+
const text = raw.trim();
|
|
38
|
+
if (!/^otpauth:\/\//i.test(text)) {
|
|
39
|
+
return { key: base32Decode(text), digits: 6, period: 30, algorithm: 'SHA1' };
|
|
40
|
+
}
|
|
41
|
+
let uri;
|
|
42
|
+
try {
|
|
43
|
+
uri = new URL(text);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw new PlaywrightMcpError('The otpauth URI is not valid', 'INVALID_INPUT');
|
|
47
|
+
}
|
|
48
|
+
if (uri.host.toLowerCase() !== 'totp') {
|
|
49
|
+
throw new PlaywrightMcpError(`Only time-based codes are supported, not ${uri.host}`, 'INVALID_INPUT', {
|
|
50
|
+
hint: 'Counter-based (hotp) codes change on every use and cannot be generated from the secret alone.',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const secret = uri.searchParams.get('secret');
|
|
54
|
+
if (!secret)
|
|
55
|
+
throw new PlaywrightMcpError('The otpauth URI has no secret', 'INVALID_INPUT');
|
|
56
|
+
const digits = Number(uri.searchParams.get('digits') ?? 6);
|
|
57
|
+
const period = Number(uri.searchParams.get('period') ?? 30);
|
|
58
|
+
const algorithm = (uri.searchParams.get('algorithm') ?? 'SHA1').toUpperCase();
|
|
59
|
+
if (![6, 7, 8].includes(digits))
|
|
60
|
+
throw new PlaywrightMcpError(`Unsupported TOTP digits: ${digits}`, 'INVALID_INPUT');
|
|
61
|
+
if (!Number.isInteger(period) || period < 10 || period > 300) {
|
|
62
|
+
throw new PlaywrightMcpError(`Unsupported TOTP period: ${period}`, 'INVALID_INPUT');
|
|
63
|
+
}
|
|
64
|
+
if (algorithm !== 'SHA1' && algorithm !== 'SHA256' && algorithm !== 'SHA512') {
|
|
65
|
+
throw new PlaywrightMcpError(`Unsupported TOTP algorithm: ${algorithm}`, 'INVALID_INPUT');
|
|
66
|
+
}
|
|
67
|
+
return { key: base32Decode(secret), digits, period, algorithm };
|
|
68
|
+
}
|
|
69
|
+
/** The code for a moment (default now). */
|
|
70
|
+
export function generateTotp(settings, atMs = Date.now()) {
|
|
71
|
+
const counter = Math.floor(atMs / 1000 / settings.period);
|
|
72
|
+
const message = Buffer.alloc(8);
|
|
73
|
+
message.writeUInt32BE(Math.floor(counter / 0x100000000), 0);
|
|
74
|
+
message.writeUInt32BE(counter % 0x100000000, 4);
|
|
75
|
+
const hmac = createHmac(settings.algorithm.toLowerCase(), settings.key).update(message).digest();
|
|
76
|
+
const offset = hmac[hmac.length - 1] & 0x0f;
|
|
77
|
+
const binary = ((hmac[offset] & 0x7f) << 24) | (hmac[offset + 1] << 16) | (hmac[offset + 2] << 8) | hmac[offset + 3];
|
|
78
|
+
return String(binary % 10 ** settings.digits).padStart(settings.digits, '0');
|
|
79
|
+
}
|
|
80
|
+
/** Seconds until the current code expires. */
|
|
81
|
+
export function totpSecondsLeft(settings, atMs = Date.now()) {
|
|
82
|
+
return settings.period - (Math.floor(atMs / 1000) % settings.period);
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=totp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"totp.js","sourceRoot":"","sources":["../../src/utils/totp.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright-e2e-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "MCP server and agent skill that run, diagnose and fix failing Playwright tests: CI report diagnosis, trace analysis, verified locator fixes, flaky verdicts, breaking commits, tests written by walking the flow, accessibility and Web Vitals audits.",
|
|
5
5
|
"author": "xsignon-secure (https://github.com/xsignon-secure)",
|
|
6
6
|
"mcpName": "io.github.xsignon-secure/E2E",
|