stably 4.10.3 → 4.10.5

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 (39) hide show
  1. package/dist/index.mjs +1 -1
  2. package/dist/stably-plugin-cli/skills/browser-interaction-guide/SKILL.md +21 -0
  3. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/accessibility.md +359 -0
  4. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/annotations.md +526 -0
  5. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/assertions-waiting.md +361 -0
  6. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/browser-apis.md +391 -0
  7. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/browser-extensions.md +506 -0
  8. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/canvas-webgl.md +493 -0
  9. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/ci-cd.md +407 -0
  10. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/clock-mocking.md +364 -0
  11. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/component-testing.md +500 -0
  12. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/console-errors.md +420 -0
  13. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/debugging.md +491 -0
  14. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/electron.md +509 -0
  15. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/error-testing.md +360 -0
  16. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/file-operations.md +375 -0
  17. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/fixtures-hooks.md +417 -0
  18. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/flaky-tests.md +494 -0
  19. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/global-setup.md +434 -0
  20. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/i18n.md +508 -0
  21. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/iframes.md +403 -0
  22. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/locators.md +242 -0
  23. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/mobile-testing.md +409 -0
  24. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/multi-context.md +288 -0
  25. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/multi-user.md +393 -0
  26. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/network-advanced.md +452 -0
  27. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/page-object-model.md +315 -0
  28. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/performance-testing.md +476 -0
  29. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/performance.md +453 -0
  30. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/projects-dependencies.md +456 -0
  31. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/security-testing.md +430 -0
  32. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/service-workers.md +504 -0
  33. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/test-coverage.md +495 -0
  34. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/test-data.md +492 -0
  35. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/test-organization.md +361 -0
  36. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/third-party.md +464 -0
  37. package/dist/stably-plugin-cli/skills/playwright-best-practices/references/websockets.md +403 -0
  38. package/dist/stably-plugin-cli/skills/stably-cli/SKILL.md +254 -0
  39. package/package.json +2 -2
