Wiki

← Tất cả bài wiki

Clock API (Đóng băng và điều khiển thời gian trong trang)

automationplaywright

page.clock (Playwright 1.45+) cho phép thay thế Date, setTimeout, setInterval, requestAnimationFrame của trang bằng fake clock điều khiển từ test — đóng băng thời điểm, nhảy qua 30 giây/5 phút tức thì, chạy hết timer giữa chừng. Giải quyết triệt để test logic phụ thuộc thời gian (OTP hết hạn, countdown flash sale, '5 phút trước') vốn flake hoặc phải chờ thật.

Khái niệm & Bản chất

  • Fake clock bên trong browser: page.clock.install() thay các time API của trang (Date.now, new Date, setTimeout, setInterval, clearTimeout, requestAnimationFrame, performance.now) bằng bản điều khiển được — code trang chạy trên 'giờ giả'.
  • Giải quyết flake kinh điển: assert 'đơn tạo 5 phút trước', OTP hết hạn sau 30s, banner 'Flash sale kết thúc lúc 10:00' — trước đây phải sleep thật (chậm) hoặc seed data với timestamp (phức tạp).
  • Chỉ ảnh hưởng code TRONG trang: timeout của chính Playwright (auto-wait, expect, waitForSelector) vẫn dùng real time → test không bao giờ kẹt vì fake clock.
  • Khác fake timers Node (Sinon/Jest): page.clock chạy trong browser context, điều khiển code trang web — không liên quan timer của process test.

Cách hoạt động & API chính

  • page.clock.install({ time? }): cài fake clock — PHẢI gọi trước khi trang load/script đọc Date (thường ngay sau newPage, trước goto). Không truyền time → bắt đầu từ giờ hiện tại.
  • page.clock.setFixedTime(time): đóng băng hoàn toàn — Date.now luôn trả đúng mốc đó; setSystemTime(time) thì set mốc rồi clock vẫn chạy tiếp.
  • page.clock.runFor(ms | 'mm:ss'): nhảy qua khoảng thời gian, FIRE tuần tự mọi timer đã lên lịch trong khoảng đó — countdown, debounce, polling đều chạy.
  • page.clock.fastForward(ms | 'mm:ss'): nhảy tới tương lai nhanh, fire các timer dọc đường — dùng cho 'đợi' 30s hết hạn mà không sleep.
  • page.clock.pauseAt(time) + resume(): chạy tới 1 mốc rồi DỪNG hẳn (timer không tự fire nữa) — resume() mở lại; tick(ms) advance thủ công khi đang pause.
  • Time chấp nhận: Date, epoch ms, hoặc string '2025-01-01T08:00:00' / '05:00' (mm:ss cho duration).

Ví dụ & Cú pháp thực tế

  • OTP hết hạn — nhảy 31 giây tức thì: ``ts test('OTP hết hạn sau 30s', async ({ page }) => { await page.clock.install(); // PHẢI trước goto await page.goto('/verify-otp'); await page.getByLabel('Mã OTP').fill('123456'); await page.clock.fastForward(31_000); // nhảy 31s, không sleep thật await expect(page.getByText('Mã đã hết hạn')).toBeVisible(); }); ``
  • Đóng băng 'hôm nay' cho UI deterministic: ``ts await page.clock.install({ time: new Date('2025-01-15T08:00:00') }); await page.clock.pauseAt(new Date('2025-01-15T10:00:00')); await page.goto('/dashboard'); // mọi new Date() trong trang = 10:00 ngày 15/01/2025 — screenshot ổn định ``
  • Countdown flash sale — chạy hết timer trong 5 phút: ``ts await page.clock.install(); await page.goto('/flash-sale'); await expect(page.getByTestId('countdown')).toHaveText('05:00'); await page.clock.runFor('05:00'); // fire tuần tự tất cả interval await expect(page.getByTestId('countdown')).toHaveText('00:00'); await expect(page.getByText('Đã kết thúc')).toBeVisible(); ``
  • Kiểm 'gửi lại OTP sau 60s': ``ts await page.clock.install(); await page.goto('/otp'); const resend = page.getByRole('button', { name: 'Gửi lại' }); await expect(resend).toBeDisabled(); await page.clock.runFor(60_000); await expect(resend).toBeEnabled(); ``

Best practices & Khi nào dùng

  • Dùng khi logic phụ thuộc thời gian: countdown, token/OTP expiry, 'x phút trước', lịch hẹn, debounce input, polling interval, cache TTL phía client.
  • install trước goto — luôn luôn: app cache binding Date lúc khởi động; install sau khi trang chạy có thể không apply cho script đã nạp.
  • pauseAt cho screenshot/snapshot: đóng băng mọi timer + rAF → trang 'đứng yên', visual test hết flake vì clock/animation.
  • runFor khi cần timer GIỮA chừng chạy (countdown từng giây, polling mỗi 5s); fastForward khi chỉ quan tâm trạng thái cuối.
  • Kết hợp route mock cho server time: response API chứa timestamp server vẫn là giờ thật — mock bằng page.route nếu UI hiển thị giờ server.

Gotchas, So sánh & Lưu ý

  • Server-side time không fake được: JWT exp server ký, created_at từ DB, signed URL — vẫn giờ thật; clock chỉ fake phía browser. Test hết hạn phải mock server response.
  • Phân biệt setFixedTime vs setSystemTime: fixed = đóng băng vĩnh viễn; system = set mốc hiện tại rồi clock chảy tiếp bình thường.
  • requestAnimationFrame cũng bị điều khiển: animation dựa trên rAF 'đứng' khi pause — cái này vừa là feature (stable screenshot) vừa là bẫy (trang chờ rAF callback không bao giờ chạy).
  • Cookie expires/max-age và storage expiry không theo fake clock ở tầng network — đừng rely clock để test cookie hết hạn.
  • WebSocket/SSE heartbeat có thể phụ thuộc real time: fake clock đôi khi làm connection treo — nếu test dùng realtime, cân nhắc chỉ install clock sau khi channel đã mở, hoặc mock.
  • So với cách cũ (page.addInitScript ghi đè Date thủ công): Clock API đầy đủ hơn — bao cả timers, rAF, performance.now — nên dùng API chính thức thay vì tự patch.