- Create user/ - User guides and configuration documentation - Create features/ - Feature specifications and business flows - Create debugging/ - Debug guides and quick references - Create testing/ - Test infrastructure, reports, and plans - Create internal/ - Internal plans, analyses, and templates - Move cleaner/*.md to cleaner/ directory - Move LOGGING_*.md to developer/guides/ Add docs/README.md as documentation index with category navigation and quick lookup guide. The reorganized structure makes it easier for users and developers to quickly locate relevant documentation.
39 KiB
ERPAuto Test Improvement Plan
Generated: 2026-04-04
Framework: Vitest 4.0.18 + Playwright 1.58.2
Current Status: 44 test files, ~200 passing tests, 48 failures (20 suites + 28 tests)
Executive Summary
| Metric | Current | Target (3mo) | Target (6mo) |
|---|---|---|---|
| Test Files | 44 | 60 | 80 |
| Passing Tests | ~200 | 350 | 500+ |
| Failed Suites | 20 | 0 | 0 |
| Failed Tests | 28 | <10 | <5 |
| Empty Test Files | ~17 | 0 | 0 |
| E2E Coverage | 4 files | 12 files | 20+ files |
| Code Coverage | ~35% | 60% | 80% |
Issue Analysis
Critical Issues Identified
- Electron Mock Incomplete (P0) - Missing
app.getVersion()and other APIs - Winston Mock Broken (P0) -
format().printf()not a function - Environment Credentials Missing (P1) - No
.envfile for ERP credentials - Empty Test Files (P1) - 17 test files with 0-2 tests
- E2E Coverage Gap (P2) - Only 4 E2E tests for entire Electron app
Phase 1: Immediate Fixes (Week 1-2)
P0 - Critical Infrastructure
Task 1.1: Complete Electron Mock in setup.ts
Priority: P0 - Blocks 20 test suites
Estimated Effort: 2 hours
Dependencies: None
Owner: Development Team
Problem: Current mock missing critical Electron APIs causing import failures:
app.getVersion()- Used by update servicesapp.getName()- Used by loggingdialog.showErrorBox()- Used by bootstrapBrowserWindow.getAllWindows()- Used by renderer
Solution:
// tests/setup.ts - Enhanced Electron Mock
vi.mock('electron', () => {
const mockApp = {
isPackaged: false,
isReady: vi.fn().mockReturnValue(true),
getPath: vi.fn((name: string) => {
const paths: Record<string, string> = {
userData: path.join(process.cwd(), 'test-user-data'),
logs: path.join(process.cwd(), 'test-logs'),
temp: path.join(process.cwd(), 'test-temp')
}
return paths[name] || process.cwd()
}),
getVersion: vi.fn(() => '1.9.0-test'),
getName: vi.fn(() => 'ERPAuto'),
on: vi.fn(),
off: vi.fn()
}
return {
app: mockApp,
ipcMain: {
handle: vi.fn(),
on: vi.fn(),
removeHandler: vi.fn()
},
dialog: {
showErrorBox: vi.fn(),
showMessageBox: vi.fn()
},
BrowserWindow: {
getAllWindows: vi.fn(() => []),
fromWebContents: vi.fn()
},
shell: {
openPath: vi.fn(),
openExternal: vi.fn()
}
}
})
Success Criteria:
- All 20 failing test suites pass
- Zero Electron import errors in test output
bootstrap-runtime.test.tspasses all 3 tests
Verification Steps:
# Run bootstrap tests specifically
npm run test:run tests/unit/bootstrap-runtime.test.ts
# Run all unit tests
npm run test:run tests/unit/
# Expected: Zero suite failures from Electron mocks
Task 1.2: Fix Winston Logger Mock
Priority: P0 - Blocks 11 logger tests
Estimated Effort: 1.5 hours
Dependencies: None
Problem:
Current winston mock doesn't properly implement format.printf():
formatFn = vi.fn((fn: any) => fn && fn()) // Returns undefined
formatFn.printf = vi.fn((fn: any) => fn) // Should return formatter
Solution:
// tests/setup.ts - Enhanced Winston Mock
vi.mock('winston', () => {
const createLoggerInstance = {
level: 'info',
add: vi.fn(),
remove: vi.fn(),
clear: vi.fn(),
child: vi.fn(function (this: any, metadata: Record<string, unknown>) {
return {
...this,
info: vi.fn((message: string, meta?: Record<string, unknown>) => {
winstonCalls.push({ level: 'info', message, meta: { ...metadata, ...meta } })
}),
error: vi.fn((message: string, meta?: Record<string, unknown>) => {
winstonCalls.push({ level: 'error', message, meta: { ...metadata, ...meta } })
}),
warn: vi.fn((message: string, meta?: Record<string, unknown>) => {
winstonCalls.push({ level: 'warn', message, meta: { ...metadata, ...meta } })
}),
debug: vi.fn((message: string, meta?: Record<string, unknown>) => {
winstonCalls.push({ level: 'debug', message, meta: { ...metadata, ...meta } })
})
}
}),
info: vi.fn((message, meta) => {
winstonCalls.push({ level: 'info', message, meta })
}),
error: vi.fn((message, meta) => {
winstonCalls.push({ level: 'error', message, meta })
}),
warn: vi.fn((message, meta) => {
winstonCalls.push({ level: 'warn', message, meta })
}),
debug: vi.fn((message, meta) => {
winstonCalls.push({ level: 'debug', message, meta })
})
}
// Properly implemented format functions
const formatFn = Object.assign(
vi.fn((callback: Function) => {
return { transform: callback }
}),
{
combine: vi.fn((...formats: any[]) => ({ type: 'combine', formats })),
timestamp: vi.fn((options?: any) => ({ type: 'timestamp', options })),
colorize: vi.fn(() => ({ type: 'colorize' })),
printf: vi.fn((callback: Function) => {
return { transform: callback }
}),
json: vi.fn(() => ({ type: 'json' })),
simple: vi.fn(() => ({ type: 'simple' })),
pretty: vi.fn(() => ({ type: 'pretty' })),
label: vi.fn((options?: any) => ({ type: 'label', options }))
}
) as any
return {
default: {
createLogger: vi.fn(() => createLoggerInstance),
format: formatFn,
transports: {
Console: vi.fn(function Console(this: any, options?: any) {
this.level = options?.level || 'info'
}),
DailyRotateFile: vi.fn(function DailyRotateFile(this: any, options?: any) {
this.options = options
}),
File: vi.fn()
},
addColors: vi.fn()
}
}
})
Success Criteria:
- All 11 logger tests in
logger.test.tspass format.printf()returns callable formatter- Child logger work with metadata
Verification Steps:
npm run test:run tests/unit/logger.test.ts
# Expected: 18/18 tests passing
Task 1.3: Create Test Environment Configuration
Priority: P0 - Blocks environment tests
Estimated Effort: 0.5 hours
Dependencies: None
Problem:
tests/debug/env.test.ts fails because no .env file exists with ERP credentials.
Solution:
Create tests/.env.test file:
# Test Environment Configuration
# DO NOT COMMIT REAL CREDENTIALS
# Test ERP Instance (use sandbox/test environment)
ERP_URL=https://test-erp.example.com
ERP_USERNAME=test_automation_user
ERP_PASSWORD=test_password_placeholder
# Test Database (use isolated test DB)
TEST_DB_HOST=localhost
TEST_DB_PORT=3306
TEST_DB_NAME=erpauto_test
TEST_DB_USERNAME=test_user
TEST_DB_PASSWORD=test_password
# Test Settings
TEST_ENV=true
CI=true
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=0
Update vitest.config.ts:
import { defineConfig } from 'vitest/config'
import path from 'path'
import dotenv from 'dotenv'
// Load test environment variables
dotenv.config({ path: path.resolve(__dirname, 'tests/.env.test') })
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['tests/**/*.{test,spec}.{ts,tsx}'],
exclude: ['node_modules', 'dist', 'out', 'tests/e2e'],
setupFiles: ['tests/setup.ts'],
env: {
NODE_ENV: 'test'
},
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html']
}
},
resolve: {
alias: {
'@main': path.resolve(__dirname, './src/main'),
'@services': path.resolve(__dirname, './src/main/services'),
'@types': path.resolve(__dirname, './src/main/types'),
'@': path.resolve(__dirname, './src')
}
}
})
Success Criteria:
env.test.tscan access environment variables- Tests run with
NODE_ENV=test - No hardcoded credentials in source
Verification Steps:
npm run test:run tests/debug/env.test.ts
# Check: Test shows loaded credentials (may skip if placeholder values)
Phase 2: Short-Term Improvements (Week 3-6)
P1 - Empty Test File Completion
Task 2.1: Populate Empty Unit Tests
Priority: P1
Estimated Effort: 16 hours (2 hours per file × 8 files)
Dependencies: Task 1.1, Task 1.2 complete
Files to Populate:
| File | Current | Target Tests | Domain |
|---|---|---|---|
unit/cleaner.test.ts |
0 | 8 | ERP Cleaner |
unit/extractor.test.ts |
2 | 10 | ERP Extractor |
unit/data-importer.test.ts |
0 | 6 | Database |
unit/erp-auth.unit.test.ts |
0 | 8 | Authentication |
unit/update-catalog-service.test.ts |
0 | 6 | Update System |
unit/update-installer.test.ts |
2 | 6 | Update System |
unit/mysql.test.ts |
0 | 5 | Database |
unit/sql-server.test.ts |
0 | 5 | Database |
Template for Unit Tests:
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { ServiceClass } from '../../../src/main/services/domain/service'
// Mock dependencies
vi.mock('../../../src/main/services/logger', () => ({
createLogger: () => ({
info: vi.fn(),
debug: vi.fn(),
warn: vi.fn(),
error: vi.fn()
})
}))
describe('ServiceClass', () => {
let service: ServiceClass
beforeEach(() => {
vi.clearAllMocks()
// Initialize service with test config
})
afterEach(() => {
vi.resetAllMocks()
})
describe('Constructor', () => {
it('should create instance with valid config', () => {
const config = {
/* test config */
}
service = new ServiceClass(config)
expect(service).toBeDefined()
})
it('should throw on invalid config', () => {
expect(() => new ServiceClass(null as any)).toThrow()
})
})
describe('Public Method A', () => {
it('should return expected result', async () => {
const result = await service.methodA('input')
expect(result).toEqual('expected')
})
it('should handle edge case', async () => {
const result = await service.methodA('')
expect(result).toBeNull()
})
it('should log errors appropriately', async () => {
await expect(service.methodA('error-case')).rejects.toThrow()
})
})
})
Success Criteria:
- All 8 files have 5-10 meaningful tests each
- Total 50+ new unit tests added
- All new tests pass
- Code coverage increases by 15%
Verification Steps:
npm run test:run tests/unit/ -- --reporter=verbose
# Check: All previously empty files now have passing tests
Task 2.2: Complete Integration Tests
Priority: P1
Estimated Effort: 12 hours
Dependencies: Task 2.1 complete
Files to Populate:
| File | Current | Target Tests | Domain |
|---|---|---|---|
integration/cleaner.test.ts |
0 | 6 | ERP Integration |
integration/extractor.test.ts |
0 | 6 | ERP Integration |
integration/mysql.test.ts |
0 | 5 | Database Integration |
integration/sql-server.test.ts |
0 | 5 | Database Integration |
integration/erp-auth.test.ts |
0 | 4 | Auth Integration |
integration/ipc-logging.test.ts |
0 | 4 | IPC Integration |
Integration Test Template:
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { ServiceClass } from '../../../src/main/services/domain/service'
describe('ServiceClass Integration', () => {
const testConfig = {
// Use test environment config
useTestDatabase: true
}
beforeAll(async () => {
// Setup test database or external service
})
afterAll(async () => {
// Cleanup test data
})
it('should perform end-to-end operation', async () => {
const service = new ServiceClass(testConfig)
const result = await service.execute()
expect(result).toBeDefined()
expect(result.status).toBe('success')
})
})
Success Criteria:
- All 6 integration test files have 4-6 tests each
- Tests use isolated test data (no production impact)
- All tests pass in CI environment
- Integration test suite runs in <5 minutes
Task 2.3: Fix Existing Test Issues
Priority: P1
Estimated Effort: 4 hours
Dependencies: Task 1.1, 1.2 complete
Known Issues to Fix:
-
getErrorMessage should handle unknown types(errors.test.ts)// Fix: Add proper type guard handling it('should handle unknown types', () => { const unknown = { message: 'test' } expect(getErrorMessage(unknown as any)).toBe('test') }) -
Repository tests (repositories.test.ts)
// Fix: Proper TypeORM mocking vi.mock('typeorm', () => ({ DataSource: vi.fn().mockImplementation(() => ({ initialize: vi.fn(), destroy: vi.fn(), getRepository: vi.fn() })) })) -
Audit logger tests (audit-logger.test.ts)
// Fix: Winston transport mocking vi.mock('winston', async (importOriginal) => { const actual = await importOriginal() return { ...actual, createLogger: vi.fn(() => ({ // ... proper mock })) } })
Success Criteria:
- All 28 previously failing tests now pass
- No test skipped or marked as todo
- Test output is clean (no warnings)
Verification:
npm run test:run 2>&1 | Select-String -Pattern "failed|FAIL" -Context 2
# Expected: Zero failures
Phase 3: Medium-Term Goals (Month 2-3)
P2 - E2E Coverage Expansion
Task 3.1: Map Critical User Journeys
Priority: P2
Estimated Effort: 4 hours
Dependencies: None
User Journeys to Cover:
-
Authentication & User Management
- Silent login with saved credentials
- Manual login with username/password
- Password recovery flow
- User role switching (Admin/User/Guest)
- Session timeout and re-authentication
-
Data Extraction Workflow
- Navigate to extractor page
- Enter order numbers (single/multiple)
- Configure batch size
- Start extraction
- Monitor progress
- View/download results
- Handle extraction errors
-
Material Cleaning Workflow
- Navigate to cleaner page
- Enter order numbers and material codes
- Toggle dry-run mode
- Execute cleaning
- Review deletion statistics
- Handle errors
-
Configuration Management
- Open settings dialog
- Update ERP credentials
- Configure database settings
- Adjust application preferences
- Save and validate configuration
-
Update System
- Check for updates
- View update catalog
- Download update
- Install update
- Handle update failures
Success Criteria:
- Document all critical user journeys
- Prioritize journeys by business impact
- Create E2E test specification document
Task 3.2: Implement E2E Test Framework Enhancements
Priority: P2
Estimated Effort: 8 hours
Dependencies: Task 3.1 complete
Enhancements Needed:
- Test Fixtures & Page Objects
// tests/e2e/fixtures/login-fixture.ts
export class LoginFixture {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/')
}
async login(username: string, password: string) {
await this.page.fill('[data-testid="username"]', username)
await this.page.fill('[data-testid="password"]', password)
await this.page.click('[data-testid="login-button"]')
await this.page.waitForSelector('[data-testid="main-content"]')
}
async logout() {
await this.page.click('[data-testid="user-menu"]')
await this.page.click('[data-testid="logout-button"]')
}
}
// tests/e2e/pages/extractor-page.ts
export class ExtractorPage {
constructor(private page: Page) {}
async goto() {
await this.page.click('[data-testid="extractor-nav"]')
}
async enterOrders(orders: string[]) {
await this.page.fill('[data-testid="order-input"]', orders.join('\n'))
}
async setBatchSize(size: number) {
await this.page.fill('[data-testid="batch-size"]', size.toString())
}
async startExtraction() {
await this.page.click('[data-testid="start-extraction"]')
}
}
- Test Utilities
// tests/e2e/utils/test-helpers.ts
export async function waitForStableUI(page: Page, timeout = 5000) {
await page.waitForLoadState('networkidle')
await page.waitForTimeout(500) // Allow animations to complete
}
export async function captureState(page: Page, name: string) {
await page.screenshot({
path: `test-results/screenshots/${name}.png`,
fullPage: true
})
}
- Shared Test Data
// tests/e2e/fixtures/test-data.ts
export const TEST_DATA = {
validUser: {
username: 'test_user',
password: 'test_password',
role: 'User'
},
adminUser: {
username: 'admin',
password: 'admin_password',
role: 'Admin'
},
testOrders: ['SC12345678901234', 'SC12345678901235'],
testMaterials: ['MAT001', 'MAT002', 'MAT003']
}
Success Criteria:
- Page object models created for all major views
- Test utilities reduce code duplication
- Test data centralized and maintainable
Task 3.3: Write E2E Tests for Core Features
Priority: P2
Estimated Effort: 24 hours (3 hours per test file × 8 new files)
Dependencies: Task 3.2 complete
New E2E Test Files:
| File | Tests | Priority | Description |
|---|---|---|---|
login-flow.test.ts |
6 | High | Complete authentication flows |
extractor-workflow.test.ts |
8 | High | Data extraction E2E |
cleaner-workflow.test.ts |
8 | High | Material cleaning E2E |
settings-management.test.ts |
6 | Medium | Configuration management |
error-handling.test.ts |
5 | Medium | Error states & recovery |
navigation.test.ts |
4 | Medium | App navigation & routing |
update-workflow.test.ts |
5 | Low | Update download/install |
accessibility.test.ts |
4 | Low | Basic accessibility checks |
Example E2E Test Structure:
// tests/e2e/login-flow.test.ts
import { test, expect } from '@playwright/test'
import { _electron as electron } from 'playwright'
import path from 'path'
import { LoginFixture } from './fixtures/login-fixture'
import { TEST_DATA } from './fixtures/test-data'
test.describe('Login Flow', () => {
let electronApp: ElectronApplication
let page: Page
let login: LoginFixture
test.beforeAll(async () => {
electronApp = await electron.launch({
args: [path.join(__dirname, '../../out/main/index.js')],
env: { NODE_ENV: 'test' }
})
page = await electronApp.firstWindow()
login = new LoginFixture(page)
})
test.afterAll(async () => {
await electronApp?.close()
})
test('should login with valid credentials', async () => {
await login.goto()
await login.login(TEST_DATA.validUser.username, TEST_DATA.validUser.password)
await expect(page.locator('[data-testid="main-content"]')).toBeVisible()
await expect(page.locator('[data-testid="user-greeting"]')).toContainText(
TEST_DATA.validUser.username
)
})
test('should show error on invalid credentials', async () => {
await login.goto()
await login.login('invalid', 'wrong')
await expect(page.locator('[data-testid="error-message"]')).toBeVisible()
await expect(page.locator('[data-testid="error-message"]')).toContainText('Invalid credentials')
})
test('should handle silent login', async () => {
// Assume previous session exists
await login.goto()
await page.waitForLoadState('networkidle')
// Should auto-navigate to main content
await expect(page.locator('[data-testid="main-content"]')).toBeVisible({ timeout: 10000 })
})
test('should logout successfully', async () => {
await login.goto()
await login.login(TEST_DATA.validUser.username, TEST_DATA.validUser.password)
await login.logout()
await expect(page.locator('[data-testid="login-form"]')).toBeVisible()
})
})
Success Criteria:
- 8 new E2E test files created
- 50+ E2E tests total
- All critical user journeys covered
- E2E tests run reliably in CI
- Test flakiness < 5%
Verification:
npm run test:e2e -- --reporter=list
# Expected: All E2E tests pass consistently
P2 - Test Data Management
Task 3.4: Create Test Data Factory
Priority: P2
Estimated Effort: 6 hours
Dependencies: None
Purpose: Centralized test data generation for consistent, isolated tests.
Implementation:
// tests/fixtures/factory.ts
export class TestFactory {
static createOrder(overrides?: Partial<Order>): Order {
return {
id: `ORD-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
number: `SC${Date.now().toString().substr(-8)}`,
status: 'pending',
...overrides
}
}
static createMaterial(overrides?: Partial<Material>): Material {
return {
id: `MAT-${Date.now()}`,
code: `TEST_MAT_${Math.random().toString(36).substr(2, 6).toUpperCase()}`,
description: 'Test Material',
...overrides
}
}
static createUser(role: 'admin' | 'user' | 'guest' = 'user'): User {
return {
id: `USR-${Date.now()}`,
username: `test_${role}_${Date.now()}`,
password: 'test_password',
role,
permissions: this.getPermissionsForRole(role)
}
}
private static getPermissionsForRole(role: string): string[] {
const permissions: Record<string, string[]> = {
admin: ['read', 'write', 'delete', 'admin'],
user: ['read', 'write'],
guest: ['read']
}
return permissions[role] || []
}
}
Success Criteria:
- Factory provides methods for all domain entities
- Tests use factory instead of hardcoded data
- Each test gets unique, isolated data
- No test pollution from shared state
Phase 4: Long-Term Strategy (Month 4-6)
P3 - CI/CD Integration
Task 4.1: Configure GitHub Actions CI
Priority: P3
Estimated Effort: 8 hours
Dependencies: Phase 1-3 complete
Workflow: .github/workflows/test.yml
name: Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
unit-tests:
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run type check
run: npm run typecheck
- name: Run unit tests
run: npm run test:run -- --coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage/coverage-final.json
flags: unit-tests
integration-tests:
runs-on: windows-latest
timeout-minutes: 20
needs: unit-tests
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: test_password
MYSQL_DATABASE: erpauto_test
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
ports:
- 3306:3306
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Setup test database
run: |
npm run db:migrate:test
- name: Run integration tests
run: npm run test:run tests/integration/
e2e-tests:
runs-on: windows-latest
timeout-minutes: 30
needs: integration-tests
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: npm run test:e2e
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
- name: Upload screenshots
uses: actions/upload-artifact@v3
if: failure()
with:
name: test-screenshots
path: test-results/
retention-days: 7
Success Criteria:
- CI pipeline runs on every PR
- Unit tests complete in <10 minutes
- Integration tests complete in <15 minutes
- E2E tests complete in <20 minutes
- Coverage reports uploaded automatically
- Failed tests create artifacts for debugging
Task 4.2: Add Coverage Thresholds
Priority: P3
Estimated Effort: 2 hours
Dependencies: Task 4.1 complete
Configuration (vitest.config.ts):
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html', 'lcov'],
exclude: [
'node_modules',
'src/tests',
'**/*.d.ts',
'**/*.config.*',
'**/types/**',
'out',
'dist'
],
thresholds: {
global: {
branches: 60,
functions: 70,
lines: 70,
statements: 70
},
'src/main/services/erp/**': {
branches: 70,
functions: 80,
lines: 80,
statements: 80
},
'src/main/services/update/**': {
branches: 75,
functions: 85,
lines: 85,
statements: 85
}
}
}
}
})
CI Enforcement (.github/workflows/test.yml):
- name: Check coverage thresholds
run: |
$coverage = Get-Content coverage/coverage-final.json | ConvertFrom-Json
$lines = $coverage.total.lines.pct
if ($lines -lt 70) {
Write-Error "Coverage $lines% is below threshold of 70%"
exit 1
}
Success Criteria:
- Coverage thresholds enforced in CI
- PRs fail if coverage drops below threshold
- Critical modules have higher thresholds
- Coverage reports accessible via CI artifacts
Task 4.3: Implement Test Health Monitoring
Priority: P3
Estimated Effort: 4 hours
Dependencies: Task 4.1 complete
Metrics to Track:
-
Test Duration Trends
- Track test execution time over builds
- Alert on tests exceeding time thresholds
- Identify slow tests for optimization
-
Flakiness Detection
- Track tests that fail intermittently
- Auto-retry flaky tests once
- Generate flakiness reports
-
Coverage Trends
- Track coverage changes per PR
- Alert on coverage regression
- Identify untested critical paths
Dashboard Integration:
# .github/workflows/test-metrics.yml
- name: Upload test metrics
run: |
npm run test:metrics
- name: Publish to dashboard
uses: ./actions/publish-metrics
with:
token: ${{ secrets.DASHBOARD_TOKEN }}
Success Criteria:
- Test metrics collected every build
- Dashboard shows test health trends
- Flaky tests automatically identified
- Coverage trends visible over time
P3 - Test Quality Improvements
Task 4.4: Add Mutation Testing
Priority: P3
Estimated Effort: 6 hours
Dependencies: Phase 1-3 complete
Tool: Stryker Mutator (when available for Vitest)
Configuration (stryker.conf.json):
{
"$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json",
"_comment": "Stryker configuration for mutation testing",
"packageManager": "npm",
"reporters": ["html", "clear-text", "progress"],
"testRunner": "vitest",
"testRunner_comment": "Vitest support via community plugin",
"coverageAnalysis": "perTest",
"thresholds": {
"high": 80,
"low": 60,
"break": 60
},
"mutate": [
"src/main/services/**/*.ts",
"!src/main/services/**/*.test.ts",
"!src/main/services/**/*.spec.ts"
]
}
Success Criteria:
- Mutation score > 60%
- Mutation report generated per build
- Critical mutations identified and tested
Task 4.5: Implement Visual Regression Testing (Optional)
Priority: P3
Estimated Effort: 8 hours
Dependencies: Task 3.2 complete
Purpose: Catch unintended UI changes
Implementation:
// tests/e2e/visual-regression.test.ts
import { test, expect } from '@playwright/test'
test.describe('Visual Regression', () => {
test('main dashboard should match baseline', async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
await expect(page).toHaveScreenshot('main-dashboard.png', {
fullPage: true,
maxDiffPixels: 100 // Allow small dynamic differences
})
})
test('login dialog should match baseline', async ({ page }) => {
await page.goto('/')
const loginDialog = page.locator('[data-testid="login-dialog"]')
await expect(loginDialog).toHaveScreenshot('login-dialog.png')
})
})
Success Criteria:
- Baseline screenshots captured
- Visual diffs on each PR
- False positive rate < 10%
Success Metrics & QA Verification
Phase Completion Criteria
| Phase | Success Metrics | QA Verification |
|---|---|---|
| Phase 1 | Zero suite failures, all mocks working | npm run test:run exits 0 |
| Phase 2 | All empty files populated, 50+ new tests | Coverage report shows +15% |
| Phase 3 | 50+ E2E tests, all journeys covered | npm run test:e2e runs clean |
| Phase 4 | CI pipeline green, coverage > 70% | PR requires passing CI |
Verification Commands
# Full test suite
npm run test:run
npm run test:e2e
# Coverage report
npm run test:coverage
# Type checking
npm run typecheck
# Build verification
npm run build
# Combined quality gate
npm run typecheck && npm run test:run && npm run build
Quality Gates
Before marking any phase complete:
- All tests pass - No failures, no skips
- No new lint errors -
npm run lintclean - TypeScript compiles -
npm run typecheckexits 0 - Application builds -
npm run buildexits 0 - Coverage maintained - No regression in critical areas
Risk Mitigation
Identified Risks
| Risk | Impact | Mitigation |
|---|---|---|
| ERP credentials unavailable for tests | High | Use mocked ERP service, sandbox environment |
| Database tests pollute production | High | Use isolated test database, transaction rollback |
| E2E tests flaky | Medium | Retry logic, better selectors, wait utilities |
| Test execution too slow | Medium | Parallel execution, test splitting, caching |
| Coverage thresholds block PRs | Low | Gradual threshold increase, exclude legitimate cases |
Rollback Plan
If test improvements cause issues:
- Revert test changes via git
- Restore previous
vitest.config.ts - Disable failing tests temporarily with
describe.skip - Fix root cause before re-enabling
Appendix A: Test File Inventory
Current Unit Tests (28 files)
tests/unit/
├── auth-handler.test.ts ✓ Passing
├── audit-logger.test.ts ⚠ Partial failures
├── bootstrap-runtime.test.ts ⚠ All failing
├── cleaner-handler.test.ts ✓ Passing
├── cleaner-helpers.test.ts ✓ Passing
├── cleaner.test.ts ✗ Empty
├── data-importer.test.ts ✗ Empty
├── errors.test.ts ⚠ Partial failures
├── excel-parser.test.ts ✓ Passing
├── extractor.test.ts ✗ Minimal tests
├── file-ipc-paths.test.ts ✓ Passing (minimal)
├── ipc-index.test.ts ✓ Passing (minimal)
├── logger-integration.test.ts ⚠ Partial failures
├── logger.test.ts ⚠ All failing
├── locators.test.ts ✓ Passing
├── mysql.test.ts ✗ Empty
├── preload-surface.test.ts ✓ Passing (minimal)
├── production-input-service.test.ts ✓ Passing (minimal)
├── repositories.test.ts ⚠ All failing
├── request-context.test.ts ✓ Passing
├── schemas.test.ts ✓ Passing
├── shared-production-ids-store.test.ts ✓ Passing
├── sql-server.test.ts ✗ Empty
├── update-catalog-service.test.ts ✗ Empty
├── update-installer.test.ts ✗ Minimal tests
├── update-service.test.ts ⚠ Partial failures
├── update-status-publisher.test.ts ✓ Passing
├── update-utils.test.ts ✓ Passing
└── services/
├── erp/
│ ├── erp-error-context.test.ts ✓ Passing
│ └── page-diagnostics.test.ts ✓ Passing
└── logger/
└── error-utils.test.ts ✓ Passing
Current Integration Tests (8 files)
tests/integration/
├── cleaner.test.ts ✗ Empty
├── erp-auth.test.ts ✗ Empty
├── extractor.test.ts ✗ Empty
├── ipc-logging.test.ts ✗ Empty
├── logger-performance.test.ts ✓ Passing
├── mysql.test.ts ✗ Empty
├── sql-server.test.ts ✗ Empty
└── test-merge.test.ts ⚠ Skipped
Current E2E Tests (4 files)
tests/e2e/
├── auth-flow.test.ts ⚠ Basic tests
├── dialog-focus.test.ts ✓ Passing
├── extractor-workflow.test.ts ✓ Passing
└── login-flow.test.ts ⚠ Needs expansion
Debug & Manual Tests (4 files)
tests/debug/
└── env.test.ts ⚠ Fails (no .env)
tests/manual/
├── cleaner-slow-motion.test.ts ✗ Manual test
└── test-merge.test.ts ⚠ Skipped
Appendix B: Recommended Project Structure
tests/
├── setup.ts # Global test setup
├── .env.test # Test environment variables
├── fixtures/
│ ├── factory.ts # Test data factory
│ ├── test-data.ts # Shared test data
│ └── index.ts # Fixture exports
├── e2e/
│ ├── fixtures/
│ │ ├── login-fixture.ts
│ │ └── test-data.ts
│ ├── pages/
│ │ ├── login-page.ts
│ │ ├── extractor-page.ts
│ │ └── cleaner-page.ts
│ ├── utils/
│ │ └── test-helpers.ts
│ ├── *.test.ts # E2E test files
│ └── playwright.config.ts # E2E config
├── integration/
│ ├── *.test.ts # Integration test files
│ └── helpers/
│ └── database-helpers.ts # DB test utilities
├── unit/
│ ├── *.test.ts # Unit test files
│ └── mocks/
│ ├── electron.ts # Electron mock
│ ├── winston.ts # Winston mock
│ └── typeorm.ts # TypeORM mock
└── debug/
└── *.test.ts # Debug tests
Appendix C: Test Writing Guidelines
Best Practices
-
Test Naming
// Good: Descriptive it('should return null when order number is invalid', () => {}) // Bad: Vague it('should work', () => {}) -
Arrange-Act-Assert Pattern
it('should create order', async () => { // Arrange const orderData = { id: '123', status: 'pending' } // Act const result = await service.createOrder(orderData) // Assert expect(result.status).toBe('pending') }) -
Test Isolation
- Each test should be independent
- Use
beforeEachfor setup,afterEachfor cleanup - Never share state between tests
-
Mock External Dependencies
vi.mock('external-lib', () => ({ functionName: vi.fn().mockResolvedValue('mocked') })) -
Test Edge Cases
- Empty inputs
- Maximum values
- Invalid formats
- Network failures
- Race conditions
Anti-Patterns to Avoid
-
Testing Implementation Details
// Bad: Tests internal state expect(service.internalCounter).toBe(5) // Good: Tests behavior expect(await service.process()).toEqual(expected) -
Over-Mocking
// Bad: Mocking everything vi.mock('all', 'the', 'dependencies') // Good: Mock only external services vi.mock('database') vi.mock('external-api') -
Magic Numbers
// Bad await page.waitForTimeout(3000) // Good const ANIMATION_DURATION = 300 await page.waitForTimeout(ANIMATION_DURATION)
Next Steps
- Week 1: Implement Phase 1 (P0 fixes)
- Week 2-3: Implement Phase 2 (populate empty tests)
- Month 2-3: Implement Phase 3 (E2E expansion)
- Month 4-6: Implement Phase 4 (CI/CD integration)
Review Cadence:
- Daily: Check test execution results
- Weekly: Review test coverage trends
- Monthly: Assess progress against milestones
Success Celebration:
- Phase 1 complete: Team demo of passing tests
- Phase 2 complete: Coverage report presentation
- Phase 3 complete: E2E demo to stakeholders
- Phase 4 complete: CI/CD pipeline showcase
Document Owner: Development Team
Last Updated: 2026-04-04
Review Schedule: Monthly