Skip to content

Commit 27611c9

Browse files
test: DocGen-Updated script to handle response status and draft validation
2 parents 8d12049 + 47f1228 commit 27611c9

File tree

6 files changed

+265
-126
lines changed

6 files changed

+265
-126
lines changed

tests/e2e-test/base/base.py

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

48-
48+
49+
def validate_generate_response_status(self, question_api=""):
50+
load_dotenv() # Ensure environment variables are loaded
51+
# URL of the API endpoint
52+
url = f"{URL}/history/generate"
53+
54+
# Prepare headers
55+
headers = {
56+
"Content-Type": "application/json",
57+
"Accept": "*/*",
58+
}
59+
60+
# Payload (data) to be sent in the POST request
61+
payload = {
62+
"chat_type": "template",
63+
"messages": [
64+
{
65+
"role": "user",
66+
"content": question_api, # Use the passed question
67+
}
68+
],
69+
}
70+
71+
# Make the POST request
72+
response = self.page.request.post(
73+
url, headers=headers, data=json.dumps(payload), timeout=120000
74+
)
75+
assert response.status == 200, (
76+
"response code is " + str(response.status) + " " + str(response.json())
77+
)
78+
79+

tests/e2e-test/config/constants.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os
2-
2+
import json
33
from dotenv import load_dotenv
44

55
load_dotenv()
@@ -11,14 +11,6 @@
1111
# Get the absolute path to the repository root
1212
repo_root = os.getenv("GITHUB_WORKSPACE", os.getcwd())
1313

14-
# # Construct the absolute path to the JSON file
15-
# #note: may have to remove 'Doc_gen' from below when running locally
16-
# json_file_path = os.path.join(repo_root,'testData', 'section_title.json')
17-
18-
# with open(json_file_path, 'r') as file:
19-
# data = json.load(file)
20-
# sectionTitle = data['sectionTitle']
21-
2214
# browse input data
2315
browse_question1 = "What are typical sections in a promissory note?"
2416
browse_question2 = "List the details of two promissory notes governed by the laws of the state of California"
@@ -29,3 +21,4 @@
2921

3022
# Response Text Data
3123
invalid_response = "I was unable to find content related to your query and could not generate a template. Please try again."
24+
invalid_response1 = "An error occurred. Answers can't be saved at this time. If the problem persists, please contact the site administrator."

tests/e2e-test/pages/draftPage.py

Lines changed: 149 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,174 @@
11
from base.base import BasePage
22
from pytest_check import check
3+
from config.constants import *
4+
import logging
5+
logger = logging.getLogger(__name__)
36
import time
4-
from collections import defaultdict
57

68

79
class DraftPage(BasePage):
8-
# Principal_Amount_and_Date = "div:nth-child(3) div:nth-child(2) span:nth-child(1) textarea:nth-child(1)"
9-
# Borrower_Information = "div:nth-child(3) div:nth-child(2) span:nth-child(1) textarea:nth-child(1)"
10-
# Payee_Information = "//div[3]//div[2]//span[1]//textarea[1]"
1110
Draft_Sections = "//textarea"
1211
Draft_headings = "//span[@class='fui-Text ___nl2uoq0 fk6fouc f4ybsrx f1i3iumi f16wzh4i fpgzoln f1w7gpdv f6juhto f1gl81tg f2jf649 fepr9ql febqm8h']"
1312
invalid_response = "The requested information is not available in the retrieved data. Please try another query or topic."
1413
invalid_response1 = "There was an issue fetching your data. Please try again."
15-
invalid_response2 = " "
14+
1615

1716
def __init__(self, page):
1817
self.page = page
1918

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()
19+
def validate_draft_sections_loaded(self):
20+
max_wait_time = 180 # seconds
21+
poll_interval = 2
2922

23+
self.page.wait_for_timeout(25000)
3024

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)
25+
# All draft section containers
26+
section_blocks = self.page.locator("//div[@class='ms-Stack ___mit7380 f4zyqsv f6m9rw3 fwbpcpn folxr9a f1s274it css-103']")
27+
total_sections = section_blocks.count()
3428

35-
section_count = section_elements.count()
36-
heading_count = heading_elements.count()
29+
logger.info(f"🔍 Total sections found: {total_sections}")
3730

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
31+
for index in range(total_sections):
32+
section = section_blocks.nth(index)
4233

43-
failed_sections = defaultdict(str)
34+
try:
35+
section.scroll_into_view_if_needed()
36+
self.page.wait_for_timeout(500)
4437

45-
for i in range(section_count):
46-
section = section_elements.nth(i)
38+
title_element = section.locator("//span[@class='fui-Text ___nl2uoq0 fk6fouc f4ybsrx f1i3iumi f16wzh4i fpgzoln f1w7gpdv f6juhto f1gl81tg f2jf649 fepr9ql febqm8h']")
39+
title_text = title_element.inner_text(timeout=5000).strip()
40+
except Exception as e:
41+
logger.error(f"❌ Could not read title for section #{index + 1}: {e}")
42+
continue
4743

