This document describes the testing infrastructure and best practices for Opencom.
Opencom uses a comprehensive testing strategy with three layers:
- Unit Tests - Vitest for testing utilities, hooks, and pure functions
- Integration Tests - Vitest with real Convex backend for testing database operations
- E2E Tests - Playwright for testing critical user flows in the browser
To avoid polluting development or production data, tests run against a dedicated Convex test deployment.
Initial Setup:
cd packages/convex
# Login to Convex (if not already)
npx convex login
# Create test deployment
npx convex dev --project opencom-test --onceConfiguration:
Create packages/convex/.env.test from the example and update it with your test deployment URL:
cp packages/convex/.env.test.example packages/convex/.env.testCONVEX_URL=https://your-test-deployment.convex.cloud
Each test suite creates isolated data using unique identifiers:
- Workspace Isolation: Each test suite creates a unique workspace with a timestamp suffix
- Automatic Cleanup: After each suite,
cleanupTestDataremoves all test data - No Cross-Contamination: Tests don't share data, enabling parallel execution
Authenticated Playwright suites use one account/workspace per parallel worker.
The fixture contract is implemented in apps/web/e2e/fixtures.ts.
- Worker bootstrap runs in a clean browser context (
storageState: undefined) - Each worker persists auth and test state to worker-unique files keyed by
parallelIndex - Tests on the same worker reuse only that worker's state
- Missing or malformed worker state fails setup with explicit actionable errors
- Auth refresh updates only the current worker's state files (no cross-worker mutation)
pnpm testNote: Convex integration suites are auto-excluded when CONVEX_URL is not configured. To run deployment-backed Convex tests, set CONVEX_URL in packages/convex/.env.test (or your shell environment) first. The target deployment must include the current test helper/testAdmin wiring used by the suites.
pnpm test:unitpnpm test:e2epnpm test:ci# In packages/convex
pnpm test:watch
# In apps/web
pnpm test:watchUse the test helpers in packages/convex/convex/testing/helpers.ts:
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { ConvexClient } from "convex/browser";
import { api } from "./_generated/api";
import { Id } from "./_generated/dataModel";
describe("myFeature", () => {
let client: ConvexClient;
let testWorkspaceId: Id<"workspaces">;
beforeAll(async () => {
const convexUrl = process.env.CONVEX_URL;
if (!convexUrl) {
throw new Error("CONVEX_URL environment variable is required");
}
client = new ConvexClient(convexUrl);
// Create isolated test workspace
const workspace = await client.mutation(api.testing.helpers.createTestWorkspace, {});
testWorkspaceId = workspace.workspaceId;
});
afterAll(async () => {
// Clean up test data
if (testWorkspaceId) {
await client.mutation(api.testing.helpers.cleanupTestData, {
workspaceId: testWorkspaceId,
});
}
await client.close();
});
it("should do something", async () => {
// Your test here
});
});| Helper | Description |
|---|---|
createTestWorkspace |
Creates an isolated workspace with unique name |
createTestUser |
Creates a user in the specified workspace |
createTestVisitor |
Creates a visitor with session ID |
createTestSessionToken |
Creates a signed session token for a visitor (required by visitor-facing endpoints) |
createTestConversation |
Creates a conversation |
createTestMessage |
Creates a message in a conversation |
cleanupTestData |
Removes all data for a workspace |
Use @testing-library/react for component tests:
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { MyComponent } from "./MyComponent";
describe("MyComponent", () => {
it("renders correctly", () => {
render(<MyComponent />);
expect(screen.getByText("Hello")).toBeInTheDocument();
});
});Use Playwright for end-to-end tests:
import { test, expect } from "@playwright/test";
test.describe("Feature", () => {
test("should work", async ({ page }) => {
await page.goto("/");
await expect(page.getByRole("heading")).toBeVisible();
});
});For authenticated suites, do not bypass the shared worker fixtures. New tests should consume the existing fixture setup so worker auth isolation and refresh behavior stay consistent.
Tests are co-located with source files:
packages/convex/convex/
workspaces.ts
workspaces.test.ts # Integration tests
apps/web/
src/
components/
ChatMessage.tsx
ChatMessage.test.tsx # Component tests
e2e/
auth.spec.ts # E2E tests
chat.spec.ts
- ✅ Create isolated test data using helpers
- ✅ Clean up after tests
- ✅ Use descriptive test names
- ✅ Test both success and error cases
- ✅ Use
data-testidattributes for E2E selectors - ✅ Keep tests focused and independent
- ❌ Share state between tests
- ❌ Rely on specific database IDs
- ❌ Skip cleanup in afterAll
- ❌ Use production or development deployments for tests
- ❌ Hard-code timeouts (use Playwright's auto-wait)
The CI pipeline runs two jobs:
1. Checks job:
- Install dependencies (
pnpm install --frozen-lockfile) - Standardized lint gates (
pnpm quality:lint) - Standardized typecheck gates (
pnpm quality:typecheck) - Security gates:
pnpm security:convex-auth-guard— unguarded handler detectionpnpm security:convex-any-args-gate—v.any()usage scanningpnpm security:secret-scan— committed secret detectionpnpm security:headers-check— security header validation
- Convex tests (
pnpm test:convex) - Web build (
pnpm --filter @opencom/web build) - Dependency audit gate
2. E2E job:
- Build widget for tests (
bash scripts/build-widget-for-tests.sh) - Playwright test suite
- Reliability report and gate (budget: 0 unexpected, <=5 flaky, <=70 skipped)
pnpm quality:lint
pnpm quality:typecheck
pnpm security:convex-auth-guard
pnpm security:convex-any-args-gate
pnpm security:secret-scan
pnpm security:headers-check
pnpm test:convex
pnpm --filter @opencom/web build
bash scripts/build-widget-for-tests.sh
pnpm web:test:e2e| Variable | Purpose |
|---|---|
CONVEX_URL |
Test deployment URL |
E2E_BACKEND_URL |
Backend target for Playwright E2E |
E2E_TEST_PASSWORD |
Password for E2E auth bootstrap |
TEST_ADMIN_SECRET |
Secret for E2E test data seeding/cleanup |
ALLOW_TEST_DATA |
Must be "true" on test Convex deployment |
CI |
Set to true for CI-specific behavior |
Security gates run as part of CI and can be run locally:
| Command | Script | Config File | Purpose |
|---|---|---|---|
pnpm security:convex-auth-guard |
scripts/ci-convex-auth-guard.js |
security/convex-raw-handler-registry.json |
Detect unguarded Convex handlers |
pnpm security:convex-any-args-gate |
scripts/ci-convex-any-args-gate.js |
security/convex-v-any-arg-exceptions.json |
Detect v.any() in function args |
pnpm security:secret-scan |
scripts/ci-secret-scan.js |
— | Scan for committed secrets |
pnpm security:headers-check |
scripts/ci-security-headers-check.js |
— | Validate security headers |
Run pnpm security:secret-scan before pushing changes. If a finding is a verified
false positive, use security/secret-scan-exceptions.json with a minimal file/rule scope
plus owner, reason, and expiry metadata instead of broad ignores.
E2E test runs are logged to test-run-log.jsonl for reliability analysis.
pnpm test:summary # Show recent run summary
pnpm test:clear # Clear run history
pnpm test:e2e:prod # Run E2E against production build pathReliability budgets (security/e2e-reliability-budget.json):
| Metric | Budget |
|---|---|
| Unexpected failures | 0 |
| Flaky tests | 5 |
| Skipped tests | 70 |
All helpers are in packages/convex/convex/testing/helpers.ts:
| Helper | Description |
|---|---|
createTestWorkspace |
Creates workspace with unique timestamp-suffixed name |
createTestUser |
Creates user with workspace membership |
createTestVisitor |
Creates visitor with session ID |
createTestSessionToken |
Creates signed session token for a visitor |
createTestConversation |
Creates conversation in workspace |
createTestMessage |
Creates message in conversation |
cleanupTestData |
Removes all data for a workspace |
E2E tests use the testAdmin:runTestMutation gateway (packages/convex/convex/testAdmin.ts) for data seeding and cleanup. This gateway:
- Requires
TEST_ADMIN_SECRETto match the deployment secret - Only works when
ALLOW_TEST_DATA=trueon the deployment - Proxies mutations through an authenticated internal path
Ensure .env.test exists in packages/convex/ with a valid test deployment URL.
Check that CONVEX_URL in .env.test points to a dedicated test deployment, not your dev or prod deployment.
- Ensure the web server is running (
pnpm dev:web) - Check that
playwright.config.tshas correctbaseURL - Increase timeout in config if needed
Ensure the Convex deployment has matching TEST_ADMIN_SECRET and ALLOW_TEST_DATA=true.
Run npx convex dev --once in packages/convex/ to regenerate types.
Convex integration suites auto-skip when CONVEX_URL is not configured. Set it in packages/convex/.env.test or your shell environment.