Local Testing Guide¶
This guide covers comprehensive testing of RapidTriageME in your local development environment, from basic functionality verification to advanced testing scenarios.
Testing Overview¶
graph TB
subgraph "Testing Layers"
UNIT[Unit Tests<br/>Individual Components]
INTEGRATION[Integration Tests<br/>Component Interaction]
E2E[End-to-End Tests<br/>Full Workflow]
MANUAL[Manual Testing<br/>User Scenarios]
end
subgraph "Test Environment"
LOCAL[Local Development]
CI[CI/CD Pipeline]
STAGING[Staging Environment]
end
UNIT --> INTEGRATION
INTEGRATION --> E2E
E2E --> MANUAL
LOCAL --> CI
CI --> STAGING
style UNIT fill:#e8f5e8
style E2E fill:#e1f5fe
style MANUAL fill:#fff3e0
Prerequisites¶
Before starting local testing, ensure you have:
# Required software
node --version # v18.0.0 or higher
npm --version # v9.0.0 or higher
google-chrome --version # Latest stable
# Optional but recommended
docker --version # For containerized testing
pytest --version # For Python test utilities
Quick Test Setup¶
1. Clone and Install¶
# Clone the repository
git clone .git
cd rapidtriage
# Install all dependencies
npm run install:all
# Build all components
npm run build:all
2. Start Test Environment¶
# Start all services for testing
npm run test:setup
# This starts:
# - Browser connector on port 3025
# - Test web server on port 3000
# - Chrome with test extension loaded
3. Run Basic Tests¶
# Run all test suites
npm test
# Run specific test categories
npm run test:unit # Unit tests only
npm run test:integration # Integration tests
npm run test:e2e # End-to-end tests
Unit Testing¶
Browser Connector Tests¶
// test/browser-connector.test.ts
import request from 'supertest';
import { BrowserConnectorServer } from '../src/browser-connector';
describe('Browser Connector Server', () => {
let server: BrowserConnectorServer;
let app: any;
beforeAll(async () => {
server = new BrowserConnectorServer({
port: 0, // Use random port for testing
host: 'localhost',
environment: 'test'
});
app = server.getApp();
await server.start();
});
afterAll(async () => {
await server.stop();
});
describe('GET /.identity', () => {
it('should return server identity', async () => {
const response = await request(app)
.get('/.identity')
.expect(200);
expect(response.body).toMatchObject({
name: 'RapidTriageME Browser Connector',
status: 'healthy',
version: expect.any(String)
});
});
});
describe('GET /console-logs', () => {
beforeEach(() => {
// Clear cache before each test
server.getDataCache().clearAll();
});
it('should return empty logs initially', async () => {
const response = await request(app)
.get('/console-logs')
.expect(200);
expect(response.body.logs).toEqual([]);
expect(response.body.total).toBe(0);
});
it('should filter logs by level', async () => {
// Add test logs
server.getDataCache().addConsoleLog({
id: 'log1',
level: 'error',
message: 'Test error',
timestamp: Date.now()
});
server.getDataCache().addConsoleLog({
id: 'log2',
level: 'info',
message: 'Test info',
timestamp: Date.now()
});
const response = await request(app)
.get('/console-logs?level=error')
.expect(200);
expect(response.body.logs).toHaveLength(1);
expect(response.body.logs[0].level).toBe('error');
});
});
describe('POST /capture-screenshot', () => {
it('should handle screenshot request', async () => {
const response = await request(app)
.post('/capture-screenshot')
.send({ options: { fullPage: true } })
.expect(200);
expect(response.body).toHaveProperty('screenshot');
expect(response.body.screenshot).toMatch(/^data:image/);
});
it('should validate screenshot options', async () => {
const response = await request(app)
.post('/capture-screenshot')
.send({ options: { quality: 150 } }) // Invalid quality
.expect(400);
expect(response.body.error).toContain('Invalid quality');
});
});
});
MCP Server Tests¶
// test/mcp-server.test.ts
import { MCPServer } from '../src/mcp-server';
import { BrowserConnectorMock } from './mocks/browser-connector-mock';
describe('MCP Server', () => {
let mcpServer: MCPServer;
let browserMock: BrowserConnectorMock;
beforeEach(() => {
browserMock = new BrowserConnectorMock();
mcpServer = new MCPServer({
browserConnectorUrl: browserMock.getUrl()
});
});
describe('Tool Execution', () => {
it('should execute screenshot_capture tool', async () => {
browserMock.mockScreenshot('data:image/png;base64,iVBORw0KGgo...');
const result = await mcpServer.executeTool('screenshot_capture', {
options: { fullPage: true }
});
expect(result.content).toHaveLength(2);
expect(result.content[0].type).toBe('image');
expect(result.content[1].type).toBe('text');
expect(result.isError).toBe(false);
});
it('should handle tool execution errors gracefully', async () => {
browserMock.mockError('Browser not connected');
const result = await mcpServer.executeTool('screenshot_capture', {});
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Screenshot capture failed');
});
});
describe('Tool Registry', () => {
it('should list all available tools', async () => {
const tools = await mcpServer.listTools();
const toolNames = tools.tools.map(tool => tool.name);
expect(toolNames).toContain('screenshot_capture');
expect(toolNames).toContain('get_console_logs');
expect(toolNames).toContain('run_lighthouse_audit');
});
});
});
Chrome Extension Tests¶
// test/extension.test.js
describe('Chrome Extension', () => {
let mockChrome;
beforeEach(() => {
mockChrome = {
runtime: {
onMessage: {
addListener: jest.fn()
},
sendMessage: jest.fn()
},
devtools: {
panels: {
create: jest.fn()
}
},
tabs: {
captureVisibleTab: jest.fn()
}
};
global.chrome = mockChrome;
});
it('should initialize DevTools panel', () => {
require('../rapidtriage-extension/devtools.js');
expect(mockChrome.devtools.panels.create).toHaveBeenCalledWith(
'RapidTriage',
expect.any(String),
'panel.html',
expect.any(Function)
);
});
it('should capture screenshot when requested', async () => {
mockChrome.tabs.captureVisibleTab.mockResolvedValue('data:image/png;base64,test');
const { captureScreenshot } = require('../rapidtriage-extension/background.js');
const result = await captureScreenshot({ fullPage: false });
expect(result).toMatch(/^data:image/);
expect(mockChrome.tabs.captureVisibleTab).toHaveBeenCalled();
});
});
Integration Testing¶
Full Stack Integration¶
// test/integration/full-stack.test.ts
import { spawn, ChildProcess } from 'child_process';
import WebSocket from 'ws';
import puppeteer, { Browser, Page } from 'puppeteer';
describe('Full Stack Integration', () => {
let browserConnector: ChildProcess;
let mcpServer: ChildProcess;
let browser: Browser;
let page: Page;
beforeAll(async () => {
// Start browser connector server
browserConnector = spawn('npm', ['run', 'start:browser-connector'], {
env: { ...process.env, PORT: '3025' }
});
// Wait for server to be ready
await waitForServer('http://localhost:3025');
// Start MCP server
mcpServer = spawn('npm', ['run', 'start:mcp-server']);
// Launch browser with extension
browser = await puppeteer.launch({
headless: false,
args: [
'--load-extension=./rapidtriage-extension',
'--disable-extensions-except=./rapidtriage-extension'
]
});
page = await browser.newPage();
}, 30000);
afterAll(async () => {
if (browser) await browser.close();
if (browserConnector) browserConnector.kill();
if (mcpServer) mcpServer.kill();
});
it('should establish WebSocket connection between extension and server', async () => {
await page.goto('http://localhost:3000/test-page.html');
// Open DevTools to trigger extension
const client = await page.target().createCDPSession();
await client.send('Runtime.enable');
// Verify WebSocket connection
const ws = new WebSocket('ws://localhost:3025/ws');
await new Promise(resolve => ws.on('open', resolve));
expect(ws.readyState).toBe(WebSocket.OPEN);
ws.close();
});
it('should capture and retrieve console logs end-to-end', async () => {
await page.goto('http://localhost:3000/test-page.html');
// Generate console log
await page.evaluate(() => {
console.error('Test integration error');
});
// Wait for log to be processed
await new Promise(resolve => setTimeout(resolve, 1000));
// Retrieve logs via API
const response = await fetch('http://localhost:3025/console-logs');
const data = await response.json();
expect(data.logs).toHaveLength(1);
expect(data.logs[0].message).toBe('Test integration error');
expect(data.logs[0].level).toBe('error');
});
it('should complete screenshot workflow', async () => {
await page.goto('http://localhost:3000/test-page.html');
// Request screenshot via API
const response = await fetch('http://localhost:3025/capture-screenshot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ options: { fullPage: true } })
});
const data = await response.json();
expect(response.status).toBe(200);
expect(data.screenshot).toMatch(/^data:image/);
expect(data.dimensions).toBeDefined();
});
});
End-to-End Testing¶
Playwright E2E Tests¶
// test/e2e/user-workflows.spec.ts
import { test, expect, chromium } from '@playwright/test';
test.describe('RapidTriageME User Workflows', () => {
test.beforeAll(async () => {
// Ensure servers are running
await fetch('http://localhost:3025/.identity');
});
test('complete debugging workflow', async () => {
const browser = await chromium.launch({
args: ['--load-extension=./rapidtriage-extension']
});
const context = await browser.newContext();
const page = await context.newPage();
// Navigate to test page
await page.goto('http://localhost:3000/problematic-page.html');
// Open DevTools (simulating user action)
await page.keyboard.press('F12');
// Wait for extension panel to load
await page.waitForTimeout(2000);
// Simulate AI assistant requesting screenshot
const screenshotResponse = await fetch('http://localhost:3025/capture-screenshot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ options: { fullPage: true } })
});
expect(screenshotResponse.status).toBe(200);
// Simulate AI assistant requesting console logs
const logsResponse = await fetch('http://localhost:3025/console-logs');
const logsData = await logsResponse.json();
expect(logsResponse.status).toBe(200);
expect(logsData.logs).toBeDefined();
await browser.close();
});
test('Lighthouse audit workflow', async () => {
const auditResponse = await fetch('http://localhost:3025/lighthouse-audit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: 'http://localhost:3000/test-page.html',
categories: ['performance', 'accessibility']
})
});
expect(auditResponse.status).toBe(200);
const auditData = await auditResponse.json();
expect(auditData.audit).toBeDefined();
expect(auditData.audit.scores).toBeDefined();
expect(auditData.audit.scores.performance).toBeGreaterThanOrEqual(0);
});
});
Manual Testing Scenarios¶
Test Scenario 1: Basic Functionality¶
# 1. Start RapidTriageME
npm run dev
# 2. Open Chrome and load extension
# chrome://extensions/ -> Load unpacked -> select rapidtriage-extension
# 3. Navigate to test page
# http://localhost:3000/test-page.html
# 4. Open DevTools and check RapidTriage panel
# F12 -> RapidTriage tab -> Should show "Connected"
# 5. Test screenshot capture
curl -X POST http://localhost:3025/capture-screenshot \
-H "Content-Type: application/json" \
-d '{"options": {"fullPage": true}}'
# Expected: JSON response with base64 screenshot
Test Scenario 2: Error Handling¶
// Navigate to test page and run in console:
console.error('Test error message');
console.warn('Test warning message');
throw new Error('Test exception');
// Then check logs:
// curl http://localhost:3025/console-logs
Test Scenario 3: Network Monitoring¶
// On test page, trigger network requests:
fetch('/api/success').then(r => console.log('Success:', r.status));
fetch('/api/404').catch(e => console.log('Error:', e));
fetch('/api/slow').then(r => console.log('Slow request completed'));
// Check network logs:
// curl http://localhost:3025/network-requests
Performance Testing¶
Load Testing Script¶
// test/performance/load-test.ts
import autocannon from 'autocannon';
async function runLoadTest() {
const result = await autocannon({
url: 'http://localhost:3025',
connections: 50,
duration: 30, // 30 seconds
requests: [
{
method: 'GET',
path: '/.identity'
},
{
method: 'GET',
path: '/console-logs?limit=100'
},
{
method: 'POST',
path: '/capture-screenshot',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ options: { fullPage: false } })
}
]
});
console.log('Load test results:');
console.log(`Requests/sec: ${result.requests.average}`);
console.log(`Latency: ${result.latency.average}ms`);
console.log(`Errors: ${result.errors}`);
// Performance assertions
expect(result.requests.average).toBeGreaterThan(100);
expect(result.latency.average).toBeLessThan(100);
expect(result.errors).toBe(0);
}
test('should handle load gracefully', runLoadTest, 60000);
Memory Testing¶
// test/performance/memory-test.ts
test('should not leak memory during extended operation', async () => {
const initialMemory = process.memoryUsage().heapUsed;
// Simulate extended operation
for (let i = 0; i < 1000; i++) {
await fetch('http://localhost:3025/console-logs');
await new Promise(resolve => setTimeout(resolve, 10));
}
// Force garbage collection
if (global.gc) global.gc();
const finalMemory = process.memoryUsage().heapUsed;
const memoryGrowth = finalMemory - initialMemory;
// Memory growth should be reasonable (less than 50MB)
expect(memoryGrowth).toBeLessThan(50 * 1024 * 1024);
}, 30000);
Test Utilities¶
Server Test Helper¶
// test/utils/server-helper.ts
export class ServerTestHelper {
static async waitForServer(url: string, timeout = 30000): Promise<void> {
const start = Date.now();
while (Date.now() - start < timeout) {
try {
const response = await fetch(url);
if (response.ok) return;
} catch (error) {
// Server not ready yet
}
await new Promise(resolve => setTimeout(resolve, 500));
}
throw new Error(`Server at ${url} not ready within ${timeout}ms`);
}
static async createTestData(count = 10): Promise<void> {
for (let i = 0; i < count; i++) {
await fetch('http://localhost:3025/test/add-log', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
level: i % 2 === 0 ? 'error' : 'info',
message: `Test log ${i}`,
timestamp: Date.now() - (i * 1000)
})
});
}
}
}
Extension Test Helper¶
// test/utils/extension-helper.js
class ExtensionTestHelper {
static mockChromeAPI() {
global.chrome = {
runtime: {
onMessage: { addListener: jest.fn() },
sendMessage: jest.fn(),
getURL: jest.fn(path => `chrome-extension://test-id/${path}`)
},
devtools: {
panels: {
create: jest.fn((name, icon, page, callback) => {
callback({ onShown: { addListener: jest.fn() } });
})
},
inspectedWindow: {
eval: jest.fn((code, callback) => callback('result', false))
}
},
tabs: {
captureVisibleTab: jest.fn(() => Promise.resolve('data:image/png;base64,test'))
},
storage: {
local: {
get: jest.fn(() => Promise.resolve({})),
set: jest.fn(() => Promise.resolve())
}
}
};
}
static async simulateExtensionInstall() {
// Simulate extension installation process
const installEvent = { reason: 'install' };
chrome.runtime.onInstalled.addListener.mock.calls[0][0](installEvent);
}
}
module.exports = ExtensionTestHelper;
Continuous Integration¶
GitHub Actions Workflow¶
# .github/workflows/test.yml
name: Test Suite
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build all packages
run: npm run build:all
- name: Run unit tests
run: npm run test:unit
- name: Run integration tests
run: npm run test:integration
- name: Install Chrome
run: |
wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
sudo apt-get update
sudo apt-get install google-chrome-stable
- name: Run E2E tests
run: npm run test:e2e
- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
Test Data Management¶
Test Fixtures¶
// test/fixtures/console-logs.ts
export const mockConsoleLogs = [
{
id: 'log_001',
level: 'error',
message: 'TypeError: Cannot read property \'foo\' of undefined',
timestamp: 1704067200000,
url: 'https://example.com/app.js',
source: 'app.js:42:15',
stack: [
"TypeError: Cannot read property 'foo' of undefined",
' at app.js:42:15',
' at HTMLButtonElement.onclick (app.js:38:9)'
]
},
{
id: 'log_002',
level: 'warn',
message: 'Deprecated API usage detected',
timestamp: 1704067260000,
url: 'https://example.com/legacy.js',
source: 'legacy.js:123:20'
}
];
export const mockNetworkRequests = [
{
id: 'req_001',
url: 'https://api.example.com/users',
method: 'GET',
status: 200,
statusText: 'OK',
timestamp: 1704067200000,
duration: 245,
size: { request: 0, response: 1024 }
},
{
id: 'req_002',
url: 'https://api.example.com/posts',
method: 'POST',
status: 500,
statusText: 'Internal Server Error',
timestamp: 1704067300000,
duration: 1500,
size: { request: 512, response: 256 }
}
];
Troubleshooting Tests¶
Common Test Issues¶
# Test server port conflicts
echo "Checking for port conflicts..."
lsof -i :3025 | grep LISTEN
# Clear test data
echo "Cleaning test environment..."
rm -rf ./test-data/*
npm run test:clean
# Reset test database
echo "Resetting test database..."
npm run test:db:reset
# Check Chrome processes
echo "Checking Chrome processes..."
ps aux | grep chrome | grep -v grep
Debug Test Failures¶
# Run tests with debug output
DEBUG=rapidtriage:* npm test
# Run specific test file
npm test -- --testPathPattern=browser-connector
# Run tests in watch mode
npm run test:watch
# Generate detailed coverage report
npm run test:coverage -- --coverage-reporters=html
open coverage/lcov-report/index.html
This comprehensive local testing guide ensures RapidTriageME works correctly across all scenarios before deployment to production.
Next Steps¶
- Deployment to Cloudflare - Deploy to production
- Production Deployment - Complete production setup
- Troubleshooting - Debug common issues