Initial commit - Selenium automation framework
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
import pytest
|
||||
import os
|
||||
import json
|
||||
|
||||
from tests.browser import create_chrome_driver
|
||||
|
||||
|
||||
def get_run_id(config):
|
||||
|
||||
run_id = getattr(config, "_pxil_run_id", None)
|
||||
if run_id:
|
||||
return run_id
|
||||
|
||||
base_folder = os.path.join("reports", "screenshots")
|
||||
os.makedirs(base_folder, exist_ok=True)
|
||||
run_prefix = _get_run_prefix(config)
|
||||
|
||||
existing = [
|
||||
name for name in os.listdir(base_folder)
|
||||
if name.startswith(f"{run_prefix}_")
|
||||
]
|
||||
|
||||
if not existing:
|
||||
next_number = 1
|
||||
else:
|
||||
numbers = [
|
||||
int(name.split("_")[-1])
|
||||
for name in existing
|
||||
if name.split("_")[-1].isdigit()
|
||||
]
|
||||
next_number = max(numbers) + 1
|
||||
|
||||
run_id = f"{run_prefix}_{next_number:03d}"
|
||||
config._pxil_run_id = run_id
|
||||
return run_id
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
|
||||
run_id = get_run_id(config)
|
||||
|
||||
if config.pluginmanager.hasplugin("html"):
|
||||
reports_dir = os.path.abspath(os.path.join("reports", "html"))
|
||||
os.makedirs(reports_dir, exist_ok=True)
|
||||
config.option.htmlpath = os.path.join(reports_dir, f"{run_id}.html")
|
||||
config.option.self_contained_html = True
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# WebDriver Fixture
|
||||
# -----------------------------
|
||||
@pytest.fixture
|
||||
def driver():
|
||||
|
||||
driver = create_chrome_driver()
|
||||
driver.maximize_window()
|
||||
|
||||
yield driver
|
||||
|
||||
driver.quit()
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Generate Incremental Test ID
|
||||
# -----------------------------
|
||||
@pytest.fixture
|
||||
def test_context(request):
|
||||
|
||||
test_id = get_run_id(request.config)
|
||||
test_name = request.node.name
|
||||
|
||||
folder = os.path.abspath(
|
||||
os.path.join("reports", "screenshots", test_id, test_name)
|
||||
)
|
||||
log_folder = os.path.abspath(
|
||||
os.path.join("reports", "logs", test_id, test_name)
|
||||
)
|
||||
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
os.makedirs(log_folder, exist_ok=True)
|
||||
|
||||
print(f"\nTEST ID: {test_id}")
|
||||
|
||||
return {
|
||||
"test_id": test_id,
|
||||
"folder": folder,
|
||||
"log_folder": log_folder,
|
||||
"steps": [],
|
||||
"extra_folders": [],
|
||||
}
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Attach Screenshot Folder Link
|
||||
# -----------------------------
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_makereport(item, call):
|
||||
|
||||
outcome = yield
|
||||
report = outcome.get_result()
|
||||
|
||||
if report.when == "call":
|
||||
|
||||
context = item.funcargs.get("test_context", None)
|
||||
|
||||
if context:
|
||||
|
||||
folder = context["folder"]
|
||||
steps = context.get("steps", [])
|
||||
|
||||
if report.failed:
|
||||
driver = item.funcargs.get("driver", None)
|
||||
failure_index = len(steps) + 1
|
||||
failure_name = "failure_state"
|
||||
failure_filename = f"{failure_index:02d}_{failure_name}.png"
|
||||
failure_path = os.path.join(folder, failure_filename)
|
||||
|
||||
if driver:
|
||||
try:
|
||||
driver.save_screenshot(failure_path)
|
||||
steps.append(
|
||||
{
|
||||
"index": failure_index,
|
||||
"name": failure_name,
|
||||
"path": failure_path,
|
||||
"status": "failed",
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if item.config.pluginmanager.hasplugin("html"):
|
||||
|
||||
from pytest_html import extras
|
||||
|
||||
extras_list = getattr(report, "extras", [])
|
||||
|
||||
for step in steps:
|
||||
extras_list.append(
|
||||
extras.url(
|
||||
_as_file_url(step["path"]),
|
||||
name=f"Step {step['index']:02d} [{step['status']}] {step['name']}"
|
||||
)
|
||||
)
|
||||
|
||||
if steps:
|
||||
extras_list.append(
|
||||
extras.html(_build_steps_html(steps))
|
||||
)
|
||||
|
||||
extras_list.append(
|
||||
extras.url(_as_file_url(folder), name="Screenshots Folder")
|
||||
)
|
||||
|
||||
log_folder = context.get("log_folder")
|
||||
if log_folder:
|
||||
extras_list.append(
|
||||
extras.url(_as_file_url(log_folder), name="Logs Folder")
|
||||
)
|
||||
|
||||
for extra_folder in context.get("extra_folders", []):
|
||||
extras_list.append(
|
||||
extras.url(
|
||||
_as_file_url(extra_folder["path"]),
|
||||
name=extra_folder["name"]
|
||||
)
|
||||
)
|
||||
|
||||
report.extras = extras_list
|
||||
|
||||
|
||||
def _as_file_url(path):
|
||||
|
||||
absolute_path = os.path.abspath(path).replace("\\", "/")
|
||||
return f"file:///{absolute_path}"
|
||||
|
||||
|
||||
def _build_steps_html(steps):
|
||||
|
||||
rows = []
|
||||
for step in steps:
|
||||
step_url = _as_file_url(step["path"])
|
||||
rows.append(
|
||||
"<tr>"
|
||||
f"<td>{step['index']:02d}</td>"
|
||||
f"<td>{step['status'].capitalize()}</td>"
|
||||
f"<td>{step['name']}</td>"
|
||||
f"<td><a href=\"{step_url}\">Open Screenshot</a></td>"
|
||||
"</tr>"
|
||||
)
|
||||
|
||||
return (
|
||||
"<div>"
|
||||
"<h4>Step Details</h4>"
|
||||
"<table border='1' cellpadding='4' cellspacing='0' "
|
||||
"style='border-collapse: collapse; margin-top: 8px;'>"
|
||||
"<thead><tr><th>#</th><th>Status</th><th>Step</th><th>Screenshot</th></tr></thead>"
|
||||
f"<tbody>{''.join(rows)}</tbody>"
|
||||
"</table>"
|
||||
"</div>"
|
||||
)
|
||||
|
||||
|
||||
def _get_run_prefix(config):
|
||||
|
||||
invocation_args = " ".join(str(arg) for arg in config.invocation_params.args).lower()
|
||||
|
||||
if "tests/setup/sessions" in invocation_args or "test_session.py" in invocation_args:
|
||||
return "PXIL_SESSION"
|
||||
|
||||
if "tests/setup/tokens" in invocation_args or "test_trading_token.py" in invocation_args:
|
||||
return "PXIL_TOKEN"
|
||||
|
||||
runtime_path = os.path.abspath(os.path.join("config", "runtime.json"))
|
||||
if os.path.exists(runtime_path):
|
||||
try:
|
||||
with open(runtime_path, "r", encoding="utf-8") as runtime_file:
|
||||
runtime_config = json.load(runtime_file)
|
||||
|
||||
execution = runtime_config.get("execution", {})
|
||||
product_family = execution.get("product_family", "regression").upper()
|
||||
flow_scope = execution.get("flow_scope", "run").upper()
|
||||
return f"{product_family}_{flow_scope}"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if "matching" in invocation_args:
|
||||
return "REGRESSION_MATCHING"
|
||||
|
||||
if "bidding" in invocation_args:
|
||||
return "REGRESSION_BIDDING"
|
||||
|
||||
if "reports" in invocation_args:
|
||||
return "REGRESSION_REPORTS"
|
||||
|
||||
if "order_book" in invocation_args:
|
||||
return "REGRESSION_ORDER_BOOK"
|
||||
|
||||
if "trade_book" in invocation_args:
|
||||
return "REGRESSION_TRADE_BOOK"
|
||||
|
||||
return "PXIL_RUN"
|
||||
Reference in New Issue
Block a user