Skip to content

Commit 6c11840

Browse files
Merge pull request #191 from microsoft/PSL-TS-19050
test: GP Test automation - removed duplicate entries in the report and updated log entries.
2 parents 9350afe + dc52712 commit 6c11840

2 files changed

Lines changed: 104 additions & 57 deletions

File tree

tests/e2e-test/tests/conftest.py

Lines changed: 58 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,121 @@
1-
import os
2-
import atexit
1+
"""
2+
Pytest configuration for browser-based testing with Playwright and HTML report customization.
3+
"""
4+
35
import io
4-
from bs4 import BeautifulSoup
5-
import pytest
6+
import atexit
67
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
813
from playwright.sync_api import sync_playwright
914

15+
from config.constants import URL
16+
17+
# Global dictionary to store log streams for each test
18+
LOG_STREAMS = {}
19+
1020

1121
@pytest.fixture(scope="session")
1222
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"])
1629
context = browser.new_context(no_viewport=True)
1730
context.set_default_timeout(80000)
1831
page = context.new_page()
19-
# Navigate to the login URL
32+
2033
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
2236
# login_page = LoginPage(page)
2337
# 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"))
2539

2640
yield page
27-
# perform close the browser
41+
2842
browser.close()
2943

30-
log_streams = {}
3144

