Codapult
FeaturesPricingAPIHelpChangelog
Codapult

Ship Your SaaS Faster

Product

  • Features
  • Pricing
  • Plugins
  • API Reference
  • Help Center
  • Feature Requests
  • Changelog

Company

  • Contact
  • GitHub

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Codapult. All rights reserved.

All articles

Getting Started

  • Introduction
  • Quick Start
  • Project Structure

Configuration

  • Environment Variables
  • App Configuration

Authentication

  • Authentication

Database

  • Database

Teams

  • Teams & Organizations

Payments

  • Payments & Billing

Api

  • API Layer

Ai

  • AI Features

Email

  • Email

Infrastructure

  • Infrastructure

Ui

  • UI & Theming

I18n

  • Internationalization

Content Management

  • Content Management

Admin

  • Admin Panel

Security

  • Security

Monitoring

  • Analytics & Monitoring

Modules

  • Module Architecture

Plugins

  • Plugin System

Deployment

  • Deployment
  • Troubleshooting

Upgrading

  • Upgrading Codapult

Developer Tools

  • MCP Server
  • Testing
Developer Tools

Testing

Unit tests with Vitest and E2E tests with Playwright.

Overview

Codapult ships with two testing frameworks:

  • Vitest for unit and integration tests
  • Playwright for end-to-end browser tests

Both are pre-configured and ready to use.

Unit Tests (Vitest)

Unit tests are co-located with the source code — each test file lives next to the module it tests.

src/lib/payments/
├── stripe.ts
├── stripe.test.ts      ← unit test
├── lemonsqueezy.ts
└── lemonsqueezy.test.ts

Writing Tests

Vitest globals (describe, it, expect) are enabled — no imports needed:

import { calculateDiscount } from './pricing';

describe('calculateDiscount', () => {
  it('applies percentage discount', () => {
    const result = calculateDiscount(100, { type: 'percent', value: 20 });
    expect(result).toBe(80);
  });

  it('applies fixed discount', () => {
    const result = calculateDiscount(100, { type: 'fixed', value: 15 });
    expect(result).toBe(85);
  });

  it('never returns negative', () => {
    const result = calculateDiscount(10, { type: 'fixed', value: 50 });
    expect(result).toBe(0);
  });
});

Running Tests

pnpm test              # run all unit tests
pnpm test -- --watch   # watch mode (re-run on file changes)
pnpm test -- pricing   # run tests matching "pricing"

Mocking

Use Vitest's built-in mocking for dependencies:

import { vi } from 'vitest';
import { sendEmail } from './email';
import { resend } from './resend-client';

vi.mock('./resend-client', () => ({
  resend: { emails: { send: vi.fn() } },
}));

describe('sendEmail', () => {
  it('calls Resend with correct parameters', async () => {
    await sendEmail({ to: '[email protected]', subject: 'Welcome' });

    expect(resend.emails.send).toHaveBeenCalledWith(
      expect.objectContaining({
        to: '[email protected]',
        subject: 'Welcome',
      }),
    );
  });
});

E2E Tests (Playwright)

End-to-end tests live in the e2e/ directory and run against a real browser.

Configuration

Playwright is configured in playwright.config.ts at the project root. It includes:

  • Browsers: Chromium, Firefox, WebKit, and Mobile Chrome
  • Dev server: Starts automatically via pnpm dev before tests run
  • Artifacts: Screenshots on failure, traces on retry

Writing Tests

Use the page object pattern for maintainable tests:

import { test, expect } from '@playwright/test';

test.describe('Sign in', () => {
  test('shows validation error for invalid email', async ({ page }) => {
    await page.goto('/sign-in');
    await page.getByLabel('Email').fill('not-an-email');
    await page.getByRole('button', { name: 'Sign in' }).click();

    await expect(page.getByText('Invalid email')).toBeVisible();
  });

  test('redirects to dashboard after sign in', async ({ page }) => {
    await page.goto('/sign-in');
    await page.getByLabel('Email').fill('[email protected]');
    await page.getByLabel('Password').fill('password123');
    await page.getByRole('button', { name: 'Sign in' }).click();

    await expect(page).toHaveURL('/dashboard');
  });
});

Running Tests

pnpm test:e2e                    # run all E2E tests (headless)
pnpm test:e2e -- --headed        # run with visible browser
pnpm test:e2e -- --ui            # open Playwright UI mode
pnpm test:e2e -- --project=chromium  # run in Chromium only

Debugging Failures

When a test fails, Playwright saves artifacts to help diagnose the issue:

  • Screenshots are captured on failure
  • Traces are retained on failure and retries — open with npx playwright show-trace
npx playwright show-trace test-results/example-test/trace.zip

Scripts Reference

CommandWhat it runs
pnpm testVitest unit tests
pnpm test:e2ePlaywright E2E tests
pnpm lintESLint
pnpm formatPrettier (write)
pnpm format:checkPrettier (check only)

Best Practices

  • Co-locate unit tests with the code they test (*.test.ts next to the source file).
  • Use descriptive test names that explain the expected behavior, not the implementation.
  • Keep E2E tests focused on critical user flows — sign in, billing, core features.
  • Use page objects in E2E tests to avoid duplicating selectors across test files.
  • Run tests before committing — add pnpm test && pnpm lint to your pre-commit workflow.
  • Mock external services in unit tests (Resend, Stripe, AI providers) to keep tests fast and deterministic.
MCP Server