Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Playwright testing guide covering E2E, component, API, visual, accessibility, and security tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
advanced/clock-mocking.md
1# Date, Time & Clock Mocking23## Table of Contents451. [Clock API Basics](#clock-api-basics)62. [Fixed Time Testing](#fixed-time-testing)73. [Time Advancement](#time-advancement)84. [Timezone Testing](#timezone-testing)95. [Timer Mocking](#timer-mocking)1011## Clock API Basics1213### Install Clock1415```typescript16test("mock current time", async ({ page }) => {17// Install clock before navigating18await page.clock.install({ time: new Date("2025-01-15T09:00:00") });1920await page.goto("/dashboard");2122// Page sees January 15, 2025 as current date23await expect(page.getByText("January 15, 2025")).toBeVisible();24});25```2627### Clock with Fixture2829```typescript30// fixtures/clock.fixture.ts31import { test as base } from "@playwright/test";3233type ClockFixtures = {34mockTime: (date: Date | string) => Promise<void>;35};3637export const test = base.extend<ClockFixtures>({38mockTime: async ({ page }, use) => {39await use(async (date) => {40const time = typeof date === "string" ? new Date(date) : date;41await page.clock.install({ time });42});43},44});4546// Usage47test("subscription expiry", async ({ page, mockTime }) => {48await mockTime("2025-12-31T23:59:00");49await page.goto("/subscription");5051await expect(page.getByText("Expires today")).toBeVisible();52});53```5455## Fixed Time Testing5657### Test Date-Dependent Features5859```typescript60test("show holiday banner in December", async ({ page }) => {61await page.clock.install({ time: new Date("2025-12-20T10:00:00") });6263await page.goto("/");6465await expect(page.getByRole("banner", { name: /holiday/i })).toBeVisible();66});6768test("no holiday banner in January", async ({ page }) => {69await page.clock.install({ time: new Date("2025-01-15T10:00:00") });7071await page.goto("/");7273await expect(page.getByRole("banner", { name: /holiday/i })).toBeHidden();74});75```7677### Test Relative Time Display7879```typescript80test("shows relative time correctly", async ({ page }) => {81// Fix time to control "posted 2 hours ago" text82await page.clock.install({ time: new Date("2025-06-15T14:00:00") });8384// Mock API to return post with known timestamp85await page.route("**/api/posts/1", (route) =>86route.fulfill({87json: {88id: 1,89title: "Test Post",90createdAt: "2025-06-15T12:00:00Z", // 2 hours before mock time91},92}),93);9495await page.goto("/posts/1");9697await expect(page.getByText("2 hours ago")).toBeVisible();98});99```100101### Test Date Boundaries102103```typescript104test.describe("end of month billing", () => {105test("shows billing on last day of month", async ({ page }) => {106await page.clock.install({ time: new Date("2025-01-31T10:00:00") });107await page.goto("/billing");108109await expect(page.getByText("Payment due today")).toBeVisible();110});111112test("shows days remaining mid-month", async ({ page }) => {113await page.clock.install({ time: new Date("2025-01-15T10:00:00") });114await page.goto("/billing");115116await expect(page.getByText("16 days until payment")).toBeVisible();117});118});119```120121## Time Advancement122123### Advance Time Manually124125```typescript126test("session timeout warning", async ({ page }) => {127await page.clock.install({ time: new Date("2025-01-15T09:00:00") });128await page.goto("/dashboard");129130// Advance 25 minutes (session timeout at 30 min)131await page.clock.fastForward("25:00");132133await expect(page.getByText("Session expires in 5 minutes")).toBeVisible();134135// Advance 5 more minutes136await page.clock.fastForward("05:00");137138await expect(page.getByText("Session expired")).toBeVisible();139});140```141142### Pause and Resume Time143144```typescript145test("countdown timer", async ({ page }) => {146await page.clock.install({ time: new Date("2025-01-15T09:00:00") });147await page.goto("/sale");148149// Initial state150await expect(page.getByText("Sale ends in 2:00:00")).toBeVisible();151152// Advance 1 hour153await page.clock.fastForward("01:00:00");154155await expect(page.getByText("Sale ends in 1:00:00")).toBeVisible();156157// Advance past end158await page.clock.fastForward("01:00:01");159160await expect(page.getByText("Sale ended")).toBeVisible();161});162```163164### Run Pending Timers165166```typescript167test("debounced search", async ({ page }) => {168await page.clock.install({ time: new Date("2025-01-15T09:00:00") });169await page.goto("/search");170171await page.getByLabel("Search").fill("playwright");172173// Search is debounced by 300ms, won't fire yet174await expect(page.getByTestId("search-results")).toBeHidden();175176// Fast forward past debounce177await page.clock.fastForward(300);178179// Now search should execute180await expect(page.getByTestId("search-results")).toBeVisible();181});182```183184## Timezone Testing185186### Test Different Timezones187188```typescript189test.describe("timezone display", () => {190test("shows correct time in PST", async ({ browser }) => {191const context = await browser.newContext({192timezoneId: "America/Los_Angeles",193});194const page = await context.newPage();195196await page.clock.install({ time: new Date("2025-01-15T17:00:00Z") }); // 5 PM UTC197198await page.goto("/schedule");199200// Should show 9 AM PST201await expect(page.getByText("9:00 AM")).toBeVisible();202203await context.close();204});205206test("shows correct time in JST", async ({ browser }) => {207const context = await browser.newContext({208timezoneId: "Asia/Tokyo",209});210const page = await context.newPage();211212await page.clock.install({ time: new Date("2025-01-15T17:00:00Z") }); // 5 PM UTC213214await page.goto("/schedule");215216// Should show 2 AM next day JST217await expect(page.getByText("2:00 AM")).toBeVisible();218219await context.close();220});221});222```223224### Timezone Fixture225226```typescript227// fixtures/timezone.fixture.ts228import { test as base } from "@playwright/test";229230type TimezoneFixtures = {231pageInTimezone: (timezone: string) => Promise<Page>;232};233234export const test = base.extend<TimezoneFixtures>({235pageInTimezone: async ({ browser }, use) => {236const pages: Page[] = [];237238await use(async (timezone) => {239const context = await browser.newContext({ timezoneId: timezone });240const page = await context.newPage();241pages.push(page);242return page;243});244245// Cleanup246for (const page of pages) {247await page.context().close();248}249},250});251```252253## Timer Mocking254255### Mock setInterval256257```typescript258test("auto-refresh data", async ({ page }) => {259await page.clock.install({ time: new Date("2025-01-15T09:00:00") });260261let apiCalls = 0;262await page.route("**/api/data", (route) => {263apiCalls++;264route.fulfill({ json: { value: apiCalls } });265});266267await page.goto("/live-data"); // Sets up 30s refresh interval268269expect(apiCalls).toBe(1); // Initial load270271// Advance 30 seconds272await page.clock.fastForward("00:30");273expect(apiCalls).toBe(2); // First refresh274275// Advance another 30 seconds276await page.clock.fastForward("00:30");277expect(apiCalls).toBe(3); // Second refresh278});279```280281### Mock setTimeout Chains282283```typescript284test("notification queue", async ({ page }) => {285await page.clock.install({ time: new Date("2025-01-15T09:00:00") });286await page.goto("/notifications");287288// Trigger 3 notifications that show sequentially289await page.getByRole("button", { name: "Show All" }).click();290291// First notification appears immediately292await expect(page.getByText("Notification 1")).toBeVisible();293294// Second appears after 2 seconds295await page.clock.fastForward("00:02");296await expect(page.getByText("Notification 2")).toBeVisible();297298// Third appears after 2 more seconds299await page.clock.fastForward("00:02");300await expect(page.getByText("Notification 3")).toBeVisible();301});302```303304### Test Animation Frames305306```typescript307test("animation completes", async ({ page }) => {308await page.clock.install({ time: new Date("2025-01-15T09:00:00") });309await page.goto("/animation-demo");310311await page.getByRole("button", { name: "Animate" }).click();312313// Animation runs for 500ms314const element = page.getByTestId("animated-box");315await expect(element).toHaveCSS("opacity", "0");316317// Fast forward through animation318await page.clock.fastForward(500);319320await expect(element).toHaveCSS("opacity", "1");321});322```323324## Best Practices325326### Always Install Clock Before Navigation327328```typescript329// Good330test("date test", async ({ page }) => {331await page.clock.install({ time: new Date("2025-01-15") });332await page.goto("/"); // Page loads with mocked time333});334335// Bad - time already captured by page336test("date test", async ({ page }) => {337await page.goto("/");338await page.clock.install({ time: new Date("2025-01-15") }); // Too late!339});340```341342### Use ISO Strings for Clarity343344```typescript345// Good - explicit timezone346await page.clock.install({ time: new Date("2025-01-15T09:00:00Z") });347348// Ambiguous - uses local timezone349await page.clock.install({ time: new Date("2025-01-15T09:00:00") });350```351352## Anti-Patterns to Avoid353354| 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 |360361## Related References362363- **Assertions**: See [assertions-waiting.md](../core/assertions-waiting.md) for time-based assertions364- **Fixtures**: See [fixtures-hooks.md](../core/fixtures-hooks.md) for clock fixtures365