T
Tenanto
Documentation / E2E Testing

E2E Testing

Updated Jan 25, 2026

E2E Testing with Playwright

Tenanto includes a comprehensive end-to-end test suite with 484 tests covering all critical user journeys.

Overview

Category Tests Description
Authentication 45 Login, logout, registration, password reset
Admin Panel 120 Tenants, users, licenses CRUD operations
Tenant Panel 95 Projects, tasks, teams, settings
API Endpoints 65 REST API authentication and CRUD
Tenant Isolation 41 Cross-tenant security, permission enforcement
Navigation 48 Sidebar, breadcrumbs, responsive layout
Error Pages 25 403, 404, 500 error handling
Onboarding 20 Wizard flow, progress tracking
Profile & Settings 25 User profile management

Why Playwright?

Installation

# Install Playwright and browsers
npm install
npx playwright install

# Or install specific browsers
npx playwright install chromium firefox webkit

Running Tests

Quick Start

# All tests (all browsers, all projects)
npx playwright test

# All chromium tests (recommended for development)
npx playwright test --project=admin-chromium --project=tenant-chromium

# With HTML report
npx playwright test --reporter=html

Available Projects

Tests are organized by category. Each project targets specific functionality:

Project Tests Description
admin-chromium 84 System admin panel (tenants, users, licenses)
tenant-chromium 171 Tenant panel (projects, tasks, teams, billing)
navigation-chromium 48 Sidebar, breadcrumbs, responsive layout
auth-chromium 45 Login, logout, registration, password reset
api-chromium 65 REST API endpoints
isolation-chromium 41 Cross-tenant security tests
profile-chromium 25 User profile management
onboarding-chromium 20 Wizard flow
errors-chromium 25 Error pages (403, 404, 500)

Cross-browser projects: admin-firefox, tenant-firefox, admin-webkit, tenant-webkit Mobile projects: tenant-mobile-chrome, tenant-mobile-safari

Running Specific Projects

# Admin panel tests only
npx playwright test --project=admin-chromium

# Tenant panel tests only
npx playwright test --project=tenant-chromium

# Multiple projects
npx playwright test --project=admin-chromium --project=tenant-chromium

# Specific file within a project
npx playwright test e2e/admin/licenses.spec.ts --project=admin-chromium

Debugging & Watching Tests

# UI mode (best for debugging - visual timeline, step through)
npx playwright test --ui

# Debug mode (pause at each step, inspector)
npx playwright test --project=tenant-chromium --debug

# Headed mode (watch browser, single worker for clarity)
npx playwright test --project=tenant-chromium --headed --workers=1

# Specific test in debug mode
npx playwright test e2e/tenant/billing.spec.ts:20 --project=tenant-chromium --debug

Test Structure

e2e/
├── admin/                      # System admin panel tests
│   ├── licenses.spec.ts        # License management CRUD
│   ├── tenants.spec.ts         # Tenant management CRUD
│   └── users.spec.ts           # User management CRUD
├── api/                        # REST API tests
│   └── api.spec.ts             # Auth, Projects, Tasks API
├── auth/                       # Authentication tests
│   ├── login.spec.ts           # Login flow
│   ├── logout.spec.ts          # Logout flow
│   ├── password-reset.spec.ts  # Password reset
│   ├── registration.spec.ts    # User registration
│   └── session.spec.ts         # Session management
├── errors/                     # Error page tests
│   └── error-pages.spec.ts     # 403, 404, 500 pages
├── helpers/                    # Test utilities
│   ├── auth.ts                 # Login helpers, test users
│   └── api.ts                  # API request helpers
├── isolation/                  # Security isolation tests
│   ├── cross-tenant-api.spec.ts    # API tenant isolation
│   └── permission-isolation.spec.ts # Role-based access
├── navigation/                 # UI navigation tests
│   └── navigation.spec.ts      # Sidebar, breadcrumbs
├── onboarding/                 # Onboarding wizard tests
│   └── onboarding.spec.ts      # Wizard flow
├── profile/                    # User profile tests
│   └── profile.spec.ts         # Profile management
└── tenant/                     # Tenant panel tests
    ├── projects.spec.ts        # Project CRUD
    ├── settings.spec.ts        # Tenant settings
    ├── tasks.spec.ts           # Task CRUD
    └── teams.spec.ts           # Team management

Test Helpers

Authentication (e2e/helpers/auth.ts)

import { testUsers, loginAsSystemAdmin, loginAsTenantOwner } from '../helpers/auth';

// Pre-configured test users
testUsers.systemAdmin    // [email protected]
testUsers.demoOwner      // [email protected]
testUsers.demoAdmin      // [email protected]
testUsers.demoMember     // [email protected]