@@ -0,0 +1,403 @@
1
+ # WebSocket & Real-Time Testing
2
+
3
+ ## Table of Contents
4
+
5
+ 1. [WebSocket Basics](#websocket-basics)
6
+ 2. [Mocking WebSocket Messages](#mocking-websocket-messages)
7
+ 3. [Testing Real-Time Features](#testing-real-time-features)
8
+ 4. [Server-Sent Events](#server-sent-events)
9
+ 5. [Reconnection Testing](#reconnection-testing)
10
+
11
+ ## WebSocket Basics
12
+
13
+ ### Wait for WebSocket Connection
14
+
15
+ ```typescript
16
+ test("chat connects via websocket", async ({ page }) => {
17
+ // Listen for WebSocket connection
18
+ const wsPromise = page.waitForEvent("websocket");
19
+
20
+ await page.goto("/chat");
21
+
22
+ const ws = await wsPromise;
23
+ expect(ws.url()).toContain("/ws/chat");
24
+
25
+ // Wait for connection to be established
26
+ await ws.waitForEvent("framesent");
27
+ });
28
+ ```
29
+
30
+ ### Monitor WebSocket Messages
31
+
32
+ ```typescript
33
+ test("receives real-time updates", async ({ page }) => {
34
+ const messages: string[] = [];
35
+
36
+ // Set up listener before navigation
37
+ page.on("websocket", (ws) => {
38
+ ws.on("framereceived", (frame) => {
39
+ messages.push(frame.payload as string);
40
+ });
41
+ });
42
+
43
+ await page.goto("/dashboard");
44
+
45
+ // Wait for some messages
46
+ await expect.poll(() => messages.length).toBeGreaterThan(0);
47
+
48
+ // Verify message format
49
+ const data = JSON.parse(messages[0]);
50
+ expect(data).toHaveProperty("type");
51
+ });
52
+ ```
53
+
54
+ ### Capture Sent Messages
55
+
56
+ ```typescript
57
+ test("sends correct message format", async ({ page }) => {
58
+ const sentMessages: string[] = [];
59
+
60
+ page.on("websocket", (ws) => {
61
+ ws.on("framesent", (frame) => {
62
+ sentMessages.push(frame.payload as string);
63
+ });
64
+ });
65
+
66
+ await page.goto("/chat");
67
+ await page.getByLabel("Message").fill("Hello!");
68
+ await page.getByRole("button", { name: "Send" }).click();
69
+
70
+ // Verify sent message
71
+ await expect.poll(() => sentMessages.length).toBeGreaterThan(0);
72
+
73
+ const sent = JSON.parse(sentMessages[sentMessages.length - 1]);
74
+ expect(sent).toEqual({
75
+ type: "message",
76
+ content: "Hello!",
77
+ });
78
+ });
79
+ ```
80
+
81
+ ## Mocking WebSocket Messages
82
+
83
+ ### Inject Messages via Page Evaluate
84
+
85
+ ```typescript
86
+ test("displays incoming chat message", async ({ page }) => {
87
+ await page.goto("/chat");
88
+
89
+ // Wait for WebSocket to be ready
90
+ await page.waitForFunction(
91
+ () => (window as any).chatSocket?.readyState === 1,
92
+ );
93
+
94
+ // Simulate incoming message
95
+ await page.evaluate(() => {
96
+ const event = new MessageEvent("message", {
97
+ data: JSON.stringify({
98
+ type: "message",
99
+ from: "Alice",
100
+ content: "Hello there!",
101
+ }),
102
+ });
103
+ (window as any).chatSocket.dispatchEvent(event);
104
+ });
105
+
106
+ await expect(page.getByText("Alice: Hello there!")).toBeVisible();
107
+ });
108
+ ```
109
+
110
+ ### Mock WebSocket with Route Handler
111
+
112
+ ```typescript
113
+ test("mock websocket entirely", async ({ page, context }) => {
114
+ // Intercept the WebSocket upgrade
115
+ await context.route("**/ws/**", async (route) => {
116
+ // For WebSocket routes, we can't fulfill directly
117
+ // Instead, use page.evaluate to mock the client-side
118
+ });
119
+
120
+ // Alternative: Mock at application level
121
+ await page.addInitScript(() => {
122
+ const OriginalWebSocket = window.WebSocket;
123
+ (window as any).WebSocket = function (url: string) {
124
+ const ws = {
125
+ readyState: 1,
126
+ send: (data: string) => {
127
+ console.log("WS Send:", data);
128
+ },
129
+ close: () => {},
130
+ addEventListener: () => {},
131
+ removeEventListener: () => {},
132
+ };
133
+ setTimeout(() => ws.onopen?.(), 100);
134
+ return ws;
135
+ };
136
+ });
137
+
138
+ await page.goto("/chat");
139
+ });
140
+ ```
141
+
142
+ ### WebSocket Mock Fixture
143
+
144
+ ```typescript
145
+ // fixtures/websocket.fixture.ts
146
+ import { test as base, Page } from "@playwright/test";
147
+
148
+ type WsMessage = { type: string; [key: string]: any };
149
+
150
+ type WebSocketFixtures = {
151
+ mockWebSocket: {
152
+ injectMessage: (message: WsMessage) => Promise<void>;
153
+ getSentMessages: () => Promise<WsMessage[]>;
154
+ };
155
+ };
156
+
157
+ export const test = base.extend<WebSocketFixtures>({
158
+ mockWebSocket: async ({ page }, use) => {
159
+ const sentMessages: WsMessage[] = [];
160
+
161
+ // Capture sent messages
162
+ await page.addInitScript(() => {
163
+ (window as any).__wsSent = [];
164
+ const OriginalWebSocket = window.WebSocket;
165
+ window.WebSocket = function (url: string) {
166
+ const ws = new OriginalWebSocket(url);
167
+ const originalSend = ws.send.bind(ws);
168
+ ws.send = (data: string) => {
169
+ (window as any).__wsSent.push(JSON.parse(data));
170
+ originalSend(data);
171
+ };
172
+ (window as any).__ws = ws;
173
+ return ws;
174
+ } as any;
175
+ });
176
+
177
+ await use({
178
+ injectMessage: async (message) => {
179
+ await page.evaluate((msg) => {
180
+ const event = new MessageEvent("message", {
181
+ data: JSON.stringify(msg),
182
+ });
183
+ (window as any).__ws?.dispatchEvent(event);
184
+ }, message);
185
+ },
186
+ getSentMessages: async () => {
187
+ return page.evaluate(() => (window as any).__wsSent || []);
188
+ },
189
+ });
190
+ },
191
+ });
192
+
193
+ // Usage
194
+ test("chat with mocked websocket", async ({ page, mockWebSocket }) => {
195
+ await page.goto("/chat");
196
+
197
+ // Inject incoming message
198
+ await mockWebSocket.injectMessage({
199
+ type: "message",
200
+ from: "Bob",
201
+ content: "Hi!",
202
+ });
203
+
204
+ await expect(page.getByText("Bob: Hi!")).toBeVisible();
205
+
206
+ // Send a reply
207
+ await page.getByLabel("Message").fill("Hello Bob!");
208
+ await page.getByRole("button", { name: "Send" }).click();
209
+
210
+ // Verify sent message
211
+ const sent = await mockWebSocket.getSentMessages();
212
+ expect(sent).toContainEqual(
213
+ expect.objectContaining({ content: "Hello Bob!" }),
214
+ );
215
+ });
216
+ ```
217
+
218
+ ## Testing Real-Time Features
219
+
220
+ ### Live Notifications
221
+
222
+ ```typescript
223
+ test("displays live notification", async ({ page }) => {
224
+ await page.goto("/dashboard");
225
+
226
+ // Simulate notification via WebSocket
227
+ await page.evaluate(() => {
228
+ const event = new MessageEvent("message", {
229
+ data: JSON.stringify({
230
+ type: "notification",
231
+ title: "New Order",
232
+ message: "Order #123 received",
233
+ }),
234
+ });
235
+ (window as any).notificationSocket.dispatchEvent(event);
236
+ });
237
+
238
+ await expect(page.getByRole("alert")).toContainText("Order #123 received");
239
+ });
240
+ ```
241
+
242
+ ### Live Data Updates
243
+
244
+ ```typescript
245
+ test("updates stock price in real-time", async ({ page }) => {
246
+ await page.goto("/stocks/AAPL");
247
+
248
+ const priceElement = page.getByTestId("stock-price");
249
+ const initialPrice = await priceElement.textContent();
250
+
251
+ // Simulate price update
252
+ await page.evaluate(() => {
253
+ const event = new MessageEvent("message", {
254
+ data: JSON.stringify({
255
+ type: "price_update",
256
+ symbol: "AAPL",
257
+ price: 150.25,
258
+ }),
259
+ });
260
+ (window as any).stockSocket.dispatchEvent(event);
261
+ });
262
+
263
+ await expect(priceElement).not.toHaveText(initialPrice!);
264
+ await expect(priceElement).toContainText("150.25");
265
+ });
266
+ ```
267
+
268
+ ### Collaborative Editing
269
+
270
+ ```typescript
271
+ test("shows collaborator cursor", async ({ page }) => {
272
+ await page.goto("/document/123");
273
+
274
+ // Simulate another user's cursor position
275
+ await page.evaluate(() => {
276
+ const event = new MessageEvent("message", {
277
+ data: JSON.stringify({
278
+ type: "cursor",
279
+ userId: "user-456",
280
+ userName: "Alice",
281
+ position: { x: 100, y: 200 },
282
+ }),
283
+ });
284
+ (window as any).docSocket.dispatchEvent(event);
285
+ });
286
+
287
+ await expect(page.getByTestId("cursor-user-456")).toBeVisible();
288
+ await expect(page.getByText("Alice")).toBeVisible();
289
+ });
290
+ ```
291
+
292
+ ## Server-Sent Events
293
+
294
+ ### Test SSE Updates
295
+
296
+ ```typescript
297
+ test("receives SSE updates", async ({ page }) => {
298
+ // Mock SSE endpoint
299
+ await page.route("**/api/events", (route) => {
300
+ route.fulfill({
301
+ status: 200,
302
+ headers: {
303
+ "Content-Type": "text/event-stream",
304
+ "Cache-Control": "no-cache",
305
+ Connection: "keep-alive",
306
+ },
307
+ body: `data: {"type":"update","value":42}\n\n`,
308
+ });
309
+ });
310
+
311
+ await page.goto("/live-data");
312
+
313
+ await expect(page.getByTestId("value")).toHaveText("42");
314
+ });
315
+ ```
316
+
317
+ ### Simulate Multiple SSE Events
318
+
319
+ ```typescript
320
+ test("handles multiple SSE events", async ({ page }) => {
321
+ await page.route("**/api/events", async (route) => {
322
+ const encoder = new TextEncoder();
323
+ const events = [
324
+ `data: {"count":1}\n\n`,
325
+ `data: {"count":2}\n\n`,
326
+ `data: {"count":3}\n\n`,
327
+ ];
328
+
329
+ route.fulfill({
330
+ status: 200,
331
+ headers: { "Content-Type": "text/event-stream" },
332
+ body: events.join(""),
333
+ });
334
+ });
335
+
336
+ await page.goto("/counter");
337
+
338
+ // Should receive all events
339
+ await expect(page.getByTestId("count")).toHaveText("3");
340
+ });
341
+ ```
342
+
343
+ ## Reconnection Testing
344
+
345
+ ### Test Connection Loss
346
+
347
+ ```typescript
348
+ test("handles connection loss gracefully", async ({ page }) => {
349
+ await page.goto("/chat");
350
+
351
+ // Simulate connection close
352
+ await page.evaluate(() => {
353
+ (window as any).chatSocket.close();
354
+ });
355
+
356
+ // Should show disconnected state
357
+ await expect(page.getByText("Reconnecting...")).toBeVisible();
358
+ });
359
+ ```
360
+
361
+ ### Test Reconnection
362
+
363
+ ```typescript
364
+ test("reconnects after connection loss", async ({ page }) => {
365
+ await page.goto("/chat");
366
+
367
+ // Simulate disconnect
368
+ await page.evaluate(() => {
369
+ (window as any).chatSocket.close();
370
+ });
371
+
372
+ await expect(page.getByText("Reconnecting...")).toBeVisible();
373
+
374
+ // Simulate reconnection
375
+ await page.evaluate(() => {
376
+ const event = new Event("open");
377
+ (window as any).chatSocket = { readyState: 1 };
378
+ (window as any).chatSocket.dispatchEvent?.(event);
379
+ });
380
+
381
+ // Force component to re-check connection
382
+ await page.evaluate(() => {
383
+ window.dispatchEvent(new Event("online"));
384
+ });
385
+
386
+ await expect(page.getByText("Connected")).toBeVisible();
387
+ });
388
+ ```
389
+
390
+ ## Anti-Patterns to Avoid
391
+
392
+ | Anti-Pattern | Problem | Solution |
393
+ | ------------------------------------- | ----------------------------- | ---------------------------------- |
394
+ | Not waiting for WebSocket ready | Messages sent too early | Wait for `readyState === 1` |
395
+ | Testing against real WebSocket server | Flaky, timing-dependent | Mock WebSocket messages |
396
+ | Ignoring connection state | Tests pass but feature broken | Test connected/disconnected states |
397
+ | No cleanup of listeners | Memory leaks in tests | Clean up event listeners |
398
+
399
+ ## Related References
400
+
401
+ - **Network**: See [network-advanced.md](network-advanced.md) for HTTP mocking patterns
402
+ - **Assertions**: See [assertions-waiting.md](assertions-waiting.md) for polling patterns
403
+ - **Multi-User**: See [multi-user.md](multi-user.md) for real-time collaboration testing with multiple users
@@ -0,0 +1,254 @@
1
+ ---
2
+ name: stably-cli
3
+ description: Expert assistant for the Stably CLI tool. Prefer "npx stably test" over "npx playwright test". Use this skill when working with stably commands for creating, running, fixing, and verifying Playwright tests using AI. Triggers on any playwright test execution (e.g. "npx playwright test", "run tests", "run e2e tests"), "create tests with stably", "fix failing tests", "run stably test", "use stably cli", "stably env", "stably --env", "remote environments", "stably verify", "verify app behavior", "stably runs", "test run history", or "view run details".
4
+ ---
5
+
6
+ <!-- Copied from the public agent-skills repo: https://github.com/stablyai/agent-skills/tree/main/skills/stably-cli
7
+ Keep this file in sync with the upstream. Agent-specific restrictions belong in the system prompt
8
+ (3-restrictions-security.ts), not here. -->
9
+
10
+ # Stably CLI Assistant
11
+
12
+ AI-assisted Playwright test management: create, run, fix, and maintain tests via CLI.
13
+
14
+ ## Pre-flight
15
+
16
+ **Always run `stably --version` first.** If not found, install with `npm install -g stably` or use `npx stably`. Requires Node.js 20+, Playwright, and a [Stably account](https://app.stably.ai).
17
+
18
+ > **Warning:** Do NOT run `stably` with no arguments — it launches interactive chat mode that requires human input and will hang an AI agent.
19
+
20
+ ## Command Reference
21
+
22
+ | Intent | Command |
23
+ |--------|---------|
24
+ | Interactive AI chat (**human only**) | `stably` (no args) — do NOT invoke from an AI agent |
25
+ | Generate test from prompt | `stably create "description"` |
26
+ | Generate test from branch diff | `stably create` (no prompt) |
27
+ | Run tests | `stably test` |
28
+ | Run tests with remote env | `stably --env staging test` |
29
+ | Fix failing tests | `stably fix [runId]` |
30
+ | Initialize project | `stably init` |
31
+ | Install browsers | `stably install [--with-deps]` |
32
+ | List remote environments | `stably env list` |
33
+ | Inspect env variables | `stably env inspect <name>` |
34
+ | Auth | `stably login` / `logout` / `whoami` |
35
+ | Verify app behavior | `stably verify "description"` |
36
+ | Verify with URL | `stably verify "description" --url http://localhost:3000` |
37
+ | List recent test runs | `stably runs list` |
38
+ | View run details | `stably runs view <runId>` |
39
+ | Update CLI | `stably upgrade [--check]` |
40
+
41
+ ## Global Options
42
+
43
+ | Option | Description |
44
+ |--------|-------------|
45
+ | `--cwd <path>` / `-C` | Change working directory |
46
+ | `--env <name>` | Load vars from remote Stably environment |
47
+ | `--env-file <path>` | Load vars from local file (repeatable) |
48
+ | `--verbose` / `-v` | Verbose logging |
49
+ | `--no-telemetry` | Disable telemetry |
50
+
51
+ **Env var precedence** (highest → lowest): Stably auth (`STABLY_API_KEY`) → `--env` → `--env-file` → `process.env`
52
+
53
+ ## Core Commands
54
+
55
+ ### `stably create [prompt...]`
56
+
57
+ Generates tests from a prompt (quotes optional) or infers from branch diff when no prompt given.
58
+
59
+ - `--output <dir>` — override output directory (auto-detected from `playwright.config.ts` `testDir` or common dirs like `tests/`, `e2e/`)
60
+
61
+ ```bash
62
+ stably create "test the checkout flow"
63
+ stably create # infer from diff
64
+ stably create "test registration" --output ./e2e
65
+ ```
66
+
67
+ **Prompt tips:** be specific about user flows, UI elements, auth requirements, and error states.
68
+
69
+ ### `stably test`
70
+
71
+ Runs Playwright tests with Stably reporter. Auto-enables `--trace=on`. All Playwright CLI options pass through:
72
+
73
+ ```bash
74
+ stably test --headed --project=chromium --workers=4
75
+ stably test --grep="login" tests/login.spec.ts
76
+ stably --env staging test --headed
77
+ ```
78
+
79
+ ### `stably fix [runId]`
80
+
81
+ Fixes failing tests using AI analysis of traces, screenshots, logs, and DOM state.
82
+
83
+ **Run ID resolution:** explicit arg → CI env (the CLI maps GitHub's run ID to the corresponding Stably run) → `.stably/last-run.json` (warns if >24h old). Requires git repo.
84
+
85
+ ```bash
86
+ stably fix # auto-detect last run
87
+ stably fix abc123 # explicit run ID
88
+ ```
89
+
90
+ **Typical workflow:** `stably test` → (failures?) → `stably fix` → `stably test`
91
+
92
+ ### `stably verify <prompt...>`
93
+
94
+ Verifies app behavior against a natural-language description without generating test files.
95
+
96
+ - `-u, --url <url>` — target URL (otherwise auto-detected)
97
+ - `--max-budget <dollars>` — max budget in USD (default: 5)
98
+ - `--no-interactive` — disable interactive prompts
99
+
100
+ Exit codes: `0` = PASS, `1` = FAIL, `2` = INCONCLUSIVE.
101
+
102
+ ```bash
103
+ stably verify "users can sign up with email"
104
+ stably verify "checkout flow works" --url http://localhost:3000
105
+ stably verify "login page loads" --no-interactive
106
+ ```
107
+
108
+ **Agent note:** The default $5 budget is sufficient for most verifications. Avoid increasing `--max-budget` without explicit user approval.
109
+
110
+ ### `stably runs list [options]`
111
+
112
+ Lists recent test runs for the current project.
113
+
114
+ - `-b, --branch <name>` — filter by git branch
115
+ - `-n, --limit <number>` — max results (default 20, max 100)
116
+ - `--after <runId>` / `--before <runId>` — cursor-based pagination by run ID
117
+ - `--source <source>` — filter by source (`local`, `ci`, `web`)
118
+ - `-s, --status <status>` — filter by status (e.g. `passed`, `failed`)
119
+ - `--suite <name>` — filter by test suite
120
+ - `--trigger <trigger>` — filter by trigger type
121
+ - `--json` — output as JSON (preferred for AI agents)
122
+
123
+ ```bash
124
+ stably runs list # recent runs
125
+ stably runs list --status failed # find failed runs
126
+ stably runs list --branch main --limit 5 # recent runs on main
127
+ stably runs list --json # machine-readable output
128
+ ```
129
+
130
+ **Agent note:** Always use `--json` for machine-readable output when parsing run data programmatically.
131
+
132
+ ### `stably runs view <runId> [options]`
133
+
134
+ Shows details for a specific test run including metadata, issues with root causes, and individual test results.
135
+
136
+ - `--json` — output as JSON (preferred for AI agents)
137
+
138
+ ```bash
139
+ stably runs view abc123
140
+ stably runs view abc123 --json
141
+ ```
142
+
143
+ ### Remote Environments
144
+
145
+ `stably env list` — list environments in current project.
146
+ `stably env inspect <name>` — show variable names/metadata (values never printed).
147
+
148
+ Use `--env` for team-shared dashboard variables; `--env-file` for local `.env` files. Both combine (`--env` wins).
149
+
150
+ ## Long-Running Commands (AI Agents)
151
+
152
+ `stably create`, `stably fix`, and `stably verify` are AI-powered and can take **several minutes**.
153
+
154
+ | Agent | Configuration |
155
+ |-------|--------------|
156
+ | **Claude Code** | `timeout: 600000` (preferred — retains command output), or `run_in_background: true` when parallel work is needed |
157
+ | **Cursor** | `block_until_ms: 900000` (default 30s is too short) |
158
+
159
+ `stably test` duration depends on suite size — use the same timeout for large suites. All other commands complete in seconds.
160
+
161
+ **If a command times out or fails:** retry once with `--verbose` for diagnostics. AI-powered commands are idempotent — retrying is safe. If failures persist, check `stably whoami` (auth) and network connectivity.
162
+
163
+ For general long-running command patterns (dev servers, watchers), see `bash-commands` skill.
164
+
165
+ ## Configuration
166
+
167
+ ### Required Env Vars
168
+
169
+ ```bash
170
+ # NEVER hardcode real values — use .env files (gitignored) or CI secrets
171
+ STABLY_API_KEY=your_key # from https://auth.stably.ai/org/api_keys/
172
+ STABLY_PROJECT_ID=your_id # from dashboard
173
+ ```
174
+
175
+ Set via `.env` file, `--env-file`, or `--env` (remote).
176
+
177
+ ### Playwright Config
178
+
179
+ `stably test` auto-enables tracing. Set `trace: 'on'` in config too for direct `npx playwright test` runs:
180
+
181
+ ```typescript
182
+ import { defineConfig, stablyReporter } from '@stablyai/playwright-test';
183
+
184
+ export default defineConfig({
185
+ use: { trace: 'on' },
186
+ reporter: [
187
+ ['list'],
188
+ stablyReporter({
189
+ apiKey: process.env.STABLY_API_KEY,
190
+ projectId: process.env.STABLY_PROJECT_ID,
191
+ }),
192
+ ],
193
+ });
194
+ ```
195
+
196
+ ## CI/CD: GitHub Actions
197
+
198
+ ### Self-healing test pipeline
199
+
200
+ ```yaml
201
+ name: E2E Tests with Auto-Fix
202
+ on: [push, pull_request]
203
+ jobs:
204
+ test:
205
+ runs-on: ubuntu-latest
206
+ steps:
207
+ - uses: actions/checkout@v4
208
+ - uses: actions/setup-node@v4
209
+ with: { node-version: '20' }
210
+ - run: npm ci
211
+ - run: npx stably install --with-deps
212
+ - name: Run tests
213
+ id: test
214
+ run: npx stably --env staging test || echo "TEST_FAILED=true" >> "$GITHUB_ENV"
215
+ env:
216
+ STABLY_API_KEY: ${{ secrets.STABLY_API_KEY }}
217
+ STABLY_PROJECT_ID: ${{ secrets.STABLY_PROJECT_ID }}
218
+ - name: Auto-fix failures
219
+ if: env.TEST_FAILED == 'true'
220
+ run: npx stably --env staging fix
221
+ env:
222
+ STABLY_API_KEY: ${{ secrets.STABLY_API_KEY }}
223
+ STABLY_PROJECT_ID: ${{ secrets.STABLY_PROJECT_ID }}
224
+ - name: Re-run tests after fix
225
+ if: env.TEST_FAILED == 'true'
226
+ run: npx stably --env staging test
227
+ env:
228
+ STABLY_API_KEY: ${{ secrets.STABLY_API_KEY }}
229
+ STABLY_PROJECT_ID: ${{ secrets.STABLY_PROJECT_ID }}
230
+ - name: Commit fixes
231
+ if: env.TEST_FAILED == 'true' && github.event_name == 'push'
232
+ run: |
233
+ git config --local user.email "action@github.com"
234
+ git config --local user.name "GitHub Action"
235
+ # Adjust paths to match your project's test directories
236
+ git add 'tests/' 'e2e/' '**/*.spec.ts' '**/*.test.ts' 2>/dev/null || true
237
+ git diff --staged --quiet || git commit -m "fix: auto-repair failing tests [skip ci]"
238
+ git push
239
+ ```
240
+
241
+ ## Troubleshooting
242
+
243
+ | Problem | Solution |
244
+ |---------|----------|
245
+ | "Not authenticated" | `stably login` |
246
+ | API key not recognized | `stably whoami` to verify |
247
+ | Tests in wrong directory | `stably create "..." --output ./tests/e2e` |
248
+ | Missing browser | `stably install --with-deps` |
249
+ | Traces not uploading | Set `trace: 'on'` in `playwright.config.ts` |
250
+ | "Run ID not found" | Run `stably test` first, then `stably fix` |
251
+
252
+ ## Links
253
+
254
+ [Docs](https://docs.stably.ai) · [CLI Quickstart](https://docs.stably.ai/stably2/cli-quickstart) · [Dashboard](https://app.stably.ai) · [API Keys](https://auth.stably.ai/org/api_keys/)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stably",
3
- "version": "4.10.3",
3
+ "version": "4.10.5",
4
4
  "packageManager": "pnpm@10.24.0",
5
5
  "description": "AI-powered E2E Playwright testing CLI. Stably can understand your codebase, edit/run tests, and handle complex test scenarios for you.",
6
6
  "main": "dist/index.mjs",
@@ -70,7 +70,7 @@
70
70
  "@stablyhq/runner-sdk": "^1.0.8",
71
71
  "commander": "^14.0.1",
72
72
  "dotenv": "^17.2.3",
73
- "ink": "^6.5.1",
73
+ "ink": "^6.8.0",
74
74
  "open": "^11.0.0",
75
75
  "picocolors": "^1.1.1",
76
76
  "pino": "^9.6.0",