Skip to main content
ChatJS includes two testing layers: Vitest for unit tests and Playwright for end-to-end browser tests. For AI-specific quality evaluation, see Evaluations.

Unit Tests (Vitest)

Unit tests cover individual functions and modules. They live alongside the source code as *.test.ts files. Vitest is configured in vitest.config.ts to exclude *.e2e.ts files so E2E tests never run as unit tests.
# Run all unit tests
bun test:unit
Tests use standard Vitest patterns with vi.mock() for mocking dependencies:
import { describe, it, expect, vi } from "vitest";

describe("myFunction", () => {
  it("returns expected result", () => {
    expect(myFunction("input")).toBe("output");
  });
});

Mocking AI models

For tests that interact with AI models, mock the model resolution layer rather than the AI SDK directly:
vi.mock("../ai/app-models", () => ({
  getAppModelDefinition: vi.fn().mockImplementation((modelId: string) => {
    const models: Record<
      string,
      { pricing?: { input: string; output: string } }
    > = {
      "test-model": { pricing: { input: "0.00001", output: "0.00003" } },
    };
    return Promise.resolve(models[modelId] || {});
  }),
}));

End-to-End Tests (Playwright)

Playwright tests simulate real user interactions in a browser. Configuration is in playwright.config.ts.
# Run E2E tests
bun test:e2e

Test projects

The Playwright config defines five test projects that run in dependency order:
ProjectFileDepends OnPurpose
setup:authauth.setup.e2e.ts-Authenticate and save session
setup:reasoningreasoning.setup.e2e.tssetup:authPrepare reasoning model state
chatchat.e2e.tssetup:authCore chat interactions
reasoningreasoning.e2e.tssetup:reasoningReasoning model tests
artifactsartifacts.e2e.tssetup:authCanvas and document tests

CI behavior

SettingLocalCI
WorkersUnlimited1
Retries12
ServerReuse running dev serverStart fresh
TracesOn first retryOn first retry

Writing E2E tests

Create test files in the tests/ directory using the *.e2e.ts naming convention. Tests have access to authenticated sessions via Playwright’s storage state:
import { test, expect } from "@playwright/test";

test("send a message and get a response", async ({ page }) => {
  await page.goto("/");
  await page.fill('[data-testid="chat-input"]', "Hello");
  await page.click('[data-testid="send-button"]');
  await expect(page.locator('[data-testid="assistant-message"]')).toBeVisible();
});

Running All Tests

# Unit + E2E
bun test
This runs Playwright first, then Vitest.