Skip to content

Commit 6575e31

Browse files
committed
Fix frame-scoped wait variants
1 parent bc88187 commit 6575e31

2 files changed

Lines changed: 192 additions & 50 deletions

File tree

cli/src/native/actions.rs

Lines changed: 174 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3162,6 +3162,16 @@ enum WaitRoute {
31623162
SameProcessFrame(String),
31633163
}
31643164

3165+
fn wait_route(state: &DaemonState) -> WaitRoute {
3166+
match state.active_frame_id.as_deref() {
3167+
Some(frame_id) => match state.iframe_sessions.get(frame_id) {
3168+
Some(frame_session) => WaitRoute::FrameSession(frame_session.clone()),
3169+
None => WaitRoute::SameProcessFrame(frame_id.to_string()),
3170+
},
3171+
None => WaitRoute::Main,
3172+
}
3173+
}
3174+
31653175
async fn handle_wait(cmd: &Value, state: &mut DaemonState) -> Result<Value, String> {
31663176
let mgr = state.browser.as_ref().ok_or("Browser not launched")?;
31673177
let session_id = mgr.active_session_id()?.to_string();
@@ -3170,13 +3180,7 @@ async fn handle_wait(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
31703180
// Honor an active `frame <sel>` selection for content waits, like element
31713181
// resolution does: an OOPIF polls on its dedicated session, a same-process
31723182
// frame polls through the owner element's contentDocument.
3173-
let frame_route = match state.active_frame_id.as_deref() {
3174-
Some(frame_id) => match state.iframe_sessions.get(frame_id) {
3175-
Some(frame_session) => WaitRoute::FrameSession(frame_session.clone()),
3176-
None => WaitRoute::SameProcessFrame(frame_id.to_string()),
3177-
},
3178-
None => WaitRoute::Main,
3179-
};
3183+
let frame_route = wait_route(state);
31803184

31813185
if let Some(text) = cmd.get("text").and_then(|v| v.as_str()) {
31823186
let quoted = serde_json::to_string(text).unwrap_or_default();
@@ -3225,37 +3229,20 @@ async fn handle_wait(cmd: &Value, state: &mut DaemonState) -> Result<Value, Stri
32253229
}
32263230

32273231
if let Some(url_pattern) = cmd.get("url").and_then(|v| v.as_str()) {
3228-
match &frame_route {
3229-
WaitRoute::Main => {
3230-
wait_for_url(&mgr.client, &session_id, url_pattern, timeout_ms).await?
3231-
}
3232-
WaitRoute::FrameSession(frame_session) => {
3233-
wait_for_url(&mgr.client, frame_session, url_pattern, timeout_ms).await?
3234-
}
3235-
WaitRoute::SameProcessFrame(frame_id) => {
3236-
let check = url_check_expression("doc.location.href", url_pattern);
3237-
poll_in_frame_until_true(&mgr.client, &session_id, frame_id, &check, timeout_ms)
3238-
.await?
3239-
}
3240-
}
3232+
wait_for_url_on_route(
3233+
&mgr.client,
3234+
&session_id,
3235+
&frame_route,
3236+
url_pattern,
3237+
timeout_ms,
3238+
)
3239+
.await?;
32413240
return Ok(json!({ "waited": "url", "url": url_pattern }));
32423241
}
32433242

32443243
if let Some(fn_str) = cmd.get("function").and_then(|v| v.as_str()) {
3245-
match &frame_route {
3246-
WaitRoute::Main => {
3247-
wait_for_function(&mgr.client, &session_id, fn_str, timeout_ms).await?
3248-
}
3249-
WaitRoute::FrameSession(frame_session) => {
3250-
wait_for_function(&mgr.client, frame_session, fn_str, timeout_ms).await?
3251-
}
3252-
WaitRoute::SameProcessFrame(frame_id) => {
3253-
let quoted = serde_json::to_string(fn_str).unwrap_or_default();
3254-
let check = format!("!!(doc.defaultView.eval({quoted}))");
3255-
poll_in_frame_until_true(&mgr.client, &session_id, frame_id, &check, timeout_ms)
3256-
.await?
3257-
}
3258-
}
3244+
wait_for_function_on_route(&mgr.client, &session_id, &frame_route, fn_str, timeout_ms)
3245+
.await?;
32593246
return Ok(json!({ "waited": "function" }));
32603247
}
32613248

@@ -3506,6 +3493,25 @@ async fn wait_for_url(
35063493
poll_until_true(client, session_id, &check_fn, timeout_ms).await
35073494
}
35083495

3496+
async fn wait_for_url_on_route(
3497+
client: &super::cdp::client::CdpClient,
3498+
session_id: &str,
3499+
route: &WaitRoute,
3500+
pattern: &str,
3501+
timeout_ms: u64,
3502+
) -> Result<(), String> {
3503+
match route {
3504+
WaitRoute::Main => wait_for_url(client, session_id, pattern, timeout_ms).await,
3505+
WaitRoute::FrameSession(frame_session) => {
3506+
wait_for_url(client, frame_session, pattern, timeout_ms).await
3507+
}
3508+
WaitRoute::SameProcessFrame(frame_id) => {
3509+
let check = url_check_expression("doc.location.href", pattern);
3510+
poll_in_frame_until_true(client, session_id, frame_id, &check, timeout_ms).await
3511+
}
3512+
}
3513+
}
3514+
35093515
/// `wait --url` accepts the glob patterns the docs advertise ("**/dashboard")
35103516
/// as well as plain substrings. A glob used to be matched literally via
35113517
/// includes(), so it could never succeed.
@@ -3577,6 +3583,115 @@ async fn wait_for_function(
35773583
poll_until_true(client, session_id, &check_fn, timeout_ms).await
35783584
}
35793585

3586+
async fn wait_for_function_on_route(
3587+
client: &super::cdp::client::CdpClient,
3588+
session_id: &str,
3589+
route: &WaitRoute,
3590+
fn_str: &str,
3591+
timeout_ms: u64,
3592+
) -> Result<(), String> {
3593+
match route {
3594+
WaitRoute::Main => wait_for_function(client, session_id, fn_str, timeout_ms).await,
3595+
WaitRoute::FrameSession(frame_session) => {
3596+
wait_for_function(client, frame_session, fn_str, timeout_ms).await
3597+
}
3598+
WaitRoute::SameProcessFrame(frame_id) => {
3599+
let quoted = serde_json::to_string(fn_str).unwrap_or_default();
3600+
let check = format!("!!(doc.defaultView.eval({quoted}))");
3601+
poll_in_frame_until_true(client, session_id, frame_id, &check, timeout_ms).await
3602+
}
3603+
}
3604+
}
3605+
3606+
async fn evaluate_wait_expression_on_session(
3607+
client: &super::cdp::client::CdpClient,
3608+
session_id: &str,
3609+
expression: &str,
3610+
) -> Result<Value, String> {
3611+
let result: super::cdp::types::EvaluateResult = client
3612+
.send_command_typed(
3613+
"Runtime.evaluate",
3614+
&super::cdp::types::EvaluateParams {
3615+
expression: format!("({})", expression),
3616+
return_by_value: Some(true),
3617+
await_promise: Some(true),
3618+
},
3619+
Some(session_id),
3620+
)
3621+
.await?;
3622+
3623+
if let Some(details) = result.exception_details {
3624+
let msg = details
3625+
.exception
3626+
.as_ref()
3627+
.and_then(|e| e.description.as_deref())
3628+
.unwrap_or(&details.text);
3629+
return Err(format!("Evaluation error: {}", msg));
3630+
}
3631+
3632+
Ok(result.result.value.unwrap_or(Value::Null))
3633+
}
3634+
3635+
async fn evaluate_wait_expression_on_route(
3636+
client: &super::cdp::client::CdpClient,
3637+
session_id: &str,
3638+
route: &WaitRoute,
3639+
expression: &str,
3640+
) -> Result<Value, String> {
3641+
match route {
3642+
WaitRoute::Main => {
3643+
evaluate_wait_expression_on_session(client, session_id, expression).await
3644+
}
3645+
WaitRoute::FrameSession(frame_session) => {
3646+
evaluate_wait_expression_on_session(client, frame_session, expression).await
3647+
}
3648+
WaitRoute::SameProcessFrame(frame_id) => {
3649+
evaluate_in_same_process_frame(client, session_id, frame_id, expression).await
3650+
}
3651+
}
3652+
}
3653+
3654+
async fn current_url_on_route(
3655+
client: &super::cdp::client::CdpClient,
3656+
session_id: &str,
3657+
route: &WaitRoute,
3658+
) -> Result<String, String> {
3659+
match route {
3660+
WaitRoute::Main => evaluate_wait_expression_on_session(client, session_id, "location.href")
3661+
.await
3662+
.map(|v| v.as_str().unwrap_or("").to_string()),
3663+
WaitRoute::FrameSession(frame_session) => {
3664+
evaluate_wait_expression_on_session(client, frame_session, "location.href")
3665+
.await
3666+
.map(|v| v.as_str().unwrap_or("").to_string())
3667+
}
3668+
WaitRoute::SameProcessFrame(frame_id) => {
3669+
let owner_object_id =
3670+
super::element::frame_owner_object_id(client, session_id, frame_id).await?;
3671+
let result = client
3672+
.send_command(
3673+
"Runtime.callFunctionOn",
3674+
Some(json!({
3675+
"objectId": owner_object_id,
3676+
"functionDeclaration": "function() {
3677+
const doc = this.contentDocument;
3678+
return doc ? doc.location.href : '';
3679+
}",
3680+
"returnByValue": true,
3681+
})),
3682+
Some(session_id),
3683+
)
3684+
.await?;
3685+
Ok(result
3686+
.get("result")
3687+
.and_then(|r| r.get("value"))
3688+
.and_then(|v| v.as_str())
3689+
.unwrap_or("")
3690+
.to_string())
3691+
}
3692+
}
3693+
}
3694+
35803695
/// wait_for_selector inside a same-process iframe selected via `frame <sel>`.
35813696
async fn wait_for_selector_in_frame(
35823697
client: &super::cdp::client::CdpClient,
@@ -5836,14 +5951,24 @@ async fn handle_screencast_stop(state: &mut DaemonState) -> Result<Value, String
58365951
async fn handle_waitforurl(cmd: &Value, state: &DaemonState) -> Result<Value, String> {
58375952
let mgr = state.browser.as_ref().ok_or("Browser not launched")?;
58385953
let session_id = mgr.active_session_id()?.to_string();
5954+
let frame_route = wait_route(state);
58395955
let url_pattern = cmd
58405956
.get("url")
58415957
.and_then(|v| v.as_str())
58425958
.ok_or("Missing 'url' parameter")?;
58435959
let timeout_ms = state.timeout_ms(cmd);
58445960

5845-
wait_for_url(&mgr.client, &session_id, url_pattern, timeout_ms).await?;
5846-
let url = mgr.get_url().await.unwrap_or_default();
5961+
wait_for_url_on_route(
5962+
&mgr.client,
5963+
&session_id,
5964+
&frame_route,
5965+
url_pattern,
5966+
timeout_ms,
5967+
)
5968+
.await?;
5969+
let url = current_url_on_route(&mgr.client, &session_id, &frame_route)
5970+
.await
5971+
.unwrap_or_default();
58475972
Ok(json!({ "url": url }))
58485973
}
58495974

@@ -5867,28 +5992,27 @@ async fn handle_waitforloadstate(cmd: &Value, state: &DaemonState) -> Result<Val
58675992
async fn handle_waitforfunction(cmd: &Value, state: &DaemonState) -> Result<Value, String> {
58685993
let mgr = state.browser.as_ref().ok_or("Browser not launched")?;
58695994
let session_id = mgr.active_session_id()?.to_string();
5995+
let frame_route = wait_route(state);
58705996
let expression = cmd
58715997
.get("expression")
58725998
.and_then(|v| v.as_str())
58735999
.ok_or("Missing 'expression' parameter")?;
58746000
let timeout_ms = state.timeout_ms(cmd);
58756001

5876-
wait_for_function(&mgr.client, &session_id, expression, timeout_ms).await?;
6002+
wait_for_function_on_route(
6003+
&mgr.client,
6004+
&session_id,
6005+
&frame_route,
6006+
expression,
6007+
timeout_ms,
6008+
)
6009+
.await?;
58776010

5878-
let result: super::cdp::types::EvaluateResult = mgr
5879-
.client
5880-
.send_command_typed(
5881-
"Runtime.evaluate",
5882-
&super::cdp::types::EvaluateParams {
5883-
expression: format!("({})", expression),
5884-
return_by_value: Some(true),
5885-
await_promise: Some(true),
5886-
},
5887-
Some(&session_id),
5888-
)
5889-
.await?;
6011+
let result =
6012+
evaluate_wait_expression_on_route(&mgr.client, &session_id, &frame_route, expression)
6013+
.await?;
58906014

5891-
Ok(json!({ "result": result.result.value.unwrap_or(Value::Null) }))
6015+
Ok(json!({ "result": result }))
58926016
}
58936017

58946018
// ---------------------------------------------------------------------------

cli/src/native/e2e_tests.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6065,6 +6065,24 @@ async fn e2e_frame_scoped_text_wait_eval_and_function_wait() {
60656065
.await;
60666066
assert_success(&resp);
60676067

6068+
// The CLI parser maps `wait --url` and `wait --fn` to the waitfor*
6069+
// daemon actions. Those variants must honor the selected frame too.
6070+
let resp = execute_command(
6071+
&json!({ "id": "7b", "action": "waitforfunction", "expression": "window.frameFlag === 42", "timeout": 5000 }),
6072+
&mut state,
6073+
)
6074+
.await;
6075+
assert_success(&resp);
6076+
assert_eq!(get_data(&resp)["result"], true);
6077+
6078+
let resp = execute_command(
6079+
&json!({ "id": "7c", "action": "waitforurl", "url": "about:srcdoc", "timeout": 5000 }),
6080+
&mut state,
6081+
)
6082+
.await;
6083+
assert_success(&resp);
6084+
assert_eq!(get_data(&resp)["url"], "about:srcdoc");
6085+
60686086
let resp = execute_command(
60696087
&json!({ "id": "8", "action": "evaluate", "script": "window.frameFlag" }),
60706088
&mut state,

0 commit comments

Comments
 (0)