stably 4.12.2 → 4.12.4

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 (24) hide show
  1. package/dist/index.mjs +1 -1
  2. package/dist/node_modules/playwright/lib/cli/daemon/daemon.js +43 -0
  3. package/dist/node_modules/playwright/lib/index.js +32 -1
  4. package/dist/node_modules/playwright/lib/mcp/browser/browserServerBackend.js +27 -0
  5. package/dist/node_modules/playwright/lib/mcp/browser/config.js.rej +11 -0
  6. package/dist/node_modules/playwright/lib/mcp/browser/context.js +52 -0
  7. package/dist/node_modules/playwright/lib/mcp/browser/sessionLog.js +50 -0
  8. package/dist/node_modules/playwright/lib/mcp/test/browserBackend.js +6 -1
  9. package/dist/{playwright-cli.js → stably-browser.js} +22 -14
  10. package/dist/stably-plugin-cli/skills/browser-interaction-guide/SKILL.md +30 -30
  11. package/dist/stably-plugin-cli/skills/bulk-test-handling/SKILL.md +1 -1
  12. package/dist/stably-plugin-cli/skills/debugging-test-failures/SKILL.md +11 -11
  13. package/dist/stably-plugin-cli/skills/playwright-best-practices/SKILL.md +6 -6
  14. package/dist/stably-plugin-cli/skills/playwright-config-auth/SKILL.md +8 -8
  15. package/dist/stably-plugin-cli/skills/{playwright-cli → stably-browser}/SKILL.md +156 -156
  16. package/dist/stably-plugin-cli/skills/{playwright-cli → stably-browser}/references/request-mocking.md +11 -11
  17. package/dist/stably-plugin-cli/skills/{playwright-cli → stably-browser}/references/running-code.md +28 -28
  18. package/dist/stably-plugin-cli/skills/{playwright-cli → stably-browser}/references/session-management.md +40 -40
  19. package/dist/stably-plugin-cli/skills/{playwright-cli → stably-browser}/references/storage-state.md +41 -41
  20. package/dist/stably-plugin-cli/skills/{playwright-cli → stably-browser}/references/test-generation.md +10 -10
  21. package/dist/stably-plugin-cli/skills/{playwright-cli → stably-browser}/references/tracing.md +23 -23
  22. package/dist/stably-plugin-cli/skills/{playwright-cli → stably-browser}/references/video-recording.md +8 -8
  23. package/dist/stably-plugin-cli/skills/test-creation-workflow/SKILL.md +15 -15
  24. package/package.json +2 -2
@@ -75,6 +75,49 @@ async function startMcpDaemonServer(config, contextFactory) {
75
75
  timestamp: Date.now()
76
76
  };
77
77
  const { browserContext, close } = await contextFactory.createContext(clientInfo, new AbortController().signal, {});
