66from bs4 import BeautifulSoup
77from config .constants import URL
88from playwright .sync_api import sync_playwright
9+ from datetime import datetime
10+ from pytest_html import extras
11+
12+ # Create screenshots directory if it doesn't exist
13+ SCREENSHOTS_DIR = os .path .join (os .path .dirname (__file__ ), ".." , "screenshots" )
14+ os .makedirs (SCREENSHOTS_DIR , exist_ok = True )
915
1016
1117@pytest .fixture (scope = "session" )
@@ -35,28 +41,44 @@ def pytest_html_report_title(report):
3541 report .title = "Test Automation DocGen"
3642
3743
38- log_streams = {}
39-
40-
41- @pytest .hookimpl (tryfirst = True )
42- def pytest_runtest_setup (item ):
43- # Prepare StringIO for capturing logs
44- stream = io .StringIO ()
45- handler = logging .StreamHandler (stream )
46- handler .setLevel (logging .INFO )
47-
48- logger = logging .getLogger ()
49- logger .addHandler (handler )
50-
51- # Save handler and stream
52- log_streams [item .nodeid ] = (handler , stream )
53-
54-
5544@pytest .hookimpl (hookwrapper = True )
5645def pytest_runtest_makereport (item , call ):
46+ """Generate test report with logs, subtest details, and screenshots on failure"""
5747 outcome = yield
5848 report = outcome .get_result ()
5949
50+ # Capture screenshot on failure
51+ if report .when == "call" and report .failed :
52+ # Get the page fixture if it exists
53+ if "login_logout" in item .fixturenames :
54+ page = item .funcargs .get ("login_logout" )
55+ if page :
56+ try :
57+ # Generate screenshot filename with timestamp
58+ timestamp = datetime .now ().strftime ("%Y%m%d_%H%M%S" )
59+ test_name = item .name .replace (" " , "_" ).replace ("/" , "_" )
60+ screenshot_name = f"screenshot_{ test_name } _{ timestamp } .png"
61+ screenshot_path = os .path .join (SCREENSHOTS_DIR , screenshot_name )
62+
63+ # Take screenshot
64+ page .screenshot (path = screenshot_path )
65+
66+ # Add screenshot link to report
67+ if not hasattr (report , 'extra' ):
68+ report .extra = []
69+
70+ # Add screenshot as a file URL that will work when report is opened in browser
71+ # Convert to absolute path and then to file URL
72+ absolute_path = os .path .abspath (screenshot_path )
73+ file_url = f"file:///{ absolute_path .replace (os .sep , '/' )} "
74+
75+ # pytest-html expects this format for extras
76+ report .extra .append (extras .url (file_url , name = 'Screenshot' ))
77+
78+ logging .info ("Screenshot saved: %s" , screenshot_path )
79+ except Exception as exc : # pylint: disable=broad-exception-caught
80+ logging .error ("Failed to capture screenshot: %s" , str (exc ))
81+
6082 handler , stream = log_streams .get (item .nodeid , (None , None ))
6183
6284 if handler and stream :
@@ -68,15 +90,68 @@ def pytest_runtest_makereport(item, call):
6890 logger = logging .getLogger ()
6991 logger .removeHandler (handler )
7092
71- # Store the log output on the report object for HTML reporting
72- report .description = f"<pre>{ log_output .strip ()} </pre>"
93+ # Check if there are subtests
94+ subtests_html = ""
95+ if hasattr (item , 'user_properties' ):
96+ item_subtests = [
97+ prop [1 ] for prop in item .user_properties if prop [0 ] == "subtest"
98+ ]
99+ if item_subtests :
100+ subtests_html = (
101+ "<div style='margin-top: 10px;'>"
102+ "<strong>Step-by-Step Details:</strong>"
103+ "<ul style='list-style: none; padding-left: 0;'>"
104+ )
105+ for idx , subtest in enumerate (item_subtests , 1 ):
106+ status = "✅ PASSED" if subtest .get ('passed' ) else "❌ FAILED"
107+ status_color = "green" if subtest .get ('passed' ) else "red"
108+ subtests_html += (
109+ f"<li style='margin: 10px 0; padding: 10px; "
110+ f"border-left: 3px solid { status_color } ; "
111+ f"background-color: #f9f9f9;'>"
112+ )
113+ subtests_html += (
114+ f"<div style='font-weight: bold; color: { status_color } ;'>"
115+ f"{ status } - { subtest .get ('msg' , f'Step { idx } ' )} </div>"
116+ )
117+ if subtest .get ('logs' ):
118+ subtests_html += (
119+ f"<pre style='margin: 5px 0; padding: 5px; "
120+ f"background-color: #fff; border: 1px solid #ddd; "
121+ f"font-size: 11px;'>{ subtest .get ('logs' ).strip ()} </pre>"
122+ )
123+ subtests_html += "</li>"
124+ subtests_html += "</ul></div>"
125+
126+ # Combine main log output with subtests
127+ if subtests_html :
128+ report .description = f"<pre>{ log_output .strip ()} </pre>{ subtests_html } "
129+ else :
130+ report .description = f"<pre>{ log_output .strip ()} </pre>"
73131
74132 # Clean up references
75133 log_streams .pop (item .nodeid , None )
76134 else :
77135 report .description = ""
78136
79137
138+ log_streams = {}
139+
140+
141+ @pytest .hookimpl (tryfirst = True )
142+ def pytest_runtest_setup (item ):
143+ # Prepare StringIO for capturing logs
144+ stream = io .StringIO ()
145+ handler = logging .StreamHandler (stream )
146+ handler .setLevel (logging .INFO )
147+
148+ logger = logging .getLogger ()
149+ logger .addHandler (handler )
150+
151+ # Save handler and stream
152+ log_streams [item .nodeid ] = (handler , stream )
153+
154+
80155def pytest_collection_modifyitems (items ):
81156 for item in items :
82157 if hasattr (item , "callspec" ):
0 commit comments