playwright_rs/protocol/page/
navigation.rs1use super::{Page, Response};
2use crate::error::{Error, Result};
3use crate::server::channel_owner::ChannelOwner;
4use serde::Deserialize;
5use std::sync::Arc;
6
7impl Page {
24 #[tracing::instrument(level = "info", skip_all, fields(guid = %self.guid(), url = %url, status = tracing::field::Empty))]
43 pub async fn goto(
44 &self,
45 url: &str,
46 options: impl Into<Option<GotoOptions>>,
47 ) -> Result<Option<Response>> {
48 let options = options.into();
49 let options = self.with_navigation_timeout(options);
51
52 let frame = self.main_frame().await.map_err(|e| match e {
54 Error::TargetClosed { context, .. } => Error::TargetClosed {
55 target_type: "Page".to_string(),
56 context,
57 },
58 other => other,
59 })?;
60
61 let response = frame.goto(url, Some(options)).await.map_err(|e| match e {
62 Error::TargetClosed { context, .. } => Error::TargetClosed {
63 target_type: "Page".to_string(),
64 context,
65 },
66 other => other,
67 })?;
68
69 if let Some(ref resp) = response {
70 tracing::Span::current().record("status", resp.status());
71 }
72 Ok(response)
73 }
74
75 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
83 pub async fn wait_for_load_state(&self, state: Option<WaitUntil>) -> Result<()> {
84 let frame = self.main_frame().await?;
85 frame.wait_for_load_state(state).await
86 }
87
88 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid(), url = %url))]
92 pub async fn wait_for_url(
93 &self,
94 url: &str,
95 options: impl Into<Option<GotoOptions>>,
96 ) -> Result<()> {
97 let options = options.into();
98 let frame = self.main_frame().await?;
99 frame.wait_for_url(url, options).await
100 }
101
102 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
108 pub async fn set_url_fragment(&self, hash: &str) -> Result<()> {
109 let normalized = if hash.starts_with('#') {
110 hash.to_string()
111 } else {
112 format!("#{hash}")
113 };
114 let json = serde_json::to_string(&normalized).map_err(|e| {
117 crate::error::Error::ProtocolError(format!("serialize url fragment: {e}"))
118 })?;
119 let js =
120 format!("history.replaceState(null, '', location.pathname + location.search + {json})");
121 self.evaluate_expression(&js).await
122 }
123
124 #[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
130 pub async fn clear_url_fragment(&self) -> Result<()> {
131 self.evaluate_expression(
132 "history.replaceState(null, '', location.pathname + location.search)",
133 )
134 .await
135 }
136
137 #[tracing::instrument(level = "info", skip_all, fields(guid = %self.guid()))]
148 pub async fn reload(
149 &self,
150 options: impl Into<Option<GotoOptions>>,
151 ) -> Result<Option<Response>> {
152 let options = options.into();
153 self.navigate_history("reload", options).await
154 }
155
156 #[tracing::instrument(level = "info", skip_all, fields(guid = %self.guid()))]
163 pub async fn go_back(
164 &self,
165 options: impl Into<Option<GotoOptions>>,
166 ) -> Result<Option<Response>> {
167 let options = options.into();
168 self.navigate_history("goBack", options).await
169 }
170
171 #[tracing::instrument(level = "info", skip_all, fields(guid = %self.guid()))]
178 pub async fn go_forward(
179 &self,
180 options: impl Into<Option<GotoOptions>>,
181 ) -> Result<Option<Response>> {
182 let options = options.into();
183 self.navigate_history("goForward", options).await
184 }
185
186 async fn navigate_history(
188 &self,
189 method: &str,
190 options: Option<GotoOptions>,
191 ) -> Result<Option<Response>> {
192 let opts = self.with_navigation_timeout(options);
194 let mut params = serde_json::json!({});
195
196 if let Some(timeout) = opts.timeout {
198 params["timeout"] = serde_json::json!(timeout.as_millis() as u64);
199 } else {
200 params["timeout"] = serde_json::json!(crate::DEFAULT_TIMEOUT_MS);
201 }
202 if let Some(wait_until) = opts.wait_until {
203 params["waitUntil"] = serde_json::json!(wait_until.as_str());
204 }
205
206 #[derive(Deserialize)]
207 struct NavigationResponse {
208 response: Option<ResponseReference>,
209 }
210
211 #[derive(Deserialize)]
212 struct ResponseReference {
213 #[serde(deserialize_with = "crate::server::connection::deserialize_arc_str")]
214 guid: Arc<str>,
215 }
216
217 let result: NavigationResponse = self.channel().send(method, params).await?;
218
219 if let Some(response_ref) = result.response {
220 let response_arc = self
222 .connection()
223 .wait_for_object(&response_ref.guid)
224 .await?;
225
226 let initializer = response_arc.initializer();
227
228 let status = initializer["status"].as_u64().ok_or_else(|| {
229 crate::error::Error::ProtocolError("Response missing status".to_string())
230 })? as u16;
231
232 let headers = initializer["headers"]
233 .as_array()
234 .ok_or_else(|| {
235 crate::error::Error::ProtocolError("Response missing headers".to_string())
236 })?
237 .iter()
238 .filter_map(|h| {
239 let name = h["name"].as_str()?;
240 let value = h["value"].as_str()?;
241 Some((name.to_string(), value.to_string()))
242 })
243 .collect();
244
245 let response = Response::new(
246 initializer["url"]
247 .as_str()
248 .ok_or_else(|| {
249 crate::error::Error::ProtocolError("Response missing url".to_string())
250 })?
251 .to_string(),
252 status,
253 initializer["statusText"].as_str().unwrap_or("").to_string(),
254 headers,
255 Some(response_arc),
256 );
257
258 Ok(Some(response))
259 } else {
260 Ok(None)
261 }
262 }
263
264 fn with_navigation_timeout(&self, options: Option<GotoOptions>) -> GotoOptions {
269 let nav_timeout = self.default_navigation_timeout_ms();
270 match options {
271 Some(opts) if opts.timeout.is_some() => opts,
272 Some(mut opts) => {
273 opts.timeout = Some(std::time::Duration::from_millis(nav_timeout as u64));
274 opts
275 }
276 None => GotoOptions {
277 timeout: Some(std::time::Duration::from_millis(nav_timeout as u64)),
278 wait_until: None,
279 },
280 }
281 }
282}
283
284#[derive(Debug, Clone)]
286#[non_exhaustive]
287pub struct GotoOptions {
288 pub timeout: Option<std::time::Duration>,
290 pub wait_until: Option<WaitUntil>,
292}
293
294impl GotoOptions {
295 pub fn new() -> Self {
297 Self {
298 timeout: None,
299 wait_until: None,
300 }
301 }
302
303 pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
305 self.timeout = Some(timeout);
306 self
307 }
308
309 pub fn wait_until(mut self, wait_until: WaitUntil) -> Self {
311 self.wait_until = Some(wait_until);
312 self
313 }
314}
315
316impl Default for GotoOptions {
317 fn default() -> Self {
318 Self::new()
319 }
320}
321
322#[derive(Debug, Clone, Copy, PartialEq, Eq)]
324#[non_exhaustive]
325pub enum WaitUntil {
326 Load,
328 DomContentLoaded,
330 NetworkIdle,
332 Commit,
334}
335
336impl WaitUntil {
337 pub(crate) fn as_str(&self) -> &'static str {
338 match self {
339 WaitUntil::Load => "load",
340 WaitUntil::DomContentLoaded => "domcontentloaded",
341 WaitUntil::NetworkIdle => "networkidle",
342 WaitUntil::Commit => "commit",
343 }
344 }
345}