3245
@pytest.hookimpl(tryfirst=True)
3346
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+
"""
3550
stream = io.StringIO()
3651
handler = logging.StreamHandler(stream)
3752
handler.setLevel(logging.INFO)
3853

3954
logger = logging.getLogger()
4055
logger.addHandler(handler)
4156

42-
# Save handler and stream
43-
log_streams[item.nodeid] = (handler, stream)
57+
LOG_STREAMS[item.nodeid] = (handler, stream)
4458

4559

4660
@pytest.hookimpl(hookwrapper=True)
4761
def pytest_runtest_makereport(item, call):
62+
"""
63+
Pytest hook to add captured logs to the test report.
64+
"""
4865
outcome = yield
4966
report = outcome.get_result()
5067

51-
handler, stream = log_streams.get(item.nodeid, (None, None))
68+
handler, stream = LOG_STREAMS.get(item.nodeid, (None, None))
5269

5370
if handler and stream:
54-
# Make sure logs are flushed
5571
handler.flush()
5672
log_output = stream.getvalue()
5773

58-
# Only remove the handler, don't close the stream yet
5974
logger = logging.getLogger()
6075
logger.removeHandler(handler)
6176

62-
# Store the log output on the report object for HTML reporting
6377
report.description = f"<pre>{log_output.strip()}</pre>"
6478

65-
# Clean up references
66-
log_streams.pop(item.nodeid, None)
79+
LOG_STREAMS.pop(item.nodeid, None)
6780
else:
6881
report.description = ""
6982

83+
7084
def pytest_collection_modifyitems(items):
85+
"""
86+
Modify test node IDs based on the test's parameterized 'prompt' value.
87+
"""
7188
for item in items:
72-
if hasattr(item, 'callspec'):
89+
if hasattr(item, "callspec"):
7390
prompt = item.callspec.params.get("prompt")
7491
if prompt:
75-
item._nodeid = prompt # This controls how the test name appears in the report
92+
item._nodeid = prompt
93+
7694

7795
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.")
81103
return
82104

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")
85107

86-
# Find and rename the header
87-
headers = soup.select('table#results-table thead th')
108+
headers = soup.select("table#results-table thead th")
88109
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"
92112
break
93113
else:
94114
print("'Duration' column not found in report.")
95115

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+
98119

99-
# Register this function to run after everything is done
120+
# Register HTML report column modification
100121
atexit.register(rename_duration_column)
Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,49 @@
11
import logging
2+
import time
23
import pytest
34
from pages.HomePage import HomePage
4-
5+
56
logger = logging.getLogger(__name__)
6-
7+
78
# Define step-wise test actions for Golden Path
89
golden_path_steps = [
910
("Validate home page is loaded", lambda home: home.validate_home_page()),
10-
("Select Invoice Schema", lambda home: home.select_schema("Invoice")),
11+
("Select Invoice Schema", lambda home: home.select_schema("Invoice")),
1112
("Upload Invoice documents", lambda home: home.upload_files("Invoice")),
12-
("Refresh page till status is updated to Completed", lambda home: home.refresh()),
13-
("Validate extracted result for Invoice", lambda home: home.validate_invoice_extracted_result()),
14-
("Modify Extracted Data JSON & submit comments", lambda home: home.modify_and_submit_extracted_data()),
13+
("Refreshing the page until the 'Invoice' file status is updated to 'Completed'", lambda home: home.refresh()),
14+
(
15+
"Validate extracted result for Invoice",
16+
lambda home: home.validate_invoice_extracted_result(),
17+
),
18+
(
19+
"Modify Extracted Data JSON & submit comments",
20+
lambda home: home.modify_and_submit_extracted_data(),
21+
),
1522
("Validate process steps for Invoice", lambda home: home.validate_process_steps()),
16-
("Select Property Loss Damage Claim Form Schema", lambda home: home.select_schema("Property")),
17-
("Upload Property Loss Damage Claim Form documents", lambda home: home.upload_files("Property")),
18-
("Refresh page till status is updated to Completed", lambda home: home.refresh()),
19-
("Validate extracted result for Property Loss Damage Claim Form", lambda home: home.validate_property_extracted_result()),
20-
("Validate process steps for Property Loss Damage Claim Form", lambda home: home.validate_process_steps()),
21-
("Validate Delete files", lambda home: home.delete_files())
23+
(
24+
"Select Property Loss Damage Claim Form Schema",
25+
lambda home: home.select_schema("Property"),
26+
),
27+
(
28+
"Upload Property Loss Damage Claim Form documents",
29+
lambda home: home.upload_files("Property"),
30+
),
31+
("Refreshing the page until the 'Claim Form' status is updated to 'Completed'", lambda home: home.refresh()),
32+
(
33+
"Validate extracted result for Property Loss Damage Claim Form",
34+
lambda home: home.validate_property_extracted_result(),
35+
),
36+
(
37+
"Validate process steps for Property Loss Damage Claim Form",
38+
lambda home: home.validate_process_steps(),
39+
),
40+
("Validate user able to delete file", lambda home: home.delete_files()),
2241
]
23-
42+
2443
# Generate readable test step IDs
25-
golden_path_ids = [f"{i+1:02d}. {desc}" for i, (desc, _) in enumerate(golden_path_steps)]
44+
golden_path_ids = [
45+
f"{i+1:02d}. {desc}" for i, (desc, _) in enumerate(golden_path_steps)
46+
]
2647

2748

2849
@pytest.mark.parametrize("description, action", golden_path_steps, ids=golden_path_ids)
@@ -31,16 +52,21 @@ def test_content_processing_steps(login_logout, description, action, request):
3152
Executes Golden Path content processing steps with individual log entries.
3253
"""
3354
request.node._nodeid = description
34-
3555
page = login_logout
3656
home = HomePage(page)
37-
57+
3858
logger.info(f"Running test step: {description}")
59+
60+
start_time = time.time()
3961
try:
4062
action(home)
41-
except Exception as e:
42-
logger.error(f"Step failed: {description}")
63+
duration = time.time() - start_time
64+
message = "Step passed: %s (Duration: %.2f seconds)" % (description, duration)
65+
logger.info(message)
66+
request.node._report_sections.append(("call", "log", message))
67+
68+
except Exception:
69+
duration = time.time() - start_time
70+
logger.error("Step failed: %s (Duration: %.2f seconds)", description, duration, exc_info=True)
4371
raise
44-
45-
# Optionally attach to report
4672
request.node._report_sections.append(("call", "log", f"Step passed: {description}"))

0 commit comments

Comments
 (0)