Last Updated: October 18, 2025
Project: DocuNote (formerly FileChat AI / NoteChat-AI)
Author: Development Team
- Overview
- Jest Tests - Unit & Integration Testing
- Playwright Tests - End-to-End Testing
- Key Differences
- When to Use Each
- Running Tests
- Project-Specific Examples
- Best Practices
DocuNote uses a two-tier testing strategy to ensure code quality and user experience:
- Jest - Fast, isolated unit and integration tests for components and logic
- Playwright - Comprehensive end-to-end tests for complete user workflows
This document explains the differences, use cases, and how to leverage both effectively.
Test individual components, functions, and logic in isolation to ensure they work correctly on their own.
✅ React Components
- Component rendering
- Props handling
- State management
- Event handlers
- Conditional rendering
✅ Utility Functions
- Data formatters
- Validators
- Helper functions
- Class name mergers (e.g.,
cn())
✅ Server Actions
- API logic
- Error handling
- Data transformations
- Business rules
✅ Integration Points
- Component + hook interactions
- Multiple components working together
- Service layer integration
- Environment: Node.js with jsdom (simulated browser DOM)
- Speed: Very fast - milliseconds per test
- Isolation: Uses mocked data, no real network calls
- No Real Browser: Simulates DOM in memory
- Framework: React Testing Library for component testing
// Jest Configuration (jest.config.ts)
{
testEnvironment: 'jsdom', // Simulates browser environment
setupFilesAfterEnv: ['jest.setup.ts'], // Setup files
moduleNameMapper: { // Path aliases
'^@/(.*)$': '<rootDir>/src/$1',
},
coverageThreshold: { // Code coverage targets
global: {
branches: 70,
functions: 70,
lines: 75,
statements: 75,
},
},
}// src/__tests__/lib/utils.test.ts
import { cn } from '@/lib/utils';
describe('cn utility function', () => {
it('should merge class names correctly', () => {
const result = cn('text-red-500', 'bg-blue-500');
expect(result).toBe('text-red-500 bg-blue-500');
});
it('should handle conditional classes', () => {
const isActive = true;
const result = cn('base-class', isActive && 'active-class');
expect(result).toBe('base-class active-class');
});
it('should remove duplicate classes', () => {
const result = cn('p-4', 'p-2');
expect(result).toBe('p-2'); // Tailwind merge removes conflicting classes
});
});// src/__tests__/components/chat-input-form.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { ChatInputForm } from '@/components/chat-input-form';
describe('ChatInputForm', () => {
it('should render textarea and submit button', () => {
render(<ChatInputForm onSubmit={jest.fn()} />);
expect(screen.getByRole('textbox')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /send/i })).toBeInTheDocument();
});
it('should call onSubmit when form is submitted', () => {
const handleSubmit = jest.fn();
render(<ChatInputForm onSubmit={handleSubmit} />);
const textarea = screen.getByRole('textbox');
const submitButton = screen.getByRole('button', { name: /send/i });
fireEvent.change(textarea, { target: { value: 'Hello AI!' } });
fireEvent.click(submitButton);
expect(handleSubmit).toHaveBeenCalledWith('Hello AI!');
});
it('should disable submit button when input is empty', () => {
render(<ChatInputForm onSubmit={jest.fn()} />);
const submitButton = screen.getByRole('button', { name: /send/i });
expect(submitButton).toBeDisabled();
});
});src/__tests__/
├── __mocks__/ # Mock data
│ ├── mockFiles.ts
│ ├── mockMessages.ts
│ ├── mockThemes.ts
│ └── mockUrls.ts
├── components/ # Component tests
│ ├── chat-input-form.test.tsx
│ └── chat-messages.test.tsx
├── integration/ # Integration tests
│ └── actions.test.ts
└── lib/ # Utility tests
└── utils.test.ts
Test the entire application as a real user would use it, from start to finish, in real browsers.
✅ Complete User Workflows
- Upload file → Ask question → Get response
- Scrape URL → View summary → Download conversation
- Change theme → Verify persistence → Navigate app
✅ Real Browser Interactions
- Mouse clicks
- Keyboard typing
- Form submissions
- File uploads/downloads
- Navigation
✅ Visual & UX Testing
- Layout rendering
- Responsive design
- Animations
- Error messages
- Loading states
✅ Cross-Browser Compatibility
- Chromium (Chrome, Edge)
- Firefox
- WebKit (Safari)
✅ Network & API
- Real HTTP requests
- API responses
- Error handling
- Timeout scenarios
- Environment: Real browsers (Chromium, Firefox, WebKit)
- Speed: Slower - seconds per test (browser startup, network calls)
- Integration: Tests entire stack (frontend + backend + AI)
- Real Browser: Actual browser automation
- Framework: Playwright Test Runner
// Playwright Configuration (playwright.config.ts)
{
testDir: './e2e', // E2E test directory
fullyParallel: true, // Run tests in parallel
forbidOnly: !!process.env.CI, // Prevent .only in CI
retries: process.env.CI ? 2 : 0, // Retry flaky tests in CI
workers: process.env.CI ? 1 : undefined,
use: {
baseURL: 'http://localhost:9002', // App URL
trace: 'on-first-retry', // Debugging traces
screenshot: 'only-on-failure', // Screenshots on failures
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
webServer: {
command: 'npm run dev', // Start dev server
url: 'http://localhost:9002',
reuseExistingServer: !process.env.CI,
},
}// e2e/file-upload.spec.ts
import { test, expect } from '@playwright/test';
test.describe('File Upload Functionality', () => {
test('should upload a text file and chat about it', async ({ page }) => {
// Navigate to the app
await page.goto('http://localhost:9002');
// Upload a file
const fileInput = page.locator('input[type="file"]');
await fileInput.setInputFiles('test-files/sample-article.txt');
// Wait for file to be processed
await expect(page.locator('.file-uploaded')).toBeVisible({ timeout: 10000 });
// Ask a question about the file
const chatInput = page.locator('textarea[placeholder*="message"]');
await chatInput.fill('What is this document about?');
// Submit the question
const submitButton = page.locator('button[type="submit"]');
await submitButton.click();
// Verify AI response appears
await expect(page.locator('.message-ai')).toBeVisible({ timeout: 15000 });
await expect(page.locator('.message-ai')).toContainText('AI');
});
test('should handle PDF upload', async ({ page }) => {
await page.goto('http://localhost:9002');
// Upload PDF
await page.setInputFiles('input[type="file"]', 'test-files/dotnet-csharp.pdf');
// Wait for PDF processing (can take longer)
await expect(page.locator('.file-uploaded')).toBeVisible({ timeout: 30000 });
// Verify file name is displayed
await expect(page.locator('.file-name')).toContainText('dotnet-csharp.pdf');
});
test('should show error for unsupported file type', async ({ page }) => {
await page.goto('http://localhost:9002');
// Try to upload unsupported file
await page.setInputFiles('input[type="file"]', 'test-files/invalid.exe');
// Verify error message
await expect(page.locator('.error-message')).toBeVisible();
await expect(page.locator('.error-message')).toContainText('not supported');
});
});// e2e/url-scraping.spec.ts
import { test, expect } from '@playwright/test';
test.describe('URL Scraping Functionality', () => {
test('should scrape a URL and summarize content', async ({ page }) => {
await page.goto('http://localhost:9002');
// Enter URL
const urlInput = page.locator('input[placeholder*="URL"]');
await urlInput.fill('https://example.com/article');
// Click scrape button
await page.click('button:has-text("Scrape")');
// Wait for scraping to complete
await expect(page.locator('.scraping-status')).toContainText('Complete');
// Ask question about scraped content
await page.fill('textarea', 'Summarize this article');
await page.click('button[type="submit"]');
// Verify summary appears
await expect(page.locator('.message-ai')).toBeVisible({ timeout: 20000 });
});
});e2e/
├── chat-functionality.spec.ts # Chat interaction tests
├── file-upload.spec.ts # File upload workflow tests
├── ui-features.spec.ts # UI/UX tests (theme, settings)
└── url-scraping.spec.ts # URL scraping workflow tests
| Aspect | Jest | Playwright |
|---|---|---|
| Speed | ⚡ Very fast (10-100ms per test) | 🐢 Slower (2-30s per test) |
| Environment | Simulated DOM (jsdom) | Real browser |
| Scope | Unit/Integration | End-to-End |
| Isolation | Highly isolated, mocked | Integrated, real data |
| Network | Mocked APIs | Real API calls |
| Browser | No real browser | Chromium, Firefox, WebKit |
| Coverage | Code-level testing | User workflow testing |
| Run Command | npm test |
npx playwright test |
| When to Run | During development (constantly) | Before commits/deployment |
| Cost | Low (fast execution) | High (resource intensive) |
| Setup Required | None (runs in Node.js) | Browsers must be installed |
| Debugging | Console logs, breakpoints | Screenshots, videos, traces |
| CI/CD | Every commit | Every PR/deployment |
| Typical Count | 100s-1000s of tests | 10s-100s of tests |
✅ Testing individual component behavior
✅ Validating utility functions
✅ Testing business logic
✅ Checking error handling in services
✅ Verifying state management
✅ Testing hooks and custom React logic
✅ Need fast feedback during development
✅ Writing lots of tests (high volume)
Example Scenarios:
- "Does the
ChatMessagecomponent render user messages correctly?" - "Does the
formatDate()function handle edge cases?" - "Does the
uploadFileaction validate file types?"
✅ Testing complete user journeys
✅ Validating critical business flows
✅ Testing cross-browser compatibility
✅ Verifying responsive design
✅ Testing file upload/download
✅ Checking navigation and routing
✅ Testing third-party integrations (AI, APIs)
✅ Smoke testing before deployment
Example Scenarios:
- "Can a user upload a PDF and ask questions about it?"
- "Does URL scraping work from entering URL to getting a summary?"
- "Can users switch themes and see the change persist?"
- "Does the app work in Safari?"
# List all discovered test files
npm test -- --listTests
# Run all tests
npm test
# Run tests in watch mode (auto-rerun on changes)
npm test -- --watch
# Run specific test file
npm test src/__tests__/lib/utils.test.ts
# Run with coverage report
npm test -- --coverage
# Run tests matching pattern
npm test -- --testNamePattern="ChatInputForm"# Install Playwright browsers (one-time setup)
npx playwright install
# Run all E2E tests
npx playwright test
# Run specific test file
npx playwright test e2e/file-upload.spec.ts
# Run tests in headed mode (see browser)
npx playwright test --headed
# Run tests in specific browser
npx playwright test --project=chromium
npx playwright test --project=firefox
npx playwright test --project=webkit
# Run tests with UI mode (interactive debugging)
npx playwright test --ui
# Generate HTML report
npx playwright show-reportComponent Tests:
// chat-input-form.test.tsx - 8 tests
✓ Renders textarea and submit button
✓ Handles user input
✓ Submits message on button click
✓ Submits message on Enter key
✓ Disables submit when input is empty
✓ Shows loading state during submission
✓ Handles errors gracefully
✓ Clears input after successful submission
// chat-messages.test.tsx - 10 tests
✓ Renders empty state when no messages
✓ Displays user messages
✓ Displays AI messages
✓ Formats timestamps correctly
✓ Handles code blocks with syntax highlighting
✓ Auto-scrolls to latest message
✓ Shows typing indicator
✓ Renders file attachments
✓ Handles long messages (truncation)
✓ Copies message to clipboardUtility Tests:
// utils.test.ts - 6 tests
✓ cn() merges class names
✓ cn() handles conditional classes
✓ cn() removes duplicate Tailwind classes
✓ formatDate() formats dates correctly
✓ formatFileSize() converts bytes to readable format
✓ validateFileType() checks allowed extensionsIntegration Tests:
// actions.test.ts - 8 tests
✓ uploadFile action validates file type
✓ uploadFile action handles large files
✓ scrapeUrl action validates URL format
✓ scrapeUrl action handles network errors
✓ sendMessage action calls AI service
✓ sendMessage action handles timeout
✓ generateTheme action creates valid theme
✓ generateTheme action handles invalid inputTotal Jest Tests: ~32 tests (run in ~2 seconds)
File Upload E2E:
// file-upload.spec.ts - 6 tests
✓ Upload text file and chat about it
✓ Upload PDF and verify processing
✓ Upload HTML file and extract content
✓ Handle multiple file uploads
✓ Show error for unsupported file type
✓ Download conversation with file contextURL Scraping E2E:
// url-scraping.spec.ts - 5 tests
✓ Scrape URL and summarize content
✓ Handle invalid URLs gracefully
✓ Scrape multiple URLs in sequence
✓ Show progress indicator during scraping
✓ Verify scraped content in chatChat Functionality E2E:
// chat-functionality.spec.ts - 7 tests
✓ Send message and receive AI response
✓ Continue multi-turn conversation
✓ Copy AI response to clipboard
✓ Clear chat history
✓ Show error on network failure
✓ Handle long responses (streaming)
✓ Regenerate AI responseUI Features E2E:
// ui-features.spec.ts - 6 tests
✓ Toggle light/dark theme
✓ Theme persists across page reload
✓ Open/close settings menu
✓ Generate custom AI theme
✓ Reset to default theme
✓ Responsive layout on mobile viewportTotal Playwright Tests: ~24 tests (run in ~60-120 seconds)
/\
/ \ E2E (Playwright)
/____\ ~24 tests (slow, expensive)
/ \
/ INTE \ Integration (Jest)
/__________\ ~8 tests (medium speed)
/ \
/ UNIT \ Unit (Jest)
/________________\ ~24 tests (fast, cheap)
Recommended Ratio:
- 70% Unit tests (Jest)
- 20% Integration tests (Jest)
- 10% E2E tests (Playwright)
✅ Write lots of small, focused tests
// Good: Focused test
it('should disable submit button when input is empty', () => {
render(<ChatInputForm />);
expect(screen.getByRole('button')).toBeDisabled();
});
// Avoid: Testing too many things at once
it('should handle all input scenarios', () => {
// 50 lines of test code...
});✅ Use descriptive test names
// Good: Clear intent
it('should show error message when file exceeds 10MB', () => {});
// Avoid: Vague names
it('should work correctly', () => {});✅ Mock external dependencies
// Mock Genkit AI calls
jest.mock('@/ai/genkit', () => ({
generateResponse: jest.fn().mockResolvedValue('Mocked AI response'),
}));✅ Test behavior, not implementation
// Good: Tests what user sees
expect(screen.getByText('Welcome!')).toBeInTheDocument();
// Avoid: Tests internal state
expect(component.state.isVisible).toBe(true);✅ Test critical user journeys only
// Good: High-value workflow
test('user can upload PDF and get AI summary', async ({ page }) => {});
// Avoid: Testing every button click
test('submit button is blue', async ({ page }) => {});✅ Use reliable selectors
// Good: Role-based selectors (accessible)
await page.locator('role=button[name="Upload"]').click();
// Good: Test IDs
await page.locator('[data-testid="chat-input"]').fill('Hello');
// Avoid: Fragile CSS selectors
await page.locator('.btn.primary.large.rounded').click();✅ Handle async operations properly
// Good: Explicit waits
await expect(page.locator('.ai-response')).toBeVisible({ timeout: 15000 });
// Avoid: Arbitrary delays
await page.waitForTimeout(5000);✅ Clean up between tests
test.beforeEach(async ({ page }) => {
// Start with clean state
await page.goto('http://localhost:9002');
await page.evaluate(() => localStorage.clear());
});During Development (TDD):
- Write Jest test for new feature
- Implement feature
- Run Jest tests (fast feedback)
- Refactor with confidence
Before Committing:
- Run full Jest suite (
npm test) - Run Playwright tests (
npx playwright test) - Check coverage report
- Commit if all green ✅
In CI/CD Pipeline:
- Run Jest tests on every commit
- Run Playwright tests on every PR
- Generate coverage reports
- Block merge if tests fail
Developer writes code
↓
Jest tests run automatically (watch mode)
↓
Green tests? → Continue development
↓
Ready to commit?
↓
Run full Jest suite
↓
Run Playwright E2E tests
↓
All green? → Commit & push
Push to GitHub
↓
Install dependencies
↓
Run Jest tests (unit + integration)
↓
Generate coverage report
↓
Run Playwright tests (E2E)
↓
All tests pass? → Deploy to staging
↓
Manual QA testing
↓
Deploy to production
// jest.config.ts
coverageThreshold: {
global: {
branches: 70, // 70% of code branches tested
functions: 70, // 70% of functions tested
lines: 75, // 75% of code lines tested
statements: 75, // 75% of statements tested
},
}Critical Workflows (100% coverage):
- ✅ File upload → AI chat → Response
- ✅ URL scraping → Summary generation
- ✅ Theme switching → Persistence
Important Features (80% coverage):
- ✅ Settings menu
- ✅ Chat history management
- ✅ Error handling flows
Nice-to-Have Features (50% coverage):
- ✅ Advanced UI interactions
- ✅ Edge cases
- ✅ Animation testing
Problem: Tests fail with "Cannot find module"
# Solution: Check moduleNameMapper in jest.config.ts
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
}Problem: "ReferenceError: fetch is not defined"
// Solution: Add polyfill in jest.setup.ts
global.fetch = jest.fn();Problem: Tests timeout
// Solution: Increase timeout
jest.setTimeout(10000); // 10 secondsProblem: "browserType.launch: Executable doesn't exist"
# Solution: Install browsers
npx playwright installProblem: Tests fail in CI but pass locally
// Solution: Use consistent waits
await expect(element).toBeVisible({ timeout: 15000 });Problem: "Target closed" errors
// Solution: Handle navigation properly
await page.goto(url, { waitUntil: 'domcontentloaded' });docs/testing-strategy.md- Overall testing approachdocs/testing-guide.md- How to write testsdocs/manual-test-scenarios.md- Manual testing checklistdocs/TESTING-README.md- Quick reference
# Jest
npm test # Run all Jest tests
npm test -- --coverage # With coverage report
npm test -- --watch # Watch mode
# Playwright
npx playwright test # Run all E2E tests
npx playwright test --ui # Interactive UI mode
npx playwright test --headed # See browser
npx playwright show-report # View test reportJest and Playwright serve different but complementary purposes:
- Jest ensures your code works correctly at the component and function level
- Playwright ensures your app works correctly from the user's perspective
Use both for comprehensive test coverage!
Remember:
- Jest = Fast, focused, developer feedback
- Playwright = Slow, comprehensive, user validation
- Write more Jest tests, fewer Playwright tests
- Run Jest constantly, Playwright before commits
Questions or need help? See the testing documentation in docs/ or reach out to the development team.