Skip to content

Commit dfe1145

Browse files
Merge pull request #568 from AutomationSolutionz/image-copy-action
Image copy action
2 parents ce7d274 + 49c7f2a commit dfe1145

File tree

3 files changed

+205
-2
lines changed

3 files changed

+205
-2
lines changed

Framework/Built_In_Automation/Sequential_Actions/action_declarations/selenium.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{ "name": "click", "function": "Click_Element", "screenshot": "web" },
33
{ "name": "click and hold", "function": "Click_and_Hold_Element", "screenshot": "web" },
44
{ "name": "click and download", "function": "Click_and_Download", "screenshot": "web" },
5-
{ "name": "right click", "function": "Right_Click_Element", "screenshot": "web" },
5+
{ "name": "right click", "function": "Right_Click_Element", "screenshot": "web" },
66
{ "name": "double click", "function": "Double_Click_Element", "screenshot": "web" },
77
{ "name": "hover", "function": "Hover_Over_Element", "screenshot": "web" },
88
{ "name": "keystroke keys", "function": "Keystroke_For_Element", "screenshot": "web" },
@@ -58,6 +58,7 @@
5858
{ "name": "change attribute value", "function": "Change_Attribute_Value", "screenshot": "web" },
5959
{ "name": "capture network log", "function": "capture_network_log", "screenshot": "web" },
6060
{ "name": "if element exists", "function": "if_element_exists", "screenshot": "web" },
61+
{ "name": "copy image into browser", "function": "copy_image_into_browser", "screenshot": "web" },
6162
) # yapf: disable
6263

6364
module_name = "selenium"

Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py

Lines changed: 202 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import socket
1919
import requests
2020
import psutil
21+
import base64, imghdr
2122
from pathlib import Path
2223
sys.path.append("..")
2324
from selenium import webdriver
@@ -555,6 +556,7 @@ def headless():
555556
browser in ("chrome", "microsoft edge chromium")
556557
):
557558
set_extension_variables()
559+
options.add_argument("--disable-features=DisableLoadExtensionCommandLineSwitch")
558560
options.add_argument(f"load-extension={aiplugin_path},{ai_recorder_path}")
559561
# This is for running extension on a http server to call a https request
560562
options.add_argument("--allow-running-insecure-content")
@@ -1262,7 +1264,63 @@ def Keystroke_For_Element(data_set):
12621264
}
12631265
for key in convert:
12641266
keystroke_value = keystroke_value.replace(key, convert[key])
1265-
if "+" in keystroke_value:
1267+
1268+
# Special handling for paste command (Ctrl+V / Command+V)
1269+
normalized_keystroke = keystroke_value.replace(" ", "").replace("_", "").lower()
1270+
if normalized_keystroke in ("ctrl+v", "control+v", "ctrlv", "controlv", "cmd+v", "cmdv", "command+v", "commandv"):
1271+
capabilities = selenium_driver.capabilities
1272+
platform_name = capabilities.get('platformName', '').lower()
1273+
browser_name = capabilities.get('browserName', '').lower()
1274+
1275+
if 'mac' in platform_name or 'os x' in platform_name:
1276+
paste_key = Keys.COMMAND
1277+
elif platform.system().lower() in ('darwin', 'macos'):
1278+
paste_key = Keys.COMMAND
1279+
else:
1280+
paste_key = Keys.CONTROL
1281+
1282+
if browser_name == 'firefox' and ('linux' in platform_name or platform.system().lower() == 'linux'):
1283+
paste_key = Keys.CONTROL
1284+
1285+
try:
1286+
if get_element:
1287+
selenium_driver.execute_script("arguments[0].focus();", Element)
1288+
time.sleep(0.1)
1289+
1290+
# Perform paste using ActionChains
1291+
actions = ActionChains(selenium_driver)
1292+
actions.key_down(paste_key, element=Element)
1293+
actions.send_keys_to_element(Element, 'v')
1294+
actions.key_up(paste_key, element=Element)
1295+
actions.perform()
1296+
else:
1297+
actions = ActionChains(selenium_driver)
1298+
actions.key_down(paste_key)
1299+
actions.send_keys('v')
1300+
actions.key_up(paste_key)
1301+
actions.perform()
1302+
1303+
CommonUtil.ExecLog(sModuleInfo, "Paste command executed successfully", 1)
1304+
return "passed"
1305+
except Exception as e:
1306+
# Fallback to JavaScript if ActionChains fails
1307+
try:
1308+
CommonUtil.ExecLog(sModuleInfo, f"Standard paste failed: {str(e)}. Trying JavaScript fallback...", 2)
1309+
if get_element:
1310+
selenium_driver.execute_script("arguments[0].focus();", Element)
1311+
selenium_driver.execute_script("arguments[0].value = arguments[1];", Element, pyperclip.paste())
1312+
else:
1313+
selenium_driver.execute_script(f"document.activeElement.value += '{pyperclip.paste()}';")
1314+
CommonUtil.ExecLog(sModuleInfo, "Paste executed via JavaScript fallback", 1)
1315+
return "passed"
1316+
except Exception as js_e:
1317+
return CommonUtil.Exception_Handler(
1318+
sys.exc_info(),
1319+
None,
1320+
f"Both methods failed for paste operation: {str(js_e)}"
1321+
)
1322+
1323+
elif "+" in keystroke_value:
12661324
hotkey_list = keystroke_value.split("+")
12671325
for i in range(len(hotkey_list)):
12681326
if hotkey_list[i] in list(dict(Keys.__dict__).keys())[2:-2]:
@@ -3458,3 +3516,146 @@ def if_element_exists(data_set):
34583516
)
34593517
return CommonUtil.Exception_Handler(sys.exc_info(), None, errMsg)
34603518

