diff --git a/Framework/Built_In_Automation/Sequential_Actions/sequential_actions.py b/Framework/Built_In_Automation/Sequential_Actions/sequential_actions.py index 9761dfdb..9c30e6b0 100755 --- a/Framework/Built_In_Automation/Sequential_Actions/sequential_actions.py +++ b/Framework/Built_In_Automation/Sequential_Actions/sequential_actions.py @@ -2525,6 +2525,9 @@ def Action_Handler(_data_set, action_row, _bypass_bug=True): CommonUtil.ExecLog(sModuleInfo, "Can't find module for %s" % module, 3) return "zeuz_failed" run_function = getattr(eval(module), function) # create a reference to the function + # Capture the BEFORE-action screen (debug/chatbot only) so the validator can + # compare it against the AFTER capture taken once the action completes. + CommonUtil.TakeScreenShot(function, pre_action=True) start_time = time.perf_counter() if pre_sleep: time.sleep(pre_sleep) diff --git a/Framework/Utilities/CommonUtil.py b/Framework/Utilities/CommonUtil.py index 28215917..9ffd93e1 100644 --- a/Framework/Utilities/CommonUtil.py +++ b/Framework/Utilities/CommonUtil.py @@ -849,9 +849,20 @@ def set_screenshot_vars(shared_variables): ExecLog(sModuleInfo, "Error setting screenshot variables", 3) -def TakeScreenShot(function_name, local_run=False): - """ Puts TakeScreenShot into a thread, so it doesn't block test case execution """ +def TakeScreenShot(function_name, local_run=False, pre_action=False): + """ Puts TakeScreenShot into a thread, so it doesn't block test case execution. + + When pre_action=True, captures the screen state *before* the action runs so the + debug/chatbot validator can compare BEFORE vs AFTER. The pre-action capture only + runs in debug mode (the only mode that streams screenshots over live_log), is taken + synchronously without the post-action settle delay (we want the current state, not a + settled one), and is tagged with a distinct log marker so consumers can label it. + """ if not ws_ss_log or performance_testing: return + # The before-image is only consumed by the debug/chatbot live_log stream, so skip it + # entirely for normal runs to avoid extra capture latency and screenshot volume. + if pre_action and not debug_status: + return try: if upload_on_fail and rerun_on_fail and not rerunning_on_fail and not debug_status: return @@ -879,9 +890,13 @@ def TakeScreenShot(function_name, local_run=False): sModuleInfo, "Skipping screenshot due to screenshot or local_run setting", 0 ) return + capture_marker = ( + "Capturing Pre-Action Screenshot for Action" if pre_action + else "Capturing Screenshot for Action" + ) ExecLog( "", - "********** Capturing Screenshot for Action: %s Method: %s **********" % (function_name, Method), + "********** %s: %s Method: %s **********" % (capture_marker, function_name, Method), 4, ) if current_action_name.strip().lower() in ("none", "undefined"): @@ -898,6 +913,15 @@ def TakeScreenShot(function_name, local_run=False): break image_name = "Step#" + current_step_no + "_Action#" + current_action_no + "_" + filename + if pre_action: + # Capture synchronously and without the settle delay so the BEFORE frame is + # streamed over live_log before the action executes (and before the AFTER + # frame), keeping the two distinguishable in order on the consumer side. + Thread_ScreenShot( + function_name, image_folder, Method, Driver, image_name + "_pre", skip_delay=True + ) + return + thread = executor.submit(Thread_ScreenShot, function_name, image_folder, Method, Driver, image_name) SaveThread("screenshot", thread) @@ -990,8 +1014,13 @@ def _get_window_screenshot_bbox(): return None -def Thread_ScreenShot(function_name, image_folder, Method, Driver, image_name): - """ Capture screen of mobile or desktop """ +def Thread_ScreenShot(function_name, image_folder, Method, Driver, image_name, skip_delay=False): + """ Capture screen of mobile or desktop. + + skip_delay=True bypasses the debug settle delay — used for pre-action (BEFORE) + captures, where the current on-screen state must be grabbed immediately rather than + after waiting for the UI to settle (which only makes sense for AFTER captures). + """ if performance_testing: return sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME chars_to_remove = [ @@ -1028,7 +1057,7 @@ def Thread_ScreenShot(function_name, image_folder, Method, Driver, image_name): return should_delay_before_capture = True - if should_delay_before_capture and not _wait_for_debug_screenshot_delay(sModuleInfo, function_name, Method): + if should_delay_before_capture and not skip_delay and not _wait_for_debug_screenshot_delay(sModuleInfo, function_name, Method): return # Capture screenshot of desktop