78
+ // For CDP connections: resize the browser window to show chrome (tabs, URL bar).
79
+ // Without this, the remote browser's viewport fills the entire window, hiding chrome.
80
+ if (config.browser.cdpEndpoint) {
81
+ try {
82
+ const pages = browserContext.pages();
83
+ const page = pages[0];
84
+ if (page) {
85
+ // Try to load viewport from the user's playwright.config (most authoritative source),
86
+ // then fall back to config.browser.contextOptions.viewport (which may just be the 1280x720 default).
87
+ let viewport = null;
88
+ if (config.playwrightConfigPath) {
89
+ try {
90
+ const { requireOrImport } = require("../../transform/transform");
91
+ let userConfig = await requireOrImport(config.playwrightConfigPath);
92
+ if (userConfig && typeof userConfig === "object" && "default" in userConfig)
93
+ userConfig = userConfig.default;
94
+ viewport = userConfig?.use?.viewport || userConfig?.projects?.[0]?.use?.viewport;
95
+ daemonDebug("extracted viewport from playwright config:", viewport);
96
+ } catch (e) {
97
+ daemonDebug("failed to load playwright config for viewport", e);
98
+ }
99
+ }
100
+ // Fall back to config viewport if playwright.config didn't provide one
101
+ if (!viewport) {
102
+ viewport = config.browser.contextOptions?.viewport;
103
+ }
104
+ if (viewport && viewport.width && viewport.height) {
105
+ await page.setViewportSize(viewport);
106
+ }
107
+ const cdpSession = await browserContext.newCDPSession(page);
108
+ const { windowId } = await cdpSession.send("Browser.getWindowForTarget");
109
+ const vp = viewport || { width: 1280, height: 720 };
110
+ const CHROME_HEADER_HEIGHT = 85;
111
+ await cdpSession.send("Browser.setWindowBounds", {
112
+ windowId,
113
+ bounds: { width: vp.width, height: vp.height + CHROME_HEADER_HEIGHT }
114
+ });
115
+ await cdpSession.detach();
116
+ }
117
+ } catch (e) {
118
+ daemonDebug("failed to resize window for CDP connection (best-effort)", e);
119
+ }
120
+ }
78
121
  browserContext.on("close", () => {
79
122
  daemonDebug("browser closed, shutting down daemon");
80
123
  shutdown(0);
@@ -92,6 +92,21 @@ const playwrightFixtures = {
92
92
  browser: [async ({ playwright, browserName, _browserOptions, connectOptions }, use) => {
93
93
  if (!["chromium", "firefox", "webkit"].includes(browserName))
94
94
  throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
95
+ // CDP endpoint support via environment variable or connectOptions with /browser-cdp/ path
96
+ const cdpEndpoint = process.env.PLAYWRIGHT_CDP_ENDPOINT;
97
+ const connectWsEndpoint = connectOptions == null ? void 0 : connectOptions.wsEndpoint;
98
+ const isCdpWsEndpoint = typeof connectWsEndpoint === "string" && (connectWsEndpoint.includes("/browser-cdp/") || connectWsEndpoint.includes("/browser/cdp"));
99
+ if (cdpEndpoint || isCdpWsEndpoint) {
100
+ if (browserName !== "chromium")
101
+ throw new Error("CDP endpoint is only supported for Chromium browsers");
102
+ const endpoint = cdpEndpoint || connectWsEndpoint;
103
+ const cdpHeadersFromEnv = process.env.PLAYWRIGHT_CDP_HEADERS ? JSON.parse(process.env.PLAYWRIGHT_CDP_HEADERS) : void 0;
104
+ const browser2 = await playwright.chromium.connectOverCDP(endpoint, {
105
+ headers: cdpHeadersFromEnv || (connectOptions == null ? void 0 : connectOptions.headers)
106
+ });
107
+ await use(browser2);
108
+ return;
109
+ }
95
110
  if (connectOptions) {
96
111
  const browser2 = await playwright[browserName].connect({
97
112
  ...connectOptions,
@@ -335,7 +350,13 @@ const playwrightFixtures = {
335
350
  size: typeof video === "string" ? void 0 : video.size
336
351
  }
337
352
  } : {};
338
- const context = await browser.newContext({ ...videoOptions, ...options });
353
+ // When connected via CDP (run-test), reuse the default context instead of
354
+ // creating a new one. This preserves browser state (pages, cookies, etc.)
355
+ // after the test completes, allowing the daemon to interact with the result.
356
+ const isCdpSession = !!process.env.PLAYWRIGHT_CDP_ENDPOINT;
357
+ const context = isCdpSession && browser.contexts().length > 0
358
+ ? browser.contexts()[0]
359
+ : await browser.newContext({ ...videoOptions, ...options });
339
360
  if (process.env.PW_CLOCK === "frozen") {
340
361
  await context._wrapApiCall(async () => {
341
362
  await context.clock.install({ time: 0 });
@@ -351,6 +372,10 @@ const playwrightFixtures = {
351
372
  if (closed)
352
373
  return;
353
374
  closed = true;
375
+ // Skip context.close() when connected via CDP — preserve browser state
376
+ // so the daemon can continue interacting with the test's pages.
377
+ if (isCdpSession)
378
+ return;
354
379
  const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
355
380
  await context.close({ reason: closeReason });
356
381
  const testFailed = testInfo.status !== testInfo.expectedStatus;
@@ -403,6 +428,12 @@ const playwrightFixtures = {
403
428
  await browserImpl._wrapApiCall(() => browserImpl._disconnectFromReusedContext(closeReason), { internal: true });
404
429
  },
405
430
  page: async ({ context, _reuseContext }, use) => {
431
+ // When connected via CDP, reuse the existing page (the daemon's page)
432
+ // instead of creating a new one, so the daemon sees the test's navigation.
433
+ if (process.env.PLAYWRIGHT_CDP_ENDPOINT && context.pages().length > 0) {
434
+ await use(context.pages()[0]);
435
+ return;
436
+ }
406
437
  if (!_reuseContext) {
407
438
  await use(await context.newPage());
408
439
  return;
@@ -27,6 +27,22 @@ var import_response = require("./response");
27
27
  var import_sessionLog = require("./sessionLog");
28
28
  var import_tools = require("./tools");
29
29
  var import_tool = require("../sdk/tool");
30
+ var import_net = require("net");
31
+ function sendRecorderEvent(event) {
32
+ const port = process.env.CLAUDE_RECORDER_PORT;
33
+ if (!port)
34
+ return;
35
+ try {
36
+ const socket = import_net.createConnection({
37
+ port: Number(port),
38
+ host: process.env.CLAUDE_RECORDER_HOST || "127.0.0.1"
39
+ });
40
+ const payload = JSON.stringify(event) + "\n";
41
+ socket.on("error", () => socket.destroy());
42
+ socket.write(payload, () => socket.end());
43
+ } catch (error) {
44
+ }
45
+ }
30
46
  class BrowserServerBackend {
31
47
  constructor(config, factory, options = {}) {
32
48
  this._config = config;
@@ -64,6 +80,17 @@ Tool "${name}" not found` }],
64
80
  await tool.handle(context, parsedArguments, response);
65
81
  responseObject = await response.serialize();
66
82
  this._sessionLog?.logResponse(name, parsedArguments, responseObject);
83
+ // Send recorder event for user action tracking
84
+ const parsed = (0, import_response.parseResponse)(responseObject);
85
+ if (parsed && parsed.code) {
86
+ sendRecorderEvent({
87
+ type: "user-action",
88
+ action: { name: name, args: parsedArguments },
89
+ code: parsed.code,
90
+ url: this._context?.currentTab()?.page?.url() || "",
91
+ timestamp: Date.now()
92
+ });
93
+ }
67
94
  } catch (error) {
68
95
  return {
69
96
  content: [{ type: "text", text: `### Error
@@ -0,0 +1,11 @@
1
+ --- lib/mcp/browser/config.js
2
+ +++ lib/mcp/browser/config.js
3
+ @@ -305,7 +305,7 @@
4
+ function outputDir(config, clientInfo) {
5
+ if (config.outputDir)
6
+ return import_path.default.resolve(config.outputDir);
7
+ const rootPath = (0, import_server2.firstRootPath)(clientInfo);
8
+ if (rootPath)
9
+ - return import_path.default.resolve(rootPath, config.skillMode ? ".playwright-cli" : ".playwright-mcp");
10
+ + return import_path.default.resolve(rootPath, config.skillMode ? ".stably-browser" : ".playwright-mcp");
11
+ const tmpDir = process.env.PW_TMPDIR_FOR_TEST ?? import_os.default.tmpdir();
@@ -40,6 +40,55 @@ var import_log = require("../log");
40
40
  var import_tab = require("./tab");
41
41
  var import_config = require("./config");
42
42
  const testDebug = (0, import_utilsBundle.debug)("pw:mcp:test");
43
+ class InputRecorder {
44
+ constructor(context, browserContext) {
45
+ this._context = context;
46
+ this._browserContext = browserContext;
47
+ }
48
+ static async create(context, browserContext) {
49
+ const recorder = new InputRecorder(context, browserContext);
50
+ await recorder._initialize();
51
+ return recorder;
52
+ }
53
+ async _initialize() {
54
+ const sessionLog = this._context.sessionLog;
55
+ if (!sessionLog) return;
56
+ await this._browserContext._enableRecorder({
57
+ mode: "recording",
58
+ recorderMode: "api"
59
+ }, {
60
+ actionAdded: (page, data, code) => {
61
+ if (this._context.isRunningTool())
62
+ return;
63
+ const tab = import_tab.Tab.forPage(page);
64
+ if (tab)
65
+ sessionLog.logUserAction(data.action, tab, code, false);
66
+ },
67
+ actionUpdated: (page, data, code) => {
68
+ if (this._context.isRunningTool())
69
+ return;
70
+ const tab = import_tab.Tab.forPage(page);
71
+ if (tab)
72
+ sessionLog.logUserAction(data.action, tab, code, true);
73
+ },
74
+ signalAdded: (page, data) => {
75
+ if (this._context.isRunningTool())
76
+ return;
77
+ if (data.signal.name !== "navigation")
78
+ return;
79
+ const tab = import_tab.Tab.forPage(page);
80
+ const navigateAction = {
81
+ name: "navigate",
82
+ url: data.signal.url,
83
+ signals: []
84
+ };
85
+ if (tab)
86
+ sessionLog.logUserAction(navigateAction, tab, `await page.goto('${data.signal.url}');`, false);
87
+ }
88
+ });
89
+ }
90
+ }
91
+
43
92
  class Context {
44
93
  constructor(options) {
45
94
  this._tabs = [];
@@ -251,6 +300,9 @@ class Context {
251
300
  _live: true
252
301
  });
253
302
  }
303
+ // Initialize InputRecorder for user action capture
304
+ if (this.sessionLog)
305
+ await InputRecorder.create(this, browserContext);
254
306
  return result;
255
307
  }
256
308
  lookupSecret(secretName) {
@@ -33,8 +33,24 @@ __export(sessionLog_exports, {
33
33
  module.exports = __toCommonJS(sessionLog_exports);
34
34
  var import_fs = __toESM(require("fs"));
35
35
  var import_path = __toESM(require("path"));
36
+ var import_net = require("net");
36
37
  var import_config = require("./config");
37
38
  var import_response = require("./response");
39
+ function sendRecorderEvent(event) {
40
+ const port = process.env.CLAUDE_RECORDER_PORT;
41
+ if (!port)
42
+ return;
43
+ try {
44
+ const socket = import_net.createConnection({
45
+ port: Number(port),
46
+ host: process.env.CLAUDE_RECORDER_HOST || "127.0.0.1"
47
+ });
48
+ const payload = JSON.stringify(event) + "\n";
49
+ socket.on("error", () => socket.destroy());
50
+ socket.write(payload, () => socket.end());
51
+ } catch (error) {
52
+ }
53
+ }
38
54
  class SessionLog {
39
55
  constructor(sessionFolder) {
40
56
  this._sessionFileQueue = Promise.resolve();
@@ -68,6 +84,40 @@ class SessionLog {
68
84
  lines.push("");
69
85
  this._sessionFileQueue = this._sessionFileQueue.then(() => import_fs.default.promises.appendFile(this._file, lines.join("\n")));
70
86
  }
87
+ logUserAction(action, tab, code, isUpdate) {
88
+ code = code.trim();
89
+ // Send recorder event for user action tracking
90
+ const actionForLog = { ...action };
91
+ delete actionForLog.ariaSnapshot;
92
+ delete actionForLog.selector;
93
+ actionForLog.isUpdate = !!isUpdate;
94
+ sendRecorderEvent({
95
+ type: "user-action",
96
+ action: actionForLog,
97
+ code,
98
+ url: tab.page.url(),
99
+ timestamp: Date.now()
100
+ });
101
+ // Also log to session file
102
+ const lines = [""];
103
+ lines.push(
104
+ `### User action: ${action.name}`,
105
+ "- Args",
106
+ "```json",
107
+ JSON.stringify(actionForLog, null, 2),
108
+ "```"
109
+ );
110
+ if (code) {
111
+ lines.push(
112
+ "- Code",
113
+ "```js",
114
+ code,
115
+ "```"
116
+ );
117
+ }
118
+ lines.push("");
119
+ this._sessionFileQueue = this._sessionFileQueue.then(() => import_fs.default.promises.appendFile(this._file, lines.join("\n")));
120
+ }
71
121
  }
72
122
  // Annotate the CommonJS export names for ESM import in node:
73
123
  0 && (module.exports = {
@@ -32,9 +32,14 @@ function createCustomMessageHandler(testInfo, context) {
32
32
  if (data.initialize) {
33
33
  if (backend)
34
34
  throw new Error("MCP backend is already initialized");
35
- backend = new import_browserServerBackend.BrowserServerBackend({ ...import_config.defaultConfig, capabilities: ["testing"] }, (0, import_browserContextFactory.identityBrowserContextFactory)(context));
35
+ backend = new import_browserServerBackend.BrowserServerBackend({ ...import_config.defaultConfig, capabilities: ["testing"], saveSession: true }, (0, import_browserContextFactory.identityBrowserContextFactory)(context));
36
36
  await backend.initialize(data.initialize.clientInfo);
37
37
  const pausedMessage = await generatePausedMessage(testInfo, context);
38
+ // Ensure recorder UI appears immediately by triggering a benign action that doesn't change page state.
39
+ try {
40
+ await backend.callTool("browser_tabs", { action: "list" });
41
+ } catch (e) {
42
+ }
38
43
  return { initialize: { pausedMessage } };
39
44
  }
40
45
  if (data.listTools) {
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // ../../app/node_modules/.pnpm/@stablyai-internal+playwright-cli@0.4.0/node_modules/@stablyai-internal/playwright-cli/playwright-cli.js
3
+ // ../../app/node_modules/.pnpm/@stablyai-internal+playwright-cli@0.4.6/node_modules/@stablyai-internal/playwright-cli/playwright-cli.js
4
4
  var fs = require("fs");
5
5
  var path = require("path");
6
6
  var argv = process.argv.slice(2);
@@ -8,7 +8,7 @@ var cmdIndex = argv.findIndex((arg) => !arg.startsWith("-"));
8
8
  if (cmdIndex !== -1 && argv[cmdIndex] === "run-file") {
9
9
  const fileArg = argv[cmdIndex + 1];
10
10
  if (!fileArg) {
11
- console.error("Usage: playwright-cli run-file <path>");
11
+ console.error("Usage: stably-browser run-file <path>");
12
12
  process.exit(1);
13
13
  }
14
14
  const filePath = path.resolve(fileArg);
@@ -23,7 +23,7 @@ if (cmdIndex !== -1 && argv[cmdIndex] === "run-file") {
23
23
  }
24
24
  if (cmdIndex !== -1 && argv[cmdIndex] === "run-test") {
25
25
  if (argv.includes("--help") || argv.includes("-h")) {
26
- console.log(`Usage: playwright-cli run-test [test-filter] [options]
26
+ console.log(`Usage: stably-browser run-test [test-filter] [options]
27
27
 
28
28
  Runs Playwright tests connected to the managed browser via CDP.
29
29
  Tests connect to the existing browser session instead of launching a new one.
@@ -147,7 +147,7 @@ Notes:
147
147
  const cwd = process.cwd();
148
148
  const wrapperConfigPath = path.join(
149
149
  cwd,
150
- `.playwright-cli-run-test-${crypto.randomUUID()}.config.js`
150
+ `.stably-browser-run-test-${crypto.randomUUID()}.config.js`
151
151
  );
152
152
  let userConfigImport = "";
153
153
  for (const name of ["playwright.config.ts", "playwright.config.js", "playwright.config.mjs"]) {
@@ -161,7 +161,7 @@ const userConfig = userConfigModule.default || userConfigModule;`;
161
161
  if (!userConfigImport) {
162
162
  userConfigImport = `const userConfig = {};`;
163
163
  }
164
- const tmpDir = path.join(os.tmpdir(), `playwright-cli-${crypto.randomUUID()}`);
164
+ const tmpDir = path.join(os.tmpdir(), `stably-browser-${crypto.randomUUID()}`);
165
165
  fs.mkdirSync(tmpDir, { recursive: true });
166
166
  let realPwTestPath;
167
167
  try {
@@ -172,7 +172,7 @@ const userConfig = userConfigModule.default || userConfigModule;`;
172
172
  }
173
173
  const fixturesPath = path.join(tmpDir, "stably-cdp-fixtures.js");
174
174
  const fixturesCode = `
175
- // Auto-generated by playwright-cli run-test
175
+ // Auto-generated by stably-browser run-test
176
176
  // Overrides browser/context/page fixtures to connect via CDP and preserve state.
177
177
  const realPw = require(${JSON.stringify(realPwTestPath)});
178
178
 
@@ -226,7 +226,7 @@ module.exports = { ...realPw, test, default: test };
226
226
  fs.writeFileSync(fixturesPath, fixturesCode);
227
227
  const shimPath = path.join(tmpDir, "stably-require-shim.js");
228
228
  const shimCode = `
229
- // Auto-generated by playwright-cli run-test
229
+ // Auto-generated by stably-browser run-test
230
230
  // Intercepts require('@playwright/test') to use CDP-aware fixtures.
231
231
  const Module = require('module');
232
232
  const originalResolve = Module._resolveFilename;
@@ -241,7 +241,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
241
241
  `;
242
242
  fs.writeFileSync(shimPath, shimCode);
243
243
  const wrapperConfig = `
244
- // Auto-generated by playwright-cli run-test \u2014 connects to daemon browser via CDP.
244
+ // Auto-generated by stably-browser run-test \u2014 connects to daemon browser via CDP.
245
245
  const { defineConfig } = require('@playwright/test');
246
246
  ${userConfigImport}
247
247
 
@@ -282,12 +282,16 @@ module.exports = defineConfig({
282
282
  `;
283
283
  fs.writeFileSync(wrapperConfigPath, wrapperConfig);
284
284
  const testArgs = argv.slice(cmdIndex + 1).filter((a) => !a.startsWith("--config=") && !a.startsWith("--session=") && !a.startsWith("-s=") && a !== "--headed");
285
- // Resolve @playwright/test CLI binary to avoid version mismatch with playwright package
286
- var _runBin = "npx", _runArgs = ["playwright", "test"];
287
- try { var _ptPkg = require.resolve("@playwright/test/package.json", { paths: [cwd] });
288
- var _ptCli = path.join(path.dirname(_ptPkg), "cli.js");
289
- if (fs.existsSync(_ptCli)) { _runBin = process.execPath; _runArgs = [_ptCli, "test"]; }
290
- } catch (_) {}
285
+ _runBin = "npx", _runArgs = ["playwright", "test"];
286
+ try {
287
+ _ptPkg = require.resolve("@playwright/test/package.json", { paths: [cwd] });
288
+ _ptCli = path.join(path.dirname(_ptPkg), "cli.js");
289
+ if (fs.existsSync(_ptCli)) {
290
+ _runBin = process.execPath;
291
+ _runArgs = [_ptCli, "test"];
292
+ }
293
+ } catch (_) {
294
+ }
291
295
  let exitCode = 0;
292
296
  try {
293
297
  execFileSync(_runBin, [..._runArgs, "--config=" + wrapperConfigPath, ...testArgs], {
@@ -313,6 +317,10 @@ module.exports = defineConfig({
313
317
  }
314
318
  process.exit(exitCode);
315
319
  }
320
+ var _runBin;
321
+ var _runArgs;
322
+ var _ptPkg;
323
+ var _ptCli;
316
324
  if (cmdIndex !== -1 && argv[cmdIndex] === "open") {
317
325
  const cwd = process.cwd();
318
326
  for (const name of ["playwright.config.ts", "playwright.config.js", "playwright.config.mjs"]) {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: browser-interaction-guide
3
- description: Guide for browser interactions in Playwright tests including playwright-cli commands, wait patterns, headless mode, locator best practices, and browser cleanup. Use when setting up browser sessions, writing wait patterns, fixing locator issues, or debugging headless failures. Triggers on browser setup, playwright-cli, run-test, snapshot, wait pattern, waitFor, headless, locator, browser cleanup.
3
+ description: Guide for browser interactions in Playwright tests including stably-browser commands, wait patterns, headless mode, locator best practices, and browser cleanup. Use when setting up browser sessions, writing wait patterns, fixing locator issues, or debugging headless failures. Triggers on browser setup, stably-browser, run-test, snapshot, wait pattern, waitFor, headless, locator, browser cleanup.
4
4
  ---
5
5
 
6
6
  # Browser Interaction Guide
@@ -9,47 +9,47 @@ Guide for browser interactions in Playwright tests.
9
9
 
10
10
  ## Browser Setup
11
11
 
12
- Use `playwright-cli` commands for browser interaction. The browser starts lazily on first command.
12
+ Use `stably-browser` commands for browser interaction. The browser starts lazily on first command.
13
13
 
14
14
  ```bash
15
15
  # Open a browser and navigate
16
- playwright-cli open https://example.com
16
+ stably-browser open https://example.com
17
17
 
18
18
  # Or open then navigate
19
- playwright-cli open
20
- playwright-cli goto https://example.com
19
+ stably-browser open
20
+ stably-browser goto https://example.com
21
21
  ```
22
22
 
23
23
  ### Browser Persists After run-test
24
24
 
25
- `playwright-cli run-test` auto-starts a browser if needed and preserves the session after tests finish. You can immediately use `playwright-cli snapshot`, `playwright-cli click`, etc. to inspect page state. No need to run `playwright-cli open` before or after `run-test`.
25
+ `stably-browser run-test` auto-starts a browser if needed and preserves the session after tests finish. You can immediately use `stably-browser snapshot`, `stably-browser click`, etc. to inspect page state. No need to run `stably-browser open` before or after `run-test`.
26
26
 
27
27
  ### Reset Browser Between Independent Test Verifications
28
28
 
29
- **CRITICAL:** When verifying that multiple independent tests pass, you **MUST** run `playwright-cli close` between each `run-test` call. Browser state (cookies, localStorage, auth sessions) persists across runs, so a stale or logged-in state from one test will contaminate the next — causing cascading login failures or unexpected behavior.
29
+ **CRITICAL:** When verifying that multiple independent tests pass, you **MUST** run `stably-browser close` between each `run-test` call. Browser state (cookies, localStorage, auth sessions) persists across runs, so a stale or logged-in state from one test will contaminate the next — causing cascading login failures or unexpected behavior.
30
30
 
31
- **IMPORTANT: `playwright-cli close` must be its own separate Bash call.** Do NOT chain it with other commands (e.g., `playwright-cli close; playwright-cli run-test ...`). The browser is only fully terminated after the Bash command finishes, so chaining means the next command runs on the old browser with stale state.
31
+ **IMPORTANT: `stably-browser close` must be its own separate Bash call.** Do NOT chain it with other commands (e.g., `stably-browser close; stably-browser run-test ...`). The browser is only fully terminated after the Bash command finishes, so chaining means the next command runs on the old browser with stale state.
32
32
 
33
33
  This does NOT apply when intentionally using `run-test` as a setup/seed mechanism to build state for subsequent interaction.
34
34
 
35
35
  ```bash
36
36
  # CORRECT: Each command is a separate Bash call
37
- playwright-cli run-test tests/test-a.spec.ts
38
- playwright-cli close
39
- playwright-cli run-test tests/test-b.spec.ts
40
- playwright-cli close
37
+ stably-browser run-test tests/test-a.spec.ts
38
+ stably-browser close
39
+ stably-browser run-test tests/test-b.spec.ts
40
+ stably-browser close
41
41
  ```
42
42
 
43
43
  ```bash
44
44
  # WRONG: Chaining close with run-test in one command
45
- playwright-cli close; playwright-cli run-test tests/test-b.spec.ts # ← run-test hits old browser
46
- playwright-cli close && playwright-cli run-test tests/test-b.spec.ts # ← same problem
45
+ stably-browser close; stably-browser run-test tests/test-b.spec.ts # ← run-test hits old browser
46
+ stably-browser close && stably-browser run-test tests/test-b.spec.ts # ← same problem
47
47
  ```
48
48
 
49
49
  ```bash
50
50
  # WRONG: Running independent tests without resetting
51
- playwright-cli run-test tests/test-a.spec.ts
52
- playwright-cli run-test tests/test-b.spec.ts # ← may fail: stale state from test-a
51
+ stably-browser run-test tests/test-a.spec.ts
52
+ stably-browser run-test tests/test-b.spec.ts # ← may fail: stale state from test-a
53
53
  ```
54
54
 
55
55
  ### Tool Decision Tree
@@ -57,19 +57,19 @@ playwright-cli run-test tests/test-b.spec.ts # ← may fail: stale state from
57
57
  ```
58
58
  What do you need to do?
59
59
  ├── Explore a page interactively
60
- │ └── playwright-cli open <url>
60
+ │ └── stably-browser open <url>
61
61
  │ Read snapshot from output → interact with refs directly
62
62
 
63
63
  ├── Run a test then explore (auth setup, state setup)
64
- │ └── playwright-cli run-test <seedFile> --project=<project>
64
+ │ └── stably-browser run-test <seedFile> --project=<project>
65
65
  │ Then: click/fill/snapshot directly (browser stays open)
66
66
 
67
67
  ├── Debug a failing test
68
- │ └── playwright-cli run-test <file>
69
- │ Read output → inspect with playwright-cli snapshot if needed
68
+ │ └── stably-browser run-test <file>
69
+ │ Read output → inspect with stably-browser snapshot if needed
70
70
 
71
71
  └── Verify a test passes
72
- └── playwright-cli run-test <file>
72
+ └── stably-browser run-test <file>
73
73
  ```
74
74
 
75
75
  For project selection (auth dependencies), see `playwright-config-auth` skill.
@@ -78,13 +78,13 @@ For project selection (auth dependencies), see `playwright-config-auth` skill.
78
78
 
79
79
  | Error Type | Action |
80
80
  |------------|--------|
81
- | **Test code error** (assertion failed, locator not found) | Test the locator interactively with `playwright-cli run-code` first (seconds vs minutes), then fix code and re-run with `playwright-cli run-test` |
81
+ | **Test code error** (assertion failed, locator not found) | Test the locator interactively with `stably-browser run-code` first (seconds vs minutes), then fix code and re-run with `stably-browser run-test` |
82
82
  | **External service error** (timeout, network) | Retry up to 3 times |
83
83
  | **Same error 3+ times** | Flag as blocked service issue, do not keep retrying |
84
84
 
85
85
  ## Browser Cleanup
86
86
 
87
- Close the browser when done with `playwright-cli close`.
87
+ Close the browser when done with `stably-browser close`.
88
88
 
89
89
  ## Wait Patterns
90
90
 
@@ -206,11 +206,11 @@ When you need to type or use environment variable values during browser interact
206
206
 
207
207
  ```bash
208
208
  # CORRECT — single quotes preserve the literal ${process.env.X} for substitution
209
- playwright-cli fill e3 '${process.env.ACCOUNT_PASSWORD}'
210
- playwright-cli goto 'https://${process.env.BASE_URL}/dashboard'
209
+ stably-browser fill e3 '${process.env.ACCOUNT_PASSWORD}'
210
+ stably-browser goto 'https://${process.env.BASE_URL}/dashboard'
211
211
 
212
212
  # WRONG — double quotes cause bash to expand ${process.env.X} as empty
213
- playwright-cli fill e3 "${process.env.ACCOUNT_PASSWORD}"
213
+ stably-browser fill e3 "${process.env.ACCOUNT_PASSWORD}"
214
214
  ```
215
215
 
216
216
  ## Browser Interaction Strategy (Priority Order)
@@ -223,13 +223,13 @@ Don't prematurely optimize with custom JS. Slower but reliable `agent.act()` is
223
223
 
224
224
  ## Page State Snapshots
225
225
 
226
- Every `playwright-cli` command that changes page state (`open`, `goto`, `click`, `fill`, `run-test`, etc.) returns a snapshot file path in its output. **Read that snapshot file directly — do not call `playwright-cli snapshot` again.** Refs (e1, e2, etc.) are only valid from the most recent snapshot. After any command that returns a new snapshot, previous refs are stale.
226
+ Every `stably-browser` command that changes page state (`open`, `goto`, `click`, `fill`, `run-test`, etc.) returns a snapshot file path in its output. **Read that snapshot file directly — do not call `stably-browser snapshot` again.** Refs (e1, e2, etc.) are only valid from the most recent snapshot. After any command that returns a new snapshot, previous refs are stale.
227
227
 
228
228
  **Efficient workflow:**
229
- 1. Run a command (e.g., `playwright-cli open https://example.com`)
229
+ 1. Run a command (e.g., `stably-browser open https://example.com`)
230
230
  2. Read the snapshot `.yml` file from the command output
231
231
  3. Use the refs from that file immediately for the next interaction
232
232
 
233
- Only use `playwright-cli snapshot` when you need to re-inspect the page without performing any action.
233
+ Only use `stably-browser snapshot` when you need to re-inspect the page without performing any action.
234
234
 
235
- For large pages, the snapshot `.yml` file may be too large for the Read tool. Use `Grep` to search within the `.playwright-cli/` directory for specific elements.
235
+ For large pages, the snapshot `.yml` file may be too large for the Read tool. Use `Grep` to search within the `.stably-browser/` directory for specific elements.
@@ -73,7 +73,7 @@ Keep the user informed:
73
73
 
74
74
  ### Between Tests (Browser Reset)
75
75
 
76
- **CRITICAL:** When verifying independent tests, run `playwright-cli close` after each `run-test` call before starting the next test. Browser state (cookies, auth, localStorage) persists between runs, causing cascading failures if not reset. **`playwright-cli close` must be its own separate Bash call** — do NOT chain it with other commands (e.g., `close; run-test`). The browser is only fully terminated after the Bash command finishes.
76
+ **CRITICAL:** When verifying independent tests, run `stably-browser close` after each `run-test` call before starting the next test. Browser state (cookies, auth, localStorage) persists between runs, causing cascading failures if not reset. **`stably-browser close` must be its own separate Bash call** — do NOT chain it with other commands (e.g., `close; run-test`). The browser is only fully terminated after the Bash command finishes.
77
77
 
78
78
  ### Between Batches - Check Your System Prompt
79
79
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: debugging-test-failures
3
- description: Guide for debugging test execution issues including assertion failures, playwright-cli errors, test setup problems, browser errors, and flaky tests. Use when tests fail, playwright-cli fails, browser setup fails, or tests are flaky. Triggers on test failure, debug, playwright-cli error, test won't run, setup failed, CDP error, flaky test, timeout.
3
+ description: Guide for debugging test execution issues including assertion failures, stably-browser errors, test setup problems, browser errors, and flaky tests. Use when tests fail, stably-browser fails, browser setup fails, or tests are flaky. Triggers on test failure, debug, stably-browser error, test won't run, setup failed, CDP error, flaky test, timeout.
4
4
  ---
5
5
 
6
6
  # Debugging Test Failures
@@ -55,9 +55,9 @@ Five types of issues cause test failures:
55
55
 
56
56
  **In `fix` mode**: Delegate browser-based exploration to the `debug-worker` subagent.
57
57
 
58
- **In `single`, `build`, `chat` modes**: Run `playwright-cli run-test` directly (no debug subagent available). Browser persists after test runs for inspection via `playwright-cli snapshot`.
58
+ **In `single`, `build`, `chat` modes**: Run `stably-browser run-test` directly (no debug subagent available). Browser persists after test runs for inspection via `stably-browser snapshot`.
59
59
 
60
- **IMPORTANT:** When verifying multiple independent tests, run `playwright-cli close` between each `run-test` call. Stale browser state (cookies, auth sessions, localStorage) from one test will contaminate the next, causing cascading failures — especially at login. **`playwright-cli close` must be its own separate Bash call** — do NOT chain it with other commands (e.g., `close; run-test`). The browser is only fully terminated after the Bash command finishes.
60
+ **IMPORTANT:** When verifying multiple independent tests, run `stably-browser close` between each `run-test` call. Stale browser state (cookies, auth sessions, localStorage) from one test will contaminate the next, causing cascading failures — especially at login. **`stably-browser close` must be its own separate Bash call** — do NOT chain it with other commands (e.g., `close; run-test`). The browser is only fully terminated after the Bash command finishes.
61
61
 
62
62
  ### When to Use Subagents
63
63
 
@@ -74,7 +74,7 @@ Five types of issues cause test failures:
74
74
 
75
75
  ## Debugging Workflow
76
76
 
77
- 1. **Run the test** and read the error output from `playwright-cli run-test`
77
+ 1. **Run the test** and read the error output from `stably-browser run-test`
78
78
  2. **Read `error-context.md`**: On failure, check `test-results/<test-name>/error-context.md`. This is the page accessibility snapshot at the moment of failure — it shows every visible element, their roles, names, and states. This tells you what the page *actually looked like* when things went wrong.
79
79
  3. **Classify the failure by symptom:**
80
80
 
@@ -94,7 +94,7 @@ Five types of issues cause test failures:
94
94
 
95
95
  ## When to Open the Browser for Manual Exploration
96
96
 
97
- **Open the browser** (`playwright-cli open` or use the persisted session after `run-test`) when:
97
+ **Open the browser** (`stably-browser open` or use the persisted session after `run-test`) when:
98
98
  - The error-context doesn't explain the failure — you can see the page state but not *why* the selector doesn't match
99
99
  - You need to discover interactive behavior (dropdowns, modals, hover states, animations) that a static snapshot can't capture
100
100
  - You're writing or rewriting a test for a flow you haven't seen before
@@ -117,17 +117,17 @@ Five types of issues cause test failures:
117
117
 
118
118
  ### Use `run-code` to Debug Locators Interactively
119
119
 
120
- **If a locator fails on the first `run-test`, don't immediately retry `run-test`.** Open the browser and test the locator interactively with `playwright-cli run-code` — it gives results in seconds instead of waiting for a full test run (~2 minutes).
120
+ **If a locator fails on the first `run-test`, don't immediately retry `run-test`.** Open the browser and test the locator interactively with `stably-browser run-code` — it gives results in seconds instead of waiting for a full test run (~2 minutes).
121
121
 
122
122
  ```bash
123
123
  # Test a locator and see what it actually returns
124
- playwright-cli run-code "async page => {
124
+ stably-browser run-code "async page => {
125
125
  const el = page.getByRole('button', { name: 'Submit' });
126
126
  return { count: await el.count(), text: await el.first().textContent() };
127
127
  }"
128
128
 
129
129
  # Explore DOM structure around an element
130
- playwright-cli run-code "async page => {
130
+ stably-browser run-code "async page => {
131
131
  let el = page.getByText('Filters', { exact: true });
132
132
  for (let i = 1; i <= 5; i++) {
133
133
  el = el.locator('..');
@@ -162,12 +162,12 @@ If you encounter `browserType.connectOverCDP` errors like "Target page has been
162
162
  This is a stale browser session issue, NOT a test or config problem.
163
163
 
164
164
  **Fix:**
165
- 1. Run `playwright-cli close` to stop the current browser session
166
- 2. Retry your operation — `playwright-cli open` will start a fresh browser
165
+ 1. Run `stably-browser close` to stop the current browser session
166
+ 2. Retry your operation — `stably-browser open` will start a fresh browser
167
167
 
168
168
  ## Empty run-test Output
169
169
 
170
- If `playwright-cli run-test` returns empty/no output (no pass, no fail, no error):
170
+ If `stably-browser run-test` returns empty/no output (no pass, no fail, no error):
171
171
 
172
172
  This is NOT a tool malfunction. Empty output means zero tests matched the project filters.
173
173