44+
logger.info(f"➡️ Validating section [{index + 1}/{total_sections}]: '{title_text}'")
45+
46+
content_locator = section.locator("//textarea")
47+
generate_btn = section.locator("//span[@class='fui-Button__icon rywnvv2 ___963sj20 f1nizpg2']")
48+
spinner_locator = section.locator("//div[@id='section-card-spinner']")
49+
50+
content_loaded = False
51+
52+
# 🚨 If spinner is visible inside this section, click generate immediately
53+
try:
54+
if spinner_locator.is_visible(timeout=1000):
55+
logger.warning(f"⏳ Spinner found in section '{title_text}'. Clicking Generate immediately.")
56+
generate_btn.click()
57+
self.page.wait_for_timeout(3000)
58+
confirm_btn = self.page.locator("//button[@class='fui-Button r1alrhcs ___zqkcn80 fd1o0ie fjxutwb fwiml72 fj8njcf fzcpov4 f1d2rq10 f1mk8lai ff3glw6']")
59+
if confirm_btn.is_visible(timeout=3000):
60+
confirm_btn.click()
61+
logger.info(f"🟢 Clicked Confirm button for section '{title_text}'")
62+
else:
63+
logger.warning(f"⚠️ Confirm button not visible for section '{title_text}'")
64+
except Exception as e:
65+
logger.error(f"❌ Error while clicking Confirm button for section '{title_text}': {e}")
66+
67+
# ⏳ Retry short wait (15s) for content to load
68+
short_wait = 15
69+
short_start = time.time()
70+
while time.time() - short_start < short_wait:
4871
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}")
72+
content = content_locator.text_content(timeout=2000).strip()
73+
if content:
74+
logger.info(f"✅ Section '{title_text}' loaded after Generate + Confirm.")
75+
content_loaded = True
76+
break
77+
except:
78+
pass
79+
time.sleep(1)
80+
81+
if not content_loaded:
82+
logger.error(f"❌ Section '{title_text}' still empty after Generate + Confirm wait ({short_wait}s). Skipping.")
83+
84+
# Step 1: Wait for content to load normally
85+
start = time.time()
86+
while time.time() - start < max_wait_time:
87+
try:
88+
content = content_locator.text_content(timeout=2000).strip()
89+
if content:
90+
logger.info(f"✅ Section '{title_text}' loaded successfully.")
91+
content_loaded = True
92+
break
93+
except:
94+
pass
6895
time.sleep(poll_interval)
6996

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()
97+
# Step 2: If still not loaded, click Generate and retry
98+
if not content_loaded:
99+
logger.warning(f"⚠️ Section '{title_text}' is empty. Attempting 'Generate'...")
83100

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")
101+
try:
102+
generate_btn.click()
103+
logger.info(f"🔄 Clicked 'Generate' for section '{title_text}'")
104+
except Exception as e:
105+
logger.error(f"❌ Failed to click 'Generate' for section '{title_text}': {e}")
106+
continue
107+
108+
# Retry wait
109+
start = time.time()
110+
while time.time() - start < max_wait_time:
111+
try:
112+
content = content_locator.text_content(timeout=2000).strip()
113+
if content:
114+
logger.info(f"✅ Section '{title_text}' loaded after clicking Generate.")
115+
content_loaded = True
116+
break
117+
except:
118+
pass
119+
time.sleep(poll_interval)
120+
121+
if not content_loaded:
122+
logger.error(f"❌ Section '{title_text}' still empty after retrying.")
123+
124+
# Optional: take screenshot
125+
screenshot_dir = "screenshots"
126+
os.makedirs(screenshot_dir, exist_ok=True)
127+
screenshot_path = os.path.join(screenshot_dir, f"section_{index + 1}_{title_text.replace(' ', '_')}.png")
128+
try:
129+
section.screenshot(path=screenshot_path)
130+
logger.error(f"📸 Screenshot saved: {screenshot_path}")
131+
except Exception as e:
132+
logger.error(f"❌ Generate click failed in section '{title_text}': {e}")
133+
continue
134+
135+
try:
136+
content = content_locator.text_content(timeout=2000).strip()
137+
with check:
138+
if content == self.invalid_response or content == self.invalid_response1:
139+
logger.warning(f"❌ Invalid response found in '{title_text}'. Retrying Generate + Confirm...")
140+
141+
try:
142+
generate_btn.click()
143+
self.page.wait_for_timeout(3000)
144+
145+
confirm_btn = self.page.locator("//button[@class='fui-Button r1alrhcs ___zqkcn80 fd1o0ie fjxutwb fwiml72 fj8njcf fzcpov4 f1d2rq10 f1mk8lai ff3glw6']")
146+
if confirm_btn.is_visible(timeout=3000):
147+
confirm_btn.click()
148+
logger.info(f"🟢 Retried Confirm for section '{title_text}'")
149+
else:
150+
logger.warning(f"⚠️ Confirm button not visible during retry for '{title_text}'")
151+
except Exception as e:
152+
logger.error(f"❌ Retry Generate/Confirm failed: {e}")
153+
154+
retry_start = time.time()
155+
while time.time() - retry_start < short_wait:
156+
try:
157+
content = content_locator.text_content(timeout=2000).strip()
158+
if content and content not in [self.invalid_response, self.invalid_response1]:
159+
logger.info(f"✅ Section '{title_text}' fixed after retry.")
160+
break
161+
except:
162+
pass
163+
time.sleep(1)
164+
165+
with check:
166+
check.not_equal(content, self.invalid_response, f"❌ '{title_text}' still has invalid response after retry")
167+
check.not_equal(content, self.invalid_response1, f"❌ '{title_text}' still has invalid response after retry")
168+
169+
else:
170+
logger.info(f"🎯 Section '{title_text}' has valid content.")
171+
except Exception as e:
172+
logger.error(f"❌ Could not validate content for '{title_text}': {e}")
173+
174+
logger.info(f"✔️ Completed section: '{title_text}'\n")

