Skip to content

Commit 5d540a2

Browse files
test: Doc Gen-Create individual test case for each prompt and capture…
2 parents 5fe69e8 + 4bb9f5f commit 5d540a2

File tree

10 files changed

+423
-147
lines changed

10 files changed

+423
-147
lines changed

tests/e2e-test/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ This will create a virtual environment directory named microsoft inside your cur
2020
Installing Playwright Pytest from Virtual Environment
2121

2222
- To install libraries run "pip install -r requirements.txt"
23-
- Install the required browsers "playwright install"
23+
2424

2525
Run test cases
2626

27-
- To run test cases from your 'tests' folder : "pytest --html=report.html --self-contained-html"
27+
- To run test cases from your 'tests/e2e-test' folder : "pytest --html=report.html --self-contained-html"
2828

2929
Create .env file in project root level with web app url and client credentials
3030

tests/e2e-test/base/base.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,4 @@ def validate_response_status(self, question_api=""):
4545
"response code is " + str(response.status) + " " + str(response.json())
4646
)
4747

48-
def validate_response_status_draft_section(self, sectionTitle):
49-
load_dotenv() # Ensure environment variables are loaded
50-
# URL of the API endpoint
51-
url = f"{URL}/section/generate"
52-
53-
# Prepare headers
54-
headers = {
55-
"Content-Type": "application/json",
56-
"Accept": "*/*",
57-
}
58-
59-
payload = {"sectionTitle": sectionTitle}
60-
61-
# Make the POST request
62-
response = self.page.request.post(
63-
url, headers=headers, data=json.dumps(payload), timeout=200000
64-
)
65-
assert response.status == 200, (
66-
"response code is " + str(response.status) + " " + str(response.json())
67-
)
48+

tests/e2e-test/pages/draftPage.py

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from base.base import BasePage
22
from pytest_check import check
3+
import time
4+
from collections import defaultdict
35

46

57
class DraftPage(BasePage):
@@ -15,25 +17,74 @@ class DraftPage(BasePage):
1517
def __init__(self, page):
1618
self.page = page
1719

18-
def check_draft_Sections(self):
19-
self.page.wait_for_timeout(10000)
20-
if self.page.locator(self.Draft_Sections).count() >= 1:
21-
for i in range(self.page.locator(self.Draft_Sections).count()):
22-
draft_sections_response = self.page.locator(self.Draft_Sections).nth(i)
23-
draft_heading = (
24-
self.page.locator(self.Draft_headings).nth(i).text_content()
25-
)
26-
check.not_equal(
27-
self.invalid_response,
28-
draft_sections_response.text_content(),
29-
f"Invalid response for {draft_heading} section",
30-
)
31-
check.not_equal(
32-
self.invalid_response1,
33-
draft_sections_response.text_content(),
34-
f"Invalid response for {draft_heading} section",
35-
)
36-
check.is_not_none(
37-
draft_sections_response.text_content(),
38-
f"Invalid response for {draft_heading} section",
39-
)
20+
def check_draft_sections(self, timeout: float = 180.0, poll_interval: float = 0.5):
21+
"""
22+
Waits for all <textarea> draft sections to load valid content using .input_value().
23+
Scrolls into view if needed, retries until timeout.
24+
Raises clear errors if validation fails.
25+
"""
26+
from collections import defaultdict
27+
import time
28+
start_time = time.time()
29+
30+
31+
while time.time() - start_time < timeout:
32+
section_elements = self.page.locator(self.Draft_Sections)
33+
heading_elements = self.page.locator(self.Draft_headings)
34+
35+
section_count = section_elements.count()
36+
heading_count = heading_elements.count()
37+
38+
if section_count < 13 or heading_count < 13:
39+
print("[WAIT] Waiting for all sections to appear...")
40+
time.sleep(poll_interval)
41+
continue
42+
43+
failed_sections = defaultdict(str)
44+
45+
for i in range(section_count):
46+
section = section_elements.nth(i)
47+
48+
try:
49+
# Scroll into view and wait a bit for rendering
50+
section.scroll_into_view_if_needed(timeout=2000)
51+
self.page.wait_for_timeout(200)
52+
53+
# Extract content from <textarea>
54+
section_text = section.input_value().strip()
55+
56+
if not section_text:
57+
failed_sections[i] = "Empty"
58+
elif section_text in (self.invalid_response, self.invalid_response1):
59+
failed_sections[i] = f"Invalid: {repr(section_text[:30])}"
60+
61+
except Exception as e:
62+
failed_sections[i] = f"Exception: {str(e)}"
63+
64+
if not failed_sections:
65+
break # ✅ All good
66+
else:
67+
print(f"[WAITING] Sections not ready yet: {failed_sections}")
68+
time.sleep(poll_interval)
69+
70+
else:
71+
raise TimeoutError(f"❌ Timeout: These sections did not load valid content: {failed_sections}")
72+
73+
# ✅ Final validations after loading
74+
for i in range(section_count):
75+
section = section_elements.nth(i)
76+
heading = heading_elements.nth(i)
77+
78+
section.scroll_into_view_if_needed(timeout=2000)
79+
self.page.wait_for_timeout(200)
80+
81+
heading_text = heading.inner_text(timeout=3000).strip()
82+
content = section.input_value().strip()
83+
84+
print(f"[VALIDATING] Section {i}: '{heading_text}' → {repr(content[:60])}...")
85+
86+
with check:
87+
check.is_not_none(content, f"❌ Section '{heading_text}' is None")
88+
check.not_equal(content, "", f"❌ Section '{heading_text}' is empty")
89+
check.not_equal(content, self.invalid_response, f"❌ '{heading_text}' has invalid response")
90+
check.not_equal(content, self.invalid_response1, f"❌ '{heading_text}' has invalid response")