3519+
3520+
@logger
3521+
def copy_image_into_browser(data_set):
3522+
"""
3523+
This action will copy an image from path or a variable into browser, and later you can paste via ctrl+v or cmd+v.
3524+
Supported formats: PNG, SVG
3525+
3526+
Example 1:
3527+
Field Sub Field Value
3528+
image file input parameter %| image.png |%
3529+
copy image into browser selenium action copy image into browser
3530+
3531+
Example 2:
3532+
Field Sub Field Value
3533+
image variable input parameter %| image_var |%
3534+
copy image into browser selenium action copy image into browser
3535+
"""
3536+
sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME
3537+
global selenium_driver
3538+
3539+
try:
3540+
image_data = None
3541+
image_path = ""
3542+
variable_name = ""
3543+
mime_type = "image/png"
3544+
3545+
# Parse
3546+
for left, mid, right in data_set:
3547+
left = left.lower().replace(" ", "")
3548+
mid = mid.lower().replace(" ", "")
3549+
right = right.strip()
3550+
3551+
if left == "imagefile":
3552+
if os.path.exists(right):
3553+
image_path = right
3554+
else:
3555+
image_path = CommonUtil.path_parser(right)
3556+
3557+
elif left == "imagevariable":
3558+
if os.path.exists(right):
3559+
image_path = right
3560+
else:
3561+
variable_name = right
3562+
3563+
if image_path:
3564+
if not os.path.exists(image_path):
3565+
CommonUtil.ExecLog(sModuleInfo, f"Image file not found: {image_path}", 3)
3566+
return "zeuz_failed"
3567+
elif variable_name:
3568+
image_path = Shared_Resources.Get_Shared_Variables(variable_name)
3569+
if not image_path:
3570+
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)
3571+
return "zeuz_failed"
3572+
else:
3573+
CommonUtil.ExecLog(sModuleInfo, "Must provide either 'image file' or 'image variable'", 3)
3574+
return "zeuz_failed"
3575+
3576+
if image_path.lower().endswith(".svg"):
3577+
mime_type = "image/svg+xml"
3578+
elif image_path.lower().endswith(".png"):
3579+
mime_type = "image/png"
3580+
else:
3581+
CommonUtil.ExecLog(sModuleInfo, "Unsupported file format. You can copy only PNG or SVG image.", 2)
3582+
return "zeuz_failed"
3583+
3584+
with open(image_path, "rb") as image_file:
3585+
image_data = image_file.read()
3586+
3587+
# Convert
3588+
image_b64 = base64.b64encode(image_data).decode('utf-8')
3589+
3590+
browser_name = selenium_driver.capabilities.get('browserName', '').lower()
3591+
if browser_name in ('chrome', 'microsoft edge', 'edge'):
3592+
try:
3593+
selenium_driver.execute_cdp_cmd('Browser.setClipboard', {
3594+
'data': image_b64,
3595+
'type': mime_type
3596+
})
3597+
CommonUtil.ExecLog(sModuleInfo, f"Image copied to clipboard via CDP: {image_path}", 1)
3598+
return "passed"
3599+
except Exception as e:
3600+
CommonUtil.ExecLog(sModuleInfo, f"CDP failed ({str(e)}). Trying fallback method", 2)
3601+
3602+
try:
3603+
# Grant clipboard permissions via CDP if possible
3604+
try:
3605+
from urllib.parse import urlparse
3606+
parsed_uri = urlparse(selenium_driver.current_url)
3607+
origin = f'{parsed_uri.scheme}://{parsed_uri.netloc}'
3608+
selenium_driver.execute_cdp_cmd('Browser.grantPermissions', {
3609+
'origin': origin,
3610+
'permissions': ['clipboardReadWrite', 'clipboardSanitizedWrite']
3611+
})
3612+
except:
3613+
pass
3614+
3615+
async_script = """
3616+
const [base64Data, mimeType, callback] = arguments;
3617+
const byteCharacters = atob(base64Data);
3618+
const byteArrays = [];
3619+
3620+
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
3621+
const slice = byteCharacters.slice(offset, offset + 512);
3622+
const byteNumbers = new Array(slice.length);
3623+
3624+
for (let i = 0; i < slice.length; i++) {
3625+
byteNumbers[i] = slice.charCodeAt(i);
3626+
}
3627+
3628+
byteArrays.push(new Uint8Array(byteNumbers));
3629+
}
3630+
3631+
const blob = new Blob(byteArrays, {type: mimeType});
3632+
const item = new ClipboardItem({ [mimeType]: blob });
3633+
3634+
window.focus();
3635+
navigator.clipboard.write([item])
3636+
.then(() => {
3637+
console.log('Successfully copied image to clipboard.');
3638+
callback(true);
3639+
})
3640+
.catch(err => {
3641+
console.log('Failed to copy image to clipboard', err);
3642+
callback(false);
3643+
});
3644+
"""
3645+
3646+
selenium_driver.switch_to.window(selenium_driver.current_window_handle)
3647+
selenium_driver.execute_script("window.focus();")
3648+
3649+
success = selenium_driver.execute_async_script(async_script, image_b64, mime_type)
3650+
if success:
3651+
CommonUtil.ExecLog(sModuleInfo, f"Image copied to clipboard: {image_path}", 1)
3652+
return "passed"
3653+
CommonUtil.ExecLog(sModuleInfo, f"Document is not focused. Failed to copy image to clipboard: {image_path}", 3)
3654+
return "zeuz_failed"
3655+
3656+
except Exception as e:
3657+
CommonUtil.ExecLog(sModuleInfo, f"Fallback method failed: {str(e)}", 3)
3658+
return "zeuz_failed"
3659+
3660+
except Exception:
3661+
return CommonUtil.Exception_Handler(sys.exc_info())

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ dependencies = [
6565
"pytz>=2025.1",
6666
"pywin32>=308 ; sys_platform == 'win32'",
6767
"pyyaml>=6.0.2",
68+
"playwright>=1.52.0",
6869
"rauth>=0.7.3",
6970
"regex>=2024.11.6",
7071
"requests>=2.32.3",

0 commit comments

Comments
 (0)