stably 4.8.9 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/index.mjs +1 -1
  2. package/dist/stably-plugin-cli/.claude-plugin/plugin.json +5 -0
  3. package/dist/stably-plugin-cli/skills/bash-commands/SKILL.md +65 -0
  4. package/dist/stably-plugin-cli/skills/browser-interaction-guide/SKILL.md +144 -0
  5. package/dist/stably-plugin-cli/skills/bulk-test-handling/SKILL.md +104 -0
  6. package/dist/stably-plugin-cli/skills/debugging-test-failures/SKILL.md +146 -0
  7. package/dist/{stably-plugin → stably-plugin-cli}/skills/playwright-best-practices/SKILL.md +11 -5
  8. package/dist/stably-plugin-cli/skills/playwright-config-auth/SKILL.md +217 -0
  9. package/dist/stably-plugin-cli/skills/stably-sdk-reference/SKILL.md +307 -0
  10. package/dist/stably-plugin-cli/skills/test-creation-workflow/SKILL.md +311 -0
  11. package/package.json +4 -1
  12. package/dist/stably-plugin/.claude-plugin/plugin.json +0 -5
  13. package/dist/stably-plugin/skills/playwright-best-practices/references/accessibility.md +0 -359
  14. package/dist/stably-plugin/skills/playwright-best-practices/references/annotations.md +0 -526
  15. package/dist/stably-plugin/skills/playwright-best-practices/references/assertions-waiting.md +0 -361
  16. package/dist/stably-plugin/skills/playwright-best-practices/references/browser-apis.md +0 -391
  17. package/dist/stably-plugin/skills/playwright-best-practices/references/browser-extensions.md +0 -506
  18. package/dist/stably-plugin/skills/playwright-best-practices/references/canvas-webgl.md +0 -493
  19. package/dist/stably-plugin/skills/playwright-best-practices/references/ci-cd.md +0 -407
  20. package/dist/stably-plugin/skills/playwright-best-practices/references/clock-mocking.md +0 -364
  21. package/dist/stably-plugin/skills/playwright-best-practices/references/component-testing.md +0 -500
  22. package/dist/stably-plugin/skills/playwright-best-practices/references/console-errors.md +0 -420
  23. package/dist/stably-plugin/skills/playwright-best-practices/references/debugging.md +0 -491
  24. package/dist/stably-plugin/skills/playwright-best-practices/references/electron.md +0 -509
  25. package/dist/stably-plugin/skills/playwright-best-practices/references/error-testing.md +0 -360
  26. package/dist/stably-plugin/skills/playwright-best-practices/references/file-operations.md +0 -375
  27. package/dist/stably-plugin/skills/playwright-best-practices/references/fixtures-hooks.md +0 -417
  28. package/dist/stably-plugin/skills/playwright-best-practices/references/flaky-tests.md +0 -494
  29. package/dist/stably-plugin/skills/playwright-best-practices/references/global-setup.md +0 -434
  30. package/dist/stably-plugin/skills/playwright-best-practices/references/i18n.md +0 -508
  31. package/dist/stably-plugin/skills/playwright-best-practices/references/iframes.md +0 -403
  32. package/dist/stably-plugin/skills/playwright-best-practices/references/locators.md +0 -242
  33. package/dist/stably-plugin/skills/playwright-best-practices/references/mobile-testing.md +0 -409
  34. package/dist/stably-plugin/skills/playwright-best-practices/references/multi-context.md +0 -288
  35. package/dist/stably-plugin/skills/playwright-best-practices/references/multi-user.md +0 -393
  36. package/dist/stably-plugin/skills/playwright-best-practices/references/network-advanced.md +0 -452
  37. package/dist/stably-plugin/skills/playwright-best-practices/references/page-object-model.md +0 -315
  38. package/dist/stably-plugin/skills/playwright-best-practices/references/performance-testing.md +0 -476
  39. package/dist/stably-plugin/skills/playwright-best-practices/references/performance.md +0 -453
  40. package/dist/stably-plugin/skills/playwright-best-practices/references/projects-dependencies.md +0 -456
  41. package/dist/stably-plugin/skills/playwright-best-practices/references/security-testing.md +0 -430
  42. package/dist/stably-plugin/skills/playwright-best-practices/references/service-workers.md +0 -504
  43. package/dist/stably-plugin/skills/playwright-best-practices/references/test-coverage.md +0 -495
  44. package/dist/stably-plugin/skills/playwright-best-practices/references/test-data.md +0 -492
  45. package/dist/stably-plugin/skills/playwright-best-practices/references/test-organization.md +0 -361
  46. package/dist/stably-plugin/skills/playwright-best-practices/references/third-party.md +0 -464
  47. package/dist/stably-plugin/skills/playwright-best-practices/references/websockets.md +0 -403
