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}