Skip to main content

playwright_rs/protocol/
clock.rs

1// Copyright 2026 Paul Adamson
2// Licensed under the Apache License, Version 2.0
3//
4// Clock — fake timer / time-manipulation API
5//
6// Architecture Reference:
7// - Python: playwright-python/playwright/_impl/_clock.py
8// - JavaScript: playwright/packages/playwright-core/src/client/clock.ts
9// - Docs: https://playwright.dev/docs/api/class-clock
10
11//! Clock — manipulate fake timers for deterministic time-dependent tests
12//!
13//! The Clock object is accessible via [`crate::protocol::BrowserContext::clock`] or
14//! [`crate::protocol::Page::clock`]. All RPC calls are sent on the BrowserContext channel.
15//!
16//! # Example
17//!
18//! ```no_run
19//! use playwright_rs::protocol::{Playwright, ClockInstallOptions};
20//!
21//! #[tokio::main]
22//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
23//!     let playwright = Playwright::launch().await?;
24//!     let browser = playwright.chromium().launch().await?;
25//!     let context = browser.new_context().await?;
26//!     let page = context.new_page().await?;
27//!
28//!     let clock = context.clock();
29//!
30//!     // Install fake timers, optionally setting an initial time (ms since epoch)
31//!     clock.install(Some(ClockInstallOptions::default().time(0))).await?;
32//!
33//!     // Freeze time at a fixed point
34//!     clock.set_fixed_time(1_000_000).await?;
35//!
36//!     // Verify via evaluate
37//!     let now: f64 = page.evaluate_value("Date.now()").await?.parse()?;
38//!     assert_eq!(now as u64, 1_000_000);
39//!
40//!     // Advance time by 5 seconds
41//!     clock.fast_forward(5_000).await?;
42//!
43//!     // Pause at a specific instant
44//!     clock.pause_at(2_000_000).await?;
45//!
46//!     // Resume normal flow
47//!     clock.resume().await?;
48//!
49//!     context.close().await?;
50//!     browser.close().await?;
51//!     Ok(())
52//! }
53//! ```
54//!
55//! See: <https://playwright.dev/docs/api/class-clock>
56
57use crate::error::Result;
58use crate::server::channel::Channel;
59
60/// Options for [`Clock::install`].
61///
62/// See: <https://playwright.dev/docs/api/class-clock#clock-install>
63#[derive(Debug, Clone, Default)]
64#[non_exhaustive]
65pub struct ClockInstallOptions {
66    /// Initial time for the fake clock in milliseconds since the Unix epoch.
67    /// When `None`, the clock starts at the current real time.
68    pub time: Option<u64>,
69}
70
71impl ClockInstallOptions {
72    /// Initial fake time, in milliseconds since the Unix epoch.
73    pub fn time(mut self, time: u64) -> Self {
74        self.time = Some(time);
75        self
76    }
77}
78
79/// Playwright Clock — provides fake timer control for deterministic tests.
80///
81/// All methods send RPC calls on the owning BrowserContext channel.
82///
83/// See: <https://playwright.dev/docs/api/class-clock>
84#[derive(Clone)]
85pub struct Clock {
86    channel: Channel,
87}
88
89impl std::fmt::Debug for Clock {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        f.debug_struct("Clock").finish_non_exhaustive()
92    }
93}
94
95impl Clock {
96    /// Creates a new Clock backed by the given BrowserContext channel.
97    pub fn new(channel: Channel) -> Self {
98        Self { channel }
99    }
100
101    /// Installs fake timers, replacing the browser's built-in clock APIs
102    /// (`Date`, `setTimeout`, `setInterval`, etc.) with controlled equivalents.
103    ///
104    /// # Arguments
105    ///
106    /// * `options` - Optional configuration; set `time` to fix the starting epoch
107    ///   timestamp in milliseconds.
108    ///
109    /// # Errors
110    ///
111    /// Returns error if:
112    /// - Context has been closed
113    /// - Communication with browser process fails
114    ///
115    /// See: <https://playwright.dev/docs/api/class-clock#clock-install>
116    pub async fn install(&self, options: Option<ClockInstallOptions>) -> Result<()> {
117        let mut params = serde_json::json!({});
118        if let Some(opts) = options
119            && let Some(time) = opts.time
120        {
121            params["timeNumber"] = serde_json::Value::Number(time.into());
122        }
123        self.channel.send_no_result("clockInstall", params).await
124    }
125
126    /// Advances the fake clock by the given number of milliseconds, firing any
127    /// timers that fall within that range.
128    ///
129    /// # Arguments
130    ///
131    /// * `ticks` - Number of milliseconds to advance the clock.
132    ///
133    /// # Errors
134    ///
135    /// Returns error if:
136    /// - Clock is not installed
137    /// - Context has been closed
138    /// - Communication with browser process fails
139    ///
140    /// See: <https://playwright.dev/docs/api/class-clock#clock-fast-forward>
141    pub async fn fast_forward(&self, ticks: u64) -> Result<()> {
142        self.channel
143            .send_no_result(
144                "clockFastForward",
145                serde_json::json!({ "ticksNumber": ticks }),
146            )
147            .await
148    }
149
150    /// Pauses the fake clock at the given epoch timestamp (milliseconds).
151    ///
152    /// No timers fire and time does not advance until [`Clock::resume`] is called.
153    ///
154    /// # Arguments
155    ///
156    /// * `time` - Epoch timestamp in milliseconds to pause at.
157    ///
158    /// # Errors
159    ///
160    /// Returns error if:
161    /// - Clock is not installed
162    /// - Context has been closed
163    /// - Communication with browser process fails
164    ///
165    /// See: <https://playwright.dev/docs/api/class-clock#clock-pause-at>
166    pub async fn pause_at(&self, time: u64) -> Result<()> {
167        self.channel
168            .send_no_result("clockPauseAt", serde_json::json!({ "timeNumber": time }))
169            .await
170    }
171
172    /// Resumes the fake clock after it was paused via [`Clock::pause_at`].
173    ///
174    /// # Errors
175    ///
176    /// Returns error if:
177    /// - Context has been closed
178    /// - Communication with browser process fails
179    ///
180    /// See: <https://playwright.dev/docs/api/class-clock#clock-resume>
181    pub async fn resume(&self) -> Result<()> {
182        self.channel
183            .send_no_result("clockResume", serde_json::json!({}))
184            .await
185    }
186
187    /// Freezes `Date.now()` and related APIs at the given epoch timestamp
188    /// (milliseconds), without affecting timer scheduling.
189    ///
190    /// # Arguments
191    ///
192    /// * `time` - Epoch timestamp in milliseconds.
193    ///
194    /// # Errors
195    ///
196    /// Returns error if:
197    /// - Context has been closed
198    /// - Communication with browser process fails
199    ///
200    /// See: <https://playwright.dev/docs/api/class-clock#clock-set-fixed-time>
201    pub async fn set_fixed_time(&self, time: u64) -> Result<()> {
202        self.channel
203            .send_no_result(
204                "clockSetFixedTime",
205                serde_json::json!({ "timeNumber": time }),
206            )
207            .await
208    }
209
210    /// Updates the system time reported by `Date` and related APIs without
211    /// freezing the clock or affecting timer scheduling.
212    ///
213    /// # Arguments
214    ///
215    /// * `time` - Epoch timestamp in milliseconds.
216    ///
217    /// # Errors
218    ///
219    /// Returns error if:
220    /// - Context has been closed
221    /// - Communication with browser process fails
222    ///
223    /// See: <https://playwright.dev/docs/api/class-clock#clock-set-system-time>
224    pub async fn set_system_time(&self, time: u64) -> Result<()> {
225        self.channel
226            .send_no_result(
227                "clockSetSystemTime",
228                serde_json::json!({ "timeNumber": time }),
229            )
230            .await
231    }
232}