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}