|
1 | | -import os |
| 1 | +""" |
| 2 | +Pytest configuration for browser-based testing with Playwright and HTML report customization. |
| 3 | +""" |
| 4 | + |
| 5 | +import io |
| 6 | +import atexit |
| 7 | +import logging |
| 8 | +from pathlib import Path |
| 9 | +from venv import logger |
2 | 10 |
|
3 | 11 | import pytest |
4 | | -from config.constants import URL |
| 12 | +from bs4 import BeautifulSoup |
5 | 13 | from playwright.sync_api import sync_playwright |
6 | | -from py.xml import html # type: ignore |
| 14 | + |
| 15 | +from config.constants import URL |
| 16 | + |
| 17 | +# Global dictionary to store log streams for each test |
| 18 | +LOG_STREAMS = {} |
7 | 19 |
|
8 | 20 |
|
9 | 21 | @pytest.fixture(scope="session") |
10 | 22 | def login_logout(): |
11 | | - # perform login and browser close once in a session |
12 | | - with sync_playwright() as p: |
13 | | - browser = p.chromium.launch(headless=False, args=["--start-maximized"]) |
| 23 | + """ |
| 24 | + Fixture to launch the browser, log in, and yield the page object. |
| 25 | + Closes the browser after the session ends. |
| 26 | + """ |
| 27 | + with sync_playwright() as playwright: |
| 28 | + browser = playwright.chromium.launch(headless=False, args=["--start-maximized"]) |
14 | 29 | context = browser.new_context(no_viewport=True) |
15 | 30 | context.set_default_timeout(80000) |
16 | 31 | page = context.new_page() |
17 | | - # Navigate to the login URL |
| 32 | + |
18 | 33 | page.goto(URL, wait_until="domcontentloaded") |
19 | | - # login to web url with username and password |
| 34 | + |
| 35 | + # Uncomment and complete the following to enable login |
20 | 36 | # login_page = LoginPage(page) |
21 | 37 | # load_dotenv() |
22 | | - # login_page.authenticate(os.getenv('user_name'), os.getenv('pass_word')) |
| 38 | + # login_page.authenticate(os.getenv("user_name"), os.getenv("pass_word")) |
23 | 39 |
|
24 | 40 | yield page |
25 | | - # perform close the browser |
| 41 | + |
26 | 42 | browser.close() |
27 | 43 |
|
28 | 44 |
|
29 | 45 | @pytest.hookimpl(tryfirst=True) |
30 | | -def pytest_html_report_title(report): |
31 | | - report.title = "Automation_Content_Processing" |
32 | | - |
| 46 | +def pytest_runtest_setup(item): |
| 47 | + """ |
| 48 | + Pytest hook to set up a log capture for each test. |
| 49 | + """ |
| 50 | + stream = io.StringIO() |
| 51 | + handler = logging.StreamHandler(stream) |
| 52 | + handler.setLevel(logging.INFO) |
33 | 53 |
|
34 | | -# Add a column for descriptions |
35 | | -def pytest_html_results_table_header(cells): |
36 | | - cells.insert(1, html.th("Description")) |
| 54 | + logger = logging.getLogger() |
| 55 | + logger.addHandler(handler) |
37 | 56 |
|
| 57 | + LOG_STREAMS[item.nodeid] = (handler, stream) |
38 | 58 |
|
39 | | -def pytest_html_results_table_row(report, cells): |
40 | | - cells.insert( |
41 | | - 1, html.td(report.description if hasattr(report, "description") else "") |
42 | | - ) |
43 | 59 |
|
44 | | - |
45 | | -# Add logs and docstring to report |
46 | 60 | @pytest.hookimpl(hookwrapper=True) |
47 | 61 | def pytest_runtest_makereport(item, call): |
| 62 | + """ |
| 63 | + Pytest hook to add captured logs to the test report. |
| 64 | + """ |
48 | 65 | outcome = yield |
49 | 66 | report = outcome.get_result() |
50 | | - report.description = str(item.function.__doc__) |
51 | | - os.makedirs("logs", exist_ok=True) |
52 | | - extra = getattr(report, "extra", []) |
53 | | - report.extra = extra |
| 67 | + |
| 68 | + handler, stream = LOG_STREAMS.get(item.nodeid, (None, None)) |
| 69 | + |
| 70 | + if handler and stream: |
| 71 | + handler.flush() |
| 72 | + log_output = stream.getvalue() |
| 73 | + |
| 74 | + logger = logging.getLogger() |
| 75 | + logger.removeHandler(handler) |
| 76 | + |
| 77 | + report.description = f"<pre>{log_output.strip()}</pre>" |
| 78 | + |
| 79 | + LOG_STREAMS.pop(item.nodeid, None) |
| 80 | + else: |
| 81 | + report.description = "" |
| 82 | + |
| 83 | + |
| 84 | +def pytest_collection_modifyitems(items): |
| 85 | + """ |
| 86 | + Modify test node IDs based on the test's parameterized 'prompt' value. |
| 87 | + """ |
| 88 | + for item in items: |
| 89 | + if hasattr(item, "callspec"): |
| 90 | + prompt = item.callspec.params.get("prompt") |
| 91 | + if prompt: |
| 92 | + item._nodeid = prompt |
| 93 | + |
| 94 | + |
| 95 | +def rename_duration_column(): |
| 96 | + """ |
| 97 | + Modify the HTML report to rename 'Duration' column to 'Execution Time'. |
| 98 | + Runs automatically after the test session. |
| 99 | + """ |
| 100 | + report_path = Path("report.html") |
| 101 | + if not report_path.exists(): |
| 102 | + logger.info("Report file not found, skipping column rename.") |
| 103 | + return |
| 104 | + |
| 105 | + with report_path.open("r", encoding="utf-8") as file: |
| 106 | + soup = BeautifulSoup(file, "html.parser") |
| 107 | + |
| 108 | + headers = soup.select("table#results-table thead th") |
| 109 | + for th in headers: |
| 110 | + if th.text.strip() == "Duration": |
| 111 | + th.string = "Execution Time" |
| 112 | + break |
| 113 | + else: |
| 114 | + print("'Duration' column not found in report.") |
| 115 | + |
| 116 | + with report_path.open("w", encoding="utf-8") as file: |
| 117 | + file.write(str(soup)) |
| 118 | + |
| 119 | + |
| 120 | +# Register HTML report column modification |
| 121 | +atexit.register(rename_duration_column) |
0 commit comments