tests/e2e-test/pages/generatePage.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
11
from base.base import BasePage
2-
2+
from playwright.sync_api import expect
3+
from asyncio.log import logger
34

45
class GeneratePage(BasePage):
56
GENERATE_DRAFT = "//button[@title='Generate Draft']"
67
TYPE_QUESTION = "//textarea[@placeholder='Type a new question...']"
78
SEND_BUTTON = "//div[@aria-label='Ask question button']"
9+
SHOW_CHAT_HISTORY_BUTTON="//span[text()='Show template history']"
10+
HIDE_CHAT_HISTORY_BUTTON = "//span[text()='Hide Chat History']"
11+
CHAT_HISTORY_ITEM = "//body//div[@id='root']//div[@role='presentation']//div[@role='presentation']//div[1]//div[1]//div[1]//div[1]//div[1]//div[1]"
12+
SHOW_CHAT_HISTORY = "//span//i"
13+
CHAT_HISTORY_NAME = "div[aria-label='chat history list']"
14+
CHAT_CLOSE_ICON = "button[title='Hide']"
15+
CHAT_HISTORY_OPTIONS = "//button[@id='moreButton']"
16+
CHAT_HISTORY_DELETE = "//button[@role='menuitem']"
17+
CHAT_HISTORY_CLOSE = "//i[@data-icon-name='Cancel']"
818

919
def __init__(self, page):
1020
self.page = page
1121

1222
def enter_a_question(self, text):
1323
# Type a question in the text area
24+
self.page.wait_for_timeout(3000)
1425
self.page.locator(self.TYPE_QUESTION).fill(text)
15-
self.page.wait_for_timeout(2000)
26+
self.page.wait_for_timeout(3000)
1627

1728
def click_send_button(self):
1829
# Type a question in the text area
@@ -23,3 +34,41 @@ def click_generate_draft_button(self):
2334
# Type a question in the text area
2435
self.page.locator(self.GENERATE_DRAFT).click()
2536
self.page.wait_for_timeout(15000)
37+
38+
def show_chat_history(self):
39+
"""Click to show chat history if the button is visible."""
40+
show_button = self.page.locator(self.SHOW_CHAT_HISTORY_BUTTON)
41+
if show_button.is_visible():
42+
show_button.click()
43+
self.page.wait_for_timeout(2000)
44+
expect(self.page.locator(self.CHAT_HISTORY_ITEM)).to_be_visible()
45+
else:
46+
logger.info("Chat history is not generated")
47+
48+
def close_chat_history(self):
49+
"""Click to close chat history if visible."""
50+
hide_button = self.page.locator(self.HIDE_CHAT_HISTORY_BUTTON)
51+
if hide_button.is_visible():
52+
hide_button.click()
53+
self.page.wait_for_timeout(2000)
54+
else:
55+
logger.info("Hide button not visible. Chat history might already be closed.")
56+
57+
def delete_chat_history(self):
58+
59+
self.page.locator(self.SHOW_CHAT_HISTORY_BUTTON).click()
60+
chat_history = self.page.locator("//span[contains(text(),'No chat history.')]")
61+
if chat_history.is_visible():
62+
self.page.wait_for_load_state("networkidle")
63+
self.page.locator("button[title='Hide']").wait_for(state="visible", timeout=5000)
64+
self.page.locator("button[title='Hide']").click()
65+
66+
else:
67+
self.page.locator(self.CHAT_HISTORY_OPTIONS).click()
68+
self.page.locator(self.CHAT_HISTORY_DELETE).click()
69+
self.page.get_by_role("button", name="Clear All").click()
70+
self.page.wait_for_timeout(5000)
71+
self.page.locator(self.CHAT_HISTORY_CLOSE).click()
72+
self.page.wait_for_load_state("networkidle")
73+
self.page.wait_for_timeout(2000)
74+

tests/e2e-test/pages/homePage.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def click_browse_button(self):
2424
self.page.wait_for_timeout(5000)
2525

2626
def validate_home_page(self):
27+
self.page.wait_for_timeout(5000)
2728
expect(self.page.locator(self.HOME_TITLE)).to_be_visible()
2829
expect(self.page.locator(self.BROWSE_TEXT)).to_be_visible()
2930
expect(self.page.locator(self.GENERATE_TEXT)).to_be_visible()

