Testing Overview
Meelio uses modern testing tools to ensure code quality and reliability. The web application uses Playwright for end-to-end testing, while the extension relies on manual testing workflows.
Testing Setup
The web app is configured with Playwright for comprehensive browser-based testing.
Playwright Configuration
The web app’s Playwright configuration is located at apps/web/playwright.config.ts:
export default defineConfig ({
testDir: './e2e' ,
fullyParallel: false ,
forbidOnly: !! process . env . CI ,
retries: process . env . CI ? 2 : 0 ,
workers: 1 ,
reporter: 'html' ,
use: {
baseURL: 'http://localhost:4000' ,
trace: 'on-first-retry' ,
screenshot: 'only-on-failure' ,
} ,
}) ;
Test Directory Structure
Tests are organized in the web app:
apps/web/
├── e2e/ # End-to-end tests (Playwright)
│ ├── *.spec.ts # Test specifications
│ └── fixtures/ # Test fixtures and helpers
└── playwright.config.ts # Playwright configuration
Running Tests
Run All Tests
# From repository root
pnpm test
# From web app directory
cd apps/web && pnpm test
This executes all Playwright tests using the configuration in playwright.config.ts.
Test Commands
The web app provides multiple test commands for different scenarios:
Standard Test Run
UI Mode
Debug Mode
Headed Mode
# Run tests in headless mode
pnpm test
# Equivalent to:
# playwright test
Running Specific Tests
# Run a specific test file
pnpm test e2e/timer.spec.ts
# Run tests matching a pattern
pnpm test -g "timer functionality"
# Run tests in a specific browser
pnpm test --project=chromium
Test Execution Details
Automatic Dev Server
Playwright automatically starts the development server before running tests:
webServer : {
command : 'npm run dev' ,
url : 'http://localhost:4000' ,
reuseExistingServer : ! process . env . CI ,
timeout : 120 * 1000 ,
}
You don’t need to manually start the dev server - Playwright handles it automatically!
Browser Configuration
Currently configured for:
Chromium (Desktop Chrome device emulation)
Additional browsers can be enabled by uncommenting in playwright.config.ts:
Firefox
WebKit (Safari)
Mobile Chrome
Mobile Safari
Test Execution Settings
Setting Development CI Parallel No (workers: 1) No Retries 0 2 forbidOnly No Yes Reporter HTML HTML
Tests run sequentially (workers: 1) to avoid conflicts with shared state in IndexedDB.
Writing Tests
Test File Structure
Create test files in apps/web/e2e/ with the .spec.ts extension:
import { test , expect } from '@playwright/test' ;
test . describe ( 'Timer functionality' , () => {
test . beforeEach ( async ({ page }) => {
await page . goto ( '/' );
});
test ( 'should start and pause timer' , async ({ page }) => {
// Click start button
await page . click ( '[data-testid="timer-start"]' );
// Verify timer is running
await expect ( page . locator ( '[data-testid="timer-status"]' ))
. toHaveText ( 'Running' );
// Click pause button
await page . click ( '[data-testid="timer-pause"]' );
// Verify timer is paused
await expect ( page . locator ( '[data-testid="timer-status"]' ))
. toHaveText ( 'Paused' );
});
test ( 'should reset timer' , async ({ page }) => {
await page . click ( '[data-testid="timer-start"]' );
await page . click ( '[data-testid="timer-reset"]' );
await expect ( page . locator ( '[data-testid="timer-display"]' ))
. toHaveText ( '25:00' );
});
});
Best Practices for Test Writing
Use Data Test IDs
Add data-testid attributes to components for reliable selectors: < button data-testid = "timer-start" > Start </ button >
Avoid CSS selectors that may change with styling updates.
Test User Workflows
Focus on end-to-end user journeys rather than implementation details: test ( 'complete pomodoro cycle' , async ({ page }) => {
// User starts timer
// User completes work session
// User takes break
// User sees completion stats
});
Handle Async Operations
Wait for elements and state changes: // Wait for element to be visible
await page . waitForSelector ( '[data-testid="notification"]' );
// Wait for specific state
await expect ( page . locator ( '.task-item' ))
. toHaveCount ( 3 );
Test Offline Functionality
Since Meelio is offline-first, test without network: test ( 'works offline' , async ({ page , context }) => {
await context . setOffline ( true );
await page . goto ( '/' );
// App should still function
await expect ( page . locator ( '[data-testid="timer"]' ))
. toBeVisible ();
});
Clean Up Test Data
Clear IndexedDB between tests to avoid state leakage: test . afterEach ( async ({ page }) => {
await page . evaluate (() => {
indexedDB . deleteDatabase ( 'meelio-db' );
});
});
Testing IndexedDB
Meelio stores data locally in IndexedDB. Test data persistence:
test ( 'persists tasks in IndexedDB' , async ({ page }) => {
// Create a task
await page . fill ( '[data-testid="task-input"]' , 'Test task' );
await page . click ( '[data-testid="task-add"]' );
// Verify in IndexedDB
const tasks = await page . evaluate ( async () => {
const db = await window . indexedDB . open ( 'meelio-db' );
// Query tasks from IndexedDB
return tasks ;
});
expect ( tasks ). toHaveLength ( 1 );
expect ( tasks [ 0 ]. title ). toBe ( 'Test task' );
});
Test Coverage
Generate test coverage reports:
# Run tests with coverage (if configured)
pnpm test --coverage
Coverage output is generated in apps/web/coverage/.
Coverage configuration may need to be added to playwright.config.ts depending on your coverage tool preferences.
Testing Web App vs Extension
Web App Testing
Tool : Playwright
What to Test :
Timer and Pomodoro functionality
Task and note management
Settings persistence
UI responsiveness
Offline functionality
PWA capabilities
Running Tests :
Extension Testing
Tool : Manual testing (no automated test suite currently)
What to Test :
New tab replacement
Browser action popup
Site blocker functionality
Tab stash and management
Bookmark integration
Permissions handling
Cross-browser compatibility
Manual Testing Workflow :
Load in Browser
Load the unpacked extension from: apps/extension/build/chrome-mv3-prod
Test Core Features
Open new tab (tests new tab override)
Click extension icon (tests popup)
Test site blocking in settings
Test tab stash functionality
Verify data persistence across sessions
Test Cross-Browser
Build and test in multiple browsers: pnpm build:edge
pnpm build:firefox
Extension testing is currently manual. Consider adding automated extension testing with Puppeteer or Playwright for extensions in the future.
Debugging Test Failures
Using Playwright Inspector
# Run with debugger
pnpm test:debug
Playwright Inspector allows you to:
Step through test execution
Inspect element selectors
View browser state at each step
Take screenshots at any point
Using UI Mode
# Interactive test runner
pnpm test:ui
UI Mode provides:
Visual test execution
Time travel debugging
Network inspection
Console logs
Screenshots and videos
Viewing Test Reports
After tests run, view the HTML report:
# Open test report
npx playwright show-report
Reports include:
Test results and duration
Screenshots on failure
Execution traces
Error messages and stack traces
Common Test Issues
Flaky Tests :
// Add explicit waits
await page . waitForLoadState ( 'networkidle' );
// Use expect with timeout
await expect ( page . locator ( '.element' ))
. toBeVisible ({ timeout: 5000 });
Timeout Errors :
// Increase timeout for slow operations
test ( 'slow operation' , async ({ page }) => {
test . setTimeout ( 60000 ); // 60 seconds
await page . waitForSelector ( '.result' , { timeout: 30000 });
});
Element Not Found :
// Wait for element before interacting
await page . waitForSelector ( '[data-testid="button"]' );
await page . click ( '[data-testid="button"]' );
Test Environment Configuration
Environment Variables for Tests
Set test-specific environment variables:
# .env.test.local
VITE_API_URL = http://localhost:3000/api
VITE_ENABLE_DEBUG = true
CI/CD Test Execution
In CI environments, Playwright automatically:
Uses headless mode
Enables test retries (2 attempts)
Forbids .only() to prevent running single tests
Generates HTML reports
# Example GitHub Actions
- name : Run tests
run : pnpm test
- name : Upload test results
if : always()
uses : actions/upload-artifact@v3
with :
name : playwright-report
path : apps/web/playwright-report/
Test app performance and responsiveness:
test ( 'timer renders within performance budget' , async ({ page }) => {
const startTime = Date . now ();
await page . goto ( '/' );
await page . waitForSelector ( '[data-testid="timer"]' );
const loadTime = Date . now () - startTime ;
expect ( loadTime ). toBeLessThan ( 1000 ); // 1 second
});
Next Steps
Tip : Run tests before committing code to catch issues early!