NOTE
This document provides implementation details and migration steps. For authoritative testing conventions (directory layout, naming, test types), see Testing Conventions.
Objective
Implement comprehensive automated testing across TendSocial monorepo using approved testing tools. Migrate any existing tests from other frameworks to the standard stack.
Monorepo Structure
tendsocial/
├── apps/
│ ├── api/ # Backend API (Node.js/Express)
│ ├── app/ # Frontend Application (React)
│ └── web/ # Marketing Site (React)
├── packages/ # Shared packages (if any)
└── package.json # Root package.json2
3
4
5
6
7
Approved Testing Stack
Core Testing Tools (ONLY use these)
- Vitest - Test runner for unit and integration tests (all workspaces)
- React Testing Library - React component testing (app + web)
- Playwright - End-to-end browser testing (root level)
- Supertest - API endpoint testing (api only)
- MSW (Mock Service Worker) - API mocking (app + web)
Implementation Requirements
1. Install Dependencies
Root Level (for E2E tests) - DONE!
# From monorepo root
pnpm add -D -w playwright @playwright/test
# Initialize Playwright browsers
pnpm exec playwright install2
3
4
5
apps/backend (Backend)
# From monorepo root
pnpm add -D --filter=api vitest @vitest/ui
pnpm add -D --filter=api supertest @types/supertest2
3
apps/frontend (Frontend Application) - DONE!
# From monorepo root
pnpm add -D --filter=app vitest @vitest/ui
pnpm add -D --filter=app @testing-library/react @testing-library/dom @testing-library/user-event @testing-library/jest-dom
pnpm add -D --filter=app msw2
3
4
apps/marketing (Marketing Site) - DONE!
# From monorepo root
pnpm add -D --filter=web vitest @vitest/ui
pnpm add -D --filter=web @testing-library/react @testing-library/dom @testing-library/user-event @testing-library/jest-dom
pnpm add -D --filter=web msw2
3
4
2. Configuration Files to Create
Root Level - playwright.config.ts
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './test/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000', // Adjust based on your app port
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
webServer: [
{
command: 'pnpm --filter=api dev',
url: 'http://localhost:4000',
reuseExistingServer: !process.env.CI,
},
{
command: 'pnpm --filter=app dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
],
})2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
apps/backend/vitest.config.ts
import { defineConfig } from 'vitest/config'
import path from 'path'
export default defineConfig({
test: {
globals: true,
environment: 'node',
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['**/node_modules/**', '**/dist/**'],
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apps/backend/src/test/setup.ts
import { beforeAll, afterAll, afterEach } from 'vitest'
// Global test setup for API tests
beforeAll(() => {
// Setup test database, etc.
})
afterEach(() => {
// Clean up after each test
})
afterAll(() => {
// Cleanup connections, etc.
})2
3
4
5
6
7
8
9
10
11
12
13
14
apps/frontend/vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['**/node_modules/**', '**/dist/**', '**/*.config.*'],
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apps/frontend/src/test/setup.ts
import { expect, afterEach, beforeAll, afterAll } from 'vitest'
import { cleanup } from '@testing-library/react'
import * as matchers from '@testing-library/jest-dom/matchers'
import { server } from '../mocks/server'
expect.extend(matchers)
// Start MSW server before all tests
beforeAll(() => server.listen())
// Cleanup after each test
afterEach(() => {
cleanup()
server.resetHandlers()
})
// Stop MSW server after all tests
afterAll(() => server.close())2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apps/frontend/src/mocks/handlers.ts
import { http, HttpResponse } from 'msw'
export const handlers = [
// Define your API mocks here
http.get('/api/example', () => {
return HttpResponse.json({ message: 'mocked response' })
}),
]2
3
4
5
6
7
8
apps/frontend/src/mocks/browser.ts
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)2
3
4
apps/frontend/src/mocks/server.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)2
3
4
apps/marketing/vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['**/node_modules/**', '**/dist/**', '**/*.config.*'],
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apps/marketing/src/test/setup.ts
import { expect, afterEach, beforeAll, afterAll } from 'vitest'
import { cleanup } from '@testing-library/react'
import * as matchers from '@testing-library/jest-dom/matchers'
import { server } from '../mocks/server'
expect.extend(matchers)
// Start MSW server before all tests
beforeAll(() => server.listen())
// Cleanup after each test
afterEach(() => {
cleanup()
server.resetHandlers()
})
// Stop MSW server after all tests
afterAll(() => server.close())2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apps/marketing/src/mocks/ (same structure as apps/frontend)
Create the same MSW setup files as in apps/frontend
3. Package.json Scripts
Root package.json
{
"scripts": {
"test": "pnpm -r test",
"test:api": "pnpm --filter=api test",
"test:app": "pnpm --filter=app test",
"test:web": "pnpm --filter=web test",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:coverage": "pnpm -r test:coverage"
}
}2
3
4
5
6
7
8
9
10
11
apps/backend/package.json
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage"
}
}2
3
4
5
6
7
apps/frontend/package.json
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage"
}
}2
3
4
5
6
7
apps/marketing/package.json
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage"
}
}2
3
4
5
6
7
4. Migration Tasks
Migrate FROM these (if found):
- Jest → Vitest
- Enzyme → React Testing Library
- Cypress → Playwright
- Selenium → Playwright
- TestCafe → Playwright
- react-test-renderer → React Testing Library
- Mocha/Chai → Vitest
- Jasmine → Vitest
- Any custom fetch mocking → MSW
Migration Steps:
- Search for existing test files using deprecated tools
- Convert test syntax to approved tools
- Remove deprecated dependencies from package.json
- Update imports in all test files
- Verify all tests pass after migration
5. Test Organization Structure
tendsocial/
├── test/
│ └── e2e/ # E2E tests (Playwright)
│ ├── auth.spec.ts
│ ├── user-flow.spec.ts
│ └── ...
├── apps/
│ ├── api/
│ │ ├── src/
│ │ │ ├── routes/
│ │ │ │ ├── users.ts
│ │ │ │ └── users.test.ts # Vitest + Supertest
│ │ │ ├── services/
│ │ │ │ ├── auth.ts
│ │ │ │ └── auth.test.ts # Vitest
│ │ │ └── test/
│ │ │ └── setup.ts
│ │ └── vitest.config.ts
│ ├── app/
│ │ ├── src/
│ │ │ ├── components/
│ │ │ │ ├── Button/
│ │ │ │ │ ├── Button.tsx
│ │ │ │ │ └── Button.test.tsx # Vitest + RTL
│ │ │ ├── hooks/
│ │ │ │ ├── useAuth.ts
│ │ │ │ └── useAuth.test.ts # Vitest
│ │ │ ├── mocks/
│ │ │ │ ├── handlers.ts
│ │ │ │ ├── browser.ts
│ │ │ │ └── server.ts
│ │ │ └── test/
│ │ │ └── setup.ts
│ │ └── vitest.config.ts
│ └── web/
│ ├── src/
│ │ ├── components/
│ │ │ ├── Hero/
│ │ │ │ ├── Hero.tsx
│ │ │ │ └── Hero.test.tsx # Vitest + RTL
│ │ ├── mocks/
│ │ │ ├── handlers.ts
│ │ │ ├── browser.ts
│ │ │ └── server.ts
│ │ └── test/
│ │ └── setup.ts
│ └── vitest.config.ts
└── playwright.config.ts2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
6. Test Writing Guidelines
Component Tests (Vitest + React Testing Library):
import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { Button } from './Button'
describe('Button', () => {
it('renders with correct text', () => {
render(<Button>Click me</Button>)
expect(screen.getByText('Click me')).toBeInTheDocument()
})
})2
3
4
5
6
7
8
9
10
API Tests (Vitest + Supertest):
import { describe, it, expect } from 'vitest'
import request from 'supertest'
import { app } from '../app'
describe('GET /api/users', () => {
it('returns users list', async () => {
const response = await request(app)
.get('/api/users')
.expect(200)
expect(response.body).toHaveProperty('users')
})
})2
3
4
5
6
7
8
9
10
11
12
13
E2E Tests (Playwright):
import { test, expect } from '@playwright/test'
test('user can log in', async ({ page }) => {
await page.goto('/')
await page.click('text=Login')
await page.fill('[name="email"]', 'test@example.com')
await page.fill('[name="password"]', 'password')
await page.click('button[type="submit"]')
await expect(page).toHaveURL('/dashboard')
})2
3
4
5
6
7
8
9
10
11
With MSW Mocking:
import { render, screen, waitFor } from '@testing-library/react'
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest'
import { server } from '../mocks/server'
import { UserProfile } from './UserProfile'
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
describe('UserProfile', () => {
it('loads and displays user data', async () => {
render(<UserProfile userId="123" />)
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument()
})
})
})2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Action Items for AI Agent
Phase 1: Setup & Configuration
Install dependencies in correct workspaces
- Root: Playwright only
- apps/backend: Vitest, Supertest
- apps/frontend: Vitest, React Testing Library, MSW
- apps/marketing: Vitest, React Testing Library, MSW
Create configuration files
- Root: playwright.config.ts
- apps/backend: vitest.config.ts + test setup
- apps/frontend: vitest.config.ts + test setup + MSW setup
- apps/marketing: vitest.config.ts + test setup + MSW setup
Add test scripts to all package.json files
- Root: orchestration scripts
- Each workspace: local test scripts
Phase 2: Migration
Audit existing tests in each workspace
- apps/backend: Check for Jest, Mocha, or other test runners
- apps/frontend: Check for Jest, Enzyme, Cypress
- apps/marketing: Check for Jest, Enzyme, Cypress
Migrate existing tests
- Convert test syntax to approved tools
- Update imports in all test files
- Remove deprecated dependencies from package.json files
Phase 3: Implementation
Create test folder structure as specified above
Write missing tests:
- apps/backend: All API routes and services
- apps/frontend: All React components, hooks, and utilities
- apps/marketing: All React components (especially marketing pages)
- test/e2e/: Critical user flows across the full application
Set up MSW handlers (apps/frontend and apps/marketing only)
- Define handlers for all external API calls
- Share common handlers if needed
Update CI/CD pipelines
- Ensure all workspace tests run:
pnpm test - Ensure E2E tests run:
pnpm test:e2e - Generate and upload coverage reports
- Ensure all workspace tests run:
Generate coverage reports - aim for 80%+ coverage per workspace
Validation Checklist
Workspace-Specific Validation
apps/backend
- [ ] No deprecated testing libraries in package.json
- [ ] Vitest and Supertest installed and configured
- [ ] vitest.config.ts created and working
- [ ] All API routes have tests
- [ ] All services have tests
- [ ] Tests passing:
pnpm --filter=api test
apps/frontend
- [ ] No deprecated testing libraries in package.json
- [ ] Vitest, React Testing Library, and MSW installed
- [ ] vitest.config.ts created and working
- [ ] MSW handlers defined
- [ ] All components have tests
- [ ] All hooks have tests
- [ ] Tests passing:
pnpm --filter=app test
apps/marketing
- [ ] No deprecated testing libraries in package.json
- [ ] Vitest, React Testing Library, and MSW installed
- [ ] vitest.config.ts created and working
- [ ] MSW handlers defined (if needed)
- [ ] Marketing components have tests
- [ ] Tests passing:
pnpm --filter=web test
Root (E2E)
- [ ] Playwright installed at root level
- [ ] playwright.config.ts created and working
- [ ] Critical user flows covered
- [ ] E2E tests passing:
pnpm test:e2e
Overall Validation
- [ ] All tests pass:
pnpm test - [ ] Coverage reports generating:
pnpm test:coverage - [ ] E2E tests can run locally and in CI
- [ ] CI/CD pipeline updated with monorepo test commands
Notes
General
- Use Vitest's built-in coverage (no need for separate istanbul/nyc)
- Playwright includes visual regression testing capabilities
- MSW works in both browser and Node.js environments
- React Testing Library encourages testing user behavior, not implementation
- All tools are free for commercial use and actively maintained as of November 2025
Monorepo-Specific
- Use
pnpm --filter=<workspace>to run commands in specific workspaces - Use
pnpm -rto run commands recursively in all workspaces - Playwright runs at root level and can test across all apps
- MSW is only needed in frontend workspaces (app, web)
- Supertest is only needed in backend workspace (api)
- Each workspace maintains its own test configuration
- Shared test utilities can be placed in a
packages/test-utilsworkspace if needed
Running Tests
# Run all tests in all workspaces
pnpm test
# Run tests in specific workspace
pnpm --filter=api test
pnpm --filter=app test
pnpm --filter=web test
# Run E2E tests
pnpm test:e2e
# Run with UI
pnpm --filter=app test:ui
pnpm test:e2e:ui
# Generate coverage
pnpm test:coverage2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Execute this migration systematically. Start with configuration, then migrate existing tests, then write new tests for uncovered code.