diff --git a/CLAUDE.md b/CLAUDE.md index c6c2af41f..977734efb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -146,6 +146,8 @@ Variables use `%|variable_name|%` syntax: "%|user["name"]|%" # Dictionary access ``` +Some Selenium actions, including `copy image into browser`, use a single `image file` row whose value can be a literal path or a `%|...|%` expression such as `%|var|%/whatever.png` because the framework resolves it before the action runs. + ### Element Location The unified `LocateElement.Get_Element()` supports: - Selenium WebDriver diff --git a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py index 55797f587..2378ea25d 100644 --- a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py +++ b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py @@ -1434,8 +1434,6 @@ def parse_status_codes(code_str): "method_filter": [], "include_body": False, } - - # Parse for left, mid, right in step_data: left = left.lower().strip() if left == "capture network log": @@ -5153,174 +5151,3 @@ def if_element_exists(data_set): @logger -def copy_image_into_browser(data_set): - """ - This action will copy an image from path or a variable into browser, and later you can paste via ctrl+v or cmd+v. - Supported formats: PNG, SVG - - Example 1: - Field Sub Field Value - image file input parameter %| image.png |% - copy image into browser selenium action copy image into browser - - Example 2: - Field Sub Field Value - image variable input parameter %| image_var |% - copy image into browser selenium action copy image into browser - """ - sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME - global selenium_driver - - try: - image_data = None - image_path = "" - variable_name = "" - mime_type = "image/png" - - # Parse - for left, mid, right in data_set: - left = left.lower().replace(" ", "") - mid = mid.lower().replace(" ", "") - right = right.strip() - - if left == "imagefile": - if os.path.exists(right): - image_path = right - else: - image_path = CommonUtil.path_parser(right) - - elif left == "imagevariable": - if os.path.exists(right): - image_path = right - else: - variable_name = right - - if image_path: - if not os.path.exists(image_path): - CommonUtil.ExecLog( - sModuleInfo, f"Image file not found: {image_path}", 3 - ) - return "zeuz_failed" - elif variable_name: - image_path = Shared_Resources.Get_Shared_Variables(variable_name) - if not image_path: - CommonUtil.ExecLog( - sModuleInfo, - f"Image path not found in variable: {variable_name}. Make sure you must be use '%| |%' syntax for any variable or attachment.", - 3, - ) - return "zeuz_failed" - else: - CommonUtil.ExecLog( - sModuleInfo, "Must provide either 'image file' or 'image variable'", 3 - ) - return "zeuz_failed" - - if image_path.lower().endswith(".svg"): - mime_type = "image/svg+xml" - elif image_path.lower().endswith(".png"): - mime_type = "image/png" - else: - CommonUtil.ExecLog( - sModuleInfo, - "Unsupported file format. You can copy only PNG or SVG image.", - 2, - ) - return "zeuz_failed" - - with open(image_path, "rb") as image_file: - image_data = image_file.read() - - # Convert - image_b64 = base64.b64encode(image_data).decode("utf-8") - - browser_name = selenium_driver.capabilities.get("browserName", "").lower() - if browser_name in ("chrome", "microsoft edge", "edge"): - try: - selenium_driver.execute_cdp_cmd( - "Browser.setClipboard", {"data": image_b64, "type": mime_type} - ) - CommonUtil.ExecLog( - sModuleInfo, f"Image copied to clipboard via CDP: {image_path}", 1 - ) - return "passed" - except Exception as e: - CommonUtil.ExecLog( - sModuleInfo, f"CDP failed ({str(e)}). Trying fallback method", 2 - ) - - try: - # Grant clipboard permissions via CDP if possible - try: - from urllib.parse import urlparse - - parsed_uri = urlparse(selenium_driver.current_url) - origin = f"{parsed_uri.scheme}://{parsed_uri.netloc}" - selenium_driver.execute_cdp_cmd( - "Browser.grantPermissions", - { - "origin": origin, - "permissions": [ - "clipboardReadWrite", - "clipboardSanitizedWrite", - ], - }, - ) - except: - pass - - async_script = """ - const [base64Data, mimeType, callback] = arguments; - const byteCharacters = atob(base64Data); - const byteArrays = []; - - for (let offset = 0; offset < byteCharacters.length; offset += 512) { - const slice = byteCharacters.slice(offset, offset + 512); - const byteNumbers = new Array(slice.length); - - for (let i = 0; i < slice.length; i++) { - byteNumbers[i] = slice.charCodeAt(i); - } - - byteArrays.push(new Uint8Array(byteNumbers)); - } - - const blob = new Blob(byteArrays, {type: mimeType}); - const item = new ClipboardItem({ [mimeType]: blob }); - - window.focus(); - navigator.clipboard.write([item]) - .then(() => { - console.log('Successfully copied image to clipboard.'); - callback(true); - }) - .catch(err => { - console.log('Failed to copy image to clipboard', err); - callback(false); - }); - """ - - selenium_driver.switch_to.window(selenium_driver.current_window_handle) - selenium_driver.execute_script("window.focus();") - - success = selenium_driver.execute_async_script( - async_script, image_b64, mime_type - ) - if success: - CommonUtil.ExecLog( - sModuleInfo, f"Image copied to clipboard: {image_path}", 1 - ) - return "passed" - CommonUtil.ExecLog( - sModuleInfo, - f"Document is not focused. Failed to copy image to clipboard: {image_path}", - 3, - ) - return "zeuz_failed" - - except Exception as e: - CommonUtil.ExecLog(sModuleInfo, f"Fallback method failed: {str(e)}", 3) - return "zeuz_failed" - - except Exception: - return CommonUtil.Exception_Handler(sys.exc_info()) diff --git a/tests/test_copy_image_into_browser.py b/tests/test_copy_image_into_browser.py new file mode 100644 index 000000000..e931808bf --- /dev/null +++ b/tests/test_copy_image_into_browser.py @@ -0,0 +1,80 @@ +import unittest +from unittest.mock import MagicMock, mock_open, patch + +from Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions import ( + copy_image_into_browser, +) + + +class CopyImageIntoBrowserTests(unittest.TestCase): + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.selenium_driver") + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.CommonUtil.ExecLog") + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.CommonUtil.path_parser") + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.os.access", return_value=True) + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.os.path.isfile", return_value=True) + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.os.path.exists", return_value=True) + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.open", new_callable=mock_open, read_data=b"fakepng") + def test_accepts_single_image_file_row_with_mixed_variable_path( + self, + mock_open_file, + mock_exists, + mock_isfile, + mock_access, + mock_path_parser, + mock_exec_log, + mock_driver, + ): + mock_path_parser.return_value = "/resolved/image.png" + mock_driver.capabilities = {"browserName": "chrome"} + mock_driver.execute_cdp_cmd = MagicMock() + + result = copy_image_into_browser( + [("image file", "input parameter", "%|var|%/whatever.png")] + ) + + self.assertEqual(result, "passed") + mock_path_parser.assert_called_once_with("%|var|%/whatever.png") + mock_driver.execute_cdp_cmd.assert_called_once() + mock_exec_log.assert_any_call( + "copy_image_into_browser : selenium", "Image copied to clipboard via CDP: /resolved/image.png", 1 + ) + + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.selenium_driver") + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.CommonUtil.ExecLog") + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.CommonUtil.path_parser") + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.os.access", return_value=True) + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.os.path.isfile", return_value=True) + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.os.path.exists", return_value=True) + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.open", new_callable=mock_open, read_data=b"fakepng") + def test_accepts_single_literal_image_path_row( + self, + mock_open_file, + mock_exists, + mock_isfile, + mock_access, + mock_path_parser, + mock_exec_log, + mock_driver, + ): + mock_path_parser.side_effect = lambda value: value + mock_driver.capabilities = {"browserName": "chrome"} + mock_driver.execute_cdp_cmd = MagicMock() + + result = copy_image_into_browser( + [("image file", "input parameter", "/tmp/whatever.png")] + ) + + self.assertEqual(result, "passed") + mock_path_parser.assert_called_once_with("/tmp/whatever.png") + mock_driver.execute_cdp_cmd.assert_called_once() + mock_exec_log.assert_any_call( + "copy_image_into_browser : selenium", "Image copied to clipboard via CDP: /tmp/whatever.png", 1 + ) + + @patch("Framework.Built_In_Automation.Web.Selenium.BuiltInFunctions.CommonUtil.ExecLog") + def test_rejects_missing_image_file_value(self, mock_exec_log): + result = copy_image_into_browser([]) + + self.assertEqual(result, "zeuz_failed") + mock_exec_log.assert_called_once() + self.assertIn("image file", mock_exec_log.call_args.args[1])