Testing and Screenshots with Playwright
VectorScope uses Playwright for automated browser testing and programmatic screenshot capture for documentation.
Installation
Playwright is included in the development dependencies. Install it with:
pip install playwright
playwright install chromium
Or if using pixi:
pixi run playwright install chromium
Screenshot Capture Script
The scripts/capture_screenshots.py script automatically captures UI screenshots
for documentation. It runs in headless mode and interacts with the VectorScope
application via both the UI and the backend API.
Running the Script
Before running the script, ensure both the backend and frontend are running:
# Terminal 1: Start backend
pixi run uvicorn backend.main:app --reload
# Terminal 2: Start frontend
cd frontend && npm run dev
Then run the screenshot script:
# Capture all screenshots
python scripts/capture_screenshots.py
# Capture only basic UI screenshots
python scripts/capture_screenshots.py --type basic
# Capture custom axes example screenshots
python scripts/capture_screenshots.py --type custom_axes
# Capture 3D view screenshots
python scripts/capture_screenshots.py --type 3d
Screenshot Types
The script captures several categories of screenshots:
Basic Screenshots (--type basic):
initial_state.png- Empty state with logograph_editor.png- Graph editor with Iris datasetgraph_with_view.png- Graph with t-SNE view addedgraph_with_transformation.png- Graph with scaling transformationview_editor.png- View editor showing scatter plotviewports.png- Two viewports with PCA and t-SNEdensity_view.png- Density distribution viewboxplot_view.png- Box plot viewviolin_view.png- Violin plot view
Custom Axes Screenshots (--type custom_axes):
custom_axes_graph.png- Graph editor showing custom axes setupcustom_axes_viewports.png- Two viewports with custom axes viewscustom_axes_view_editor.png- View editor with custom axes configuration
3D View Screenshots (--type 3d):
pca_3d_view.png- PCA 3D scatter plotdirect_3d_view.png- Direct Axes 3D view
Script Architecture
The screenshot script uses several techniques for reliable automation:
API-based Session Loading: Instead of clicking through the UI to load saved sessions, the script uses the backend API directly:
await page.evaluate("""async () => { const response = await fetch('http://localhost:8000/scenarios/load/example-custom-axes', { method: 'POST' }); return await response.json(); }""")
Page Refresh: After loading data via API, the page is refreshed to pick up the new state:
await page.reload() await page.wait_for_timeout(3000)
Element Selection: Uses Playwright selectors to find and interact with UI elements:
await page.click("button:has-text('Viewports')") await page.wait_for_selector("select")
Dynamic Select Handling: Iterates through select options to find specific views:
all_selects = await page.query_selector_all("select") for select in all_selects: options = await select.query_selector_all("option") for i, opt in enumerate(options): text = await opt.text_content() if "pca" in text.lower(): await select.select_option(index=i) break
Adding New Screenshots
To add new screenshots:
Create a new async function in
capture_screenshots.py:async def capture_my_feature_screenshots(): """Capture screenshots for my feature.""" SCREENSHOTS_DIR.mkdir(parents=True, exist_ok=True) async with async_playwright() as p: browser = await p.chromium.launch(headless=True) page = await browser.new_page(viewport={"width": 1400, "height": 900}) # Navigate and interact await page.goto(BASE_URL) # ... your interactions ... # Capture screenshot await page.screenshot(path=SCREENSHOTS_DIR / "my_feature.png") await browser.close()
Add the function to the
main()dispatcher:async def main(capture_type="all"): if capture_type == "all": # ... existing ... await capture_my_feature_screenshots() elif capture_type == "my_feature": await capture_my_feature_screenshots()
Add the option to the argument parser:
parser.add_argument( "--type", choices=["all", "basic", "custom_axes", "3d", "my_feature"], default="all", )
End-to-End Testing
Playwright can also be used for end-to-end testing. While VectorScope’s test suite primarily uses pytest for backend testing, Playwright enables full integration tests.
Example Test Structure
import pytest
from playwright.async_api import async_playwright
@pytest.fixture
async def page():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
yield page
await browser.close()
@pytest.mark.asyncio
async def test_load_iris_dataset(page):
await page.goto("http://localhost:5173")
# Click Load Dataset
await page.click("text=Load Dataset")
await page.click("text=iris")
# Wait for layer to appear
await page.wait_for_selector("text=Iris")
# Verify layer count
stats = await page.text_content(".layer-stats")
assert "150" in stats
Test Configuration
For CI/CD integration, configure Playwright in pyproject.toml:
[tool.pytest.ini_options]
asyncio_mode = "auto"
[tool.playwright]
browser = "chromium"
headless = true
Best Practices
Use Headless Mode: Always run in headless mode for CI/CD and scripted execution.
Wait for State: Use explicit waits instead of fixed timeouts when possible:
# Prefer this await page.wait_for_selector("text=Iris") # Over this await page.wait_for_timeout(2000)
API Fallback: When UI interactions are unreliable, use the backend API directly and then refresh the page.
Screenshot on Failure: Capture screenshots when tests fail for debugging:
try: await some_interaction() except Exception as e: await page.screenshot(path="debug_failure.png") raise
Clean State: Start each test/capture with a clean state by clearing data:
curl -X DELETE http://localhost:8000/scenarios/data
Troubleshooting
- Dialog Not Opening:
If clicking a button doesn’t open its dialog, try using JavaScript click:
await page.evaluate("""() => { document.querySelector('button:has-text("Open")').click(); }""")
- Element Not Found:
Increase timeouts or use more specific selectors:
await page.wait_for_selector("button:has-text('Submit')", timeout=10000)
- Screenshots Blank or Wrong State:
Add explicit waits after actions:
await page.click("button") await page.wait_for_timeout(1000) # Allow state to settle await page.screenshot(...)