|
1 | | -import os |
2 | | -import atexit |
| 1 | +""" |
| 2 | +Pytest configuration for browser-based testing with Playwright and HTML report customization. |
| 3 | +""" |
| 4 | + |
3 | 5 | import io |
4 | | -from bs4 import BeautifulSoup |
5 | | -import pytest |
| 6 | +import atexit |
6 | 7 | import logging |
7 | | -from config.constants import URL |
| 8 | +from pathlib import Path |
| 9 | +from venv import logger |
| 10 | + |
| 11 | +import pytest |
| 12 | +from bs4 import BeautifulSoup |
8 | 13 | from playwright.sync_api import sync_playwright |
9 | 14 |
|
| 15 | +from config.constants import URL |
| 16 | + |
| 17 | +# Global dictionary to store log streams for each test |
| 18 | +LOG_STREAMS = {} |
| 19 | + |
10 | 20 |
|
11 | 21 | @pytest.fixture(scope="session") |
12 | 22 | def login_logout(): |
13 | | - # perform login and browser close once in a session |
14 | | - with sync_playwright() as p: |
15 | | - 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"]) |
16 | 29 | context = browser.new_context(no_viewport=True) |
17 | 30 | context.set_default_timeout(80000) |
18 | 31 | page = context.new_page() |
19 | | - # Navigate to the login URL |
| 32 | + |
20 | 33 | page.goto(URL, wait_until="domcontentloaded") |
21 | | - # login to web url with username and password |
| 34 | + |
| 35 | + # Uncomment and complete the following to enable login |
22 | 36 | # login_page = LoginPage(page) |
23 | 37 | # load_dotenv() |
24 | | - # login_page.authenticate(os.getenv('user_name'), os.getenv('pass_word')) |
| 38 | + # login_page.authenticate(os.getenv("user_name"), os.getenv("pass_word")) |
25 | 39 |
|
26 | 40 | yield page |
27 | | - # perform close the browser |
| 41 | + |
28 | 42 | browser.close() |
29 | 43 |
|
30 | | -log_streams = {} |
31 | 44 |
|
32 | 45 | @pytest.hookimpl(tryfirst=True) |
33 | 46 | def pytest_runtest_setup(item): |
34 | | - # Prepare StringIO for capturing logs |
| 47 | + """ |
| 48 | + Pytest hook to set up a log capture for each test. |
| 49 | + """ |
35 | 50 | stream = io.StringIO() |
36 | 51 | handler = logging.StreamHandler(stream) |
37 | 52 | handler.setLevel(logging.INFO) |
38 | 53 |
|
39 | 54 | logger = logging.getLogger() |
40 | 55 | logger.addHandler(handler) |
41 | 56 |
|
42 | | - # Save handler and stream |
43 | | - log_streams[item.nodeid] = (handler, stream) |
| 57 | + LOG_STREAMS[item.nodeid] = (handler, stream) |
44 | 58 |
|
45 | 59 |
|
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 | 67 |
|
51 | | - handler, stream = log_streams.get(item.nodeid, (None, None)) |
| 68 | + handler, stream = LOG_STREAMS.get(item.nodeid, (None, None)) |
52 | 69 |
|
53 | 70 | if handler and stream: |
54 | | - # Make sure logs are flushed |
55 | 71 | handler.flush() |
56 | 72 | log_output = stream.getvalue() |
57 | 73 |
|
58 | | - # Only remove the handler, don't close the stream yet |
59 | 74 | logger = logging.getLogger() |
60 | 75 | logger.removeHandler(handler) |
61 | 76 |
|
62 | | - # Store the log output on the report object for HTML reporting |
63 | 77 | report.description = f"<pre>{log_output.strip()}</pre>" |
64 | 78 |
|
65 | | - # Clean up references |
66 | | - log_streams.pop(item.nodeid, None) |
| 79 | + LOG_STREAMS.pop(item.nodeid, None) |
67 | 80 | else: |
68 | 81 | report.description = "" |
69 | 82 |
|
| 83 | + |
70 | 84 | def pytest_collection_modifyitems(items): |
| 85 | + """ |
| 86 | + Modify test node IDs based on the test's parameterized 'prompt' value. |
| 87 | + """ |
71 | 88 | for item in items: |
72 | | - if hasattr(item, 'callspec'): |
| 89 | + if hasattr(item, "callspec"): |
73 | 90 | prompt = item.callspec.params.get("prompt") |
74 | 91 | if prompt: |
75 | | - item._nodeid = prompt # This controls how the test name appears in the report |
| 92 | + item._nodeid = prompt |
| 93 | + |
76 | 94 |
|
77 | 95 | def rename_duration_column(): |
78 | | - report_path = os.path.abspath("report.html") # or your report filename |
79 | | - if not os.path.exists(report_path): |
80 | | - print("Report file not found, skipping column rename.") |
| 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.") |
81 | 103 | return |
82 | 104 |
|
83 | | - with open(report_path, 'r', encoding='utf-8') as f: |
84 | | - soup = BeautifulSoup(f, 'html.parser') |
| 105 | + with report_path.open("r", encoding="utf-8") as file: |
| 106 | + soup = BeautifulSoup(file, "html.parser") |
85 | 107 |
|
86 | | - # Find and rename the header |
87 | | - headers = soup.select('table#results-table thead th') |
| 108 | + headers = soup.select("table#results-table thead th") |
88 | 109 | for th in headers: |
89 | | - if th.text.strip() == 'Duration': |
90 | | - th.string = 'Execution Time' |
91 | | - #print("Renamed 'Duration' to 'Execution Time'") |
| 110 | + if th.text.strip() == "Duration": |
| 111 | + th.string = "Execution Time" |
92 | 112 | break |
93 | 113 | else: |
94 | 114 | print("'Duration' column not found in report.") |
95 | 115 |
|
96 | | - with open(report_path, 'w', encoding='utf-8') as f: |
97 | | - f.write(str(soup)) |
| 116 | + with report_path.open("w", encoding="utf-8") as file: |
| 117 | + file.write(str(soup)) |
| 118 | + |
98 | 119 |
|
99 | | -# Register this function to run after everything is done |
| 120 | +# Register HTML report column modification |
100 | 121 | atexit.register(rename_duration_column) |
0 commit comments