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}