dd-poc / tests /e2e /test_user_journeys.py
Juan Salas
Refactor test suite: Remove implementation tests, add comprehensive E2E coverage
32ea56b
#!/usr/bin/env python3
"""
E2E Tests for User Journeys
Tests that simulate realistic user journeys and scenarios:
- New user onboarding flow
- Experienced user workflows
- Multi-session scenarios
- Different use cases (M&A analyst, lawyer, consultant)
"""
import pytest
from playwright.sync_api import Page, expect
from .conftest import StreamlitPageHelpers
class TestUserJourneys:
"""Test realistic user journey scenarios"""
def test_new_user_onboarding_journey(self, page: Page, streamlit_helpers: StreamlitPageHelpers):
"""Test the journey of a new user discovering the application"""
streamlit_helpers.wait_for_streamlit_load()
# New user sees the main interface
expect(page.locator("h1")).to_contain_text("AI Due Diligence")
# User explores the sidebar to understand data room setup
sidebar = page.locator("[data-testid='stSidebar']")
expect(sidebar).to_be_visible()
# User sees data room configuration options
data_room_section = sidebar.locator("text=/.*[Dd]ata.*[Rr]oom.*/")
expect(data_room_section.first).to_be_visible()
# User discovers the main functionality tabs
tabs = page.locator("[data-testid='stTabs'] button, .stTabs button")
if tabs.count() >= 3:
# User explores Company Analysis first (most common workflow)
analysis_tab = tabs.first
analysis_tab.click()
page.wait_for_timeout(1000)
# User sees explanation of what this tab does
analysis_content = page.locator("text=/.*[Aa]nalysis.*|.*[Cc]ompany.*|.*[Dd]ue.*[Dd]iligence.*/")
# User tries to generate analysis (should see API key requirement)
generate_buttons = page.locator("button:has-text(/.*[Gg]enerate.*/)")
if generate_buttons.count() > 0:
generate_buttons.first.click()
page.wait_for_timeout(2000)
# Should see guidance about API key or processing requirements
guidance_text = page.locator("text=/.*API.*key.*|.*[Cc]onfigure.*|.*[Pp]rocess.*data.*room.*/")
# User explores other tabs to understand available features
for i in range(1, min(tabs.count(), 4)):
tabs.nth(i).click()
page.wait_for_timeout(1000)
# Each tab should be accessible and show relevant content
expect(page.locator("[data-testid='stApp']")).to_be_visible()
def test_ma_analyst_workflow(self, page: Page, streamlit_helpers: StreamlitPageHelpers, sample_test_data):
"""Test workflow of an M&A analyst conducting due diligence"""
streamlit_helpers.wait_for_streamlit_load()
# M&A analyst workflow:
# 1. Set up data room
# 2. Process documents
# 3. Generate comprehensive company analysis
# 4. Review checklist items
# 5. Export findings
sidebar = page.locator("[data-testid='stSidebar']")
# Step 1: Configure data room path
path_inputs = sidebar.locator("input[type='text']")
if path_inputs.count() > 0 and sample_test_data["vdr_path"].exists():
path_inputs.first.fill(str(sample_test_data["vdr_path"]))
# Step 2: Initiate processing
process_buttons = sidebar.locator("button:has-text(/.*[Pp]rocess.*/)")
if process_buttons.count() > 0:
process_buttons.first.click()
page.wait_for_timeout(5000)
# Step 3: Generate company analysis (primary focus for M&A analyst)
tabs = page.locator("[data-testid='stTabs'] button, .stTabs button")
# Navigate to Company Analysis tab
if tabs.count() > 0:
tabs.first.click() # Usually Company Analysis is first
page.wait_for_timeout(1000)
generate_buttons = page.locator("button:has-text(/.*[Gg]enerate.*[Aa]nalysis.*|.*[Dd]ue.*[Dd]iligence.*/)")
if generate_buttons.count() > 0:
generate_buttons.first.click()
page.wait_for_timeout(5000)
# Step 4: Review checklist (compliance focus)
checklist_tab = page.locator("button:has-text('Checklist'), text='Checklist'").first
if checklist_tab.count() > 0:
checklist_tab.click()
page.wait_for_timeout(1000)
# Process checklist items
process_buttons = page.locator("button:has-text(/.*[Pp]rocess.*|.*[Mm]atch.*/)")
if process_buttons.count() > 0:
process_buttons.first.click()
page.wait_for_timeout(3000)
# Step 5: Export findings
export_buttons = page.locator("button:has-text(/.*[Ee]xport.*|.*[Dd]ownload.*/)")
download_links = page.locator("a[download]")
export_available = export_buttons.count() > 0 or download_links.count() > 0
# Workflow should complete successfully
expect(page.locator("[data-testid='stApp']")).to_be_visible()
def test_legal_counsel_workflow(self, page: Page, streamlit_helpers: StreamlitPageHelpers):
"""Test workflow of legal counsel reviewing due diligence items"""
streamlit_helpers.wait_for_streamlit_load()
# Legal counsel workflow:
# 1. Review due diligence questions
# 2. Check specific legal items via Q&A
# 3. Export legal findings
# Step 1: Focus on Questions tab (legal due diligence items)
questions_tab = page.locator("button:has-text('Questions'), text='Questions'").first
if questions_tab.count() > 0:
questions_tab.click()
page.wait_for_timeout(1000)
# Process legal questions
process_buttons = page.locator("button:has-text(/.*[Pp]rocess.*|.*[Qq]uestions.*/)")
if process_buttons.count() > 0:
process_buttons.first.click()
page.wait_for_timeout(3000)
# Step 2: Use Q&A for specific legal queries
qa_tab = page.locator("button:has-text('Q&A'), text='Q&A'").first
if qa_tab.count() > 0:
qa_tab.click()
page.wait_for_timeout(1000)
# Legal counsel asks specific questions
question_inputs = page.locator("input[placeholder*='question'], textarea[placeholder*='question']")
if question_inputs.count() > 0:
legal_questions = [
"What are the key legal risks?",
"Are there any pending litigations?",
"What intellectual property does the company own?"
]
for question in legal_questions[:1]: # Test one question
question_inputs.first.fill(question)
ask_buttons = page.locator("button:has-text(/.*[Aa]sk.*|.*[Ss]ubmit.*/)")
if ask_buttons.count() > 0:
ask_buttons.first.click()
page.wait_for_timeout(3000)
break
# Legal workflow should complete
expect(page.locator("[data-testid='stApp']")).to_be_visible()
def test_consultant_workflow(self, page: Page, streamlit_helpers: StreamlitPageHelpers):
"""Test workflow of consultant conducting comprehensive analysis"""
streamlit_helpers.wait_for_streamlit_load()
# Consultant workflow:
# 1. Comprehensive company analysis
# 2. Strategic assessment
# 3. Knowledge graph exploration
# 4. Export comprehensive report
tabs = page.locator("[data-testid='stTabs'] button, .stTabs button")
# Step 1: Company Analysis
if tabs.count() > 0:
tabs.first.click()
page.wait_for_timeout(1000)
generate_buttons = page.locator("button:has-text(/.*[Gg]enerate.*/)")
if generate_buttons.count() > 0:
generate_buttons.first.click()
page.wait_for_timeout(5000)
# Step 2: Knowledge Graph exploration (strategic insights)
graph_tab = page.locator("button:has-text('Graph'), text='Graph'").first
if graph_tab.count() > 0:
graph_tab.click()
page.wait_for_timeout(1000)
# Generate or explore knowledge graph
graph_buttons = page.locator("button:has-text(/.*[Gg]enerate.*|.*[Bb]uild.*|.*[Ss]how.*/)")
if graph_buttons.count() > 0:
graph_buttons.first.click()
page.wait_for_timeout(3000)
# Step 3: Export comprehensive findings
export_buttons = page.locator("button:has-text(/.*[Ee]xport.*|.*[Cc]ombined.*|.*[Cc]omplete.*/)")
if export_buttons.count() > 0:
export_buttons.first.click()
page.wait_for_timeout(2000)
# Consultant workflow should complete
expect(page.locator("[data-testid='stApp']")).to_be_visible()
def test_power_user_advanced_workflow(self, page: Page, streamlit_helpers: StreamlitPageHelpers, sample_test_data):
"""Test advanced workflow of experienced power user"""
streamlit_helpers.wait_for_streamlit_load()
# Power user workflow:
# 1. Quick data room setup
# 2. Parallel processing across multiple tabs
# 3. Advanced Q&A sessions
# 4. Multiple export formats
# Step 1: Efficient data room setup
sidebar = page.locator("[data-testid='stSidebar']")
path_inputs = sidebar.locator("input[type='text']")
if path_inputs.count() > 0 and sample_test_data["vdr_path"].exists():
# Power user knows exact path
path_inputs.first.fill(str(sample_test_data["vdr_path"]))
process_buttons = sidebar.locator("button:has-text(/.*[Pp]rocess.*/)")
if process_buttons.count() > 0:
process_buttons.first.click()
page.wait_for_timeout(3000) # Power user doesn't wait for full completion
# Step 2: Rapid navigation and processing across tabs
tabs = page.locator("[data-testid='stTabs'] button, .stTabs button")
if tabs.count() >= 3:
# Power user efficiently processes multiple workflows
for i in range(min(tabs.count(), 4)):
tabs.nth(i).click()
page.wait_for_timeout(500) # Quick switching
# Trigger key actions rapidly
action_buttons = page.locator("button:has-text(/.*[Gg]enerate.*|.*[Pp]rocess.*|.*[Aa]nalyze.*/)")
if action_buttons.count() > 0:
action_buttons.first.click()
page.wait_for_timeout(1000) # Don't wait for completion
# Step 3: Advanced Q&A session
qa_tab = page.locator("button:has-text('Q&A'), text='Q&A'").first
if qa_tab.count() > 0:
qa_tab.click()
page.wait_for_timeout(500)
question_inputs = page.locator("input[placeholder*='question'], textarea[placeholder*='question']")
if question_inputs.count() > 0:
# Power user asks complex questions
advanced_question = "Provide a detailed risk assessment including financial, operational, and strategic risks with specific citations from the documents"
question_inputs.first.fill(advanced_question)
ask_buttons = page.locator("button:has-text(/.*[Aa]sk.*/)")
if ask_buttons.count() > 0:
ask_buttons.first.click()
page.wait_for_timeout(2000)
# Power user workflow should be highly efficient
expect(page.locator("[data-testid='stApp']")).to_be_visible()
def test_multi_session_continuity(self, page: Page, streamlit_helpers: StreamlitPageHelpers):
"""Test that user can effectively work across multiple sessions"""
streamlit_helpers.wait_for_streamlit_load()
# Simulate work in first session
# Set some configuration
sidebar = page.locator("[data-testid='stSidebar']")
text_inputs = sidebar.locator("input[type='text']")
if text_inputs.count() > 0:
test_path = "/test/session/continuity"
text_inputs.first.fill(test_path)
# Navigate through tabs and perform actions
tabs = page.locator("[data-testid='stTabs'] button, .stTabs button")
if tabs.count() > 0:
tabs.first.click()
page.wait_for_timeout(1000)
# Simulate session break (page refresh)
page.reload()
streamlit_helpers.wait_for_streamlit_load()
# Verify app starts cleanly in new session
expect(page.locator("[data-testid='stApp']")).to_be_visible()
expect(page.locator("[data-testid='stSidebar']")).to_be_visible()
# User should be able to reconfigure and continue work
sidebar_after_reload = page.locator("[data-testid='stSidebar']")
expect(sidebar_after_reload).to_be_visible()
# Navigation should work normally
tabs_after_reload = page.locator("[data-testid='stTabs'] button, .stTabs button")
if tabs_after_reload.count() > 0:
tabs_after_reload.first.click()
page.wait_for_timeout(1000)
expect(page.locator("[data-testid='stApp']")).to_be_visible()
def test_error_recovery_user_journey(self, page: Page, streamlit_helpers: StreamlitPageHelpers):
"""Test user journey when encountering and recovering from errors"""
streamlit_helpers.wait_for_streamlit_load()
# User makes mistake in data room path
sidebar = page.locator("[data-testid='stSidebar']")
path_inputs = sidebar.locator("input[type='text']")
if path_inputs.count() > 0:
# Enter invalid path
path_inputs.first.fill("/completely/invalid/path")
process_buttons = sidebar.locator("button:has-text(/.*[Pp]rocess.*/)")
if process_buttons.count() > 0:
process_buttons.first.click()
page.wait_for_timeout(3000)
# User should see error message
error_elements = page.locator(".stError, [data-testid='stError'], text=/.*[Ee]rror.*|.*[Nn]ot found.*/")
# User corrects the mistake
path_inputs.first.clear()
path_inputs.first.fill("") # Clear invalid path
# User tries AI features without API key
tabs = page.locator("[data-testid='stTabs'] button, .stTabs button")
if tabs.count() > 0:
tabs.first.click()
page.wait_for_timeout(1000)
generate_buttons = page.locator("button:has-text(/.*[Gg]enerate.*/)")
if generate_buttons.count() > 0:
generate_buttons.first.click()
page.wait_for_timeout(2000)
# Should see API key requirement
api_error = page.locator("text=/.*API.*key.*|.*[Cc]onfigure.*AI.*/")
# After errors, user can still navigate and use app
expect(page.locator("[data-testid='stApp']")).to_be_visible()
# User can navigate to other tabs
if tabs.count() > 1:
tabs.nth(1).click()
page.wait_for_timeout(1000)
expect(page.locator("[data-testid='stApp']")).to_be_visible()
def test_accessibility_focused_user_journey(self, page: Page, streamlit_helpers: StreamlitPageHelpers):
"""Test user journey with focus on accessibility"""
streamlit_helpers.wait_for_streamlit_load()
# Test keyboard navigation
# Focus on first interactive element
first_button = page.locator("button").first
if first_button.count() > 0:
first_button.focus()
expect(first_button).to_be_focused()
# Test Tab navigation
page.keyboard.press("Tab")
page.wait_for_timeout(500)
# Some element should be focused after Tab
focused_element = page.locator(":focus")
expect(focused_element).to_have_count(1)
# Test that all major UI components have proper ARIA labels or text
main_content = page.locator("main, [role='main']")
expect(main_content).to_be_visible()
# Sidebar should be accessible
sidebar = page.locator("[data-testid='stSidebar']")
expect(sidebar).to_be_visible()
# Tabs should be accessible
tabs = page.locator("[data-testid='stTabs'] button, .stTabs button")
if tabs.count() > 0:
for i in range(min(tabs.count(), 3)):
tab = tabs.nth(i)
# Tab should have text or aria-label
tab_text = tab.inner_text()
assert len(tab_text) > 0, f"Tab {i} should have accessible text"
def test_mobile_user_journey(self, page: Page, streamlit_helpers: StreamlitPageHelpers):
"""Test user journey on mobile device"""
# Set mobile viewport
page.set_viewport_size({"width": 375, "height": 667})
streamlit_helpers.wait_for_streamlit_load()
# Mobile user can see main interface
expect(page.locator("[data-testid='stApp']")).to_be_visible()
# Sidebar might be collapsed on mobile - check if accessible
sidebar = page.locator("[data-testid='stSidebar']")
# If sidebar is not visible, there might be a mobile menu button
if not sidebar.is_visible():
menu_buttons = page.locator("button:has-text('☰'), button[aria-label*='menu']")
if menu_buttons.count() > 0:
menu_buttons.first.click()
page.wait_for_timeout(1000)
# Mobile user can navigate tabs
tabs = page.locator("[data-testid='stTabs'] button, .stTabs button")
if tabs.count() > 0:
# Tabs might be stacked on mobile
tabs.first.click()
page.wait_for_timeout(1000)
expect(page.locator("[data-testid='stApp']")).to_be_visible()
# Test touch interactions work
buttons = page.locator("button")
if buttons.count() > 0:
# Tap (click) should work
buttons.first.click()
page.wait_for_timeout(1000)
expect(page.locator("[data-testid='stApp']")).to_be_visible()
# Reset viewport
page.set_viewport_size({"width": 1280, "height": 720})