Initial commit - Selenium automation framework
This commit is contained in:
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.support.select import Select
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
|
||||
from tests.regression.ui_helpers import open_bootstrap_nav_submenu
|
||||
|
||||
|
||||
MATCHING_URL_FRAGMENT = "../Application/IDASInitiateMatching.aspx"
|
||||
GENERATE_FLOW_REQUEST_BUTTON_ID = "btnGenerateProvisionalFlowRequest"
|
||||
REVIEW_RESULT_BUTTON_ID = "btnReviewResult"
|
||||
REVIEW_RESULT_MODAL_ID = "divReviewResult"
|
||||
SUCCESS_CONTAINER_ID = "divSuccess"
|
||||
SUCCESS_MESSAGE_ID = "successMsg"
|
||||
|
||||
|
||||
def open_idas_matching_page(driver):
|
||||
|
||||
open_bootstrap_nav_submenu(
|
||||
driver,
|
||||
"Trade Processing",
|
||||
"IDAS",
|
||||
MATCHING_URL_FRAGMENT
|
||||
)
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
wait.until(EC.visibility_of_element_located((By.ID, "ddlproduct")))
|
||||
|
||||
|
||||
def configure_matching_request(driver, product_code, matching_type, source_type=None):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
if source_type:
|
||||
source_type_select = wait.until(
|
||||
EC.visibility_of_element_located(
|
||||
(
|
||||
By.XPATH,
|
||||
"//label[normalize-space()='SourceType']/following::select[1]"
|
||||
)
|
||||
)
|
||||
)
|
||||
Select(source_type_select).select_by_visible_text(source_type)
|
||||
|
||||
product_select = wait.until(
|
||||
EC.visibility_of_element_located((By.ID, "ddlproduct"))
|
||||
)
|
||||
_select_product_code(product_select, product_code)
|
||||
|
||||
matching_type_select = wait.until(
|
||||
EC.visibility_of_element_located((By.ID, "ddlMatchingTypeIM"))
|
||||
)
|
||||
Select(matching_type_select).select_by_visible_text(matching_type)
|
||||
|
||||
|
||||
def _select_product_code(select_element, product_code):
|
||||
|
||||
normalized_code = product_code.strip().lower()
|
||||
product_select = Select(select_element)
|
||||
for option in product_select.options:
|
||||
option_text = option.text.strip()
|
||||
option_code = option_text.split("(", 1)[0].strip().lower()
|
||||
if option_text.lower() == normalized_code or option_code == normalized_code:
|
||||
product_select.select_by_visible_text(option_text)
|
||||
return
|
||||
|
||||
available_options = [option.text.strip() for option in product_select.options]
|
||||
raise ValueError(
|
||||
f"Product code '{product_code}' is unavailable on the matching page. "
|
||||
f"Available options: {available_options}"
|
||||
)
|
||||
|
||||
|
||||
def start_provisional_match(driver):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
provisional_button = wait.until(
|
||||
EC.element_to_be_clickable((By.ID, "btnProvisionalMatch"))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", provisional_button)
|
||||
try:
|
||||
provisional_button.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", provisional_button)
|
||||
|
||||
confirm_provisional_match(driver)
|
||||
|
||||
|
||||
def confirm_provisional_match(driver):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
modal = wait.until(
|
||||
EC.visibility_of_element_located((By.ID, "matchingConfirmation"))
|
||||
)
|
||||
|
||||
ok_button = wait.until(
|
||||
EC.element_to_be_clickable((By.ID, "btnProvisional"))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", ok_button)
|
||||
try:
|
||||
ok_button.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", ok_button)
|
||||
|
||||
wait.until(EC.invisibility_of_element(modal))
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
def poll_provisional_match_status(driver, interval_seconds=2, timeout_seconds=120):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
end_time = time.monotonic() + timeout_seconds
|
||||
last_message = ""
|
||||
|
||||
while time.monotonic() < end_time:
|
||||
check_status_button = wait.until(
|
||||
EC.element_to_be_clickable((By.ID, "lnkCheckStatusProvisional"))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", check_status_button)
|
||||
try:
|
||||
check_status_button.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", check_status_button)
|
||||
|
||||
message, state = _read_matching_status(driver)
|
||||
if message:
|
||||
last_message = message
|
||||
|
||||
if state == "success":
|
||||
return message
|
||||
|
||||
if state == "error":
|
||||
raise AssertionError(f"Matching failed: {message}")
|
||||
|
||||
time.sleep(interval_seconds)
|
||||
|
||||
raise AssertionError(
|
||||
"Matching status did not reach success within the timeout. "
|
||||
f"Last visible message: {last_message or 'No status message found'}"
|
||||
)
|
||||
|
||||
|
||||
def prepare_matching_download_folder(test_context, folder_name="matching_files"):
|
||||
|
||||
download_folder = Path(test_context["folder"]) / folder_name
|
||||
download_folder.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
extra_folders = test_context.setdefault("extra_folders", [])
|
||||
if not any(item.get("path") == str(download_folder) for item in extra_folders):
|
||||
extra_folders.append(
|
||||
{
|
||||
"name": "Matching Files Folder",
|
||||
"path": str(download_folder),
|
||||
}
|
||||
)
|
||||
|
||||
return download_folder
|
||||
|
||||
|
||||
def configure_browser_downloads(driver, download_folder):
|
||||
|
||||
driver.execute_cdp_cmd(
|
||||
"Page.setDownloadBehavior",
|
||||
{
|
||||
"behavior": "allow",
|
||||
"downloadPath": str(download_folder),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def click_generate_flow_request(driver):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
generate_button = wait.until(
|
||||
EC.element_to_be_clickable((By.ID, GENERATE_FLOW_REQUEST_BUTTON_ID))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", generate_button)
|
||||
try:
|
||||
generate_button.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", generate_button)
|
||||
|
||||
|
||||
def wait_for_flow_request_success(driver, timeout_seconds=30):
|
||||
|
||||
wait = WebDriverWait(driver, timeout_seconds)
|
||||
success_container = wait.until(
|
||||
EC.visibility_of_element_located((By.ID, SUCCESS_CONTAINER_ID))
|
||||
)
|
||||
|
||||
try:
|
||||
message = success_container.find_element(By.ID, SUCCESS_MESSAGE_ID).text.strip()
|
||||
except Exception:
|
||||
message = success_container.text.strip()
|
||||
|
||||
if not message:
|
||||
raise AssertionError("Flow request success banner appeared without a message.")
|
||||
|
||||
return message
|
||||
|
||||
|
||||
def export_review_result_for_source(driver, source_type, download_folder, timeout_seconds=60):
|
||||
|
||||
existing_files = {path.name for path in Path(download_folder).iterdir()}
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
review_result_button = wait.until(
|
||||
EC.element_to_be_clickable((By.ID, REVIEW_RESULT_BUTTON_ID))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", review_result_button)
|
||||
try:
|
||||
review_result_button.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", review_result_button)
|
||||
|
||||
modal = wait.until(
|
||||
EC.visibility_of_element_located((By.ID, REVIEW_RESULT_MODAL_ID))
|
||||
)
|
||||
|
||||
export_button = wait.until(
|
||||
EC.element_to_be_clickable(
|
||||
(
|
||||
By.CSS_SELECTOR,
|
||||
f"#{REVIEW_RESULT_MODAL_ID} .pExcel.pButton"
|
||||
)
|
||||
)
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", export_button)
|
||||
try:
|
||||
export_button.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", export_button)
|
||||
|
||||
downloaded_file = wait_for_new_download(
|
||||
download_folder,
|
||||
existing_files=existing_files,
|
||||
timeout_seconds=timeout_seconds
|
||||
)
|
||||
renamed_file = rename_download_with_source_type(downloaded_file, source_type)
|
||||
|
||||
close_button = wait.until(
|
||||
EC.element_to_be_clickable(
|
||||
(
|
||||
By.CSS_SELECTOR,
|
||||
f"#{REVIEW_RESULT_MODAL_ID} button.close"
|
||||
)
|
||||
)
|
||||
)
|
||||
try:
|
||||
close_button.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", close_button)
|
||||
|
||||
wait.until(EC.invisibility_of_element(modal))
|
||||
return renamed_file
|
||||
|
||||
|
||||
def wait_for_new_download(download_folder, existing_files=None, timeout_seconds=60):
|
||||
|
||||
download_path = Path(download_folder)
|
||||
existing_files = existing_files or set()
|
||||
end_time = time.monotonic() + timeout_seconds
|
||||
|
||||
while time.monotonic() < end_time:
|
||||
current_files = [path for path in download_path.iterdir() if path.is_file()]
|
||||
new_files = [path for path in current_files if path.name not in existing_files]
|
||||
partial_downloads = [
|
||||
path for path in current_files
|
||||
if path.suffix.lower() in {".crdownload", ".tmp", ".part"}
|
||||
]
|
||||
|
||||
completed_files = [
|
||||
path for path in new_files
|
||||
if path.suffix.lower() not in {".crdownload", ".tmp", ".part"}
|
||||
]
|
||||
if completed_files and not partial_downloads:
|
||||
completed_files.sort(key=lambda item: item.stat().st_mtime, reverse=True)
|
||||
return str(completed_files[0])
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
visible_names = sorted(path.name for path in download_path.iterdir() if path.is_file())
|
||||
raise AssertionError(
|
||||
"No completed matching download was detected within the timeout. "
|
||||
f"Folder: {download_folder}. Current files: {visible_names}"
|
||||
)
|
||||
|
||||
|
||||
def rename_download_with_source_type(downloaded_file, source_type):
|
||||
|
||||
file_path = Path(downloaded_file)
|
||||
normalized_source_type = source_type.strip().lower().replace(" ", "_")
|
||||
target_path = file_path.with_name(f"{normalized_source_type}_{file_path.name}")
|
||||
|
||||
if target_path.exists():
|
||||
stem = file_path.stem
|
||||
suffix = file_path.suffix
|
||||
counter = 2
|
||||
while True:
|
||||
candidate = file_path.with_name(
|
||||
f"{normalized_source_type}_{stem}_{counter}{suffix}"
|
||||
)
|
||||
if not candidate.exists():
|
||||
target_path = candidate
|
||||
break
|
||||
counter += 1
|
||||
|
||||
file_path.rename(target_path)
|
||||
return str(target_path)
|
||||
|
||||
|
||||
def _read_matching_status(driver):
|
||||
|
||||
success_divs = driver.find_elements(By.ID, "divSuccess")
|
||||
for current in success_divs:
|
||||
if current.is_displayed():
|
||||
text = current.text.strip()
|
||||
if text:
|
||||
return text, "success"
|
||||
|
||||
error_divs = driver.find_elements(By.ID, "divError")
|
||||
for current in error_divs:
|
||||
if current.is_displayed():
|
||||
text = current.text.strip()
|
||||
if text:
|
||||
return text, "error"
|
||||
|
||||
warning_divs = driver.find_elements(By.ID, "divMatchingStatus")
|
||||
for current in warning_divs:
|
||||
if current.is_displayed():
|
||||
text = current.text.strip()
|
||||
if text:
|
||||
return text, "in_progress"
|
||||
|
||||
warning_messages = driver.find_elements(By.ID, "warningMsg")
|
||||
for current in warning_messages:
|
||||
text = current.text.strip()
|
||||
if text:
|
||||
return text, "in_progress"
|
||||
|
||||
return "", "unknown"
|
||||
@@ -0,0 +1,207 @@
|
||||
import re
|
||||
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 (
|
||||
open_bootstrap_nav_submenu,
|
||||
select_select2_value_by_container_id,
|
||||
)
|
||||
|
||||
|
||||
BID_TRANSFER_URL_FRAGMENT = "../MCO/IDASBidTransfer.aspx"
|
||||
SUPPORTED_MARKETS = ("GDAM", "DAM", "HPDAM")
|
||||
|
||||
|
||||
def open_idas_bid_transfer_page(driver):
|
||||
|
||||
open_bootstrap_nav_submenu(
|
||||
driver,
|
||||
"Trade Processing",
|
||||
"IDAS",
|
||||
BID_TRANSFER_URL_FRAGMENT,
|
||||
)
|
||||
WebDriverWait(driver, 20).until(
|
||||
EC.visibility_of_element_located((By.ID, "s2id_ddlproduct"))
|
||||
)
|
||||
|
||||
|
||||
def select_bid_transfer_product(driver, product_code):
|
||||
|
||||
select_select2_value_by_container_id(
|
||||
driver,
|
||||
"s2id_ddlproduct",
|
||||
product_code,
|
||||
)
|
||||
WebDriverWait(driver, 20).until(
|
||||
lambda current_driver: product_code.lower()
|
||||
in current_driver.find_element(By.ID, "s2id_ddlproduct").text.lower()
|
||||
)
|
||||
|
||||
|
||||
def transfer_market_orders(driver, market, timeout_seconds=30):
|
||||
|
||||
market = _validate_market(market)
|
||||
wait = WebDriverWait(driver, timeout_seconds)
|
||||
row = _market_row(driver, market)
|
||||
old_request_id = row.find_elements(By.TAG_NAME, "td")[1].text.strip()
|
||||
old_status = _status_element(driver, market).text.strip()
|
||||
|
||||
transfer_button = wait.until(EC.element_to_be_clickable((By.ID, market)))
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", transfer_button)
|
||||
transfer_button.click()
|
||||
|
||||
wait.until(
|
||||
lambda current_driver: _transfer_response_received(
|
||||
current_driver,
|
||||
market,
|
||||
old_request_id,
|
||||
old_status,
|
||||
)
|
||||
)
|
||||
|
||||
error_message = _visible_message(driver, "divError")
|
||||
if error_message:
|
||||
raise AssertionError(f"{market} bid transfer failed: {error_message}")
|
||||
|
||||
success_message = _visible_message(driver, "successMsg")
|
||||
status = _read_status(driver, market)
|
||||
if not success_message:
|
||||
raise AssertionError(
|
||||
f"{market} bid transfer did not show a success message. Current status: {status['text']}"
|
||||
)
|
||||
_assert_not_failed(market, "transfer", status)
|
||||
return {"message": success_message, **status}
|
||||
|
||||
|
||||
def check_market_transfer_status(driver, market, timeout_seconds=30):
|
||||
|
||||
market = _validate_market(market)
|
||||
check_button = WebDriverWait(driver, 20).until(
|
||||
EC.element_to_be_clickable(
|
||||
(
|
||||
By.XPATH,
|
||||
f"//button[contains(@onclick,\"GetMCOstatus('{market}')\")]",
|
||||
)
|
||||
)
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", check_button)
|
||||
check_button.click()
|
||||
|
||||
modal = WebDriverWait(driver, timeout_seconds).until(_visible_status_modal)
|
||||
|
||||
error_message = _visible_message(driver, "divError")
|
||||
if error_message:
|
||||
raise AssertionError(f"{market} status check failed: {error_message}")
|
||||
|
||||
modal_text = modal.text.strip()
|
||||
status_match = re.search(
|
||||
r"(?:^|\n)\s*Status\s*:\s*([^\r\n]+)",
|
||||
modal_text,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
if not status_match:
|
||||
raise AssertionError(
|
||||
f"Could not read {market} MCO status from the Send Order Details popup."
|
||||
)
|
||||
|
||||
status_text = status_match.group(1).strip()
|
||||
return {
|
||||
"text": status_text,
|
||||
"normalized": _normalize_popup_status(status_text),
|
||||
"modal": modal,
|
||||
}
|
||||
|
||||
|
||||
def close_market_transfer_status(driver, status_result):
|
||||
|
||||
modal = status_result["modal"]
|
||||
close_button = modal.find_element(
|
||||
By.XPATH,
|
||||
".//button[normalize-space()='Close' or @data-dismiss='modal']",
|
||||
)
|
||||
close_button.click()
|
||||
WebDriverWait(driver, 20).until(EC.invisibility_of_element(modal))
|
||||
|
||||
|
||||
def _validate_market(market):
|
||||
|
||||
normalized_market = market.strip().upper()
|
||||
if normalized_market not in SUPPORTED_MARKETS:
|
||||
raise ValueError(
|
||||
f"Unsupported IDAS bid-transfer market '{market}'. Use one of {SUPPORTED_MARKETS}."
|
||||
)
|
||||
return normalized_market
|
||||
|
||||
|
||||
def _market_row(driver, market):
|
||||
|
||||
return driver.find_element(
|
||||
By.XPATH,
|
||||
f"//div[@id='SendOrderId']//tr[td[1][normalize-space()='{market}']]",
|
||||
)
|
||||
|
||||
|
||||
def _status_element(driver, market):
|
||||
|
||||
return driver.find_element(By.ID, f"{market.lower()}sat")
|
||||
|
||||
|
||||
def _transfer_response_received(driver, market, old_request_id, old_status):
|
||||
|
||||
if _visible_message(driver, "divError"):
|
||||
return True
|
||||
row = _market_row(driver, market)
|
||||
request_id = row.find_elements(By.TAG_NAME, "td")[1].text.strip()
|
||||
status = _status_element(driver, market).text.strip()
|
||||
return bool(request_id and request_id != old_request_id) or status != old_status
|
||||
|
||||
|
||||
def _visible_message(driver, element_id):
|
||||
|
||||
elements = driver.find_elements(By.ID, element_id)
|
||||
if not elements or not elements[0].is_displayed():
|
||||
return ""
|
||||
return elements[0].text.strip()
|
||||
|
||||
|
||||
def _visible_status_modal(driver):
|
||||
|
||||
modals = driver.find_elements(
|
||||
By.XPATH,
|
||||
"//div[contains(@class,'modal')][.//*[contains(normalize-space(),'Send Order Details')]]",
|
||||
)
|
||||
for modal in modals:
|
||||
if modal.is_displayed():
|
||||
return modal
|
||||
return False
|
||||
|
||||
|
||||
def _normalize_popup_status(status):
|
||||
|
||||
return re.sub(r"[\s-]+", "_", status.strip()).upper()
|
||||
|
||||
|
||||
def _read_status(driver, market):
|
||||
|
||||
element = _status_element(driver, market)
|
||||
background_color = element.value_of_css_property("background-color")
|
||||
return {
|
||||
"text": element.text.strip(),
|
||||
"background_color": background_color,
|
||||
"is_green": _is_green(background_color),
|
||||
}
|
||||
|
||||
|
||||
def _is_green(css_color):
|
||||
|
||||
channels = [int(value) for value in re.findall(r"\d+", css_color)[:3]]
|
||||
return len(channels) == 3 and channels[1] > channels[0] and channels[1] > channels[2]
|
||||
|
||||
|
||||
def _assert_not_failed(market, action, status):
|
||||
|
||||
if "fail" in status["text"].lower():
|
||||
raise AssertionError(
|
||||
f"{market} {action} returned '{status['text']}'. Matching has been stopped."
|
||||
)
|
||||
@@ -0,0 +1,215 @@
|
||||
import pytest
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
|
||||
from tests.regression.auth import login_to_trading_application
|
||||
from tests.regression.config_reader import load_regression_config, should_run_flow
|
||||
from tests.regression.exchange.idas.matching.actions import (
|
||||
click_generate_flow_request,
|
||||
configure_browser_downloads,
|
||||
configure_matching_request,
|
||||
export_review_result_for_source,
|
||||
open_idas_matching_page,
|
||||
poll_provisional_match_status,
|
||||
prepare_matching_download_folder,
|
||||
start_provisional_match,
|
||||
wait_for_flow_request_success,
|
||||
)
|
||||
from tests.regression.exchange.idas.matching.bid_transfer_actions import (
|
||||
SUPPORTED_MARKETS,
|
||||
check_market_transfer_status,
|
||||
close_market_transfer_status,
|
||||
open_idas_bid_transfer_page,
|
||||
select_bid_transfer_product,
|
||||
transfer_market_orders,
|
||||
)
|
||||
from tests.step_actions import StepActions
|
||||
|
||||
|
||||
def get_exchange_matching_context(driver, test_context):
|
||||
|
||||
config = load_regression_config()
|
||||
|
||||
assert config["selected_role"] == "exchange", (
|
||||
"IDAS matching is available only for exchange login. "
|
||||
"Set execution.login_role to 'exchange' in config/runtime.json."
|
||||
)
|
||||
|
||||
if not should_run_flow("matching"):
|
||||
pytest.skip(
|
||||
f"Skipping exchange matching flow because flow_scope is '{config['selected_flow']}'."
|
||||
)
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
step = StepActions(driver, wait, test_context)
|
||||
return config, step
|
||||
|
||||
|
||||
def open_app_and_login(driver, step, config):
|
||||
|
||||
driver.get(config["portal_urls"]["pratyay"])
|
||||
step.capture("open_pratyay_application")
|
||||
|
||||
login_to_trading_application(
|
||||
driver,
|
||||
config["login"]["username"],
|
||||
config["login"]["password"],
|
||||
config["timing"],
|
||||
login_role=config["selected_role"]
|
||||
)
|
||||
step.capture("pratyay_login_submitted")
|
||||
|
||||
|
||||
def _step_slug(source_type):
|
||||
|
||||
return source_type.strip().lower().replace(" ", "_")
|
||||
|
||||
|
||||
def _configured_transfer_markets(matching_config):
|
||||
|
||||
markets = matching_config.get("bid_transfer_markets") or list(SUPPORTED_MARKETS)
|
||||
normalized_markets = [market.strip().upper() for market in markets]
|
||||
if "ALL" in normalized_markets:
|
||||
return list(SUPPORTED_MARKETS)
|
||||
return list(dict.fromkeys(normalized_markets))
|
||||
|
||||
|
||||
@pytest.mark.regression
|
||||
@pytest.mark.exchange
|
||||
@pytest.mark.matching
|
||||
def test_idas_exchange_matching_flow(driver, test_context):
|
||||
|
||||
config, step = get_exchange_matching_context(driver, test_context)
|
||||
matching_config = config["exchange_matching"]
|
||||
|
||||
if not matching_config.get("portals", {}).get("pratyay", {}).get("enabled", True):
|
||||
pytest.skip("Pratyay matching preparation is disabled in config/runtime.json.")
|
||||
|
||||
open_app_and_login(driver, step, config)
|
||||
|
||||
product_config = config["product"]
|
||||
transfer_markets = _configured_transfer_markets(matching_config)
|
||||
transfer_timeout = matching_config.get("bid_transfer_timeout_seconds", 30)
|
||||
accepted_transfer_statuses = {
|
||||
status.strip().upper()
|
||||
for status in matching_config.get(
|
||||
"accepted_bid_transfer_statuses",
|
||||
["ACCEPTED", "PARTIALLY_ACCEPTED", "NO_BIDS"],
|
||||
)
|
||||
}
|
||||
|
||||
open_idas_bid_transfer_page(driver)
|
||||
step.capture("open_idas_bid_transfer")
|
||||
select_bid_transfer_product(driver, product_config["product_code"])
|
||||
step.capture("select_bid_transfer_product")
|
||||
|
||||
for index, market in enumerate(transfer_markets, start=1):
|
||||
transfer_result = transfer_market_orders(
|
||||
driver,
|
||||
market,
|
||||
timeout_seconds=transfer_timeout,
|
||||
)
|
||||
step.capture(f"bid_transfer_{index}_{market.lower()}")
|
||||
|
||||
status_result = check_market_transfer_status(
|
||||
driver,
|
||||
market,
|
||||
timeout_seconds=transfer_timeout,
|
||||
)
|
||||
popup_status = status_result["normalized"]
|
||||
step.capture(
|
||||
f"bid_transfer_status_{index}_{market.lower()}_{popup_status.lower()}"
|
||||
)
|
||||
|
||||
if popup_status == "REJECTED":
|
||||
step.test_context["steps"][-1]["status"] = "failed"
|
||||
raise AssertionError(
|
||||
f"{market} MCO bid-transfer status is REJECTED. "
|
||||
"Matching has been stopped before processing the next market."
|
||||
)
|
||||
if popup_status not in accepted_transfer_statuses:
|
||||
step.test_context["steps"][-1]["status"] = "failed"
|
||||
raise AssertionError(
|
||||
f"{market} returned unsupported MCO bid-transfer status "
|
||||
f"'{status_result['text']}'. Matching has been stopped. "
|
||||
f"Accepted statuses: {sorted(accepted_transfer_statuses)}"
|
||||
)
|
||||
|
||||
close_market_transfer_status(driver, status_result)
|
||||
print(
|
||||
f"IDAS bid transfer completed for market={market}, "
|
||||
f"transfer_message={transfer_result['message']}, "
|
||||
f"popup_status={status_result['text']}"
|
||||
)
|
||||
|
||||
open_idas_matching_page(driver)
|
||||
step.capture("open_idas_initiate_matching")
|
||||
|
||||
if matching_config.get("initiation_mode", "provisional_match") != "provisional_match":
|
||||
raise ValueError(
|
||||
"Only 'provisional_match' initiation_mode is supported right now."
|
||||
)
|
||||
|
||||
download_folder = prepare_matching_download_folder(
|
||||
test_context,
|
||||
folder_name=matching_config.get("download_folder_name", "matching_files")
|
||||
)
|
||||
configure_browser_downloads(driver, download_folder)
|
||||
|
||||
source_types = matching_config.get(
|
||||
"source_types",
|
||||
["Green", "Conventional", "HPDAM"]
|
||||
)
|
||||
exported_files = []
|
||||
|
||||
for index, source_type in enumerate(source_types, start=1):
|
||||
source_slug = _step_slug(source_type)
|
||||
|
||||
configure_matching_request(
|
||||
driver,
|
||||
product_config["product_code"],
|
||||
matching_config["matching_type"],
|
||||
source_type=source_type
|
||||
)
|
||||
step.capture(f"select_matching_filters_{index}_{source_slug}")
|
||||
|
||||
start_provisional_match(driver)
|
||||
step.capture(f"start_provisional_match_{index}_{source_slug}")
|
||||
|
||||
final_status = poll_provisional_match_status(
|
||||
driver,
|
||||
interval_seconds=matching_config.get("check_status_interval_seconds", 2),
|
||||
timeout_seconds=matching_config.get("check_status_timeout_seconds", 120)
|
||||
)
|
||||
step.capture(f"provisional_matching_completed_{index}_{source_slug}")
|
||||
|
||||
click_generate_flow_request(driver)
|
||||
success_message = wait_for_flow_request_success(
|
||||
driver,
|
||||
timeout_seconds=matching_config.get("flow_request_success_timeout_seconds", 30)
|
||||
)
|
||||
step.capture(f"generate_flow_request_{index}_{source_slug}")
|
||||
|
||||
exported_file = export_review_result_for_source(
|
||||
driver,
|
||||
source_type,
|
||||
download_folder,
|
||||
timeout_seconds=matching_config.get("download_timeout_seconds", 60)
|
||||
)
|
||||
exported_files.append(exported_file)
|
||||
step.capture(f"review_result_exported_{index}_{source_slug}")
|
||||
|
||||
print(
|
||||
f"Matching review export completed for source_type={source_type}, "
|
||||
f"status={final_status}, "
|
||||
f"flow_request_message={success_message}, "
|
||||
f"exported_file={exported_file}"
|
||||
)
|
||||
|
||||
print(
|
||||
"IDAS exchange matching completed for: "
|
||||
f"matching_product_code={product_config['product_code']}, "
|
||||
f"matching_type={matching_config['matching_type']}, "
|
||||
f"source_types={source_types}, "
|
||||
f"matching_files_folder={download_folder}, "
|
||||
f"exported_files={exported_files}"
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
import pytest
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
|
||||
from tests.regression.auth import login_to_mco_application
|
||||
from tests.regression.config_reader import load_regression_config, should_run_flow
|
||||
from tests.step_actions import StepActions
|
||||
|
||||
|
||||
@pytest.mark.regression
|
||||
@pytest.mark.exchange
|
||||
@pytest.mark.matching
|
||||
@pytest.mark.mco
|
||||
def test_idas_mco_login(driver, test_context):
|
||||
|
||||
config = load_regression_config()
|
||||
matching_config = config["exchange_matching"]
|
||||
mco_config = matching_config.get("portals", {}).get("mco", {})
|
||||
|
||||
if not should_run_flow("matching"):
|
||||
pytest.skip(
|
||||
f"Skipping MCO login because flow_scope is '{config['selected_flow']}'."
|
||||
)
|
||||
if not mco_config.get("enabled", False):
|
||||
pytest.skip("MCO matching portal is disabled in config/runtime.json.")
|
||||
|
||||
step = StepActions(driver, WebDriverWait(driver, 20), test_context)
|
||||
driver.get(config["portal_urls"]["mco"])
|
||||
step.capture("open_mco_application")
|
||||
|
||||
login_to_mco_application(
|
||||
driver,
|
||||
config["mco_login"].get("username"),
|
||||
config["mco_login"].get("password"),
|
||||
config["timing"],
|
||||
)
|
||||
step.capture("mco_dashboard_loaded")
|
||||
|
||||
print(
|
||||
"IDAS MCO login completed for "
|
||||
f"environment={config['portal_urls']['environment']}, "
|
||||
f"dashboard_url={driver.current_url}"
|
||||
)
|
||||
Reference in New Issue
Block a user