401 lines
12 KiB
Python
401 lines
12 KiB
Python
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],
|
|
}
|