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//! ```ignore
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 { time: Some(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)]
64pub struct ClockInstallOptions {
65    /// Initial time for the fake clock in milliseconds since the Unix epoch.
66    /// When `None`, the clock starts at the current real time.
67    pub time: Option<u64>,
68}
69
70/// Playwright Clock — provides fake timer control for deterministic tests.
71///
72/// All methods send RPC calls on the owning BrowserContext channel.
73///
74/// See: <https://playwright.dev/docs/api/class-clock>
75#[derive(Clone)]
76pub struct Clock {
77    channel: Channel,
78}
79
80impl std::fmt::Debug for Clock {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        f.debug_struct("Clock").finish_non_exhaustive()
83    }
84}
85
86impl Clock {
87    /// Creates a new Clock backed by the given BrowserContext channel.
88    pub fn new(channel: Channel) -> Self {
89        Self { channel }
90    }
91
92    /// Installs fake timers, replacing the browser's built-in clock APIs
93    /// (`Date`, `setTimeout`, `setInterval`, etc.) with controlled equivalents.
94    ///
95    /// # Arguments
96    ///
97    /// * `options` - Optional configuration; set `time` to fix the starting epoch
98    ///   timestamp in milliseconds.
99    ///
100    /// # Errors
101    ///
102    /// Returns error if:
103    /// - Context has been closed
104    /// - Communication with browser process fails
105    ///
106    /// See: <https://playwright.dev/docs/api/class-clock#clock-install>
107    pub async fn install(&self, options: Option<ClockInstallOptions>) -> Result<()> {
108        let mut params = serde_json::json!({});
109        if let Some(opts) = options
110            && let Some(time) = opts.time
111        {
112            params["timeNumber"] = serde_json::Value::Number(time.into());
113        }
114        self.channel.send_no_result("clockInstall", params).await
115    }
116
117    /// Advances the fake clock by the given number of milliseconds, firing any
118    /// timers that fall within that range.
119    ///
120    /// # Arguments
121    ///
122    /// * `ticks` - Number of milliseconds to advance the clock.
123    ///
124    /// # Errors
125    ///
126    /// Returns error if:
127    /// - Clock is not installed
128    /// - Context has been closed
129    /// - Communication with browser process fails
130    ///
131    /// See: <https://playwright.dev/docs/api/class-clock#clock-fast-forward>
132    pub async fn fast_forward(&self, ticks: u64) -> Result<()> {
133        self.channel
134            .send_no_result(
135                "clockFastForward",
136                serde_json::json!({ "ticksNumber": ticks }),
137            )
138            .await
139    }
140
141    /// Pauses the fake clock at the given epoch timestamp (milliseconds).
142    ///
143    /// No timers fire and time does not advance until [`Clock::resume`] is called.
144    ///
145    /// # Arguments
146    ///
147    /// * `time` - Epoch timestamp in milliseconds to pause at.
148    ///
149    /// # Errors
150    ///
151    /// Returns error if:
152    /// - Clock is not installed
153    /// - Context has been closed
154    /// - Communication with browser process fails
155    ///
156    /// See: <https://playwright.dev/docs/api/class-clock#clock-pause-at>
157    pub async fn pause_at(&self, time: u64) -> Result<()> {
158        self.channel
159            .send_no_result("clockPauseAt", serde_json::json!({ "timeNumber": time }))
160            .await
161    }
162
163    /// Resumes the fake clock after it was paused via [`Clock::pause_at`].
164    ///
165    /// # Errors
166    ///
167    /// Returns error if:
168    /// - Context has been closed
169    /// - Communication with browser process fails
170    ///
171    /// See: <https://playwright.dev/docs/api/class-clock#clock-resume>
172    pub async fn resume(&self) -> Result<()> {
173        self.channel
174            .send_no_result("clockResume", serde_json::json!({}))
175            .await
176    }
177
178    /// Freezes `Date.now()` and related APIs at the given epoch timestamp
179    /// (milliseconds), without affecting timer scheduling.
180    ///
181    /// # Arguments
182    ///
183    /// * `time` - Epoch timestamp in milliseconds.
184    ///
185    /// # Errors
186    ///
187    /// Returns error if:
188    /// - Context has been closed
189    /// - Communication with browser process fails
190    ///
191    /// See: <https://playwright.dev/docs/api/class-clock#clock-set-fixed-time>
192    pub async fn set_fixed_time(&self, time: u64) -> Result<()> {
193        self.channel
194            .send_no_result(
195                "clockSetFixedTime",
196                serde_json::json!({ "timeNumber": time }),
197            )
198            .await
199    }
200
201    /// Updates the system time reported by `Date` and related APIs without
202    /// freezing the clock or affecting timer scheduling.
203    ///
204    /// # Arguments
205    ///
206    /// * `time` - Epoch timestamp in milliseconds.
207    ///
208    /// # Errors
209    ///
210    /// Returns error if:
211    /// - Context has been closed
212    /// - Communication with browser process fails
213    ///
214    /// See: <https://playwright.dev/docs/api/class-clock#clock-set-system-time>
215    pub async fn set_system_time(&self, time: u64) -> Result<()> {
216        self.channel
217            .send_no_result(
218                "clockSetSystemTime",
219                serde_json::json!({ "timeNumber": time }),
220            )
221            .await
222    }
223}