@@ -273,3 +273,50 @@ def test_build_step_end_event_empty_elements():
273273 # Should have elements field but it's empty
274274 assert "elements" in result ["pre" ]
275275 assert len (result ["pre" ]["elements" ]) == 0
276+
277+
278+ def test_build_step_end_event_with_none_verify_data ():
279+ """Test step_end event building when verify_data is None (failed steps).
280+
281+ This test ensures that failed steps can emit step_end events even when
282+ verify_data is None, which happens when a step fails before verification.
283+ """
284+ llm_data = {
285+ "response_text" : "click(123)" ,
286+ "response_hash" : "sha256:abc123" ,
287+ "usage" : {"prompt_tokens" : 100 , "completion_tokens" : 10 , "total_tokens" : 110 },
288+ }
289+
290+ exec_data = {
291+ "success" : False ,
292+ "action" : "error" ,
293+ "outcome" : "Element not found" ,
294+ "duration_ms" : 500 ,
295+ }
296+
297+ # verify_data is None for failed steps
298+ result = TraceEventBuilder .build_step_end_event (
299+ step_id = "step-1" ,
300+ step_index = 1 ,
301+ goal = "Click the button" ,
302+ attempt = 2 ,
303+ pre_url = "http://example.com/page1" ,
304+ post_url = "http://example.com/page1" ,
305+ snapshot_digest = "sha256:digest123" ,
306+ llm_data = llm_data ,
307+ exec_data = exec_data ,
308+ verify_data = None , # None for failed steps
309+ )
310+
311+ # Verify basic structure
312+ assert result ["v" ] == 1
313+ assert result ["step_id" ] == "step-1"
314+ assert result ["step_index" ] == 1
315+ assert result ["attempt" ] == 2
316+
317+ # Verify exec shows failure
318+ assert result ["exec" ]["success" ] is False
319+ assert result ["exec" ]["action" ] == "error"
320+
321+ # Verify should be empty dict when verify_data is None
322+ assert result ["verify" ] == {}
0 commit comments