tests/e2e-test/requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ pytest-reporter-html1
33
python-dotenv
44
pytest-check
55
pytest-html
6-
py
6+
py
7+
beautifulsoup4

tests/e2e-test/testData/section_title.json

Lines changed: 0 additions & 21 deletions
This file was deleted.

tests/e2e-test/tests/conftest.py

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
import os
2-
1+
from pathlib import Path
32
import pytest
4-
from config.constants import URL
53
from playwright.sync_api import sync_playwright
6-
from py.xml import html # type: ignore
4+
from config.constants import *
5+
from slugify import slugify
6+
from dotenv import load_dotenv
7+
import os
8+
from py.xml import html # type: ignore
9+
import io
10+
import logging
11+
from bs4 import BeautifulSoup
12+
import atexit
713

814

915
@pytest.fixture(scope="session")
@@ -13,11 +19,16 @@ def login_logout():
1319
browser = p.chromium.launch(headless=False, args=["--start-maximized"])
1420
context = browser.new_context(no_viewport=True)
1521
context.set_default_timeout(120000)
22+
context.clear_cookies()
1623
page = context.new_page()
1724
# Navigate to the login URL
1825
page.goto(URL)
1926
# Wait for the login form to appear
2027
# page.wait_for_load_state('networkidle')
28+
# login to web url with username and password
29+
#login_page = LoginPage(page)
30+
#load_dotenv()
31+
#login_page.authenticate(os.getenv('user_name'),os.getenv('pass_word'))
2132
yield page
2233

2334
# perform close the browser
@@ -26,26 +37,77 @@ def login_logout():
2637

2738
@pytest.hookimpl(tryfirst=True)
2839
def pytest_html_report_title(report):
29-
report.title = "Automation_DOCGEN"
40+
report.title = "Test Automation DocGen"
41+
3042

43+
log_streams = {}
3144

32-
# Add a column for descriptions
33-
def pytest_html_results_table_header(cells):
34-
cells.insert(1, html.th("Description"))
45+
@pytest.hookimpl(tryfirst=True)
46+
def pytest_runtest_setup(item):
47+
# Prepare StringIO for capturing logs
48+
stream = io.StringIO()
49+
handler = logging.StreamHandler(stream)
50+
handler.setLevel(logging.INFO)
3551

52+
logger = logging.getLogger()
53+
logger.addHandler(handler)
3654

37-
def pytest_html_results_table_row(report, cells):
38-
cells.insert(
39-
1, html.td(report.description if hasattr(report, "description") else "")
40-
)
55+
# Save handler and stream
56+
log_streams[item.nodeid] = (handler, stream)
4157

4258

43-
# Add logs and docstring to report
4459
@pytest.hookimpl(hookwrapper=True)
4560
def pytest_runtest_makereport(item, call):
4661
outcome = yield
4762
report = outcome.get_result()
48-
report.description = str(item.function.__doc__)
49-
os.makedirs("logs", exist_ok=True)
50-
extra = getattr(report, "extra", [])
51-
report.extra = extra
63+
64+
handler, stream = log_streams.get(item.nodeid, (None, None))
65+
66+
if handler and stream:
67+
# Make sure logs are flushed
68+
handler.flush()
69+
log_output = stream.getvalue()
70+
71+
# Only remove the handler, don't close the stream yet
72+
logger = logging.getLogger()
73+
logger.removeHandler(handler)
74+
75+
# Store the log output on the report object for HTML reporting
76+
report.description = f"<pre>{log_output.strip()}</pre>"
77+
78+
# Clean up references
79+
log_streams.pop(item.nodeid, None)
80+
else:
81+
report.description = ""
82+
83+
def pytest_collection_modifyitems(items):
84+
for item in items:
85+
if hasattr(item, 'callspec'):
86+
prompt = item.callspec.params.get("prompt")
87+
if prompt:
88+
item._nodeid = prompt # This controls how the test name appears in the report
89+
90+
def rename_duration_column():
91+
report_path = os.path.abspath("report.html") # or your report filename
92+
if not os.path.exists(report_path):
93+
print("Report file not found, skipping column rename.")
94+
return
95+
96+
with open(report_path, 'r', encoding='utf-8') as f:
97+
soup = BeautifulSoup(f, 'html.parser')
98+
99+
# Find and rename the header
100+
headers = soup.select('table#results-table thead th')
101+
for th in headers:
102+
if th.text.strip() == 'Duration':
103+
th.string = 'Execution Time'
104+
#print("Renamed 'Duration' to 'Execution Time'")
105+
break
106+
else:
107+
print("'Duration' column not found in report.")
108+
109+
with open(report_path, 'w', encoding='utf-8') as f:
110+
f.write(str(soup))
111+
112+
# Register this function to run after everything is done
113+
atexit.register(rename_duration_column)

0 commit comments

Comments
 (0)