@@ -1,526 +0,0 @@
1
- # Test Annotations & Organization (Tags)
2
-
3
- > **"Tags" = "Annotations"**: In Playwright, "tags" and "annotations" refer to the same thing. When someone asks to "add a tag" to a test, they mean adding a Playwright annotation.
4
-
5
- ## Table of Contents
6
-
7
- 1. [Adding Tags/Annotations (Recommended)](#adding-tagsannotations-recommended)
8
- 2. [Filtering Tests by Tag](#filtering-tests-by-tag)
9
- 3. [Skip Annotations](#skip-annotations)
10
- 4. [Fixme & Fail Annotations](#fixme--fail-annotations)
11
- 5. [Slow Tests](#slow-tests)
12
- 6. [Test Steps](#test-steps)
13
- 7. [Custom Annotations (Imperative)](#custom-annotations-imperative)
14
- 8. [Conditional Annotations](#conditional-annotations)
15
-
16
- ## Adding Tags/Annotations (Recommended)
17
-
18
- Use the **declarative syntax** to add tags/annotations to tests. This is the cleanest and most readable approach.
19
-
20
- ### Basic Tag Syntax
21
-
22
- ```typescript
23
- // Add a single tag
24
- test('user can login', {
25
- annotation: { type: 'owner', description: 'Jinjing' }
26
- }, async ({ page }) => {
27
- await page.goto('/login');
28
- // test code
29
- });
30
-
31
- // Add multiple tags
32
- test('checkout completes successfully', {
33
- annotation: [
34
- { type: 'owner', description: 'Jinjing' },
35
- { type: 'priority', description: 'P1' },
36
- { type: 'feature', description: 'checkout' }
37
- ]
38
- }, async ({ page }) => {
39
- await page.goto('/checkout');
40
- // test code
41
- });
42
- ```
43
-
44
- ### Common Tag Types
45
-
46
- | Tag Type | Purpose | Example |
47
- |----------|---------|---------|
48
- | `owner` | Test ownership | `{ type: 'owner', description: 'Jinjing' }` |
49
- | `priority` | Test priority | `{ type: 'priority', description: 'P1' }` |
50
- | `feature` | Feature area | `{ type: 'feature', description: 'auth' }` |
51
- | `ticket` | Link to issue | `{ type: 'ticket', description: 'JIRA-123' }` |
52
- | `smoke` | Smoke test | `{ type: 'smoke', description: '' }` |
53
- | `regression` | Regression test | `{ type: 'regression', description: '' }` |
54
-
55
- ### Tags on Describe Blocks
56
-
57
- ```typescript
58
- test.describe('Payment flows', {
59
- annotation: [
60
- { type: 'feature', description: 'payments' },
61
- { type: 'owner', description: 'Alice' }
62
- ]
63
- }, () => {
64
- test('credit card payment', async ({ page }) => {
65
- // inherits annotations from describe block
66
- });
67
-
68
- test('paypal payment', async ({ page }) => {
69
- // inherits annotations from describe block
70
- });
71
- });
72
- ```
73
-
74
- ## Filtering Tests by Tag
75
-
76
- Run specific tests based on their tags using the `--grep` flag.
77
-
78
- ### Basic Filtering
79
-
80
- ```bash
81
- # Run tests owned by Jinjing
82
- npx playwright test --grep "@owner:Jinjing"
83
-
84
- # Run P1 priority tests
85
- npx playwright test --grep "@priority:P1"
86
-
87
- # Run smoke tests
88
- npx playwright test --grep "@smoke"
89
-
90
- # Run tests for a specific feature
91
- npx playwright test --grep "@feature:checkout"
92
- ```
93
-
94
- ### Advanced Filtering
95
-
96
- ```bash
97
- # Run tests matching multiple criteria (AND)
98
- npx playwright test --grep "(?=.*@owner:Jinjing)(?=.*@priority:P1)"
99
-
100
- # Exclude tests with a tag
101
- npx playwright test --grep-invert "@slow"
102
-
103
- # Combine with file patterns
104
- npx playwright test tests/checkout/ --grep "@smoke"
105
- ```
106
-
107
- ### Tag Format in grep
108
-
109
- The `--grep` flag matches against the test title AND annotations. Annotations are formatted as `@type:description` or just `@type` if description is empty.
110
-
111
-
112
-
113
- ## Skip Annotations
114
-
115
- ### Basic Skip
116
-
117
- ```typescript
118
- // Skip unconditionally
119
- test.skip("feature not implemented", async ({ page }) => {
120
- // This test won't run
121
- });
122
-
123
- // Skip with reason
124
- test("payment flow", async ({ page }) => {
125
- test.skip(true, "Payment gateway in maintenance");
126
- // Test body won't execute
127
- });
128
- ```
129
-
130
- ### Conditional Skip
131
-
132
- ```typescript
133
- test("webkit-specific feature", async ({ page, browserName }) => {
134
- test.skip(browserName !== "webkit", "This feature only works in WebKit");
135
-
136
- await page.goto("/webkit-feature");
137
- });
138
-
139
- test("production only", async ({ page }) => {
140
- test.skip(process.env.ENV !== "production", "Only runs against production");
141
-
142
- await page.goto("/prod-feature");
143
- });
144
- ```
145
-
146
- ### Skip by Platform
147
-
148
- ```typescript
149
- test("windows-specific", async ({ page }) => {
150
- test.skip(process.platform !== "win32", "Windows only");
151
- });
152
-
153
- test("not on CI", async ({ page }) => {
154
- test.skip(!!process.env.CI, "Skipped in CI environment");
155
- });
156
- ```
157
-
158
- ### Skip Describe Block
159
-
160
- ```typescript
161
- test.describe("Admin features", () => {
162
- test.skip(
163
- ({ browserName }) => browserName === "firefox",
164
- "Firefox admin bug",
165
- );
166
-
167
- test("admin dashboard", async ({ page }) => {
168
- // Skipped in Firefox
169
- });
170
-
171
- test("admin settings", async ({ page }) => {
172
- // Skipped in Firefox
173
- });
174
- });
175
- ```
176
-
177
- ## Fixme & Fail Annotations
178
-
179
- ### Fixme - Known Issues
180
-
181
- ```typescript
182
- // Mark test as needing fix (skips the test)
183
- test.fixme("broken after refactor", async ({ page }) => {
184
- // Test won't run but is tracked
185
- });
186
-
187
- // Conditional fixme
188
- test("flaky on CI", async ({ page }) => {
189
- test.fixme(!!process.env.CI, "Investigate CI flakiness - ticket #123");
190
-
191
- await page.goto("/flaky-feature");
192
- });
193
- ```
194
-
195
- ### Fail - Expected Failures
196
-
197
- ```typescript
198
- // Test is expected to fail (runs but expects failure)
199
- test("known bug", async ({ page }) => {
200
- test.fail();
201
-
202
- await page.goto("/buggy-page");
203
- // If this passes, the test fails (bug was fixed!)
204
- await expect(page.getByText("Working")).toBeVisible();
205
- });
206
-
207
- // Conditional fail
208
- test("fails on webkit", async ({ page, browserName }) => {
209
- test.fail(browserName === "webkit", "WebKit rendering bug #456");
210
-
211
- await page.goto("/render-test");
212
- await expect(page.getByTestId("element")).toHaveCSS("width", "100px");
213
- });
214
- ```
215
-
216
- ### Difference Between Skip, Fixme, Fail
217
-
218
- | Annotation | Runs? | Use Case |
219
- | -------------- | ----- | -------------------------------- |
220
- | `test.skip()` | No | Feature not applicable |
221
- | `test.fixme()` | No | Known bug, needs investigation |
222
- | `test.fail()` | Yes | Expected to fail, tracking a bug |
223
-
224
- ## Slow Tests
225
-
226
- ### Mark Slow Tests
227
-
228
- ```typescript
229
- // Triple the default timeout
230
- test("large data import", async ({ page }) => {
231
- test.slow();
232
-
233
- await page.goto("/import");
234
- await page.setInputFiles("#file", "large-file.csv");
235
- await page.getByRole("button", { name: "Import" }).click();
236
-
237
- await expect(page.getByText("Import complete")).toBeVisible();
238
- });
239
-
240
- // Conditional slow
241
- test("video processing", async ({ page, browserName }) => {
242
- test.slow(browserName === "webkit", "WebKit video processing is slow");
243
-
244
- await page.goto("/video-editor");
245
- });
246
- ```
247
-
248
- ### Custom Timeout
249
-
250
- ```typescript
251
- test("very long operation", async ({ page }) => {
252
- // Set specific timeout (in milliseconds)
253
- test.setTimeout(120000); // 2 minutes
254
-
255
- await page.goto("/long-operation");
256
- });
257
-
258
- // Timeout for describe block
259
- test.describe("Integration tests", () => {
260
- test.describe.configure({ timeout: 60000 });
261
-
262
- test("test 1", async ({ page }) => {
263
- // Has 60 second timeout
264
- });
265
- });
266
- ```
267
-
268
- ## Test Steps
269
-
270
- ### Basic Steps
271
-
272
- ```typescript
273
- test("checkout flow", async ({ page }) => {
274
- await test.step("Add item to cart", async () => {
275
- await page.goto("/products");
276
- await page.getByRole("button", { name: "Add to Cart" }).click();
277
- });
278
-
279
- await test.step("Go to checkout", async () => {
280
- await page.getByRole("link", { name: "Cart" }).click();
281
- await page.getByRole("button", { name: "Checkout" }).click();
282
- });
283
-
284
- await test.step("Fill shipping info", async () => {
285
- await page.getByLabel("Address").fill("123 Test St");
286
- await page.getByLabel("City").fill("Test City");
287
- });
288
-
289
- await test.step("Complete payment", async () => {
290
- await page.getByLabel("Card").fill("4242424242424242");
291
- await page.getByRole("button", { name: "Pay" }).click();
292
- });
293
-
294
- await expect(page.getByText("Order confirmed")).toBeVisible();
295
- });
296
- ```
297
-
298
- ### Nested Steps
299
-
300
- ```typescript
301
- test("user registration", async ({ page }) => {
302
- await test.step("Fill registration form", async () => {
303
- await page.goto("/register");
304
-
305
- await test.step("Personal info", async () => {
306
- await page.getByLabel("Name").fill("John Doe");
307
- await page.getByLabel("Email").fill("john@example.com");
308
- });
309
-
310
- await test.step("Security", async () => {
311
- await page.getByLabel("Password").fill("SecurePass123");
312
- await page.getByLabel("Confirm Password").fill("SecurePass123");
313
- });
314
- });
315
-
316
- await test.step("Submit and verify", async () => {
317
- await page.getByRole("button", { name: "Register" }).click();
318
- await expect(page.getByText("Welcome")).toBeVisible();
319
- });
320
- });
321
- ```
322
-
323
- ### Steps with Return Values
324
-
325
- ```typescript
326
- test("verify order", async ({ page }) => {
327
- const orderId = await test.step("Create order", async () => {
328
- await page.goto("/checkout");
329
- await page.getByRole("button", { name: "Place Order" }).click();
330
-
331
- // Return value from step
332
- return await page.getByTestId("order-id").textContent();
333
- });
334
-
335
- await test.step("Verify order details", async () => {
336
- await page.goto(`/orders/${orderId}`);
337
- await expect(page.getByText(`Order #${orderId}`)).toBeVisible();
338
- });
339
- });
340
- ```
341
-
342
- ### Step in Page Object
343
-
344
- ```typescript
345
- // pages/checkout.page.ts
346
- export class CheckoutPage {
347
- async fillShippingInfo(address: string, city: string) {
348
- await test.step("Fill shipping information", async () => {
349
- await this.page.getByLabel("Address").fill(address);
350
- await this.page.getByLabel("City").fill(city);
351
- });
352
- }
353
-
354
- async completePayment(cardNumber: string) {
355
- await test.step("Complete payment", async () => {
356
- await this.page.getByLabel("Card").fill(cardNumber);
357
- await this.page.getByRole("button", { name: "Pay" }).click();
358
- });
359
- }
360
- }
361
- ```
362
-
363
- ## Custom Annotations (Imperative)
364
-
365
- > **Prefer declarative syntax**: The [declarative approach](#adding-tagsannotations-recommended) above is cleaner. Use imperative annotations only when you need to add them conditionally at runtime.
366
-
367
- ### Add Annotations Imperatively
368
-
369
- ```typescript
370
- test("important feature", async ({ page }, testInfo) => {
371
- // Add custom annotation at runtime
372
- testInfo.annotations.push({
373
- type: "priority",
374
- description: "high",
375
- });
376
-
377
- testInfo.annotations.push({
378
- type: "ticket",
379
- description: "JIRA-123",
380
- });
381
-
382
- await page.goto("/feature");
383
- });
384
- ```
385
-
386
- ### Annotation Fixture
387
-
388
- ```typescript
389
- // fixtures/annotations.fixture.ts
390
- import { test as base, TestInfo } from "@playwright/test";
391
-
392
- type AnnotationFixtures = {
393
- annotate: {
394
- ticket: (id: string) => void;
395
- priority: (level: "low" | "medium" | "high") => void;
396
- owner: (name: string) => void;
397
- };
398
- };
399
-
400
- export const test = base.extend<AnnotationFixtures>({
401
- annotate: async ({}, use, testInfo) => {
402
- await use({
403
- ticket: (id) => {
404
- testInfo.annotations.push({ type: "ticket", description: id });
405
- },
406
- priority: (level) => {
407
- testInfo.annotations.push({ type: "priority", description: level });
408
- },
409
- owner: (name) => {
410
- testInfo.annotations.push({ type: "owner", description: name });
411
- },
412
- });
413
- },
414
- });
415
-
416
- // Usage
417
- test("critical feature", async ({ page, annotate }) => {
418
- annotate.ticket("JIRA-456");
419
- annotate.priority("high");
420
- annotate.owner("Alice");
421
-
422
- await page.goto("/critical");
423
- });
424
- ```
425
-
426
- ### Read Annotations in Reporter
427
-
428
- ```typescript
429
- // reporters/annotation-reporter.ts
430
- import { Reporter, TestCase, TestResult } from "@playwright/test/reporter";
431
-
432
- class AnnotationReporter implements Reporter {
433
- onTestEnd(test: TestCase, result: TestResult) {
434
- const ticket = test.annotations.find((a) => a.type === "ticket");
435
- const priority = test.annotations.find((a) => a.type === "priority");
436
-
437
- if (ticket) {
438
- console.log(`Test linked to: ${ticket.description}`);
439
- }
440
-
441
- if (priority?.description === "high" && result.status === "failed") {
442
- console.log(`HIGH PRIORITY FAILURE: ${test.title}`);
443
- }
444
- }
445
- }
446
-
447
- export default AnnotationReporter;
448
- ```
449
-
450
- ## Conditional Annotations
451
-
452
- ### Annotation Helper
453
-
454
- ```typescript
455
- // helpers/test-annotations.ts
456
- import { test } from "@playwright/test";
457
-
458
- export function skipInCI(reason = "Skipped in CI") {
459
- test.skip(!!process.env.CI, reason);
460
- }
461
-
462
- export function skipInBrowser(browser: string, reason: string) {
463
- test.beforeEach(({ browserName }) => {
464
- test.skip(browserName === browser, reason);
465
- });
466
- }
467
-
468
- export function onlyInEnv(env: string) {
469
- test.skip(process.env.ENV !== env, `Only runs in ${env}`);
470
- }
471
- ```
472
-
473
- ```typescript
474
- // tests/feature.spec.ts
475
- import { skipInCI, onlyInEnv } from "../helpers/test-annotations";
476
-
477
- test("local only feature", async ({ page }) => {
478
- skipInCI("Uses local resources");
479
-
480
- await page.goto("/local-feature");
481
- });
482
-
483
- test("production check", async ({ page }) => {
484
- onlyInEnv("production");
485
-
486
- await page.goto("/prod-only");
487
- });
488
- ```
489
-
490
- ### Describe-Level Conditions
491
-
492
- ```typescript
493
- test.describe("Mobile features", () => {
494
- test.beforeEach(({ isMobile }) => {
495
- test.skip(!isMobile, "Mobile only tests");
496
- });
497
-
498
- test("touch gestures", async ({ page }) => {
499
- // Only runs on mobile
500
- });
501
- });
502
-
503
- test.describe("Desktop features", () => {
504
- test.beforeEach(({ isMobile }) => {
505
- test.skip(isMobile, "Desktop only tests");
506
- });
507
-
508
- test("hover interactions", async ({ page }) => {
509
- // Only runs on desktop
510
- });
511
- });
512
- ```
513
-
514
- ## Anti-Patterns to Avoid
515
-
516
- | Anti-Pattern | Problem | Solution |
517
- | --------------------------- | ---------------------- | -------------------------------- |
518
- | Skipping without reason | Hard to track why | Always provide description |
519
- | Too many skipped tests | Test debt accumulates | Review and clean up regularly |
520
- | Using skip instead of fixme | Loses intent | Use fixme for bugs, skip for N/A |
521
- | Not using steps | Hard to debug failures | Group logical actions in steps |
522
-
523
- ## Related References
524
-
525
- - **Test Organization**: See [test-organization.md](test-organization.md) for structuring tests
526
- - **Debugging**: See [debugging.md](debugging.md) for troubleshooting