test(ui): add e2e and integration tests

This commit is contained in:
Tuan-Dat Tran
2026-02-23 13:48:13 +01:00
parent 00af8862d3
commit 4198b91498
9 changed files with 632 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Regression: API Snapshots > GET /api/cv response matches snapshot > cv-data-response 1`] = `
{
"education": [
{
"degree": "CS Degree",
"id": 1,
"institution": "Test University",
"period": "2016-2020",
"status": "Completed",
},
],
"experience": [
{
"company": "Test Corp",
"description": "Testing",
"highlights": [
"Automated tests",
],
"id": 1,
"period": "2020-2024",
"role": "QA Engineer",
"type": "Vollzeit",
},
],
"personal": {
"email": "regression@test.com",
"name": "Regression Test User",
"title": "Test Engineer",
},
"projects": [
{
"description": "Test framework",
"id": 1,
"name": "Test Project",
"tech": [
"Vitest",
],
"url": "https://test.com",
},
],
"skills": {
"Testing": [
"Vitest",
"Playwright",
],
},
}
`;
exports[`Regression: API Snapshots > education section matches snapshot > education-section 1`] = `
[
{
"degree": "CS Degree",
"id": 1,
"institution": "Test University",
"period": "2016-2020",
"status": "Completed",
},
]
`;
exports[`Regression: API Snapshots > experience section matches snapshot > experience-section 1`] = `
[
{
"company": "Test Corp",
"description": "Testing",
"highlights": [
"Automated tests",
],
"id": 1,
"period": "2020-2024",
"role": "QA Engineer",
"type": "Vollzeit",
},
]
`;
exports[`Regression: API Snapshots > personal section matches snapshot > personal-section 1`] = `
{
"email": "regression@test.com",
"name": "Regression Test User",
"title": "Test Engineer",
}
`;
exports[`Regression: API Snapshots > projects section matches snapshot > projects-section 1`] = `
[
{
"description": "Test framework",
"id": 1,
"name": "Test Project",
"tech": [
"Vitest",
],
"url": "https://test.com",
},
]
`;
exports[`Regression: API Snapshots > skills section matches snapshot > skills-section 1`] = `
{
"Testing": [
"Vitest",
"Playwright",
],
}
`;

View File

@@ -0,0 +1,104 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import express from 'express';
import cors from 'cors';
import { mkdirSync, existsSync, rmSync } from 'fs';
import { join } from 'path';
import Database from 'better-sqlite3';
const testDbPath = join(process.cwd(), 'tests', 'regression', 'api-snapshots', 'test.db');
const snapshotDir = join(process.cwd(), 'tests', 'regression', 'api-snapshots');
let app;
let db;
function setupTestDB() {
if (!existsSync(snapshotDir)) {
mkdirSync(snapshotDir, { recursive: true });
}
if (existsSync(testDbPath)) {
rmSync(testDbPath);
}
db = new Database(testDbPath);
db.exec(`
CREATE TABLE IF NOT EXISTS cv_data (
id INTEGER PRIMARY KEY CHECK (id = 1),
data TEXT NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
const defaultData = {
personal: { name: 'Regression Test User', title: 'Test Engineer', email: 'regression@test.com' },
experience: [{ id: 1, role: 'QA Engineer', company: 'Test Corp', period: '2020-2024', type: 'Vollzeit', description: 'Testing', highlights: ['Automated tests'] }],
skills: { 'Testing': ['Vitest', 'Playwright'] },
education: [{ id: 1, degree: 'CS Degree', institution: 'Test University', period: '2016-2020', status: 'Completed' }],
projects: [{ id: 1, name: 'Test Project', description: 'Test framework', url: 'https://test.com', tech: ['Vitest'] }]
};
db.prepare('INSERT INTO cv_data (id, data) VALUES (1, ?)').run(JSON.stringify(defaultData));
}
function createTestApp() {
const app = express();
app.use(cors());
app.use(express.json());
app.get('/api/cv', (req, res) => {
const row = db.prepare('SELECT data FROM cv_data WHERE id = 1').get();
if (!row) return res.status(404).json({ error: 'CV data not found' });
res.json(JSON.parse(row.data));
});
return app;
}
describe('Regression: API Snapshots', () => {
beforeAll(() => {
setupTestDB();
app = createTestApp();
});
afterAll(() => {
if (db) {
db.close();
}
if (existsSync(testDbPath)) {
rmSync(testDbPath);
}
});
it('GET /api/cv response matches snapshot', async () => {
const response = await request(app).get('/api/cv');
expect(response.status).toBe(200);
expect(response.body).toMatchSnapshot('cv-data-response');
});
it('personal section matches snapshot', async () => {
const response = await request(app).get('/api/cv');
expect(response.body.personal).toMatchSnapshot('personal-section');
});
it('experience section matches snapshot', async () => {
const response = await request(app).get('/api/cv');
expect(response.body.experience).toMatchSnapshot('experience-section');
});
it('skills section matches snapshot', async () => {
const response = await request(app).get('/api/cv');
expect(response.body.skills).toMatchSnapshot('skills-section');
});
it('education section matches snapshot', async () => {
const response = await request(app).get('/api/cv');
expect(response.body.education).toMatchSnapshot('education-section');
});
it('projects section matches snapshot', async () => {
const response = await request(app).get('/api/cv');
expect(response.body.projects).toMatchSnapshot('projects-section');
});
});

View File

@@ -0,0 +1,44 @@
import { test, expect } from '@playwright/test';
test.describe('Visual Regression: CV Page', () => {
test('hero section visual snapshot', async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('networkidle');
const hero = page.locator('section').first();
await expect(hero).toHaveScreenshot('hero-section.png', {
maxDiffPixels: 100,
});
});
test('full page visual snapshot', async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('networkidle');
await expect(page).toHaveScreenshot('full-page.png', {
fullPage: true,
maxDiffPixels: 500,
});
});
});
test.describe('Visual Regression: Admin Page', () => {
test('admin panel visual snapshot', async ({ page }) => {
await page.goto('/admin');
await page.waitForLoadState('networkidle');
await expect(page).toHaveScreenshot('admin-panel.png', {
maxDiffPixels: 200,
});
});
test('personal form visual snapshot', async ({ page }) => {
await page.goto('/admin');
await page.waitForLoadState('networkidle');
const form = page.locator('form').first();
await expect(form).toHaveScreenshot('personal-form.png', {
maxDiffPixels: 100,
});
});
});