tests/e2e-test/pages/generatePage.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
from asyncio.log import logger
12
from base.base import BasePage
23
from playwright.sync_api import expect
3-
from asyncio.log import logger
4+
import logging
5+
logger = logging.getLogger(__name__)
6+
47

58
class GeneratePage(BasePage):
69
GENERATE_DRAFT = "//button[@title='Generate Draft']"
710
TYPE_QUESTION = "//textarea[@placeholder='Type a new question...']"
811
SEND_BUTTON = "//div[@aria-label='Ask question button']"
9-
SHOW_CHAT_HISTORY_BUTTON="//span[text()='Show template history']"
12+
SHOW_CHAT_HISTORY_BUTTON = "//span[text()='Show template history']"
1013
HIDE_CHAT_HISTORY_BUTTON = "//span[text()='Hide Chat History']"
1114
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]"
1215
SHOW_CHAT_HISTORY = "//span//i"
@@ -28,7 +31,25 @@ def enter_a_question(self, text):
2831
def click_send_button(self):
2932
# Type a question in the text area
3033
self.page.locator(self.SEND_BUTTON).click()
31-
self.page.wait_for_timeout(10000)
34+
locator = self.page.locator("//p[contains(text(),'Generating template...this may take up to 30 secon')]")
35+
stop_button = self.page.locator("//div[@aria-label='Stop generating']")
36+
37+
try:
38+
# Wait up to 60s for the element to become **hidden**
39+
locator.wait_for(state="hidden", timeout=60000)
40+
except TimeoutError:
41+
msg = "❌ TIMED-OUT: Not recieved response within 60 sec."
42+
logger.info(msg) # ✅ log to console/log file
43+
raise AssertionError(msg)
44+
45+
finally:
46+
if stop_button.is_visible():
47+
stop_button.click()
48+
logger.info("Clicked on 'Stop generating' button after timeout.")
49+
else:
50+
logger.info("'Stop generating' button not visible.")
51+
52+
self.page.wait_for_timeout(5000)
3253

3354
def click_generate_draft_button(self):
3455
# Type a question in the text area
@@ -52,23 +73,29 @@ def close_chat_history(self):
5273
hide_button.click()
5374
self.page.wait_for_timeout(2000)
5475
else:
55-
logger.info("Hide button not visible. Chat history might already be closed.")
76+
logger.info(
77+
"Hide button not visible. Chat history might already be closed."
78+
)
5679

5780
def delete_chat_history(self):
5881

5982
self.page.locator(self.SHOW_CHAT_HISTORY_BUTTON).click()
83+
self.page.wait_for_timeout(4000)
6084
chat_history = self.page.locator("//span[contains(text(),'No chat history.')]")
6185
if chat_history.is_visible():
6286
self.page.wait_for_load_state("networkidle")
63-
self.page.locator("button[title='Hide']").wait_for(state="visible", timeout=5000)
87+
self.page.locator("button[title='Hide']").wait_for(
88+
state="visible", timeout=5000
89+
)
6490
self.page.locator("button[title='Hide']").click()
6591

6692
else:
6793
self.page.locator(self.CHAT_HISTORY_OPTIONS).click()
6894
self.page.locator(self.CHAT_HISTORY_DELETE).click()
95+
self.page.wait_for_timeout(5000)
6996
self.page.get_by_role("button", name="Clear All").click()
7097
self.page.wait_for_timeout(5000)
98+
expect(self.page.locator("//span[contains(text(),'No chat history.')]")).to_be_visible()
7199
self.page.locator(self.CHAT_HISTORY_CLOSE).click()
72100
self.page.wait_for_load_state("networkidle")
73101
self.page.wait_for_timeout(2000)
74-

0 commit comments

Comments
 (0)