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: impl Into<Option<ClockInstallOptions>>) -> Result<()> {
117        let options = options.into();
118        let mut params = serde_json::json!({});
119        if let Some(opts) = options
120            && let Some(time) = opts.time
121        {
122            params["timeNumber"] = serde_json::Value::Number(time.into());
123        }
124        self.channel.send_no_result("clockInstall", params).await
125    }
126
127    /// Advances the fake clock by the given number of milliseconds, firing any
128    /// timers that fall within that range.
129    ///
130    /// # Arguments
131    ///
132    /// * `ticks` - Number of milliseconds to advance the clock.
133    ///
134    /// # Errors
135    ///
136    /// Returns error if:
137    /// - Clock is not installed
138    /// - Context has been closed
139    /// - Communication with browser process fails
140    ///
141    /// See: <https://playwright.dev/docs/api/class-clock#clock-fast-forward>
142    pub async fn fast_forward(&self, ticks: u64) -> Result<()> {
143        self.channel
144            .send_no_result(
145                "clockFastForward",
146                serde_json::json!({ "ticksNumber": ticks }),
147            )
148            .await
149    }
150
151    /// Pauses the fake clock at the given epoch timestamp (milliseconds).
152    ///
153    /// No timers fire and time does not advance until [`Clock::resume`] is called.
154    ///
155    /// # Arguments
156    ///
157    /// * `time` - Epoch timestamp in milliseconds to pause at.
158    ///
159    /// # Errors
160    ///
161    /// Returns error if:
162    /// - Clock is not installed
163    /// - Context has been closed
164    /// - Communication with browser process fails
165    ///
166    /// See: <https://playwright.dev/docs/api/class-clock#clock-pause-at>
167    pub async fn pause_at(&self, time: u64) -> Result<()> {
168        self.channel
169            .send_no_result("clockPauseAt", serde_json::json!({ "timeNumber": time }))
170            .await
171    }
172
173    /// Resumes the fake clock after it was paused via [`Clock::pause_at`].
174    ///
175    /// # Errors
176    ///
177    /// Returns error if:
178    /// - Context has been closed
179    /// - Communication with browser process fails
180    ///
181    /// See: <https://playwright.dev/docs/api/class-clock#clock-resume>
182    pub async fn resume(&self) -> Result<()> {
183        self.channel
184            .send_no_result("clockResume", serde_json::json!({}))
185            .await
186    }
187
188    /// Freezes `Date.now()` and related APIs at the given epoch timestamp
189    /// (milliseconds), without affecting timer scheduling.
190    ///
191    /// # Arguments
192    ///
193    /// * `time` - Epoch timestamp in milliseconds.
194    ///
195    /// # Errors
196    ///
197    /// Returns error if:
198    /// - Context has been closed
199    /// - Communication with browser process fails
200    ///
201    /// See: <https://playwright.dev/docs/api/class-clock#clock-set-fixed-time>
202    pub async fn set_fixed_time(&self, time: u64) -> Result<()> {
203        self.channel
204            .send_no_result(
205                "clockSetFixedTime",
206                serde_json::json!({ "timeNumber": time }),
207            )
208            .await
209    }
210
211    /// Updates the system time reported by `Date` and related APIs without
212    /// freezing the clock or affecting timer scheduling.
213    ///
214    /// # Arguments
215    ///
216    /// * `time` - Epoch timestamp in milliseconds.
217    ///
218    /// # Errors
219    ///
220    /// Returns error if:
221    /// - Context has been closed
222    /// - Communication with browser process fails
223    ///
224    /// See: <https://playwright.dev/docs/api/class-clock#clock-set-system-time>
225    pub async fn set_system_time(&self, time: u64) -> Result<()> {
226        self.channel
227            .send_no_result(
228                "clockSetSystemTime",
229                serde_json::json!({ "timeNumber": time }),
230            )
231            .await
232    }
233}