@assertrx/dino 1.0.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.
- package/README.md +293 -0
- package/extensions/dino.ts +70 -0
- package/extensions/mabl-mcp.ts +306 -0
- package/extensions/playwright-mcp.ts +142 -0
- package/package.json +47 -0
- package/prompts/create-pr.md +110 -0
- package/prompts/generate-bdd-from-url.md +65 -0
- package/prompts/generate-pom-from-url-cli.md +48 -0
- package/prompts/generate-pom-from-url.md +45 -0
- package/prompts/generate-spec-from-feature.md +64 -0
- package/prompts/heal-playwright-failures.md +106 -0
- package/prompts/setup-playwright.md +81 -0
- package/scripts/start-dino.cmd +8 -0
- package/scripts/start-dino.mjs +226 -0
- package/scripts/start-dino.ps1 +9 -0
- package/scripts/start-dino.sh +10 -0
- package/skills/acli/SKILL.md +292 -0
- package/skills/bdd-gherkin/SKILL.md +552 -0
- package/skills/build-review-interface/SKILL.md +96 -0
- package/skills/error-analysis/SKILL.md +164 -0
- package/skills/eval-audit/SKILL.md +183 -0
- package/skills/evaluate-rag/SKILL.md +177 -0
- package/skills/generate-synthetic-data/SKILL.md +131 -0
- package/skills/mabl-mcp/SKILL.md +79 -0
- package/skills/playwright-cli/SKILL.md +246 -0
- package/skills/playwright-cli/advanced-workflows.md +679 -0
- package/skills/playwright-cli/core-commands.md +375 -0
- package/skills/playwright-cli/device-emulation.md +475 -0
- package/skills/playwright-cli/request-mocking.md +373 -0
- package/skills/playwright-cli/running-custom-code.md +553 -0
- package/skills/playwright-cli/screenshots-and-media.md +457 -0
- package/skills/playwright-cli/session-management.md +425 -0
- package/skills/playwright-cli/storage-and-auth.md +487 -0
- package/skills/playwright-cli/test-generation.md +336 -0
- package/skills/playwright-cli/tracing-and-debugging.md +501 -0
- package/skills/playwright-core/SKILL.md +101 -0
- package/skills/playwright-core/accessibility.md +1517 -0
- package/skills/playwright-core/angular.md +1086 -0
- package/skills/playwright-core/api-testing.md +1623 -0
- package/skills/playwright-core/assertions-and-waiting.md +703 -0
- package/skills/playwright-core/auth-flows.md +1081 -0
- package/skills/playwright-core/authentication.md +1409 -0
- package/skills/playwright-core/browser-apis.md +673 -0
- package/skills/playwright-core/browser-extensions.md +319 -0
- package/skills/playwright-core/canvas-and-webgl.md +494 -0
- package/skills/playwright-core/clock-and-time-mocking.md +427 -0
- package/skills/playwright-core/common-pitfalls.md +1318 -0
- package/skills/playwright-core/component-testing.md +1179 -0
- package/skills/playwright-core/configuration.md +731 -0
- package/skills/playwright-core/crud-testing.md +945 -0
- package/skills/playwright-core/debugging.md +827 -0
- package/skills/playwright-core/drag-and-drop.md +992 -0
- package/skills/playwright-core/electron-testing.md +622 -0
- package/skills/playwright-core/error-and-edge-cases.md +1181 -0
- package/skills/playwright-core/error-index.md +1903 -0
- package/skills/playwright-core/file-operations.md +749 -0
- package/skills/playwright-core/file-upload-download.md +1012 -0
- package/skills/playwright-core/fixtures-and-hooks.md +1072 -0
- package/skills/playwright-core/flaky-tests.md +883 -0
- package/skills/playwright-core/forms-and-validation.md +1056 -0
- package/skills/playwright-core/i18n-and-localization.md +622 -0
- package/skills/playwright-core/iframes-and-shadow-dom.md +488 -0
- package/skills/playwright-core/locator-strategy.md +588 -0
- package/skills/playwright-core/locators.md +743 -0
- package/skills/playwright-core/mobile-and-responsive.md +1669 -0
- package/skills/playwright-core/multi-context-and-popups.md +581 -0
- package/skills/playwright-core/multi-user-and-collaboration.md +477 -0
- package/skills/playwright-core/network-mocking.md +1395 -0
- package/skills/playwright-core/nextjs.md +1013 -0
- package/skills/playwright-core/performance-testing.md +608 -0
- package/skills/playwright-core/react.md +1082 -0
- package/skills/playwright-core/search-and-filter.md +1366 -0
- package/skills/playwright-core/security-testing.md +624 -0
- package/skills/playwright-core/service-workers-and-pwa.md +524 -0
- package/skills/playwright-core/test-architecture.md +569 -0
- package/skills/playwright-core/test-data-management.md +1209 -0
- package/skills/playwright-core/test-organization.md +947 -0
- package/skills/playwright-core/third-party-integrations.md +754 -0
- package/skills/playwright-core/trace-analysis.md +161 -0
- package/skills/playwright-core/visual-regression.md +1027 -0
- package/skills/playwright-core/vue.md +1162 -0
- package/skills/playwright-core/websockets-and-realtime.md +623 -0
- package/skills/playwright-core/when-to-mock.md +827 -0
- package/skills/playwright-pom/SKILL.md +94 -0
- package/skills/playwright-pom/page-object-model.md +921 -0
- package/skills/playwright-pom/pom-vs-fixtures-vs-helpers.md +937 -0
- package/skills/validate-evaluator/SKILL.md +215 -0
- package/skills/write-judge-prompt/SKILL.md +144 -0
- package/themes/dino.json +66 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# Clock and Time Mocking
|
|
2
|
+
|
|
3
|
+
> **When to use**: Testing time-dependent features -- countdown timers, scheduled events, expiration dates, age gates, session timeouts, or any UI that behaves differently based on the current time. Playwright's `page.clock` API lets you control time without waiting in real-time.
|
|
4
|
+
> **Prerequisites**: [core/assertions-and-waiting.md](assertions-and-waiting.md), [core/configuration.md](configuration.md)
|
|
5
|
+
|
|
6
|
+
## Quick Reference
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
// Freeze time at a specific moment
|
|
10
|
+
await page.clock.install({ time: new Date('2025-03-15T10:00:00Z') });
|
|
11
|
+
await page.goto('/dashboard');
|
|
12
|
+
|
|
13
|
+
// Advance time by 5 minutes
|
|
14
|
+
await page.clock.fastForward('05:00');
|
|
15
|
+
|
|
16
|
+
// Set time to a specific point (jumps, does not tick through)
|
|
17
|
+
await page.clock.setFixedTime(new Date('2025-12-31T23:59:59Z'));
|
|
18
|
+
|
|
19
|
+
// Let time resume ticking from current mocked point
|
|
20
|
+
await page.clock.resume();
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Key concept**: `page.clock.install()` replaces `Date`, `setTimeout`, `setInterval`, and `requestAnimationFrame` in the page. Call it before `page.goto()` so the page loads with mocked time from the start.
|
|
24
|
+
|
|
25
|
+
## Patterns
|
|
26
|
+
|
|
27
|
+
### Frozen Time with `install()` and `setFixedTime()`
|
|
28
|
+
|
|
29
|
+
**Use when**: Your test needs time to stand still at a specific moment -- verifying what the UI shows at a particular date/time.
|
|
30
|
+
**Avoid when**: The feature under test depends on timers ticking (use `fastForward` instead).
|
|
31
|
+
|
|
32
|
+
**TypeScript**
|
|
33
|
+
```typescript
|
|
34
|
+
import { test, expect } from '@playwright/test';
|
|
35
|
+
|
|
36
|
+
test('dashboard shows correct greeting based on time of day', async ({ page }) => {
|
|
37
|
+
// Install clock BEFORE navigating
|
|
38
|
+
await page.clock.install({ time: new Date('2025-06-15T08:30:00') });
|
|
39
|
+
await page.goto('/dashboard');
|
|
40
|
+
await expect(page.getByText('Good morning')).toBeVisible();
|
|
41
|
+
|
|
42
|
+
// Jump to afternoon
|
|
43
|
+
await page.clock.setFixedTime(new Date('2025-06-15T14:00:00'));
|
|
44
|
+
await page.reload();
|
|
45
|
+
await expect(page.getByText('Good afternoon')).toBeVisible();
|
|
46
|
+
|
|
47
|
+
// Jump to evening
|
|
48
|
+
await page.clock.setFixedTime(new Date('2025-06-15T20:00:00'));
|
|
49
|
+
await page.reload();
|
|
50
|
+
await expect(page.getByText('Good evening')).toBeVisible();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('subscription shows correct expiration status', async ({ page }) => {
|
|
54
|
+
// Freeze time to a date when subscription is active
|
|
55
|
+
await page.clock.install({ time: new Date('2025-06-01T12:00:00Z') });
|
|
56
|
+
await page.goto('/account');
|
|
57
|
+
|
|
58
|
+
await expect(page.getByTestId('subscription-status')).toHaveText('Active');
|
|
59
|
+
await expect(page.getByTestId('days-remaining')).toContainText('29');
|
|
60
|
+
|
|
61
|
+
// Jump to the expiration date
|
|
62
|
+
await page.clock.setFixedTime(new Date('2025-06-30T12:00:00Z'));
|
|
63
|
+
await page.reload();
|
|
64
|
+
|
|
65
|
+
await expect(page.getByTestId('subscription-status')).toHaveText('Expiring today');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('content displays correctly on a specific holiday', async ({ page }) => {
|
|
69
|
+
await page.clock.install({ time: new Date('2025-12-25T10:00:00') });
|
|
70
|
+
await page.goto('/');
|
|
71
|
+
|
|
72
|
+
await expect(page.getByText('Happy Holidays')).toBeVisible();
|
|
73
|
+
await expect(page.getByTestId('holiday-banner')).toBeVisible();
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**JavaScript**
|
|
78
|
+
```javascript
|
|
79
|
+
const { test, expect } = require('@playwright/test');
|
|
80
|
+
|
|
81
|
+
test('dashboard shows correct greeting based on time of day', async ({ page }) => {
|
|
82
|
+
await page.clock.install({ time: new Date('2025-06-15T08:30:00') });
|
|
83
|
+
await page.goto('/dashboard');
|
|
84
|
+
await expect(page.getByText('Good morning')).toBeVisible();
|
|
85
|
+
|
|
86
|
+
await page.clock.setFixedTime(new Date('2025-06-15T14:00:00'));
|
|
87
|
+
await page.reload();
|
|
88
|
+
await expect(page.getByText('Good afternoon')).toBeVisible();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('subscription shows correct expiration status', async ({ page }) => {
|
|
92
|
+
await page.clock.install({ time: new Date('2025-06-01T12:00:00Z') });
|
|
93
|
+
await page.goto('/account');
|
|
94
|
+
|
|
95
|
+
await expect(page.getByTestId('subscription-status')).toHaveText('Active');
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Fast-Forwarding Time with `fastForward()`
|
|
100
|
+
|
|
101
|
+
**Use when**: Testing timers, countdowns, debounced actions, or any feature that reacts to elapsed time. `fastForward` fires all pending timers up to the specified duration.
|
|
102
|
+
**Avoid when**: You just need to check a static time-dependent display -- use `setFixedTime` instead.
|
|
103
|
+
|
|
104
|
+
**TypeScript**
|
|
105
|
+
```typescript
|
|
106
|
+
import { test, expect } from '@playwright/test';
|
|
107
|
+
|
|
108
|
+
test('countdown timer reaches zero', async ({ page }) => {
|
|
109
|
+
await page.clock.install({ time: new Date('2025-03-15T10:00:00Z') });
|
|
110
|
+
await page.goto('/sale');
|
|
111
|
+
|
|
112
|
+
// Sale countdown starts at 2 hours
|
|
113
|
+
await expect(page.getByTestId('countdown')).toContainText('2:00:00');
|
|
114
|
+
|
|
115
|
+
// Fast-forward 1 hour
|
|
116
|
+
await page.clock.fastForward('01:00:00');
|
|
117
|
+
await expect(page.getByTestId('countdown')).toContainText('1:00:00');
|
|
118
|
+
|
|
119
|
+
// Fast-forward remaining time
|
|
120
|
+
await page.clock.fastForward('01:00:00');
|
|
121
|
+
await expect(page.getByTestId('countdown')).toContainText('0:00:00');
|
|
122
|
+
await expect(page.getByText('Sale ended')).toBeVisible();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('auto-save triggers after 30 seconds of inactivity', async ({ page }) => {
|
|
126
|
+
await page.clock.install({ time: new Date('2025-03-15T10:00:00Z') });
|
|
127
|
+
await page.goto('/editor');
|
|
128
|
+
|
|
129
|
+
// Type something
|
|
130
|
+
await page.getByRole('textbox', { name: 'Content' }).fill('Draft content');
|
|
131
|
+
|
|
132
|
+
// Fast-forward past the auto-save interval
|
|
133
|
+
await page.clock.fastForward('00:30');
|
|
134
|
+
|
|
135
|
+
await expect(page.getByText('Saved')).toBeVisible();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('session timeout warning appears after 25 minutes', async ({ page }) => {
|
|
139
|
+
await page.clock.install({ time: new Date('2025-03-15T10:00:00Z') });
|
|
140
|
+
await page.goto('/dashboard');
|
|
141
|
+
|
|
142
|
+
// Fast-forward to just before the warning (25 min)
|
|
143
|
+
await page.clock.fastForward('24:59');
|
|
144
|
+
await expect(page.getByRole('dialog', { name: 'Session timeout' })).not.toBeVisible();
|
|
145
|
+
|
|
146
|
+
// One more minute triggers the warning
|
|
147
|
+
await page.clock.fastForward('00:01');
|
|
148
|
+
await expect(page.getByRole('dialog', { name: 'Session timeout' })).toBeVisible();
|
|
149
|
+
await expect(page.getByText('Your session will expire in 5 minutes')).toBeVisible();
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**JavaScript**
|
|
154
|
+
```javascript
|
|
155
|
+
const { test, expect } = require('@playwright/test');
|
|
156
|
+
|
|
157
|
+
test('countdown timer reaches zero', async ({ page }) => {
|
|
158
|
+
await page.clock.install({ time: new Date('2025-03-15T10:00:00Z') });
|
|
159
|
+
await page.goto('/sale');
|
|
160
|
+
|
|
161
|
+
await expect(page.getByTestId('countdown')).toContainText('2:00:00');
|
|
162
|
+
|
|
163
|
+
await page.clock.fastForward('01:00:00');
|
|
164
|
+
await expect(page.getByTestId('countdown')).toContainText('1:00:00');
|
|
165
|
+
|
|
166
|
+
await page.clock.fastForward('01:00:00');
|
|
167
|
+
await expect(page.getByText('Sale ended')).toBeVisible();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('auto-save triggers after inactivity', async ({ page }) => {
|
|
171
|
+
await page.clock.install({ time: new Date('2025-03-15T10:00:00Z') });
|
|
172
|
+
await page.goto('/editor');
|
|
173
|
+
|
|
174
|
+
await page.getByRole('textbox', { name: 'Content' }).fill('Draft content');
|
|
175
|
+
await page.clock.fastForward('00:30');
|
|
176
|
+
|
|
177
|
+
await expect(page.getByText('Saved')).toBeVisible();
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Resuming Time with `resume()`
|
|
182
|
+
|
|
183
|
+
**Use when**: You need to start with a mocked time, then let time flow normally for interaction-dependent behavior.
|
|
184
|
+
**Avoid when**: The entire test should use mocked time.
|
|
185
|
+
|
|
186
|
+
**TypeScript**
|
|
187
|
+
```typescript
|
|
188
|
+
import { test, expect } from '@playwright/test';
|
|
189
|
+
|
|
190
|
+
test('notification appears in real-time after scheduled trigger', async ({ page }) => {
|
|
191
|
+
// Start at a known time
|
|
192
|
+
await page.clock.install({ time: new Date('2025-03-15T09:59:55Z') });
|
|
193
|
+
await page.goto('/dashboard');
|
|
194
|
+
|
|
195
|
+
// Notification is scheduled for 10:00:00 — advance to 5 seconds before
|
|
196
|
+
await expect(page.getByTestId('notification-bell')).not.toHaveAttribute('data-count');
|
|
197
|
+
|
|
198
|
+
// Let real time tick from this point
|
|
199
|
+
await page.clock.resume();
|
|
200
|
+
|
|
201
|
+
// The notification should appear within a few seconds
|
|
202
|
+
await expect(page.getByTestId('notification-bell')).toHaveAttribute('data-count', '1', {
|
|
203
|
+
timeout: 10000,
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**JavaScript**
|
|
209
|
+
```javascript
|
|
210
|
+
const { test, expect } = require('@playwright/test');
|
|
211
|
+
|
|
212
|
+
test('notification appears after scheduled trigger', async ({ page }) => {
|
|
213
|
+
await page.clock.install({ time: new Date('2025-03-15T09:59:55Z') });
|
|
214
|
+
await page.goto('/dashboard');
|
|
215
|
+
|
|
216
|
+
await page.clock.resume();
|
|
217
|
+
|
|
218
|
+
await expect(page.getByTestId('notification-bell')).toHaveAttribute('data-count', '1', {
|
|
219
|
+
timeout: 10000,
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Testing Date-Dependent UI
|
|
225
|
+
|
|
226
|
+
**Use when**: Features that change based on the current date -- age verification, expiration warnings, seasonal content, date pickers.
|
|
227
|
+
**Avoid when**: The date is passed from the server and does not depend on client-side `Date`.
|
|
228
|
+
|
|
229
|
+
**TypeScript**
|
|
230
|
+
```typescript
|
|
231
|
+
import { test, expect } from '@playwright/test';
|
|
232
|
+
|
|
233
|
+
test('age gate blocks users under 18', async ({ page }) => {
|
|
234
|
+
// User born on 2010-01-15 — under 18 as of 2025-06-01
|
|
235
|
+
await page.clock.install({ time: new Date('2025-06-01T12:00:00') });
|
|
236
|
+
await page.goto('/age-restricted');
|
|
237
|
+
|
|
238
|
+
await page.getByLabel('Date of birth').fill('2010-01-15');
|
|
239
|
+
await page.getByRole('button', { name: 'Verify age' }).click();
|
|
240
|
+
|
|
241
|
+
await expect(page.getByText('You must be 18 or older')).toBeVisible();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('age gate allows users 18 and older', async ({ page }) => {
|
|
245
|
+
await page.clock.install({ time: new Date('2025-06-01T12:00:00') });
|
|
246
|
+
await page.goto('/age-restricted');
|
|
247
|
+
|
|
248
|
+
await page.getByLabel('Date of birth').fill('2005-01-15');
|
|
249
|
+
await page.getByRole('button', { name: 'Verify age' }).click();
|
|
250
|
+
|
|
251
|
+
await expect(page.getByText('Welcome')).toBeVisible();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('trial expiration banner shows at correct times', async ({ page }) => {
|
|
255
|
+
// Day 1 of 14-day trial
|
|
256
|
+
await page.clock.install({ time: new Date('2025-03-01T12:00:00Z') });
|
|
257
|
+
await page.goto('/dashboard');
|
|
258
|
+
await expect(page.getByTestId('trial-banner')).toContainText('13 days remaining');
|
|
259
|
+
|
|
260
|
+
// Day 12 — warning state
|
|
261
|
+
await page.clock.setFixedTime(new Date('2025-03-12T12:00:00Z'));
|
|
262
|
+
await page.reload();
|
|
263
|
+
await expect(page.getByTestId('trial-banner')).toContainText('2 days remaining');
|
|
264
|
+
await expect(page.getByTestId('trial-banner')).toHaveCSS('background-color', /rgb\(255/); // Red/warning
|
|
265
|
+
|
|
266
|
+
// Day 15 — expired
|
|
267
|
+
await page.clock.setFixedTime(new Date('2025-03-15T12:00:00Z'));
|
|
268
|
+
await page.reload();
|
|
269
|
+
await expect(page.getByText('Your trial has expired')).toBeVisible();
|
|
270
|
+
await expect(page.getByRole('button', { name: 'Upgrade now' })).toBeVisible();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test('date picker defaults to current mocked date', async ({ page }) => {
|
|
274
|
+
await page.clock.install({ time: new Date('2025-07-04T12:00:00') });
|
|
275
|
+
await page.goto('/booking');
|
|
276
|
+
|
|
277
|
+
await page.getByLabel('Check-in date').click();
|
|
278
|
+
|
|
279
|
+
// Calendar should open to July 2025
|
|
280
|
+
await expect(page.getByText('July 2025')).toBeVisible();
|
|
281
|
+
|
|
282
|
+
// Today (July 4) should be highlighted
|
|
283
|
+
const today = page.locator('[aria-current="date"]');
|
|
284
|
+
await expect(today).toHaveText('4');
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**JavaScript**
|
|
289
|
+
```javascript
|
|
290
|
+
const { test, expect } = require('@playwright/test');
|
|
291
|
+
|
|
292
|
+
test('age gate blocks users under 18', async ({ page }) => {
|
|
293
|
+
await page.clock.install({ time: new Date('2025-06-01T12:00:00') });
|
|
294
|
+
await page.goto('/age-restricted');
|
|
295
|
+
|
|
296
|
+
await page.getByLabel('Date of birth').fill('2010-01-15');
|
|
297
|
+
await page.getByRole('button', { name: 'Verify age' }).click();
|
|
298
|
+
|
|
299
|
+
await expect(page.getByText('You must be 18 or older')).toBeVisible();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test('trial expiration shows correct days remaining', async ({ page }) => {
|
|
303
|
+
await page.clock.install({ time: new Date('2025-03-01T12:00:00Z') });
|
|
304
|
+
await page.goto('/dashboard');
|
|
305
|
+
await expect(page.getByTestId('trial-banner')).toContainText('13 days remaining');
|
|
306
|
+
|
|
307
|
+
await page.clock.setFixedTime(new Date('2025-03-15T12:00:00Z'));
|
|
308
|
+
await page.reload();
|
|
309
|
+
await expect(page.getByText('Your trial has expired')).toBeVisible();
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Timezone-Dependent Features
|
|
314
|
+
|
|
315
|
+
**Use when**: Testing features that combine mocked time with specific timezones.
|
|
316
|
+
**Avoid when**: The feature uses only UTC and does not render local times.
|
|
317
|
+
|
|
318
|
+
**TypeScript**
|
|
319
|
+
```typescript
|
|
320
|
+
import { test, expect } from '@playwright/test';
|
|
321
|
+
|
|
322
|
+
test('business hours banner shows open/closed status', async ({ browser }) => {
|
|
323
|
+
// Business hours: 9 AM - 5 PM Eastern
|
|
324
|
+
const context = await browser.newContext({ timezoneId: 'America/New_York' });
|
|
325
|
+
const page = await context.newPage();
|
|
326
|
+
|
|
327
|
+
// 10 AM Eastern — should show "Open"
|
|
328
|
+
await page.clock.install({ time: new Date('2025-03-15T14:00:00Z') }); // 10 AM ET
|
|
329
|
+
await page.goto('/contact');
|
|
330
|
+
await expect(page.getByTestId('business-hours')).toContainText('Open');
|
|
331
|
+
|
|
332
|
+
// 6 PM Eastern — should show "Closed"
|
|
333
|
+
await page.clock.setFixedTime(new Date('2025-03-15T22:00:00Z')); // 6 PM ET
|
|
334
|
+
await page.reload();
|
|
335
|
+
await expect(page.getByTestId('business-hours')).toContainText('Closed');
|
|
336
|
+
|
|
337
|
+
await context.close();
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test('scheduled event shows correct local time', async ({ browser }) => {
|
|
341
|
+
// Event at 2025-03-20T18:00:00Z
|
|
342
|
+
|
|
343
|
+
// User in Tokyo (UTC+9)
|
|
344
|
+
const tokyoCtx = await browser.newContext({ timezoneId: 'Asia/Tokyo' });
|
|
345
|
+
const tokyoPage = await tokyoCtx.newPage();
|
|
346
|
+
await tokyoPage.clock.install({ time: new Date('2025-03-20T10:00:00Z') });
|
|
347
|
+
await tokyoPage.goto('/events/upcoming');
|
|
348
|
+
// 18:00 UTC = 03:00 AM next day in Tokyo (UTC+9)
|
|
349
|
+
await expect(tokyoPage.getByTestId('event-time')).toContainText('3:00 AM');
|
|
350
|
+
await tokyoCtx.close();
|
|
351
|
+
|
|
352
|
+
// User in London (UTC+0 in March, before DST)
|
|
353
|
+
const londonCtx = await browser.newContext({ timezoneId: 'Europe/London' });
|
|
354
|
+
const londonPage = await londonCtx.newPage();
|
|
355
|
+
await londonPage.clock.install({ time: new Date('2025-03-20T10:00:00Z') });
|
|
356
|
+
await londonPage.goto('/events/upcoming');
|
|
357
|
+
await expect(londonPage.getByTestId('event-time')).toContainText('6:00 PM');
|
|
358
|
+
await londonCtx.close();
|
|
359
|
+
});
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**JavaScript**
|
|
363
|
+
```javascript
|
|
364
|
+
const { test, expect } = require('@playwright/test');
|
|
365
|
+
|
|
366
|
+
test('business hours banner shows open/closed status', async ({ browser }) => {
|
|
367
|
+
const context = await browser.newContext({ timezoneId: 'America/New_York' });
|
|
368
|
+
const page = await context.newPage();
|
|
369
|
+
|
|
370
|
+
await page.clock.install({ time: new Date('2025-03-15T14:00:00Z') });
|
|
371
|
+
await page.goto('/contact');
|
|
372
|
+
await expect(page.getByTestId('business-hours')).toContainText('Open');
|
|
373
|
+
|
|
374
|
+
await page.clock.setFixedTime(new Date('2025-03-15T22:00:00Z'));
|
|
375
|
+
await page.reload();
|
|
376
|
+
await expect(page.getByTestId('business-hours')).toContainText('Closed');
|
|
377
|
+
|
|
378
|
+
await context.close();
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Decision Guide
|
|
383
|
+
|
|
384
|
+
| Scenario | API | Why |
|
|
385
|
+
|---|---|---|
|
|
386
|
+
| Check UI at a specific date/time | `clock.install()` + `clock.setFixedTime()` | Time is frozen; no timer ticking |
|
|
387
|
+
| Test countdown or timer behavior | `clock.install()` + `clock.fastForward()` | Fires timers as time advances without real waiting |
|
|
388
|
+
| Test after a long idle period | `clock.install()` + `clock.fastForward('30:00')` | Simulates 30 minutes without waiting 30 minutes |
|
|
389
|
+
| Start mocked, then tick normally | `clock.install()` + `clock.resume()` | Useful when you need real `requestAnimationFrame` after setup |
|
|
390
|
+
| Different timezone display | `browser.newContext({ timezoneId })` | Affects `Date` timezone rendering |
|
|
391
|
+
| Timezone + mocked time | `newContext({ timezoneId })` + `clock.install()` | Both timezone and absolute time are controlled |
|
|
392
|
+
| Test date picker defaults | `clock.install()` with target date | Calendar opens to the mocked "today" |
|
|
393
|
+
| Test DST transitions | Set `timezoneId` + `install` at DST boundary | Tests the most common timezone bugs |
|
|
394
|
+
|
|
395
|
+
## Anti-Patterns
|
|
396
|
+
|
|
397
|
+
| Don't Do This | Problem | Do This Instead |
|
|
398
|
+
|---|---|---|
|
|
399
|
+
| Calling `clock.install()` after `page.goto()` | Page already loaded with real `Date`; timers already fired | Call `clock.install()` BEFORE `page.goto()` |
|
|
400
|
+
| Using `page.waitForTimeout(30000)` to test a 30-second timer | Wastes 30 real seconds per test run | `page.clock.fastForward('00:30')` completes instantly |
|
|
401
|
+
| Testing time without mocking the clock | Results depend on when the test runs (morning vs evening, Monday vs Sunday) | Always mock time for time-dependent assertions |
|
|
402
|
+
| Using `setFixedTime` when you need timers to fire | `setFixedTime` freezes time; `setInterval`/`setTimeout` will not trigger | Use `fastForward` to advance time and fire pending timers |
|
|
403
|
+
| Mocking only `Date.now()` via `page.evaluate` | Does not affect `setTimeout`, `setInterval`, or `requestAnimationFrame` | Use `page.clock.install()` which mocks all time APIs |
|
|
404
|
+
| Forgetting timezone when testing dates | Test passes locally but fails in CI (different timezone) | Always set `timezoneId` in context or use UTC dates |
|
|
405
|
+
| Advancing time in very small increments | Slow test; many unnecessary timer firings | Advance to the exact time of interest in one call |
|
|
406
|
+
| Not calling `resume()` before real-time-dependent assertions | Mocked timers will not fire naturally; assertions time out | Call `clock.resume()` when you need real time to flow |
|
|
407
|
+
|
|
408
|
+
## Troubleshooting
|
|
409
|
+
|
|
410
|
+
| Symptom | Likely Cause | Fix |
|
|
411
|
+
|---|---|---|
|
|
412
|
+
| `clock.install()` has no effect | Called after `page.goto()`; page already has real `Date` | Move `clock.install()` before navigation |
|
|
413
|
+
| Timer callbacks never fire | Time is frozen with `setFixedTime`; timers need advancing | Use `fastForward()` to advance past the timer delay |
|
|
414
|
+
| `fastForward` does not trigger timer | Timer was registered with a delay longer than the fast-forward amount | Fast-forward by at least the timer's delay |
|
|
415
|
+
| Date is correct but timezone display is wrong | `clock.install` sets time in UTC; `timezoneId` not set on context | Create context with `{ timezoneId: 'Your/Timezone' }` |
|
|
416
|
+
| Animations break with mocked clock | `requestAnimationFrame` is mocked and does not fire naturally | Call `clock.resume()` before animation-dependent assertions |
|
|
417
|
+
| Test passes locally but fails in CI | Local timezone differs from CI timezone | Always set `timezoneId` explicitly in the context |
|
|
418
|
+
| `setFixedTime` throws "clock not installed" | `install()` was not called first | Call `page.clock.install()` before any other clock methods |
|
|
419
|
+
| Page makes fetch requests with wrong timestamps | Server sees mocked `Date.now()` in request payloads | This is expected; mock server responses if needed |
|
|
420
|
+
|
|
421
|
+
## Related
|
|
422
|
+
|
|
423
|
+
- [core/i18n-and-localization.md](i18n-and-localization.md) -- timezone and locale testing patterns
|
|
424
|
+
- [core/assertions-and-waiting.md](assertions-and-waiting.md) -- auto-waiting vs time-based assertions
|
|
425
|
+
- [core/error-and-edge-cases.md](error-and-edge-cases.md) -- testing timeout and expiration edge cases
|
|
426
|
+
- [core/performance-testing.md](performance-testing.md) -- timing-related performance measurement
|
|
427
|
+
- [core/websockets-and-realtime.md](websockets-and-realtime.md) -- real-time features that depend on timing
|