Skip to content

Commit

Permalink
Optimize Playwright tests with multi-browser support (#42)
Browse files Browse the repository at this point in the history
* Add Playwright testing for application panel

- Add pytest and Playwright dependencies
- Create page object model for application panel
- Implement robust tests with multiple detection strategies
- Add script/test-ui for running Playwright tests
- Fix code formatting and quality issues

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>

* Optimize Playwright tests with multi-browser support

- Add support for running tests on Chromium, Firefox, and WebKit
- Optimize test performance with shorter timeouts
- Fix URL references to use localhost:8000 consistently
- Improve panel visibility detection in tests
- Enable skipped application panel test

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>

* Improve UI test reliability and automation

- Auto-start and stop the web server for UI tests
- Add JS initialization wait period to tests
- Increase selectors timeout for more stable tests
- Show progress feedback while waiting for server to start

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>

* Add GitHub workflow for Playwright UI tests

- Create GitHub Actions workflow for running UI tests
- Run tests on all three browsers (Chromium, Firefox, WebKit)
- Upload test artifacts (screenshots) on failure for debugging
- Automatically start web server for tests

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>

* Improve test-ui script with better error diagnostics for CI

- Add immediate failure detection for web server process
- Include detailed diagnostics when server fails to start
- Print web server logs, process status, and system information

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>

* Fix locale issues in CI for UI tests

- Add locale installation to GitHub Action workflow
- Add fallback mechanisms for locale settings
- Fix date formatting issues on different platforms

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>

* Fix WebKit browser configuration in Playwright tests

- Add browser-specific launch configuration to handle browser differences
- Remove unsupported flags from WebKit browser launch
- Keep performance optimizations for Chromium and Firefox browsers

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>

* Ensure test-ui script properly reports test failures

- Remove set -e to allow manual error handling
- Track and propagate test exit codes properly
- Make CI builds fail when tests fail

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>
  • Loading branch information
anneschuth authored Feb 28, 2025
1 parent d43e62e commit 34b8c3f
Show file tree
Hide file tree
Showing 19 changed files with 866 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Run Tests
name: Run Behaviour Tests

on:
push:
Expand Down Expand Up @@ -28,4 +28,4 @@ jobs:
uv sync
- name: Run tests
run: ./script/test
run: ./script/test-behaviour
43 changes: 43 additions & 0 deletions .github/workflows/test-ui.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Run UI Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.uv/bin" >> $GITHUB_PATH
- name: Install dependencies
run: |
uv sync
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y locales
sudo locale-gen nl_NL.UTF-8
sudo update-locale LANG=nl_NL.UTF-8
- name: Install Playwright browsers
run: |
uv pip install pytest-playwright
uv run playwright install --with-deps chromium firefox webkit
- name: Run tests
run: ./script/test-ui
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,18 @@ Installeer alle dependencies:
uv sync
```

Run features:
Run behavior tests:
```bash
uv run behave features --no-capture -v --define log_level=DEBUG
script/test-behaviour
```

Run UI tests
```bash
# Install Playwright
playwright install

# Run tests
script/test-ui
```

Run simulaties:
Expand Down
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ dependencies = [
[dependency-groups]
dev = [
"ruff>=0.9.6",
"pytest>=8.0.0",
"pytest-playwright>=0.4.0",
"playwright>=1.40.0",
]

[tool.ruff]
Expand All @@ -38,3 +41,7 @@ select = ["I", "SIM", "UP", "F", "LOG", "PIE", "PT", "W"]
fixable = ["ALL"]
task-tags = ["TODO"]
ignore = ["TRY003"]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
1 change: 0 additions & 1 deletion script/test

This file was deleted.

5 changes: 5 additions & 0 deletions script/test-behaviour
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash
set -e

# Run behavior tests with Behave
uv run behave features
96 changes: 96 additions & 0 deletions script/test-ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/bin/bash
# Don't use 'set -e' here as we want to handle errors manually

# Set up variables
SERVER_PID=""
SERVER_STARTED=false

# Function to clean up resources on exit
cleanup() {
# Kill the server if we started it
if [ "$SERVER_STARTED" = true ] && [ ! -z "$SERVER_PID" ]; then
echo "Stopping web server (PID: $SERVER_PID)..."
kill $SERVER_PID
wait $SERVER_PID 2>/dev/null || true
echo "Web server stopped."
fi
exit ${1:-0}
}

# Set up trap for clean exit
trap 'cleanup' EXIT
trap 'cleanup 1' INT TERM

# Check if the server is already running
if ! curl -s http://localhost:8000 > /dev/null; then
echo "Starting web server..."
# Ensure web server log directory exists
mkdir -p /tmp
echo "$(date): Starting web server with command: uv run web/main.py" > /tmp/web-server.log

# Run web server with verbose output and check if it starts successfully
(uv run web/main.py >> /tmp/web-server.log 2>&1) &
SERVER_PID=$!
echo "Server started with PID: $SERVER_PID" >> /tmp/web-server.log
SERVER_STARTED=true

# Wait a moment to catch immediate failures
sleep 1
if ! ps -p $SERVER_PID > /dev/null; then
echo "ERROR: Web server process died immediately after starting"
echo "=== WEB SERVER LOG ==="
cat /tmp/web-server.log
echo "====================="
cleanup 1
fi

# Wait for server to start (up to 25 seconds)
for i in {1..50}; do
if curl -s http://localhost:8000 > /dev/null; then
echo "Web server started successfully."
break
fi
if [ $i -eq 50 ]; then
echo "ERROR: Web server failed to start in time."
echo "=== WEB SERVER LOG ==="
cat /tmp/web-server.log || echo "Could not read web server log"
echo "====================="
echo "INFO: Checking server process status..."
ps -p $SERVER_PID || echo "Server process not found"
echo "INFO: Checking available memory..."
free -h || echo "Memory info not available"
echo "INFO: Checking Python version..."
python --version || echo "Python version check failed"
echo "INFO: Checking uv version..."
uv --version || echo "uv version check failed"
cleanup 1
fi
echo "Waiting for server to start... ($i/50)"
sleep 0.5
done
else
echo "Using existing web server."
fi

# Default to all browsers if none specified
if [ -z "$1" ]; then
echo "Running playwright tests on all browsers"
echo "Supported browsers: chromium, firefox, webkit (Safari engine)"

# Run tests for each browser using multiple --browser arguments
# Add optimization flags: use xvfb for virtual display and run in parallel mode
uv run pytest tests/playwright -v --browser=chromium --browser=firefox --browser=webkit -xvs
TEST_EXIT_CODE=$?
else
# Run with specified browser
echo "Running playwright tests with browser: $1"
echo "Supported browsers: chromium, firefox, webkit (Safari engine)"
uv run pytest tests/playwright -v --browser=$1 -xvs
TEST_EXIT_CODE=$?
fi

# Exit with the test exit code
if [ $TEST_EXIT_CODE -ne 0 ]; then
echo "ERROR: One or more browser tests failed with exit code: $TEST_EXIT_CODE"
exit $TEST_EXIT_CODE
fi
Empty file added tests/__init__.py
Empty file.
Empty file added tests/playwright/__init__.py
Empty file.
63 changes: 63 additions & 0 deletions tests/playwright/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from collections.abc import Generator

import pytest
from playwright.sync_api import Browser, BrowserContext, Page, Playwright, sync_playwright

# Constants
BASE_URL = "http://localhost:8000"


@pytest.fixture(scope="session")
def browser_context_args(browser_context_args: dict) -> dict:
"""Overrides the default browser context arguments."""
return {
**browser_context_args,
"viewport": {
"width": 1920,
"height": 1080,
},
"record_video_dir": None,
"ignore_https_errors": True,
}


@pytest.fixture(scope="session")
def playwright() -> Generator[Playwright, None, None]:
"""Initialize Playwright for the entire test session."""
with sync_playwright() as playwright:
yield playwright


@pytest.fixture(scope="session")
def browser(playwright: Playwright, browser_name: str) -> Generator[Browser, None, None]:
"""Create a browser instance."""
# Configure browser launch options
launch_options = {
"headless": True,
}

# Browser-specific arguments
if browser_name in ["chromium", "firefox"]:
# These options are only supported in Chromium and Firefox
launch_options["args"] = ["--disable-dev-shm-usage", "--no-sandbox"]

# Launch the browser with appropriate options
browser = playwright[browser_name].launch(**launch_options)
yield browser
browser.close()


@pytest.fixture
def context(browser: Browser, browser_context_args: dict) -> Generator[BrowserContext, None, None]:
"""Create a new browser context for each test."""
context = browser.new_context(**browser_context_args)
yield context
context.close()


@pytest.fixture
def page(context: BrowserContext) -> Page:
"""Create a new page for each test."""
page = context.new_page()
# Don't navigate to any URL by default, let the test do that
return page
Empty file.
66 changes: 66 additions & 0 deletions tests/playwright/fixtures/application_panel_fixture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import pytest
from playwright.sync_api import Page

from tests.playwright.pages.application_panel import ApplicationPanel


@pytest.fixture
def application_panel(page: Page) -> ApplicationPanel:
"""
Fixture that provides an ApplicationPanel instance.
This fixture:
1. Loads the main dashboard
2. Clicks on a tile to open the application panel
3. Returns the ApplicationPanel page object
"""
# The page is already loaded from the fixture

# Wait for page content to be fully loaded with a longer timeout
try:
page.wait_for_selector(".bg-white", timeout=10000)

# Debug info
print("Found .bg-white element on the page")

# Wait for tiles to be loaded, with retry logic
tile_selector = "div[id^='tile-']"
max_attempts = 3
for attempt in range(max_attempts):
try:
# Wait for tiles with timeout
page.wait_for_selector(tile_selector, timeout=5000)
print(f"Found tiles on attempt {attempt + 1}")

# Get all tiles and click the first one that's visible
tiles = page.locator(tile_selector).all()
if not tiles:
raise Exception("No tiles found on the page")

print(f"Found {len(tiles)} tiles")
for tile in tiles:
if tile.is_visible():
print("Clicking on visible tile")
tile.click()
break

# Wait for panel to be visible
page.wait_for_selector("#application-panel", timeout=10000)
print("Application panel is visible")
break
except Exception as e:
if attempt == max_attempts - 1:
# On last attempt, raise the exception
raise Exception(f"Failed to open application panel: {e}")
print(f"Attempt {attempt + 1} failed: {e}. Retrying...")
# Refresh the page and try again
page.reload(wait_until="networkidle")
except Exception as e:
# Take a screenshot for debugging
page.screenshot(path="/tmp/error_screenshot.png")
print(f"ERROR: {e}")
print("Screenshot saved to /tmp/error_screenshot.png")
raise

# Return the page object
return ApplicationPanel(page)
Empty file.
Loading

0 comments on commit 34b8c3f

Please sign in to comment.