Skip to content

test: establish Playwright E2E UI testing foundation (#426)#428

Open
tejassinghbhati wants to merge 5 commits intoEAPD-DRB:mainfrom
tejassinghbhati:feature/426-playwright-e2e
Open

test: establish Playwright E2E UI testing foundation (#426)#428
tejassinghbhati wants to merge 5 commits intoEAPD-DRB:mainfrom
tejassinghbhati:feature/426-playwright-e2e

Conversation

@tejassinghbhati
Copy link
Copy Markdown
Contributor

@tejassinghbhati tejassinghbhati commented Apr 10, 2026

Resolves #426

What This PR Does

I have built a stable, cross-platform Playwright E2E testing foundation for the MUIOGO frontend.
It introduces:

  • A dedicated ui-test job in CI
  • A live_server pytest fixture that starts Flask reliably before browser tests
  • A focused smoke test suite that validates what Flask guarantees

This approach deliberately avoids:

  • Dependence on JS execution timing
  • jQuery AJAX completion
  • External CDN availability

The Problems Encountered (and what I have learned)

The integration was not that straightforward, I had to identify three distinct failure classes and resolve them:

Problem 1: Subprocess Flask dev server hangs on Python 3.12 (Windows)

Initial Approach:

  • Spawned API/app.py as a subprocess using Flask dev server (app.run())

Observed Behavior:

  • /health responded instantly
  • page.goto("/") timed out at 30 seconds

Root Cause:

  • Flask (Werkzeug) dev server is single-threaded
  • Python 3.12 Windows uses ProactorEventLoop
  • Playwright triggers ~20 parallel requests for <script> tags
  • Server handled /health but got stuck queueing asset requests

Fix:

  • Replaced subprocess with in-process Waitress daemon thread
  • Waitress uses thread-based I/O (no asyncio)
  • Configuration:
    • threads=16
    • channel_timeout=10

Problem 2: Waitress thread exhaustion between test sessions

Observed Behavior:

  • First test passed
  • Second test always timed out

Root Cause:

  • Playwright left connections in TIME_WAIT
  • Waitress (default 4 threads) got blocked
  • No threads available for next request

Fix:

  • Increased threads:
    • threads=16
  • Reduced idle connection time:
    • channel_timeout=10 (from 120)

Problem 3: CDN resources blocking DOMContentLoaded

Observed Behavior:

  • Switching to wait_until="domcontentloaded" still caused timeouts

Root Cause:

  • DOMContentLoaded waits for:
    • All synchronous <script> downloads + execution
  • index.html contains 20+ scripts
  • External CDN (MathJax) delays loading

Fix:

  • Use:
    wait_until="commit"

Then validate elements using:

  • to_have_title
  • to_be_attached

Use independent timeouts (15s–60s) for stability.

Problem 4: SPA routes are unreliable in headless CI

Affected Routes

  • /#AddCase
  • /#Home
  • /#Config

Root Cause

  • Routes rely on jQuery $.load() (AJAX)
  • Require:
    • Session state
    • Pre-seeded data
  • Not reliable in a headless CI environment

Fix

Scope tests only to Flask-guaranteed outputs:

  • /health API
  • /getSession API
  • Page <title>
  • Static footer text

Evidence: Stable Test Runs (Python 3.12, Windows)

image image

Tests Included

  • test_health_endpoint/health returns { "status": "ok" }
  • test_session_api/getSession returns session data
  • test_load_app[chromium] → page title matches MUIO 5.5
  • test_static_footer[chromium] → footer contains MUIO ver.5.5

Files Changed

File Change
API/app.py Added /health endpoint
ui_tests/conftest.py live_server fixture using Waitress
ui_tests/test_ui_smoke.py 4 stable smoke tests
.github/workflows/ci.yml Added ui-test job
README.md Added local testing instructions

Dependency Hygiene

pytest-playwright and Chromium

  • Installed dynamically in CI

Production Environment

  • Remains unaffected

@github-actions github-actions bot added the needs-intake-fix PR intake structure needs maintainer follow-up label Apr 10, 2026
…/CI server flakiness

- Replace subprocess server launch with an in-process Waitress daemon thread.
  This eliminates cross-platform CWD/sys.path/env-var inheritance issues that
  caused page.goto() timeouts on Python 3.12 Windows when using Flask dev server.
- Configure Waitress with threads=16 and channel_timeout=10 to prevent thread
  exhaustion during Playwright's burst of concurrent static asset requests.
- Use wait_until='commit' on all page.goto() calls so browser tests return the
  instant Flask starts sending bytes, without blocking on CDN resources (MathJax)
  or synchronous <script> downloads that precede DOM parsing.
- Scope assertions to elements guaranteed present in raw server-rendered HTML:
  page title (in <head>), footer copyright text (hardcoded in index.html).
- All 4 tests now pass locally in 8s: test_health_endpoint, test_session_api,
  test_load_app, test_static_footer.
@tejassinghbhati
Copy link
Copy Markdown
Contributor Author

tejassinghbhati commented Apr 11, 2026

Greeting to everyone, I apologize for the time this took and it involved the closing of another PR earlier, just pushed a significant fix to this branch, wanted to document what changed and why.

When I first opened this PR, the test suite was failing in CI with AssertionError: element(s) not found on tests like test_case_management and test_navigation_diagnostics. The original approach was trying to simulate real user flows — creating cases with a name, navigating to /#Config, asserting heading text like "Parameters". These seemed reasonable but turned out to be fundamentally unreliable because those routes depend on jQuery's $.load() completing AJAX fetches of HTML partials, which never finishes consistently in a headless browser without pre-existing session state or seeded data.

After debugging further locally, I also ran into a deeper infrastructure problem. The conftest.py was spawning the Flask server as a subprocess using Flask's dev server (app.run()). On Python 3.12 Windows, that server would start fine and respond to /health, but any browser navigation to / would hang for 30 seconds and timeout. The root cause was Playwright's headless Chrome firing ~20 parallel requests for the blocking <script> tags in index.html the moment it received the page — Flask's single-threaded dev server couldn't handle that burst.

So I reworked the entire server fixture from scratch. Instead of a subprocess, the server now runs as a Waitress daemon thread inside the pytest process — same WSGI server the app uses in production, configured with 16 threads and a 10-second idle connection timeout. This eliminates the OS-specific dev server issues and ensures threads are always available across test sessions.

On the test side, I also switched from wait_until="load" / wait_until="domcontentloaded" to wait_until="commit" — meaning page.goto() returns the instant Flask starts sending bytes, instead of blocking until CDN resources like MathJax load. Element assertions then use their own timeouts independently.

The result: 4 tests, 3 consecutive passing runs locally, under 6 seconds each. The tests now only assert what Flask unconditionally guarantees, the /health endpoint, the /getSession API, the page title, and the footer text hardcoded in index.html. No SPA routes, no jQuery-injected content, no timing assumptions.

This should be stable for the contribution as of now. Thank you so much for your time. @m13v

@tejassinghbhati tejassinghbhati changed the title test: establish Playwright E2E UI testing foundation (#426) test: establish Playwright E2E UI testing foundation (#428) Apr 11, 2026
@tejassinghbhati tejassinghbhati changed the title test: establish Playwright E2E UI testing foundation (#428) test: establish Playwright E2E UI testing foundation (#426) Apr 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-intake-fix PR intake structure needs maintainer follow-up

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Task] Expand CI/CD pipeline with Playwright End-to-End frontend tests

1 participant