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], }