Skip to main content

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:
# 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

SettingDevelopmentCI
ParallelNo (workers: 1)No
Retries02
forbidOnlyNoYes
ReporterHTMLHTML
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

1

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.
2

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
});
3

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);
4

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();
});
5

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:
cd apps/web
pnpm test

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:
1

Build Extension

pnpm build:extension
2

Load in Browser

Load the unpacked extension from:
apps/extension/build/chrome-mv3-prod
3

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
4

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/

Performance Testing

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!
pnpm lint && pnpm test

Build docs developers (and LLMs) love