Initial commit - Selenium automation framework
This commit is contained in:
@@ -0,0 +1,400 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from selenium.common.exceptions import NoAlertPresentException, TimeoutException
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
|
||||
from tests.regression.ui_helpers import select_select2_value_by_container_id
|
||||
|
||||
|
||||
def prepare_block_bulk_upload_page(driver, config, entry, upload_method):
|
||||
|
||||
product = config["product"]
|
||||
bidding_config = config["exchange_bidding"]
|
||||
block_config = bidding_config.get("block_bid", {})
|
||||
|
||||
_click_tab(driver, upload_method)
|
||||
_click_block_bid_tab(driver)
|
||||
_select_product_type(driver, product["product_type"])
|
||||
_select_first_available_select2_value(
|
||||
driver,
|
||||
block_config.get("product_container_id", "s2id_ddlproduct3"),
|
||||
[
|
||||
block_config.get("product_code"),
|
||||
product["product_code"],
|
||||
],
|
||||
)
|
||||
_select_first_available_select2_value(
|
||||
driver,
|
||||
block_config.get("segment_container_id", "s2id_ddlSegment3"),
|
||||
[product["trading_market"]],
|
||||
)
|
||||
|
||||
if config["selected_role"] != "trading_member":
|
||||
_select_first_available_select2_value(
|
||||
driver,
|
||||
block_config.get("tm_container_id", "s2id_ddlTMID3"),
|
||||
[entry["tm_id"]],
|
||||
)
|
||||
|
||||
|
||||
def upload_block_bulk_file(
|
||||
driver,
|
||||
file_path,
|
||||
success_timeout_seconds=1800,
|
||||
poll_seconds=2,
|
||||
progress_log_interval_seconds=60,
|
||||
progress_log_label="block_file_upload",
|
||||
):
|
||||
|
||||
upload_path = Path(file_path)
|
||||
if not upload_path.exists() or not upload_path.is_file():
|
||||
raise FileNotFoundError(f"Block bid upload file not found: {upload_path}")
|
||||
|
||||
file_input = _find_block_upload_input(driver)
|
||||
_prepare_file_input(driver, file_input)
|
||||
selected_at = datetime.now()
|
||||
selected_monotonic = time.monotonic()
|
||||
file_input.send_keys(str(upload_path.resolve()))
|
||||
_accept_browser_upload_confirmation_if_present(driver)
|
||||
success = wait_for_block_upload_success(
|
||||
driver,
|
||||
timeout_seconds=success_timeout_seconds,
|
||||
poll_seconds=poll_seconds,
|
||||
progress_log_interval_seconds=progress_log_interval_seconds,
|
||||
progress_log_label=progress_log_label,
|
||||
)
|
||||
return _build_upload_timing(selected_at, selected_monotonic, success, [upload_path])
|
||||
|
||||
|
||||
def upload_block_bulk_folder(
|
||||
driver,
|
||||
folder_path,
|
||||
success_timeout_seconds=1800,
|
||||
poll_seconds=2,
|
||||
progress_log_interval_seconds=60,
|
||||
progress_log_label="block_folder_upload",
|
||||
):
|
||||
|
||||
upload_path = Path(folder_path)
|
||||
if not upload_path.exists() or not upload_path.is_dir():
|
||||
raise FileNotFoundError(f"Block bid upload folder not found: {upload_path}")
|
||||
|
||||
upload_files = [path for path in upload_path.rglob("*") if path.is_file()]
|
||||
file_input = _find_block_upload_input(driver)
|
||||
_prepare_file_input(driver, file_input)
|
||||
selected_at = datetime.now()
|
||||
selected_monotonic = time.monotonic()
|
||||
file_input.send_keys(str(upload_path.resolve()))
|
||||
_accept_browser_upload_confirmation_if_present(driver)
|
||||
success = wait_for_block_upload_success(
|
||||
driver,
|
||||
timeout_seconds=success_timeout_seconds,
|
||||
poll_seconds=poll_seconds,
|
||||
progress_log_interval_seconds=progress_log_interval_seconds,
|
||||
progress_log_label=progress_log_label,
|
||||
)
|
||||
return _build_upload_timing(selected_at, selected_monotonic, success, upload_files)
|
||||
|
||||
|
||||
def wait_for_block_upload_success(
|
||||
driver,
|
||||
timeout_seconds=1800,
|
||||
poll_seconds=2,
|
||||
progress_log_interval_seconds=60,
|
||||
progress_log_label="block_upload",
|
||||
):
|
||||
|
||||
end_time = time.monotonic() + timeout_seconds
|
||||
next_progress_log = time.monotonic() + progress_log_interval_seconds
|
||||
last_message = ""
|
||||
|
||||
while time.monotonic() < end_time:
|
||||
message = _current_upload_message(driver)
|
||||
if message:
|
||||
last_message = message
|
||||
if _is_success_message(message):
|
||||
return {
|
||||
"message": message,
|
||||
"displayed_at": datetime.now().isoformat(timespec="seconds"),
|
||||
}
|
||||
|
||||
if progress_log_interval_seconds and time.monotonic() >= next_progress_log:
|
||||
print(
|
||||
f"Block upload progress [{progress_log_label}]: "
|
||||
f"waiting for success message. Last message: {last_message or 'none'}"
|
||||
)
|
||||
next_progress_log = time.monotonic() + progress_log_interval_seconds
|
||||
|
||||
time.sleep(poll_seconds)
|
||||
|
||||
raise TimeoutException(
|
||||
"Block upload success message did not appear within the timeout. "
|
||||
f"Last visible message: {last_message or 'No status message found'}"
|
||||
)
|
||||
|
||||
|
||||
def save_block_bulk_upload_log(test_context, market, entry, upload_method, timing):
|
||||
|
||||
folder = Path(test_context.get("log_folder", test_context["folder"]))
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
side_name = entry.get("side", "")
|
||||
log_name_parts = ["block_bulk_upload", upload_method, market]
|
||||
if side_name:
|
||||
log_name_parts.append(side_name)
|
||||
log_name_parts.append(entry["tm_id"])
|
||||
path = folder / f"{'_'.join(log_name_parts)}.json"
|
||||
payload = {
|
||||
"market": market,
|
||||
"side": side_name,
|
||||
"tm_id": entry["tm_id"],
|
||||
"folder_name": entry.get("folder_name", ""),
|
||||
"folder_path": entry.get("folder_path", ""),
|
||||
"file_name": entry.get("file_name", ""),
|
||||
"file_path": entry.get("upload_path", ""),
|
||||
"upload_method": upload_method,
|
||||
"timing": timing,
|
||||
}
|
||||
|
||||
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
||||
return str(path)
|
||||
|
||||
|
||||
def _click_tab(driver, upload_method):
|
||||
|
||||
tab_href = "#Folder" if upload_method == "folder_upload" else "#File"
|
||||
_click_by_xpath(
|
||||
driver,
|
||||
f"//a[@href='{tab_href}' or normalize-space()='{tab_href.lstrip('#')}']"
|
||||
)
|
||||
|
||||
|
||||
def _click_block_bid_tab(driver):
|
||||
|
||||
if _block_bid_fields_are_visible(driver):
|
||||
return
|
||||
|
||||
_click_visible_by_xpath(
|
||||
driver,
|
||||
"//a[contains(normalize-space(),'Block Bid')]"
|
||||
"|//span[contains(normalize-space(),'Block Bid')]/ancestor::a[1]"
|
||||
"|//*[self::button][contains(normalize-space(),'Block Bid')]"
|
||||
)
|
||||
|
||||
WebDriverWait(driver, 20).until(lambda _driver: _block_bid_fields_are_visible(_driver))
|
||||
|
||||
|
||||
def _select_product_type(driver, product_type):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
candidates = driver.find_elements(
|
||||
By.XPATH,
|
||||
"//select[contains(@id,'ProductType') or contains(@name,'ProductType')]"
|
||||
"|//select[option[normalize-space()='IDAS']]"
|
||||
)
|
||||
for candidate in candidates:
|
||||
if not candidate.is_displayed():
|
||||
continue
|
||||
try:
|
||||
from selenium.webdriver.support.select import Select
|
||||
Select(candidate).select_by_visible_text(product_type)
|
||||
return
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
wait.until(lambda _driver: candidates or True)
|
||||
|
||||
|
||||
def _select_first_available_select2_value(driver, container_id, values):
|
||||
|
||||
last_error = None
|
||||
for value in values:
|
||||
if not value:
|
||||
continue
|
||||
try:
|
||||
select_select2_value_by_container_id(
|
||||
driver,
|
||||
container_id,
|
||||
value,
|
||||
exact=True
|
||||
)
|
||||
return value
|
||||
except TimeoutException as exc:
|
||||
last_error = exc
|
||||
|
||||
if last_error:
|
||||
raise last_error
|
||||
|
||||
raise ValueError(f"No values configured for select2 container '{container_id}'.")
|
||||
|
||||
|
||||
def _find_block_upload_input(driver):
|
||||
|
||||
wait = WebDriverWait(driver, 30)
|
||||
return wait.until(
|
||||
lambda _driver: _first_displayed_or_existing(
|
||||
_driver.find_elements(
|
||||
By.XPATH,
|
||||
"//input[@type='file' and ("
|
||||
"@id='filepicker3' or contains(@id,'filepicker') or "
|
||||
"contains(@id,'file') or contains(@name,'file') or @webkitdirectory"
|
||||
")]"
|
||||
)
|
||||
) or False,
|
||||
"No block bid upload input was found."
|
||||
)
|
||||
|
||||
|
||||
def _first_displayed_or_existing(elements):
|
||||
|
||||
for element in elements:
|
||||
if element.is_displayed() and element.is_enabled():
|
||||
return element
|
||||
if elements:
|
||||
return elements[0]
|
||||
return None
|
||||
|
||||
|
||||
def _prepare_file_input(driver, file_input):
|
||||
|
||||
driver.execute_script(
|
||||
"""
|
||||
arguments[0].style.display = 'block';
|
||||
arguments[0].style.visibility = 'visible';
|
||||
arguments[0].style.opacity = 1;
|
||||
arguments[0].style.height = '20px';
|
||||
arguments[0].style.width = '120px';
|
||||
arguments[0].removeAttribute('disabled');
|
||||
arguments[0].value = '';
|
||||
""",
|
||||
file_input
|
||||
)
|
||||
|
||||
|
||||
def _click_by_xpath(driver, xpath):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
element = wait.until(EC.element_to_be_clickable((By.XPATH, xpath)))
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element)
|
||||
try:
|
||||
element.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", element)
|
||||
|
||||
|
||||
def _click_visible_by_xpath(driver, xpath):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
element = wait.until(lambda _driver: _first_clickable(_driver, xpath) or False)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element)
|
||||
try:
|
||||
element.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", element)
|
||||
|
||||
|
||||
def _first_clickable(driver, xpath):
|
||||
|
||||
for element in driver.find_elements(By.XPATH, xpath):
|
||||
try:
|
||||
if element.is_displayed() and element.is_enabled():
|
||||
return element
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _block_bid_fields_are_visible(driver):
|
||||
|
||||
product_fields = driver.find_elements(By.ID, "s2id_ddlproduct3")
|
||||
segment_fields = driver.find_elements(By.ID, "s2id_ddlSegment3")
|
||||
upload_inputs = driver.find_elements(By.ID, "filepicker3")
|
||||
|
||||
return any(_is_visible(element) for element in product_fields) and (
|
||||
any(_is_visible(element) for element in segment_fields)
|
||||
or any(_is_visible(element) for element in upload_inputs)
|
||||
)
|
||||
|
||||
|
||||
def _is_visible(element):
|
||||
|
||||
try:
|
||||
return element.is_displayed()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _accept_browser_upload_confirmation_if_present(driver):
|
||||
|
||||
try:
|
||||
alert = driver.switch_to.alert
|
||||
alert.accept()
|
||||
return
|
||||
except NoAlertPresentException:
|
||||
pass
|
||||
|
||||
try:
|
||||
ActionChains(driver).send_keys("\ue007").perform()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _current_upload_message(driver):
|
||||
|
||||
candidates = []
|
||||
for locator in (
|
||||
(By.ID, "successMsg"),
|
||||
(By.ID, "divSuccess"),
|
||||
(By.ID, "divInfo"),
|
||||
(By.ID, "divError"),
|
||||
):
|
||||
candidates.extend(driver.find_elements(*locator))
|
||||
|
||||
candidates.extend(
|
||||
driver.find_elements(
|
||||
By.XPATH,
|
||||
"//*[contains(@class,'alert') and not(contains(@style,'display: none'))]"
|
||||
)
|
||||
)
|
||||
|
||||
for candidate in candidates:
|
||||
try:
|
||||
if candidate.is_displayed():
|
||||
text = candidate.text.strip()
|
||||
if text:
|
||||
return text
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def _is_success_message(message):
|
||||
|
||||
normalized_message = " ".join(str(message or "").split()).lower()
|
||||
return (
|
||||
"folder_uploaded_successfully" in normalized_message
|
||||
or "folder uploaded successfully" in normalized_message
|
||||
or "file_uploaded_successfully" in normalized_message
|
||||
or "file uploaded successfully" in normalized_message
|
||||
)
|
||||
|
||||
|
||||
def _build_upload_timing(selected_at, selected_monotonic, success, upload_files):
|
||||
|
||||
success_monotonic = time.monotonic()
|
||||
return {
|
||||
"files_selected_at": selected_at.isoformat(timespec="seconds"),
|
||||
"success_displayed_at": success["displayed_at"],
|
||||
"duration_seconds": round(success_monotonic - selected_monotonic, 3),
|
||||
"success_message": success["message"],
|
||||
"file_count": len(upload_files),
|
||||
"files": [str(path) for path in upload_files],
|
||||
}
|
||||
Reference in New Issue
Block a user