playwright-e2e-mcp 0.6.0 → 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 +74 -8
- 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/audit-accessibility.d.ts +3 -0
- package/dist/tools/audit-performance.d.ts +3 -0
- package/dist/tools/audit-performance.js +1 -1
- package/dist/tools/audit-performance.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/compare-visual-state.d.ts +3 -0
- package/dist/tools/generate-e2e-test.js +1 -1
- package/dist/tools/generate-e2e-test.js.map +1 -1
- package/dist/tools/get-run-status.js +8 -2
- package/dist/tools/get-run-status.js.map +1 -1
- package/dist/tools/inspect-page.d.ts +3 -0
- package/dist/tools/inspect-page.js +9 -5
- package/dist/tools/inspect-page.js.map +1 -1
- package/dist/tools/probe-script.js +158 -10
- 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 +30 -1
- package/dist/tools/shared.js +51 -27
- package/dist/tools/shared.js.map +1 -1
- package/dist/tools/structured.d.ts +16 -1
- package/dist/tools/structured.js +14 -1
- package/dist/tools/structured.js.map +1 -1
- package/dist/tools/suggest-fix.js +1 -1
- package/dist/tools/suggest-fix.js.map +1 -1
- package/dist/tools/validate-selector.d.ts +6 -0
- package/dist/tools/validate-selector.js +28 -5
- package/dist/tools/validate-selector.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/locator-expr.d.ts +3 -1
- package/dist/utils/locator-expr.js +25 -7
- package/dist/utils/locator-expr.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 +46 -7
- package/skill/playwright-e2e-mcp/references/setup.md +7 -1
- package/skill/playwright-e2e-mcp/references/tools.md +43 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool — save-login: log in once and save the session as a Playwright
|
|
3
|
+
* storageState file that tests and the page tools reuse. Before logging in
|
|
4
|
+
* again it checks whether the saved session still works, so expired logins
|
|
5
|
+
* refresh themselves and valid ones are left alone.
|
|
6
|
+
*
|
|
7
|
+
* Passwords and one-time codes never pass through the chat: fill values
|
|
8
|
+
* may say {{env:E2E_PASSWORD}} (read from the server's environment) and
|
|
9
|
+
* {{totp}} (an authenticator code generated from a secret in an
|
|
10
|
+
* environment variable at the moment it is typed).
|
|
11
|
+
*/
|
|
12
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import { PlaywrightMcpError } from '../types/index.js';
|
|
16
|
+
import { relativeToRoot, sanitizeUserPath, toNativePath, writeFileInsideRoot } from '../utils/path-utils.js';
|
|
17
|
+
import { parseLocator } from '../utils/locator-expr.js';
|
|
18
|
+
import { parseTotpSecret } from '../utils/totp.js';
|
|
19
|
+
import { assertUrlAllowed } from '../utils/url-policy.js';
|
|
20
|
+
import { assertHttpUrl, guard, pageActionSchema, resolvePageState, resolveProjectRoot, runBrowserScript, toolError, toolText, } from './shared.js';
|
|
21
|
+
const saveLoginInput = z.object({
|
|
22
|
+
url: z.string().describe('Login page URL (http/https)'),
|
|
23
|
+
actions: z
|
|
24
|
+
.array(pageActionSchema)
|
|
25
|
+
.max(20)
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Login steps. In fill values write {{env:E2E_PASSWORD}} for a secret from the environment and {{totp}} for an authenticator code, never the secret itself. End with a 'wait' on something only signed-in users see"),
|
|
28
|
+
verify: z
|
|
29
|
+
.object({
|
|
30
|
+
url: z.string().optional().describe('Page to open to check the session, e.g. /account (default: where login ends)'),
|
|
31
|
+
visible: z.string().optional().describe("Locator only a signed-in user sees, e.g. getByRole('button', { name: 'Sign out' })"),
|
|
32
|
+
notUrl: z.string().optional().describe("The session is bad if the page lands on a URL containing this, e.g. '/login'"),
|
|
33
|
+
})
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('How to tell the session works; also used to decide whether a saved session is still valid'),
|
|
36
|
+
name: z
|
|
37
|
+
.string()
|
|
38
|
+
.regex(/^[\w.-]{1,40}$/)
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Session name, e.g. 'admin' (default 'user'); saved to playwright/.auth/<name>.json"),
|
|
41
|
+
path: z.string().optional().describe('Where to save the storageState JSON inside the project (overrides name)'),
|
|
42
|
+
totpSecretEnv: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe('Environment variable holding the authenticator secret (base32 or otpauth:// URI), for {{totp}}'),
|
|
46
|
+
reuse: z.boolean().optional().describe('Keep a saved session that still passes verify instead of logging in again (default true)'),
|
|
47
|
+
headers: z.record(z.string(), z.string()).optional().describe('Extra HTTP headers'),
|
|
48
|
+
viewport: z.object({ width: z.number().int().min(200).max(4_000), height: z.number().int().min(200).max(4_000) }).optional(),
|
|
49
|
+
actionTimeoutMs: z.number().int().min(1_000).max(120_000).optional().describe('How long each step waits for its element (default 10000)'),
|
|
50
|
+
projectRoot: z.string().optional().describe('Project directory (default: server root)'),
|
|
51
|
+
timeoutMs: z.number().int().min(5_000).max(300_000).optional().describe('Overall limit in ms (default 60000)'),
|
|
52
|
+
});
|
|
53
|
+
export const saveLoginSchema = saveLoginInput;
|
|
54
|
+
const ENV_TOKEN_RE = /\{\{\s*env:([A-Za-z0-9_]+)\s*\}\}/g;
|
|
55
|
+
const TOTP_TOKEN_RE = /\{\{\s*totp\s*\}\}/;
|
|
56
|
+
/**
|
|
57
|
+
* Environment variables a login may read. A page the agent visits could
|
|
58
|
+
* ask it to type {{env:AWS_SECRET_ACCESS_KEY}} into a form, so only names
|
|
59
|
+
* that are plainly test logins are allowed, plus any the user lists.
|
|
60
|
+
*/
|
|
61
|
+
export function loginEnvAllowed(name) {
|
|
62
|
+
if (/^(E2E|TEST|PW|PLAYWRIGHT|LOGIN)_[A-Z0-9_]+$/.test(name) && name !== 'PW_MCP_HTTP_TOKEN')
|
|
63
|
+
return true;
|
|
64
|
+
const extra = (process.env.PW_MCP_LOGIN_ENV ?? '')
|
|
65
|
+
.split(',')
|
|
66
|
+
.map((entry) => entry.trim())
|
|
67
|
+
.filter(Boolean);
|
|
68
|
+
return extra.includes(name);
|
|
69
|
+
}
|
|
70
|
+
/** The environment values the fill steps ask for, checked and read. */
|
|
71
|
+
export function resolveLoginValues(actions) {
|
|
72
|
+
const names = new Set();
|
|
73
|
+
for (const action of actions ?? []) {
|
|
74
|
+
if (action.type !== 'fill' || !action.value)
|
|
75
|
+
continue;
|
|
76
|
+
for (const match of action.value.matchAll(ENV_TOKEN_RE))
|
|
77
|
+
names.add(match[1]);
|
|
78
|
+
}
|
|
79
|
+
const values = {};
|
|
80
|
+
const missing = [];
|
|
81
|
+
for (const name of names) {
|
|
82
|
+
if (!loginEnvAllowed(name)) {
|
|
83
|
+
throw new PlaywrightMcpError(`{{env:${name}}} is not an allowed login variable`, 'INVALID_INPUT', {
|
|
84
|
+
hint: 'Name login secrets E2E_*, TEST_*, PW_*, PLAYWRIGHT_* or LOGIN_* (e.g. E2E_PASSWORD), or list the name in PW_MCP_LOGIN_ENV.',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const value = process.env[name];
|
|
88
|
+
if (value === undefined || value === '')
|
|
89
|
+
missing.push(name);
|
|
90
|
+
else
|
|
91
|
+
values[name] = value;
|
|
92
|
+
}
|
|
93
|
+
if (missing.length > 0) {
|
|
94
|
+
throw new PlaywrightMcpError(`Not set in the server's environment: ${missing.join(', ')}`, 'INVALID_INPUT', {
|
|
95
|
+
hint: 'Set them where the MCP server starts (the env block of your MCP client config, or the shell), then restart the server.',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return values;
|
|
99
|
+
}
|
|
100
|
+
/** A literal typed into a password field: worth moving into an env variable. */
|
|
101
|
+
function literalPasswordStep(actions) {
|
|
102
|
+
return (actions ?? []).some((a) => a.type === 'fill' && /pass(word|code)?|pin\b/i.test(a.locator ?? '') && !!a.value && !a.value.includes('{{'));
|
|
103
|
+
}
|
|
104
|
+
function expiryText(scan) {
|
|
105
|
+
if (scan.firstExpiry === null)
|
|
106
|
+
return scan.cookies > 0 ? 'session cookies only (they end when the browser closes)' : 'no cookies';
|
|
107
|
+
const seconds = scan.firstExpiry - Date.now() / 1000;
|
|
108
|
+
const when = new Date(scan.firstExpiry * 1000).toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
|
|
109
|
+
if (seconds <= 0)
|
|
110
|
+
return `a cookie already expired (${when})`;
|
|
111
|
+
const hours = seconds / 3600;
|
|
112
|
+
const span = hours < 48 ? `${Math.max(1, Math.round(hours))} h` : `${Math.round(hours / 24)} days`;
|
|
113
|
+
return `first cookie expires in ${span} (${when})`;
|
|
114
|
+
}
|
|
115
|
+
/** Is the session file covered by .gitignore? A rough check that catches the common miss. */
|
|
116
|
+
async function ignoredByGit(root, relative) {
|
|
117
|
+
const text = await readFile(toNativePath(path.posix.join(root, '.gitignore')), 'utf8').catch(() => '');
|
|
118
|
+
const lines = text.split(/\r?\n/).map((line) => line.trim().replace(/^\//, '').replace(/\/$/, ''));
|
|
119
|
+
const parts = relative.split('/');
|
|
120
|
+
for (let i = 1; i <= parts.length; i += 1) {
|
|
121
|
+
const prefix = parts.slice(0, i).join('/');
|
|
122
|
+
if (lines.includes(prefix) || lines.includes(`${prefix}/*`) || lines.includes(`${prefix}/**`))
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return lines.some((line) => line === '*.auth.json' || line === '.auth' || (line.endsWith('.json') && relative.endsWith(line)));
|
|
126
|
+
}
|
|
127
|
+
function scanLines(scan) {
|
|
128
|
+
return [
|
|
129
|
+
`**Session:** ${scan.cookies} cookie${scan.cookies === 1 ? '' : 's'}, ${scan.origins} origin${scan.origins === 1 ? '' : 's'} with local storage · ${expiryText(scan)}`,
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
export const saveLoginTool = {
|
|
133
|
+
name: 'save-login',
|
|
134
|
+
description: "Log in once and save the session as a storageState file (default playwright/.auth/<name>.json) for tests and the page tools. If a saved session still passes verify it is reused; expired ones log in again. Secrets stay out of chat: fill values use {{env:E2E_PASSWORD}} and {{totp}} (authenticator code from totpSecretEnv).",
|
|
135
|
+
inputSchema: saveLoginSchema,
|
|
136
|
+
handler: async (args, ctx) => guard('save-login', async () => {
|
|
137
|
+
if (ctx.restricted) {
|
|
138
|
+
throw new PlaywrightMcpError('save-login is not available on this server', 'INVALID_PATH', {
|
|
139
|
+
hint: 'It writes a session file into the project. Run the server locally.',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const url = assertHttpUrl(args.url);
|
|
143
|
+
await assertUrlAllowed(url);
|
|
144
|
+
const root = await resolveProjectRoot(args.projectRoot, ctx);
|
|
145
|
+
const timeoutMs = args.timeoutMs ?? 60_000;
|
|
146
|
+
const relativeTarget = args.path?.trim() || `playwright/.auth/${args.name ?? 'user'}.json`;
|
|
147
|
+
const target = sanitizeUserPath(relativeTarget, root);
|
|
148
|
+
if (!target.endsWith('.json')) {
|
|
149
|
+
throw new PlaywrightMcpError(`path must end in .json: ${relativeTarget}`, 'INVALID_PATH');
|
|
150
|
+
}
|
|
151
|
+
const shown = relativeToRoot(root, target);
|
|
152
|
+
let verifyUrl;
|
|
153
|
+
if (args.verify?.url) {
|
|
154
|
+
verifyUrl = new URL(args.verify.url, url).toString();
|
|
155
|
+
assertHttpUrl(verifyUrl);
|
|
156
|
+
await assertUrlAllowed(verifyUrl);
|
|
157
|
+
}
|
|
158
|
+
const visible = args.verify?.visible ? parseLocator(args.verify.visible) : undefined;
|
|
159
|
+
const loginVerify = args.verify ? { url: verifyUrl, visible, notUrl: args.verify.notUrl } : undefined;
|
|
160
|
+
const hasCheck = !!(visible || args.verify?.notUrl);
|
|
161
|
+
// A fresh login never starts from a session (PW_MCP_STORAGE_STATE included).
|
|
162
|
+
const { storageState: _default, ...base } = await resolvePageState({ headers: args.headers, viewport: args.viewport, actionTimeoutMs: args.actionTimeoutMs, actions: args.actions }, root, url, ctx);
|
|
163
|
+
const common = (state) => ({
|
|
164
|
+
...state,
|
|
165
|
+
projectRoot: root,
|
|
166
|
+
loginVerify,
|
|
167
|
+
gotoTimeout: Math.min(30_000, Math.max(5_000, timeoutMs - 5_000)),
|
|
168
|
+
waitTimeout: 10_000,
|
|
169
|
+
startedAt: Date.now(),
|
|
170
|
+
});
|
|
171
|
+
// 1. A saved session that still works is kept.
|
|
172
|
+
const existing = await stat(toNativePath(target)).catch(() => null);
|
|
173
|
+
if (existing?.isFile() && args.reuse !== false && hasCheck) {
|
|
174
|
+
const check = await runBrowserScript({
|
|
175
|
+
...common({ headers: base.headers, viewport: base.viewport }),
|
|
176
|
+
loginVerify: { ...loginVerify, url: undefined },
|
|
177
|
+
mode: 'login',
|
|
178
|
+
url: verifyUrl ?? url,
|
|
179
|
+
storageState: toNativePath(target),
|
|
180
|
+
loginCheckOnly: true,
|
|
181
|
+
}, { timeoutMs, signal: ctx.signal });
|
|
182
|
+
const scan = check.ok ? check.data?.login : undefined;
|
|
183
|
+
if (scan?.verified === true) {
|
|
184
|
+
return toolText([
|
|
185
|
+
`## ✅ STILL VALID — \`${shown}\``,
|
|
186
|
+
'',
|
|
187
|
+
`The saved session passed the check at ${verifyUrl ?? url}, so no new login was needed.`,
|
|
188
|
+
'',
|
|
189
|
+
...scanLines(scan),
|
|
190
|
+
'',
|
|
191
|
+
`Use it with \`storageState: '${shown}'\` in the page tools, or \`use: { storageState: '${shown}' }\` in playwright.config.`,
|
|
192
|
+
].join('\n'), { status: 'valid', valid: true, path: shown, url: verifyUrl ?? url, ...(scan.firstExpiry ? { expiresAt: new Date(scan.firstExpiry * 1000).toISOString() } : {}) });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!args.actions?.length) {
|
|
196
|
+
throw new PlaywrightMcpError('Pass the login steps in actions', 'INVALID_INPUT', {
|
|
197
|
+
hint: existing?.isFile()
|
|
198
|
+
? `The saved session \`${shown}\` ${hasCheck ? 'failed the check' : 'cannot be checked without verify.visible or verify.notUrl'}, so a new login is needed.`
|
|
199
|
+
: "E.g. fill getByLabel('Email') with {{env:E2E_USER}}, fill getByLabel('Password') with {{env:E2E_PASSWORD}}, click getByRole('button', { name: 'Sign in' }).",
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
// 2. Log in, check, save.
|
|
203
|
+
const env = {};
|
|
204
|
+
const values = resolveLoginValues(args.actions);
|
|
205
|
+
if (Object.keys(values).length > 0)
|
|
206
|
+
env.PW_MCP_FILL_VALUES = JSON.stringify(values);
|
|
207
|
+
const wantsTotp = args.actions.some((a) => a.type === 'fill' && TOTP_TOKEN_RE.test(a.value ?? ''));
|
|
208
|
+
if (wantsTotp) {
|
|
209
|
+
const name = args.totpSecretEnv;
|
|
210
|
+
if (!name)
|
|
211
|
+
throw new PlaywrightMcpError('{{totp}} needs totpSecretEnv', 'INVALID_INPUT', { hint: 'Name the environment variable that holds the authenticator secret, e.g. E2E_TOTP_SECRET.' });
|
|
212
|
+
if (!loginEnvAllowed(name)) {
|
|
213
|
+
throw new PlaywrightMcpError(`${name} is not an allowed login variable`, 'INVALID_INPUT', {
|
|
214
|
+
hint: 'Name it E2E_*, TEST_*, PW_*, PLAYWRIGHT_* or LOGIN_* (e.g. E2E_TOTP_SECRET), or list it in PW_MCP_LOGIN_ENV.',
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
const secret = process.env[name];
|
|
218
|
+
if (!secret)
|
|
219
|
+
throw new PlaywrightMcpError(`${name} is not set in the server's environment`, 'INVALID_INPUT');
|
|
220
|
+
const totp = parseTotpSecret(secret);
|
|
221
|
+
env.PW_MCP_TOTP = JSON.stringify({ key: totp.key.toString('hex'), digits: totp.digits, period: totp.period, algorithm: totp.algorithm });
|
|
222
|
+
}
|
|
223
|
+
const outcome = await runBrowserScript({ ...common(base), mode: 'login', url }, { timeoutMs, signal: ctx.signal, env });
|
|
224
|
+
if (!outcome.ok) {
|
|
225
|
+
const done = outcome.actionsDone?.length ? `\nSteps done: ${outcome.actionsDone.join(' → ')}` : '';
|
|
226
|
+
return toolError(outcome.kind ?? 'UNKNOWN', `Login on ${url} failed: ${outcome.error ?? 'unknown error'}${done}`, outcome.hint, outcome.stderrTail);
|
|
227
|
+
}
|
|
228
|
+
const scan = outcome.data?.login;
|
|
229
|
+
if (!scan)
|
|
230
|
+
return toolError('UNKNOWN', 'The browser returned no session.', undefined, outcome.stderrTail);
|
|
231
|
+
if (scan.verified === false) {
|
|
232
|
+
return toolText([
|
|
233
|
+
`## ❌ LOGIN FAILED — nothing saved`,
|
|
234
|
+
'',
|
|
235
|
+
`The steps ran, but the check failed: ${scan.verifyNote || 'the session did not work'}. Ended on ${outcome.data?.finalUrl ?? url}.`,
|
|
236
|
+
'',
|
|
237
|
+
'**How to fix:** check the credentials in the environment, add a `wait` step for the page after sign-in, or run **inspect-page** with the same actions to see where the login stops (an MFA prompt, a consent screen, an error message).',
|
|
238
|
+
].join('\n'), { ok: false, status: 'failed', valid: false, path: shown, url });
|
|
239
|
+
}
|
|
240
|
+
await writeFileInsideRoot(target, `${JSON.stringify(scan.state, null, 2)}\n`, root);
|
|
241
|
+
const lines = [`## ✅ SAVED — \`${shown}\``, ''];
|
|
242
|
+
lines.push(scan.verified === true
|
|
243
|
+
? `Logged in and checked: the session works${verifyUrl ? ` on ${verifyUrl}` : ''}.`
|
|
244
|
+
: 'Logged in. No check was given, so the session was saved without one; add `verify` so expired sessions are caught next time.', '', ...scanLines(scan));
|
|
245
|
+
if (scan.cookies === 0 && scan.origins === 0) {
|
|
246
|
+
lines.push('', '> ⚠️ The session is empty: no cookies or local storage were set. The login may not have finished; end the steps with a `wait` on the signed-in page.');
|
|
247
|
+
}
|
|
248
|
+
if (literalPasswordStep(args.actions)) {
|
|
249
|
+
lines.push('', '> 🔐 A password was typed as plain text, so it is now in this chat. Put it in an environment variable (E2E_PASSWORD) and write `{{env:E2E_PASSWORD}}` instead.');
|
|
250
|
+
}
|
|
251
|
+
if (!(await ignoredByGit(root, shown))) {
|
|
252
|
+
lines.push('', `> ⚠️ \`${shown}\` holds live session cookies and is not in .gitignore. Add \`${path.posix.dirname(shown)}/\` to .gitignore so it is never committed.`);
|
|
253
|
+
}
|
|
254
|
+
lines.push('', `**Use it:** \`storageState: '${shown}'\` in inspect-page, validate-selector and the audits, or \`use: { storageState: '${shown}' }\` in playwright.config. Call save-login again before a run: it keeps the session while it works and logs in again when it expires.`);
|
|
255
|
+
return toolText(lines.join('\n'), {
|
|
256
|
+
status: 'saved',
|
|
257
|
+
valid: true,
|
|
258
|
+
path: shown,
|
|
259
|
+
url,
|
|
260
|
+
...(scan.firstExpiry ? { expiresAt: new Date(scan.firstExpiry * 1000).toISOString() } : {}),
|
|
261
|
+
});
|
|
262
|
+
}),
|
|
263
|
+
};
|
|
264
|
+
//# sourceMappingURL=save-login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"save-login.js","sourceRoot":"","sources":["../../src/tools/save-login.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -113,6 +113,7 @@ export declare const pageStateShape: {
|
|
|
113
113
|
url: z.ZodOptional<z.ZodString>;
|
|
114
114
|
ms: z.ZodOptional<z.ZodNumber>;
|
|
115
115
|
}, z.core.$strip>>>;
|
|
116
|
+
actionTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
116
117
|
routes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
117
118
|
url: z.ZodString;
|
|
118
119
|
method: z.ZodOptional<z.ZodString>;
|
|
@@ -154,6 +155,7 @@ export declare const pageStateShape: {
|
|
|
154
155
|
}>>;
|
|
155
156
|
};
|
|
156
157
|
export interface PageStateInput {
|
|
158
|
+
actionTimeoutMs?: number;
|
|
157
159
|
storageState?: string;
|
|
158
160
|
headers?: Record<string, string>;
|
|
159
161
|
actions?: Array<z.infer<typeof pageActionSchema>>;
|
|
@@ -186,6 +188,8 @@ export interface ResolvedRouteMock {
|
|
|
186
188
|
delayMs?: number;
|
|
187
189
|
}
|
|
188
190
|
export interface ResolvedPageState {
|
|
191
|
+
/** Per-action wait in ms (the probe's default is 10000). */
|
|
192
|
+
actionTimeout?: number;
|
|
189
193
|
storageState?: string;
|
|
190
194
|
headers?: Record<string, string>;
|
|
191
195
|
actions?: Array<{
|
|
@@ -227,7 +231,7 @@ export declare function describeRouteHits(state: ResolvedPageState, hits: number
|
|
|
227
231
|
/** A hint when an unauthenticated page landed on a login screen. */
|
|
228
232
|
export declare function loginHint(finalUrl: string | undefined, state: ResolvedPageState): string | undefined;
|
|
229
233
|
export interface BrowserScriptConfig extends Omit<ResolvedPageState, never> {
|
|
230
|
-
mode: 'inspect' | 'validate' | 'screenshot' | 'heal' | 'a11y' | 'perf';
|
|
234
|
+
mode: 'inspect' | 'validate' | 'screenshot' | 'heal' | 'a11y' | 'perf' | 'login';
|
|
231
235
|
projectRoot: string;
|
|
232
236
|
/** Page to open; omitted when rendering a snapshot. */
|
|
233
237
|
url?: string;
|
|
@@ -264,6 +268,13 @@ export interface BrowserScriptConfig extends Omit<ResolvedPageState, never> {
|
|
|
264
268
|
mobile?: boolean;
|
|
265
269
|
/** perf: ms to keep observing after load so late shifts and LCP land. */
|
|
266
270
|
settleMs?: number;
|
|
271
|
+
/** login: how to tell the session works, and whether to only check it. */
|
|
272
|
+
loginVerify?: {
|
|
273
|
+
url?: string;
|
|
274
|
+
visible?: LocatorCall[];
|
|
275
|
+
notUrl?: string;
|
|
276
|
+
};
|
|
277
|
+
loginCheckOnly?: boolean;
|
|
267
278
|
startedAt: number;
|
|
268
279
|
}
|
|
269
280
|
/** Network emulation in Chrome DevTools terms (throughput in bytes/s). */
|
|
@@ -299,6 +310,23 @@ export interface BrowserScriptData {
|
|
|
299
310
|
ariaTruncated?: boolean;
|
|
300
311
|
/** Requests each network mock in `routes` handled, by index. */
|
|
301
312
|
routeHits?: number[];
|
|
313
|
+
/** HTTP status of the page's main document, when it was fetched over the network. */
|
|
314
|
+
httpStatus?: number;
|
|
315
|
+
/** mode 'login': the session check and what the session holds. */
|
|
316
|
+
login?: LoginScan;
|
|
317
|
+
}
|
|
318
|
+
export interface LoginScan {
|
|
319
|
+
/** null when no check was asked for. */
|
|
320
|
+
verified: boolean | null;
|
|
321
|
+
verifyNote?: string;
|
|
322
|
+
/** The storageState to save; absent when checking only or the check failed. */
|
|
323
|
+
state?: unknown;
|
|
324
|
+
cookies: number;
|
|
325
|
+
sessionCookies: number;
|
|
326
|
+
origins: number;
|
|
327
|
+
/** Earliest cookie expiry, in Unix seconds. */
|
|
328
|
+
firstExpiry: number | null;
|
|
329
|
+
expired: number;
|
|
302
330
|
}
|
|
303
331
|
export interface PerformanceResource {
|
|
304
332
|
url: string;
|
|
@@ -380,6 +408,7 @@ export interface BrowserScriptOutcome {
|
|
|
380
408
|
export declare function runBrowserScript(config: BrowserScriptConfig, options: {
|
|
381
409
|
timeoutMs?: number;
|
|
382
410
|
signal?: AbortSignal;
|
|
411
|
+
env?: Record<string, string>;
|
|
383
412
|
}): Promise<BrowserScriptOutcome>;
|
|
384
413
|
/** Accept only http(s) URLs for page-facing tools. */
|
|
385
414
|
export declare function assertHttpUrl(url: string): string;
|
package/dist/tools/shared.js
CHANGED
|
@@ -294,7 +294,18 @@ export const routeMockSchema = z.object({
|
|
|
294
294
|
/** Inputs shared by every tool that opens a page. */
|
|
295
295
|
export const pageStateShape = {
|
|
296
296
|
...sessionShape,
|
|
297
|
-
actions: z
|
|
297
|
+
actions: z
|
|
298
|
+
.array(pageActionSchema)
|
|
299
|
+
.max(20)
|
|
300
|
+
.optional()
|
|
301
|
+
.describe("Steps run in order after load to reach a state (modal, step 3). One call = one fresh browser, so include the login steps; end with a 'wait' on what the state shows"),
|
|
302
|
+
actionTimeoutMs: z
|
|
303
|
+
.number()
|
|
304
|
+
.int()
|
|
305
|
+
.min(1_000)
|
|
306
|
+
.max(120_000)
|
|
307
|
+
.optional()
|
|
308
|
+
.describe('How long each action waits for its element (default 10000); keep timeoutMs above the total'),
|
|
298
309
|
routes: z
|
|
299
310
|
.array(routeMockSchema)
|
|
300
311
|
.max(20)
|
|
@@ -365,7 +376,7 @@ async function resolveHar(har, root, ctx) {
|
|
|
365
376
|
function assertHeaders(headers, what) {
|
|
366
377
|
for (const [name, value] of Object.entries(headers)) {
|
|
367
378
|
if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name) || /[\r\n]/.test(value)) {
|
|
368
|
-
throw new PlaywrightMcpError(`Invalid ${what} "${name}"`, '
|
|
379
|
+
throw new PlaywrightMcpError(`Invalid ${what} "${name}"`, 'INVALID_INPUT', {
|
|
369
380
|
hint: 'Header names must be tokens and values must be single-line.',
|
|
370
381
|
});
|
|
371
382
|
}
|
|
@@ -377,13 +388,13 @@ export function resolveRouteMocks(routes) {
|
|
|
377
388
|
const step = `routes[${i}] (${route.url})`;
|
|
378
389
|
const method = route.method?.trim().toUpperCase();
|
|
379
390
|
if (method !== undefined && !/^[A-Z]+$/.test(method)) {
|
|
380
|
-
throw new PlaywrightMcpError(`${step}: "${route.method}" is not an HTTP method`, '
|
|
391
|
+
throw new PlaywrightMcpError(`${step}: "${route.method}" is not an HTTP method`, 'INVALID_INPUT');
|
|
381
392
|
}
|
|
382
393
|
if (route.json !== undefined && route.body !== undefined) {
|
|
383
|
-
throw new PlaywrightMcpError(`${step}: pass json or body, not both`, '
|
|
394
|
+
throw new PlaywrightMcpError(`${step}: pass json or body, not both`, 'INVALID_INPUT');
|
|
384
395
|
}
|
|
385
396
|
if (route.abort && (route.json !== undefined || route.body !== undefined || route.status !== undefined)) {
|
|
386
|
-
throw new PlaywrightMcpError(`${step}: abort cannot be combined with a response`, '
|
|
397
|
+
throw new PlaywrightMcpError(`${step}: abort cannot be combined with a response`, 'INVALID_INPUT');
|
|
387
398
|
}
|
|
388
399
|
if (route.headers)
|
|
389
400
|
assertHeaders(route.headers, `${step} header`);
|
|
@@ -392,7 +403,7 @@ export function resolveRouteMocks(routes) {
|
|
|
392
403
|
if (route.json !== undefined) {
|
|
393
404
|
body = JSON.stringify(route.json);
|
|
394
405
|
if (body === undefined || body.length > MAX_MOCK_BODY) {
|
|
395
|
-
throw new PlaywrightMcpError(`${step}: json must be serialisable and under ${MAX_MOCK_BODY} characters`, '
|
|
406
|
+
throw new PlaywrightMcpError(`${step}: json must be serialisable and under ${MAX_MOCK_BODY} characters`, 'INVALID_INPUT');
|
|
396
407
|
}
|
|
397
408
|
contentType ??= 'application/json';
|
|
398
409
|
}
|
|
@@ -461,12 +472,12 @@ export async function resolvePageState(input, root, entryUrl, ctx) {
|
|
|
461
472
|
out.har = await resolveHar(input.har, root, ctx);
|
|
462
473
|
if (input.browser && input.browser !== 'chromium') {
|
|
463
474
|
if (serverlessChromiumEnabled()) {
|
|
464
|
-
throw new PlaywrightMcpError(`${input.browser} is not available on the hosted endpoint`, '
|
|
475
|
+
throw new PlaywrightMcpError(`${input.browser} is not available on the hosted endpoint`, 'INVALID_INPUT', {
|
|
465
476
|
hint: "The hosted endpoint bundles Chromium only. Use 'chromium', or run the server locally for Firefox and WebKit.",
|
|
466
477
|
});
|
|
467
478
|
}
|
|
468
479
|
if (input.browser === 'webkit' && blockPrivateUrls()) {
|
|
469
|
-
throw new PlaywrightMcpError('webkit is not available while private addresses are blocked', '
|
|
480
|
+
throw new PlaywrightMcpError('webkit is not available while private addresses are blocked', 'INVALID_INPUT', {
|
|
470
481
|
hint: "WebKit cannot route every connection through the address guard. Use 'chromium' or 'firefox'.",
|
|
471
482
|
});
|
|
472
483
|
}
|
|
@@ -475,7 +486,7 @@ export async function resolvePageState(input, root, entryUrl, ctx) {
|
|
|
475
486
|
if (input.device) {
|
|
476
487
|
// Checked against the project's own Playwright device list in the browser child.
|
|
477
488
|
if (!/^[\w .()+-]+$/.test(input.device)) {
|
|
478
|
-
throw new PlaywrightMcpError(`device "${input.device}" is not a Playwright device name`, '
|
|
489
|
+
throw new PlaywrightMcpError(`device "${input.device}" is not a Playwright device name`, 'INVALID_INPUT', {
|
|
479
490
|
hint: "Use a name from Playwright's device list, e.g. 'iPhone 15' or 'Pixel 7'.",
|
|
480
491
|
});
|
|
481
492
|
}
|
|
@@ -483,12 +494,12 @@ export async function resolvePageState(input, root, entryUrl, ctx) {
|
|
|
483
494
|
}
|
|
484
495
|
if (input.channel) {
|
|
485
496
|
if ((input.browser ?? 'chromium') !== 'chromium') {
|
|
486
|
-
throw new PlaywrightMcpError(`channel ${input.channel} needs the chromium engine`, '
|
|
497
|
+
throw new PlaywrightMcpError(`channel ${input.channel} needs the chromium engine`, 'INVALID_INPUT', {
|
|
487
498
|
hint: 'Leave browser unset (or chromium) when you pick a channel.',
|
|
488
499
|
});
|
|
489
500
|
}
|
|
490
501
|
if (serverlessChromiumEnabled()) {
|
|
491
|
-
throw new PlaywrightMcpError(`channel ${input.channel} is not available on the hosted endpoint`, '
|
|
502
|
+
throw new PlaywrightMcpError(`channel ${input.channel} is not available on the hosted endpoint`, 'INVALID_INPUT', {
|
|
492
503
|
hint: 'The hosted endpoint bundles one Chromium build. Run the server locally to test in Chrome or Edge.',
|
|
493
504
|
});
|
|
494
505
|
}
|
|
@@ -496,6 +507,8 @@ export async function resolvePageState(input, root, entryUrl, ctx) {
|
|
|
496
507
|
}
|
|
497
508
|
if (input.routes && input.routes.length > 0)
|
|
498
509
|
out.routes = resolveRouteMocks(input.routes);
|
|
510
|
+
if (input.actionTimeoutMs !== undefined)
|
|
511
|
+
out.actionTimeout = input.actionTimeoutMs;
|
|
499
512
|
if (input.actions && input.actions.length > 0) {
|
|
500
513
|
out.actions = [];
|
|
501
514
|
let current = entryUrl;
|
|
@@ -503,13 +516,13 @@ export async function resolvePageState(input, root, entryUrl, ctx) {
|
|
|
503
516
|
const step = `actions[${i}] (${action.type})`;
|
|
504
517
|
if (action.type === 'goto') {
|
|
505
518
|
if (!action.url)
|
|
506
|
-
throw new PlaywrightMcpError(`${step} needs a url`, '
|
|
519
|
+
throw new PlaywrightMcpError(`${step} needs a url`, 'INVALID_INPUT');
|
|
507
520
|
let next;
|
|
508
521
|
try {
|
|
509
522
|
next = new URL(action.url, current).toString();
|
|
510
523
|
}
|
|
511
524
|
catch {
|
|
512
|
-
throw new PlaywrightMcpError(`${step}: "${action.url}" is not a valid URL`, '
|
|
525
|
+
throw new PlaywrightMcpError(`${step}: "${action.url}" is not a valid URL`, 'INVALID_INPUT');
|
|
513
526
|
}
|
|
514
527
|
assertHttpUrl(next);
|
|
515
528
|
await assertUrlAllowed(next);
|
|
@@ -518,10 +531,10 @@ export async function resolvePageState(input, root, entryUrl, ctx) {
|
|
|
518
531
|
continue;
|
|
519
532
|
}
|
|
520
533
|
if (NEEDS_LOCATOR.has(action.type) && !action.locator) {
|
|
521
|
-
throw new PlaywrightMcpError(`${step} needs a locator`, '
|
|
534
|
+
throw new PlaywrightMcpError(`${step} needs a locator`, 'INVALID_INPUT');
|
|
522
535
|
}
|
|
523
536
|
if ((action.type === 'press' || action.type === 'select') && action.value === undefined) {
|
|
524
|
-
throw new PlaywrightMcpError(`${step} needs a value`, '
|
|
537
|
+
throw new PlaywrightMcpError(`${step} needs a value`, 'INVALID_INPUT');
|
|
525
538
|
}
|
|
526
539
|
const locator = action.locator ? parseLocator(action.locator) : undefined;
|
|
527
540
|
out.actions.push({
|
|
@@ -613,7 +626,7 @@ export async function runBrowserScript(config, options) {
|
|
|
613
626
|
try {
|
|
614
627
|
const outcome = await runProcess(process.execPath, [scriptPath, configPath], {
|
|
615
628
|
cwd: config.projectRoot,
|
|
616
|
-
env: childEnv({ FORCE_COLOR: '0', ...serverless?.env }),
|
|
629
|
+
env: childEnv({ FORCE_COLOR: '0', ...serverless?.env, ...options.env }),
|
|
617
630
|
timeoutMs: options.timeoutMs ?? 45_000,
|
|
618
631
|
signal: options.signal,
|
|
619
632
|
});
|
|
@@ -628,6 +641,26 @@ export async function runBrowserScript(config, options) {
|
|
|
628
641
|
if (outcome.aborted) {
|
|
629
642
|
return { ok: false, kind: 'CLIENT_DISCONNECT', error: 'Inspection aborted (client disconnected).' };
|
|
630
643
|
}
|
|
644
|
+
// Checked before the child's output: killing it closes the browser, and the
|
|
645
|
+
// "browser has been closed" error it prints then is not a crash.
|
|
646
|
+
if (outcome.timedOut) {
|
|
647
|
+
const done = extractJsonFromText(outcome.stdout);
|
|
648
|
+
let steps;
|
|
649
|
+
try {
|
|
650
|
+
steps = done ? JSON.parse(done).actionsDone : undefined;
|
|
651
|
+
}
|
|
652
|
+
catch {
|
|
653
|
+
/* no usable output */
|
|
654
|
+
}
|
|
655
|
+
return {
|
|
656
|
+
ok: false,
|
|
657
|
+
kind: 'TIMEOUT',
|
|
658
|
+
error: `The whole call exceeded timeoutMs (${options.timeoutMs ?? 45_000}ms) and was stopped.`,
|
|
659
|
+
hint: 'Raise timeoutMs. It covers launch, navigation and every action, so keep it above the sum of the waits (each action waits up to actionTimeoutMs, default 10000).',
|
|
660
|
+
...(steps?.length ? { actionsDone: steps } : {}),
|
|
661
|
+
stderrTail: tailText(outcome.stderr),
|
|
662
|
+
};
|
|
663
|
+
}
|
|
631
664
|
const json = extractJsonFromText(outcome.stdout);
|
|
632
665
|
if (json !== null) {
|
|
633
666
|
try {
|
|
@@ -647,15 +680,6 @@ export async function runBrowserScript(config, options) {
|
|
|
647
680
|
/* fall through to generic handling */
|
|
648
681
|
}
|
|
649
682
|
}
|
|
650
|
-
if (outcome.timedOut) {
|
|
651
|
-
return {
|
|
652
|
-
ok: false,
|
|
653
|
-
kind: 'TIMEOUT',
|
|
654
|
-
error: `Page inspection exceeded ${options.timeoutMs ?? 45_000}ms and was killed.`,
|
|
655
|
-
hint: 'The page may be blocking on network requests. Raise timeoutMs or use waitUntil: "domcontentloaded".',
|
|
656
|
-
stderrTail: tailText(outcome.stderr),
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
683
|
return {
|
|
660
684
|
ok: false,
|
|
661
685
|
kind: 'UNKNOWN',
|
|
@@ -681,12 +705,12 @@ export function assertHttpUrl(url) {
|
|
|
681
705
|
parsed = new URL(trimmed);
|
|
682
706
|
}
|
|
683
707
|
catch {
|
|
684
|
-
throw new PlaywrightMcpError(`"${url}" is not a valid URL`, '
|
|
708
|
+
throw new PlaywrightMcpError(`"${url}" is not a valid URL`, 'INVALID_INPUT', {
|
|
685
709
|
hint: 'Use a full URL, e.g. http://localhost:3000/login',
|
|
686
710
|
});
|
|
687
711
|
}
|
|
688
712
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
689
|
-
throw new PlaywrightMcpError(`Unsupported URL scheme "${parsed.protocol}"`, '
|
|
713
|
+
throw new PlaywrightMcpError(`Unsupported URL scheme "${parsed.protocol}"`, 'INVALID_INPUT', {
|
|
690
714
|
hint: 'Only http:// and https:// URLs are supported.',
|
|
691
715
|
});
|
|
692
716
|
}
|