@@ -50,6 +50,8 @@ def __init__(
5050 * ,
5151 client_factory : Optional [Callable [[], StdioMcpClient ]] = None ,
5252 ) -> None :
53+ if not settings .command :
54+ raise ValueError ("Playwright MCP command must not be empty" )
5355 self ._settings = settings
5456 self ._client_factory = client_factory or (
5557 lambda : StdioMcpClient (
@@ -95,21 +97,25 @@ def from_config(
9597 def start_session (self , run_context : Dict [str , Any ]) -> SessionHandle :
9698 del run_context
9799 client = self ._client_factory ()
98- client .start ()
99- tools = client .list_tools ()
100- self ._call_tool (
101- client ,
102- self ._settings .navigate_tool ,
103- {"url" : self ._settings .frontend_url },
104- )
105- initial_observation = self ._snapshot_observation (client )
106- return SessionHandle (
107- session_id = str (uuid4 ()),
108- backend_type = self .backend_type ,
109- raw = {"client" : client , "tools" : tools },
110- metadata = {"frontend_url" : self ._settings .frontend_url },
111- initial_observation = initial_observation ,
112- )
100+ try :
101+ client .start ()
102+ tools = client .list_tools ()
103+ self ._call_tool (
104+ client ,
105+ self ._settings .navigate_tool ,
106+ {"url" : self ._settings .frontend_url },
107+ )
108+ initial_observation = self ._snapshot_observation (client )
109+ return SessionHandle (
110+ session_id = str (uuid4 ()),
111+ backend_type = self .backend_type ,
112+ raw = {"client" : client , "tools" : tools },
113+ metadata = {"frontend_url" : self ._settings .frontend_url },
114+ initial_observation = initial_observation ,
115+ )
116+ except Exception :
117+ client .close ()
118+ raise
113119
114120 def describe_capabilities (
115121 self ,
@@ -200,40 +206,33 @@ def execute(
200206 attempt .final_status = "completed"
201207 observation .execution = {
202208 "attempts" : [self ._attempt_to_dict (attempt )],
203- "diagnostics" : {"backend_type" : self .backend_type },
209+ "diagnostics" : {
210+ "backend_type" : self .backend_type ,
211+ "per_call_results" : per_call_results ,
212+ },
204213 }
205214 return BackendExecutionResult (
206215 observation = observation ,
207216 attempts = [attempt ],
208- diagnostics = {"backend_type" : self .backend_type },
217+ diagnostics = {
218+ "backend_type" : self .backend_type ,
219+ "per_call_results" : per_call_results ,
220+ },
209221 )
210222 except McpProtocolError as exc :
211- error_text = str (exc )
212- error_kind = self ._error_kind (error_text )
213- attempt .per_call_results = per_call_results
214- attempt .error = error_text
215- attempt .suspected_origin = "execution"
216- observation = Observation (
217- success = False ,
218- message = error_text ,
219- state = {},
220- summary = f"Execution failure in Playwright MCP: { error_text } " ,
221- env_state = {},
222- artifacts = {},
223- execution = {
224- "attempts" : [self ._attempt_to_dict (attempt )],
225- "diagnostics" : {
226- "backend_type" : self .backend_type ,
227- "error" : error_text ,
228- "error_kind" : error_kind ,
229- },
230- "suspected_origin" : "execution" ,
231- },
223+ return self ._execution_failure_result (
224+ attempt = attempt ,
225+ per_call_results = per_call_results ,
226+ error_text = str (exc ),
227+ error_kind = self ._error_kind (str (exc )),
232228 )
233- return BackendExecutionResult (
234- observation = observation ,
235- attempts = [attempt ],
236- diagnostics = {"backend_type" : self .backend_type , "error" : error_text },
229+ except Exception as exc : # noqa: BLE001
230+ return self ._execution_failure_result (
231+ attempt = attempt ,
232+ per_call_results = per_call_results ,
233+ error_text = str (exc ),
234+ error_kind = "backend_exception" ,
235+ exception_type = type (exc ).__name__ ,
237236 )
238237
239238 def close_session (self , session : SessionHandle ) -> None :
@@ -249,6 +248,9 @@ def _snapshot_observation(
249248 screenshots : Optional [List [Dict [str , Any ]]] = None ,
250249 ) -> Observation :
251250 snapshot = self ._call_tool (client , self ._settings .snapshot_tool , {})
251+ snapshot_error = self ._tool_result_error (snapshot )
252+ if snapshot_error :
253+ raise McpProtocolError (snapshot_error )
252254 snapshot_text = self ._extract_text (snapshot )
253255 env_state = self ._extract_env_state (snapshot_text )
254256 artifacts = {"snapshot" : snapshot }
@@ -292,7 +294,9 @@ def _map_call(self, call: ExecutionCall) -> tuple[str, Dict[str, Any]]:
292294 if call .kind == "press" :
293295 return self ._settings .press_tool , {"key" : call .text or call .target }
294296 if call .kind == "wait" :
295- return self ._settings .wait_tool , {"time" : call .duration_ms or 1000 }
297+ return self ._settings .wait_tool , {
298+ "time" : max (call .duration_ms or 1000 , 0 ) / 1000.0
299+ }
296300 if call .kind == "screenshot" :
297301 filename = self ._screenshot_filename (call )
298302 arguments : Dict [str , Any ] = {"filename" : filename }
@@ -501,6 +505,45 @@ def _result_excerpt(payload: Dict[str, Any]) -> str:
501505 return text [:500 ]
502506 return json .dumps (payload , ensure_ascii = False )[:500 ]
503507
508+ def _execution_failure_result (
509+ self ,
510+ * ,
511+ attempt : ExecutionAttempt ,
512+ per_call_results : List [Dict [str , Any ]],
513+ error_text : str ,
514+ error_kind : str ,
515+ exception_type : str = "" ,
516+ ) -> BackendExecutionResult :
517+ attempt .per_call_results = per_call_results
518+ attempt .error = error_text
519+ attempt .suspected_origin = "execution"
520+ diagnostics : Dict [str , Any ] = {
521+ "backend_type" : self .backend_type ,
522+ "error" : error_text ,
523+ "error_kind" : error_kind ,
524+ "per_call_results" : per_call_results ,
525+ }
526+ if exception_type :
527+ diagnostics ["exception_type" ] = exception_type
528+ observation = Observation (
529+ success = False ,
530+ message = error_text ,
531+ state = {},
532+ summary = f"Execution failure in Playwright MCP: { error_text } " ,
533+ env_state = {},
534+ artifacts = {},
535+ execution = {
536+ "attempts" : [self ._attempt_to_dict (attempt )],
537+ "diagnostics" : diagnostics ,
538+ "suspected_origin" : "execution" ,
539+ },
540+ )
541+ return BackendExecutionResult (
542+ observation = observation ,
543+ attempts = [attempt ],
544+ diagnostics = diagnostics ,
545+ )
546+
504547 @staticmethod
505548 def _attempt_to_dict (attempt : ExecutionAttempt ) -> Dict [str , Any ]:
506549 payload = {
0 commit comments