@@ -90,6 +90,7 @@ impl Toolset {
9090 browser_click_def( ) ,
9191 browser_type_def( ) ,
9292 browser_press_key_def( ) ,
93+ browser_wait_for_def( ) ,
9394 browser_read_page_def( ) ,
9495 browser_screenshot_def( ) ,
9596 browser_close_def( ) ,
@@ -270,6 +271,16 @@ impl Toolset {
270271 self . browser_press_key ( args. selector . as_deref ( ) , & args. key )
271272 . await
272273 }
274+ "browser_wait_for" => {
275+ let args: BrowserWaitForArgs = serde_json:: from_str ( arguments_json)
276+ . context ( "parse browser_wait_for arguments" ) ?;
277+ self . browser_wait_for (
278+ args. selector . as_deref ( ) ,
279+ args. text . as_deref ( ) ,
280+ args. timeout_ms ,
281+ )
282+ . await
283+ }
273284 "browser_read_page" => {
274285 let _args: serde_json:: Value = serde_json:: from_str ( arguments_json)
275286 . context ( "parse browser_read_page arguments" ) ?;
@@ -1314,6 +1325,39 @@ impl Toolset {
13141325 Ok ( resp. into_tool_output ( ) ?)
13151326 }
13161327
1328+ async fn browser_wait_for (
1329+ & self ,
1330+ selector : Option < & str > ,
1331+ text : Option < & str > ,
1332+ timeout_ms : Option < u64 > ,
1333+ ) -> anyhow:: Result < String > {
1334+ if selector. unwrap_or ( "" ) . trim ( ) . is_empty ( ) && text. unwrap_or ( "" ) . trim ( ) . is_empty ( ) {
1335+ bail ! ( "browser_wait_for requires selector or text" ) ;
1336+ }
1337+
1338+ let mut guard = self . browser . lock ( ) . await ;
1339+ let session = guard
1340+ . as_mut ( )
1341+ . context ( "browser session not started; call browser_navigate first" ) ?;
1342+
1343+ let mut cmd = serde_json:: json!( {
1344+ "action" : "WaitFor" ,
1345+ } ) ;
1346+
1347+ if let Some ( selector) = selector {
1348+ cmd[ "selector" ] = serde_json:: Value :: String ( selector. to_string ( ) ) ;
1349+ }
1350+ if let Some ( text) = text {
1351+ cmd[ "text" ] = serde_json:: Value :: String ( text. to_string ( ) ) ;
1352+ }
1353+ if let Some ( timeout_ms) = timeout_ms {
1354+ cmd[ "timeout_ms" ] = serde_json:: Value :: Number ( timeout_ms. into ( ) ) ;
1355+ }
1356+
1357+ let resp = session. send ( cmd) . await ?;
1358+ Ok ( resp. into_tool_output ( ) ?)
1359+ }
1360+
13171361 async fn browser_read_page ( & self ) -> anyhow:: Result < String > {
13181362 let mut guard = self . browser . lock ( ) . await ;
13191363 let session = guard
@@ -1769,6 +1813,16 @@ struct BrowserPressKeyArgs {
17691813 selector : Option < String > ,
17701814}
17711815
1816+ #[ derive( Debug , serde:: Deserialize ) ]
1817+ struct BrowserWaitForArgs {
1818+ #[ serde( default ) ]
1819+ selector : Option < String > ,
1820+ #[ serde( default ) ]
1821+ text : Option < String > ,
1822+ #[ serde( default ) ]
1823+ timeout_ms : Option < u64 > ,
1824+ }
1825+
17721826#[ derive( Debug , serde:: Deserialize ) ]
17731827struct BrowserScreenshotArgs {
17741828 #[ serde( default ) ]
@@ -3236,6 +3290,25 @@ fn browser_press_key_def() -> ToolDefinition {
32363290 }
32373291}
32383292
3293+ fn browser_wait_for_def ( ) -> ToolDefinition {
3294+ ToolDefinition {
3295+ kind : "function" . to_string ( ) ,
3296+ function : ToolFunctionDefinition {
3297+ name : "browser_wait_for" . to_string ( ) ,
3298+ description : "Wait for a selector or text to appear on the page." . to_string ( ) ,
3299+ parameters : serde_json:: json!( {
3300+ "type" : "object" ,
3301+ "properties" : {
3302+ "selector" : { "type" : "string" , "description" : "Optional CSS selector to wait for." } ,
3303+ "text" : { "type" : "string" , "description" : "Optional visible text to wait for." } ,
3304+ "timeout_ms" : { "type" : "integer" , "description" : "Optional timeout in milliseconds." , "minimum" : 1 }
3305+ } ,
3306+ "additionalProperties" : false
3307+ } ) ,
3308+ } ,
3309+ }
3310+ }
3311+
32393312fn browser_read_page_def ( ) -> ToolDefinition {
32403313 ToolDefinition {
32413314 kind : "function" . to_string ( ) ,
@@ -3385,6 +3458,7 @@ mod tests {
33853458 "browser_click" ,
33863459 "browser_type" ,
33873460 "browser_press_key" ,
3461+ "browser_wait_for" ,
33883462 "browser_read_page" ,
33893463 "browser_screenshot" ,
33903464 "browser_close" ,
@@ -4074,6 +4148,22 @@ mod tests {
40744148 ) ;
40754149 }
40764150
4151+ #[ tokio:: test]
4152+ async fn browser_wait_for_requires_session ( ) {
4153+ let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
4154+ let tools = Toolset :: new ( tmp. path ( ) . to_path_buf ( ) ) . unwrap ( ) ;
4155+
4156+ let err = tools
4157+ . call ( "browser_wait_for" , r#"{ "text": "hello" }"# )
4158+ . await
4159+ . unwrap_err ( ) ;
4160+ let msg = err. to_string ( ) ;
4161+ assert ! (
4162+ msg. contains( "browser_navigate" ) || msg. contains( "session" ) ,
4163+ "{msg}"
4164+ ) ;
4165+ }
4166+
40774167 #[ tokio:: test]
40784168 async fn browser_read_page_requires_session ( ) {
40794169 let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
@@ -4135,6 +4225,13 @@ mod tests {
41354225 let v: serde_json:: Value = serde_json:: from_str ( & out) . unwrap ( ) ;
41364226 assert_eq ! ( v[ "key" ] , "Enter" ) ;
41374227
4228+ let out = tools
4229+ . call ( "browser_wait_for" , r#"{ "text": "hello", "timeout_ms": 1 }"# )
4230+ . await
4231+ . unwrap ( ) ;
4232+ let v: serde_json:: Value = serde_json:: from_str ( & out) . unwrap ( ) ;
4233+ assert_eq ! ( v[ "waited_for" ] [ "text" ] , "hello" ) ;
4234+
41384235 let page = tools. call ( "browser_read_page" , r#"{}"# ) . await . unwrap ( ) ;
41394236 let v: serde_json:: Value = serde_json:: from_str ( & page) . unwrap ( ) ;
41404237 assert_eq ! ( v[ "title" ] , "Stub" ) ;
@@ -4215,6 +4312,13 @@ for line in sys.stdin:
42154312 resp = {"success": True, "data": {"typed": cmd.get("text", ""), "selector": cmd.get("selector", "")}}
42164313 elif action == "PressKey":
42174314 resp = {"success": True, "data": {"key": cmd.get("key", ""), "selector": cmd.get("selector", "")}}
4315+ elif action == "WaitFor":
4316+ waited_for = {}
4317+ if cmd.get("selector"):
4318+ waited_for["selector"] = cmd.get("selector", "")
4319+ if cmd.get("text"):
4320+ waited_for["text"] = cmd.get("text", "")
4321+ resp = {"success": True, "data": {"waited_for": waited_for, "timeout_ms": cmd.get("timeout_ms")}}
42184322 elif action == "Close":
42194323 resp = {"success": True, "data": {"status": "closed"}}
42204324 sys.stdout.write(json.dumps(resp) + "\n")
0 commit comments