266 lines
7.7 KiB
Python
266 lines
7.7 KiB
Python
import pytest
|
|
import os
|
|
import json
|
|
|
|
from tests.browser import create_chrome_driver
|
|
|
|
|
|
def get_run_id(config, prefix=None):
|
|
|
|
run_prefix = prefix or _get_run_prefix(config)
|
|
run_ids = getattr(config, "_pxil_run_ids", {})
|
|
if run_prefix in run_ids:
|
|
return run_ids[run_prefix]
|
|
|
|
base_folder = os.path.join("reports", "screenshots")
|
|
os.makedirs(base_folder, exist_ok=True)
|
|
|
|
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, default=0) + 1
|
|
|
|
run_id = f"{run_prefix}_{next_number:03d}"
|
|
run_ids[run_prefix] = run_id
|
|
config._pxil_run_ids = run_ids
|
|
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(scope="module")
|
|
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, _setup_item_prefix(request.node))
|
|
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 _setup_item_prefix(item):
|
|
"""Use the collected product and flow, including when -k selects a subset."""
|
|
nodeid = item.nodeid.replace("\\", "/")
|
|
if "tests/setup/" not in nodeid:
|
|
return None
|
|
callspec = getattr(item, "callspec", None)
|
|
product_config = callspec.params.get("config") if callspec else None
|
|
if not isinstance(product_config, dict):
|
|
return None
|
|
product = product_config["product_name"].upper()
|
|
if "test_combined_setup.py" in nodeid:
|
|
return f"{product}_SESSION_TOKEN"
|
|
if "/sessions/" in nodeid:
|
|
return f"{product}_SESSION"
|
|
if "/tokens/" in nodeid:
|
|
return f"{product}_TOKEN"
|
|
return f"{product}_SETUP"
|
|
|
|
|
|
def _get_run_prefix(config):
|
|
|
|
invocation_args = " ".join(str(arg) for arg in config.invocation_params.args).replace("\\", "/").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"
|
|
|
|
if "tests/setup" in invocation_args or "test_combined_setup.py" in invocation_args:
|
|
return "PXIL_SETUP"
|
|
|
|
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"
|