// Login helpers
await loginAsSystemAdmin(page);
await loginAsTenantOwner(page);
await loginAsTenantAdmin(page);
await loginAsTenantMember(page);

API Helpers (e2e/helpers/api.ts)

import { apiHeaders, authHeader, getProjects } from '../helpers/api';

// Get API token
const response = await request.post(`${baseUrl}/api/v1/auth/login`, {
    headers: apiHeaders,
    data: { email, password },
});
const { token } = (await response.json()).data;

// Make authenticated request
const projects = await getProjects(request, token, baseUrl);

Configuration

The test configuration is in playwright.config.ts:

export default defineConfig({
    testDir: './e2e',
    fullyParallel: true,
    forbidOnly: !!process.env.CI,
    retries: process.env.CI ? 2 : 2,
    workers: process.env.CI ? 1 : 2,
    timeout: 60000,

    use: {
        baseURL: 'http://demo.tenanto.local',
        trace: 'on-first-retry',
        screenshot: 'only-on-failure',
        video: 'on-first-retry',
        actionTimeout: 15000,
    },

    projects: [
        { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
        { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
        { name: 'webkit', use: { ...devices['Desktop Safari'] } },
        { name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
        { name: 'Mobile Safari', use: { ...devices['iPhone 12'] } },
    ],
});

Rate Limiting

E2E tests perform many login requests which would normally hit rate limits. Tenanto handles this by increasing rate limits in local and testing environments:

This is implemented in:

Both use app()->environment('local', 'testing') to detect the environment and apply higher limits.

Note: No session persistence (storageState) is needed. Each test logs in fresh, which provides better isolation and simpler test maintenance.

Test Isolation

Tests are configured for proper isolation:

// Run tests in order (no parallel)
test.describe.configure({ mode: 'serial' });

// Skip on non-chromium (API tests don't need all browsers)
test.beforeEach(async ({ browserName }) => {
    if (browserName !== 'chromium') test.skip();
});

Example Tests

Login Test

test('user can login with valid credentials', async ({ page }) => {
    await page.goto('/login');
    await page.fill('input[name="email"]', '[email protected]');
    await page.fill('input[name="password"]', 'password');
    await page.click('button[type="submit"]');
    await expect(page).toHaveURL(/.*\/app/);
});

API Test

test('can create project via API', async ({ request }) => {
    const token = await getToken(request);
    const response = await request.post(`${baseUrl}/api/v1/projects`, {
        headers: authHeader(token),
        data: { name: `Test Project ${Date.now()}` },
    });
    expect(response.ok()).toBeTruthy();
});

Tenant Isolation Test

test('user from tenant A cannot access tenant B projects via API', async ({ request }) => {
    // Login as demo tenant user
    const demoToken = await getTokenForTenant(request, 'demo');

    // Login as acme tenant user
    const acmeToken = await getTokenForTenant(request, 'acme');

    // Create project in demo tenant
    const projectRes = await request.post(`${demoUrl}/api/v1/projects`, {
        headers: authHeader(demoToken),
        data: { name: 'Secret Project' },
    });
    const projectId = (await projectRes.json()).data.id;

    // Acme user should NOT be able to access demo's project
    const accessRes = await request.get(`${acmeUrl}/api/v1/projects/${projectId}`, {
        headers: authHeader(acmeToken),
    });
    expect(accessRes.status()).toBe(404); // Or 403 - either is secure
});

Debugging

Trace Viewer

When tests fail, trace files are saved automatically:

npx playwright show-trace test-results/*/trace.zip

Screenshots

Screenshots are saved on failure in test-results/.

Video Recording

Videos are recorded on first retry. View in test-results/.

Inspector Mode

Step through tests interactively:

npx playwright test --debug

CI/CD Integration

GitLab CI

e2e-tests:
  stage: test
  image: mcr.microsoft.com/playwright:v1.40.0-jammy
  script:
    - npm ci
    - npx playwright install --with-deps
    - npx playwright test --project=chromium
  artifacts:
    when: always
    paths:
      - playwright-report/
      - test-results/
    expire_in: 1 week
  only:
    - merge_requests
    - main

GitHub Actions

- name: Run E2E tests
  run: |
    npm ci
    npx playwright install --with-deps
    npx playwright test
- uses: actions/upload-artifact@v3
  if: always()
  with:
    name: playwright-report
    path: playwright-report/

Best Practices

  1. Test Isolation - Each test should be independent
  2. Use Helpers - Centralize login logic and common operations
  3. Avoid Sleep - Playwright auto-waits, use expect() assertions
  4. Serial for State - Use serial mode when tests share state (tokens)
  5. Browser Targeting - Skip non-chromium for API-only tests
  6. Fresh Logins - Each test logs in fresh (rate limit is 100 in local/testing)

Related Documentation