stably 4.9.0 → 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 +2 -2
  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,364 +0,0 @@
1
- # Date, Time & Clock Mocking
2
-
3
- ## Table of Contents
4
-
5
- 1. [Clock API Basics](#clock-api-basics)
6
- 2. [Fixed Time Testing](#fixed-time-testing)
7
- 3. [Time Advancement](#time-advancement)
8
- 4. [Timezone Testing](#timezone-testing)
9
- 5. [Timer Mocking](#timer-mocking)
10
-
11
- ## Clock API Basics
12
-
13
- ### Install Clock
14
-
15
- ```typescript
16
- test("mock current time", async ({ page }) => {
17
- // Install clock before navigating
18
- await page.clock.install({ time: new Date("2025-01-15T09:00:00") });
19
-
20
- await page.goto("/dashboard");
21
-
22
- // Page sees January 15, 2025 as current date
23
- await expect(page.getByText("January 15, 2025")).toBeVisible();
24
- });
25
- ```
26
-
27
- ### Clock with Fixture
28
-
29
- ```typescript
30
- // fixtures/clock.fixture.ts
31
- import { test as base } from "@playwright/test";
32
-
33
- type ClockFixtures = {
34
- mockTime: (date: Date | string) => Promise<void>;
35
- };
36
-
37
- export const test = base.extend<ClockFixtures>({
38
- mockTime: async ({ page }, use) => {
39
- await use(async (date) => {
40
- const time = typeof date === "string" ? new Date(date) : date;
41
- await page.clock.install({ time });
42
- });
43
- },
44
- });
45
-
46
- // Usage
47
- test("subscription expiry", async ({ page, mockTime }) => {
48
- await mockTime("2025-12-31T23:59:00");
49
- await page.goto("/subscription");
50
-
51
- await expect(page.getByText("Expires today")).toBeVisible();
52
- });
53
- ```
54
-
55
- ## Fixed Time Testing
56
-
57
- ### Test Date-Dependent Features
58
-
59
- ```typescript
60
- test("show holiday banner in December", async ({ page }) => {
61
- await page.clock.install({ time: new Date("2025-12-20T10:00:00") });
62
-
63
- await page.goto("/");
64
-
65
- await expect(page.getByRole("banner", { name: /holiday/i })).toBeVisible();
66
- });
67
-
68
- test("no holiday banner in January", async ({ page }) => {
69
- await page.clock.install({ time: new Date("2025-01-15T10:00:00") });
70
-
71
- await page.goto("/");
72
-
73
- await expect(page.getByRole("banner", { name: /holiday/i })).toBeHidden();
74
- });
75
- ```
76
-
77
- ### Test Relative Time Display
78
-
79
- ```typescript
80
- test("shows relative time correctly", async ({ page }) => {
81
- // Fix time to control "posted 2 hours ago" text
82
- await page.clock.install({ time: new Date("2025-06-15T14:00:00") });
83
-
84
- // Mock API to return post with known timestamp
85
- await page.route("**/api/posts/1", (route) =>
86
- route.fulfill({
87
- json: {
88
- id: 1,
89
- title: "Test Post",
90
- createdAt: "2025-06-15T12:00:00Z", // 2 hours before mock time
91
- },
92
- }),
93
- );
94
-
95
- await page.goto("/posts/1");
96
-
97
- await expect(page.getByText("2 hours ago")).toBeVisible();
98
- });
99
- ```
100
-
101
- ### Test Date Boundaries
102
-
103
- ```typescript
104
- test.describe("end of month billing", () => {
105
- test("shows billing on last day of month", async ({ page }) => {
106
- await page.clock.install({ time: new Date("2025-01-31T10:00:00") });
107
- await page.goto("/billing");
108
-
109
- await expect(page.getByText("Payment due today")).toBeVisible();
110
- });
111
-
112
- test("shows days remaining mid-month", async ({ page }) => {
113
- await page.clock.install({ time: new Date("2025-01-15T10:00:00") });
114
- await page.goto("/billing");
115
-
116
- await expect(page.getByText("16 days until payment")).toBeVisible();
117
- });
118
- });
119
- ```
120
-
121
- ## Time Advancement
122
-
123
- ### Advance Time Manually
124
-
125
- ```typescript
126
- test("session timeout warning", async ({ page }) => {
127
- await page.clock.install({ time: new Date("2025-01-15T09:00:00") });
128
- await page.goto("/dashboard");
129
-
130
- // Advance 25 minutes (session timeout at 30 min)
131
- await page.clock.fastForward("25:00");
132
-
133
- await expect(page.getByText("Session expires in 5 minutes")).toBeVisible();
134
-
135
- // Advance 5 more minutes
136
- await page.clock.fastForward("05:00");
137
-
138
- await expect(page.getByText("Session expired")).toBeVisible();
139
- });
140
- ```
141
-
142
- ### Pause and Resume Time
143
-
144
- ```typescript
145
- test("countdown timer", async ({ page }) => {
146
- await page.clock.install({ time: new Date("2025-01-15T09:00:00") });
147
- await page.goto("/sale");
148
-
149
- // Initial state
150
- await expect(page.getByText("Sale ends in 2:00:00")).toBeVisible();
151
-
152
- // Advance 1 hour
153
- await page.clock.fastForward("01:00:00");
154
-
155
- await expect(page.getByText("Sale ends in 1:00:00")).toBeVisible();
156
-
157
- // Advance past end
158
- await page.clock.fastForward("01:00:01");
159
-
160
- await expect(page.getByText("Sale ended")).toBeVisible();
161
- });
162
- ```
163
-
164
- ### Run Pending Timers
165
-
166
- ```typescript
167
- test("debounced search", async ({ page }) => {
168
- await page.clock.install({ time: new Date("2025-01-15T09:00:00") });
169
- await page.goto("/search");
170
-
171
- await page.getByLabel("Search").fill("playwright");
172
-
173
- // Search is debounced by 300ms, won't fire yet
174
- await expect(page.getByTestId("search-results")).toBeHidden();
175
-
176
- // Fast forward past debounce
177
- await page.clock.fastForward(300);
178
-
179
- // Now search should execute
180
- await expect(page.getByTestId("search-results")).toBeVisible();
181
- });
182
- ```
183
-
184
- ## Timezone Testing
185
-
186
- ### Test Different Timezones
187
-
188
- ```typescript
189
- test.describe("timezone display", () => {
190
- test("shows correct time in PST", async ({ browser }) => {
191
- const context = await browser.newContext({
192
- timezoneId: "America/Los_Angeles",
193
- });
194
- const page = await context.newPage();
195
-
196
- await page.clock.install({ time: new Date("2025-01-15T17:00:00Z") }); // 5 PM UTC
197
-
198
- await page.goto("/schedule");
199
-
200
- // Should show 9 AM PST
201
- await expect(page.getByText("9:00 AM")).toBeVisible();
202
-
203
- await context.close();
204
- });
205
-
206
- test("shows correct time in JST", async ({ browser }) => {
207
- const context = await browser.newContext({
208
- timezoneId: "Asia/Tokyo",
209
- });
210
- const page = await context.newPage();
211
-
212
- await page.clock.install({ time: new Date("2025-01-15T17:00:00Z") }); // 5 PM UTC
213
-
214
- await page.goto("/schedule");
215
-
216
- // Should show 2 AM next day JST
217
- await expect(page.getByText("2:00 AM")).toBeVisible();
218
-
219
- await context.close();
220
- });
221
- });
222
- ```
223
-
224
- ### Timezone Fixture
225
-
226
- ```typescript
227
- // fixtures/timezone.fixture.ts
228
- import { test as base } from "@playwright/test";
229
-
230
- type TimezoneFixtures = {
231
- pageInTimezone: (timezone: string) => Promise<Page>;
232
- };
233
-
234
- export const test = base.extend<TimezoneFixtures>({
235
- pageInTimezone: async ({ browser }, use) => {
236
- const pages: Page[] = [];
237
-
238
- await use(async (timezone) => {
239
- const context = await browser.newContext({ timezoneId: timezone });
240
- const page = await context.newPage();
241
- pages.push(page);
242
- return page;
243
- });
244
-
245
- // Cleanup
246
- for (const page of pages) {
247
- await page.context().close();
248
- }
249
- },
250
- });
251
- ```
252
-
253
- ## Timer Mocking
254
-
255
- ### Mock setInterval
256
-
257
- ```typescript
258
- test("auto-refresh data", async ({ page }) => {
259
- await page.clock.install({ time: new Date("2025-01-15T09:00:00") });
260
-
261
- let apiCalls = 0;
262
- await page.route("**/api/data", (route) => {
263
- apiCalls++;
264
- route.fulfill({ json: { value: apiCalls } });
265
- });
266
-
267
- await page.goto("/live-data"); // Sets up 30s refresh interval
268
-
269
- expect(apiCalls).toBe(1); // Initial load
270
-
271
- // Advance 30 seconds
272
- await page.clock.fastForward("00:30");
273
- expect(apiCalls).toBe(2); // First refresh
274
-
275
- // Advance another 30 seconds
276
- await page.clock.fastForward("00:30");
277
- expect(apiCalls).toBe(3); // Second refresh
278
- });
279
- ```
280
-
281
- ### Mock setTimeout Chains
282
-
283
- ```typescript
284
- test("notification queue", async ({ page }) => {
285
- await page.clock.install({ time: new Date("2025-01-15T09:00:00") });
286
- await page.goto("/notifications");
287
-
288
- // Trigger 3 notifications that show sequentially
289
- await page.getByRole("button", { name: "Show All" }).click();
290
-
291
- // First notification appears immediately
292
- await expect(page.getByText("Notification 1")).toBeVisible();
293
-
294
- // Second appears after 2 seconds
295
- await page.clock.fastForward("00:02");
296
- await expect(page.getByText("Notification 2")).toBeVisible();
297
-
298
- // Third appears after 2 more seconds
299
- await page.clock.fastForward("00:02");
300
- await expect(page.getByText("Notification 3")).toBeVisible();
301
- });
302
- ```
303
-
304
- ### Test Animation Frames
305
-
306
- ```typescript
307
- test("animation completes", async ({ page }) => {
308
- await page.clock.install({ time: new Date("2025-01-15T09:00:00") });
309
- await page.goto("/animation-demo");
310
-
311
- await page.getByRole("button", { name: "Animate" }).click();
312
-
313
- // Animation runs for 500ms
314
- const element = page.getByTestId("animated-box");
315
- await expect(element).toHaveCSS("opacity", "0");
316
-
317
- // Fast forward through animation
318
- await page.clock.fastForward(500);
319
-
320
- await expect(element).toHaveCSS("opacity", "1");
321
- });
322
- ```
323
-
324
- ## Best Practices
325
-
326
- ### Always Install Clock Before Navigation
327
-
328
- ```typescript
329
- // Good
330
- test("date test", async ({ page }) => {
331
- await page.clock.install({ time: new Date("2025-01-15") });
332
- await page.goto("/"); // Page loads with mocked time
333
- });
334
-
335
- // Bad - time already captured by page
336
- test("date test", async ({ page }) => {
337
- await page.goto("/");
338
- await page.clock.install({ time: new Date("2025-01-15") }); // Too late!
339
- });
340
- ```
341
-
342
- ### Use ISO Strings for Clarity
343
-
344
- ```typescript
345
- // Good - explicit timezone
346
- await page.clock.install({ time: new Date("2025-01-15T09:00:00Z") });
347
-
348
- // Ambiguous - uses local timezone
349
- await page.clock.install({ time: new Date("2025-01-15T09:00:00") });
350
- ```
351
-
352
- ## Anti-Patterns to Avoid
353
-
354
- | Anti-Pattern | Problem | Solution |
355
- | ---------------------------------------- | ------------------------------- | -------------------------------------- |
356
- | Installing clock after navigation | Page already captured real time | Install clock before `goto()` |
357
- | Hardcoded relative dates | Tests break over time | Use fixed dates with clock mock |
358
- | Not accounting for timezone | Tests fail in different regions | Use explicit UTC times or set timezone |
359
- | Using `waitForTimeout` with mocked clock | Conflicts with mocked timers | Use `fastForward` instead |
360
-
361
- ## Related References
362
-
363
- - **Assertions**: See [assertions-waiting.md](assertions-waiting.md) for time-based assertions
364
- - **Fixtures**: See [fixtures-hooks.md](fixtures-hooks.md) for clock fixtures