stably 4.9.0 → 4.10.1
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/dist/index.mjs +1 -1
- package/dist/stably-plugin-cli/.claude-plugin/plugin.json +5 -0
- package/dist/stably-plugin-cli/skills/bash-commands/SKILL.md +65 -0
- package/dist/stably-plugin-cli/skills/browser-interaction-guide/SKILL.md +144 -0
- package/dist/stably-plugin-cli/skills/bulk-test-handling/SKILL.md +104 -0
- package/dist/stably-plugin-cli/skills/debugging-test-failures/SKILL.md +146 -0
- package/dist/{stably-plugin → stably-plugin-cli}/skills/playwright-best-practices/SKILL.md +11 -5
- package/dist/stably-plugin-cli/skills/playwright-config-auth/SKILL.md +217 -0
- package/dist/stably-plugin-cli/skills/stably-sdk-reference/SKILL.md +307 -0
- package/dist/stably-plugin-cli/skills/test-creation-workflow/SKILL.md +311 -0
- package/package.json +2 -2
- package/dist/stably-plugin/.claude-plugin/plugin.json +0 -5
- package/dist/stably-plugin/skills/playwright-best-practices/references/accessibility.md +0 -359
- package/dist/stably-plugin/skills/playwright-best-practices/references/annotations.md +0 -526
- package/dist/stably-plugin/skills/playwright-best-practices/references/assertions-waiting.md +0 -361
- package/dist/stably-plugin/skills/playwright-best-practices/references/browser-apis.md +0 -391
- package/dist/stably-plugin/skills/playwright-best-practices/references/browser-extensions.md +0 -506
- package/dist/stably-plugin/skills/playwright-best-practices/references/canvas-webgl.md +0 -493
- package/dist/stably-plugin/skills/playwright-best-practices/references/ci-cd.md +0 -407
- package/dist/stably-plugin/skills/playwright-best-practices/references/clock-mocking.md +0 -364
- package/dist/stably-plugin/skills/playwright-best-practices/references/component-testing.md +0 -500
- package/dist/stably-plugin/skills/playwright-best-practices/references/console-errors.md +0 -420
- package/dist/stably-plugin/skills/playwright-best-practices/references/debugging.md +0 -491
- package/dist/stably-plugin/skills/playwright-best-practices/references/electron.md +0 -509
- package/dist/stably-plugin/skills/playwright-best-practices/references/error-testing.md +0 -360
- package/dist/stably-plugin/skills/playwright-best-practices/references/file-operations.md +0 -375
- package/dist/stably-plugin/skills/playwright-best-practices/references/fixtures-hooks.md +0 -417
- package/dist/stably-plugin/skills/playwright-best-practices/references/flaky-tests.md +0 -494
- package/dist/stably-plugin/skills/playwright-best-practices/references/global-setup.md +0 -434
- package/dist/stably-plugin/skills/playwright-best-practices/references/i18n.md +0 -508
- package/dist/stably-plugin/skills/playwright-best-practices/references/iframes.md +0 -403
- package/dist/stably-plugin/skills/playwright-best-practices/references/locators.md +0 -242
- package/dist/stably-plugin/skills/playwright-best-practices/references/mobile-testing.md +0 -409
- package/dist/stably-plugin/skills/playwright-best-practices/references/multi-context.md +0 -288
- package/dist/stably-plugin/skills/playwright-best-practices/references/multi-user.md +0 -393
- package/dist/stably-plugin/skills/playwright-best-practices/references/network-advanced.md +0 -452
- package/dist/stably-plugin/skills/playwright-best-practices/references/page-object-model.md +0 -315
- package/dist/stably-plugin/skills/playwright-best-practices/references/performance-testing.md +0 -476
- package/dist/stably-plugin/skills/playwright-best-practices/references/performance.md +0 -453
- package/dist/stably-plugin/skills/playwright-best-practices/references/projects-dependencies.md +0 -456
- package/dist/stably-plugin/skills/playwright-best-practices/references/security-testing.md +0 -430
- package/dist/stably-plugin/skills/playwright-best-practices/references/service-workers.md +0 -504
- package/dist/stably-plugin/skills/playwright-best-practices/references/test-coverage.md +0 -495
- package/dist/stably-plugin/skills/playwright-best-practices/references/test-data.md +0 -492
- package/dist/stably-plugin/skills/playwright-best-practices/references/test-organization.md +0 -361
- package/dist/stably-plugin/skills/playwright-best-practices/references/third-party.md +0 -464
- 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
|