49b413bf07
* feat: implement comprehensive testing infrastructure - Fix image dataURL truncation bug in security.ts with configurable size limits - Add backend integration tests (22 tests) with Vitest for API validation - Add frontend unit tests (11 tests) for JSON serialization - Implement browser-based E2E tests (8 tests) with Playwright - Create Docker setup for repeatable E2E testing environment - Add GitHub Actions CI workflow for automated testing - Update .gitignore for test artifacts and temporary files Testing Infrastructure: - Backend: Vitest + Supertest for API integration tests - Frontend: Vitest + Testing Library for component tests - E2E: Playwright with Chromium for full browser automation - CI/CD: GitHub Actions with parallel test execution Security Improvements: - Make dataURL size limit configurable (default: 10MB) - Enhanced validation for image dataURLs - Block malicious content (javascript:, script tags) All tests pass: 41 total (22 backend + 11 frontend + 8 E2E) * feat(tests): add comprehensive E2E tests for dashboard workflows and image persistence chore(env): update environment variables for consistent API URL usage fix(api): centralize API request helpers for drawing and collection management style(DrawingCard): enhance accessibility with ARIA attributes and data-testid for testing * cleanup/revise documentation * cleanup/revise documentation * Add end-to-end tests for drawing CRUD, export/import, search/sort, and theme toggle functionalities - Implemented E2E tests for drawing creation, editing, and deletion in `drawing-crud.spec.ts`. - Added tests for export and import features, including JSON and SQLite formats in `export-import.spec.ts`. - Created tests for searching and sorting drawings by name and date in `search-and-sort.spec.ts`. - Developed tests for theme toggle functionality to ensure persistence across sessions in `theme-toggle.spec.ts`. * fix: exclude test files from production build to fix Docker build * feat: implement comprehensive testing infrastructure (#19) * bump version 0.1.7 * feat: implement comprehensive testing infrastructure - Fix image dataURL truncation bug in security.ts with configurable size limits - Add backend integration tests (22 tests) with Vitest for API validation - Add frontend unit tests (11 tests) for JSON serialization - Implement browser-based E2E tests (8 tests) with Playwright - Create Docker setup for repeatable E2E testing environment - Add GitHub Actions CI workflow for automated testing - Update .gitignore for test artifacts and temporary files Testing Infrastructure: - Backend: Vitest + Supertest for API integration tests - Frontend: Vitest + Testing Library for component tests - E2E: Playwright with Chromium for full browser automation - CI/CD: GitHub Actions with parallel test execution Security Improvements: - Make dataURL size limit configurable (default: 10MB) - Enhanced validation for image dataURLs - Block malicious content (javascript:, script tags) All tests pass: 41 total (22 backend + 11 frontend + 8 E2E) * feat(tests): add comprehensive E2E tests for dashboard workflows and image persistence chore(env): update environment variables for consistent API URL usage fix(api): centralize API request helpers for drawing and collection management style(DrawingCard): enhance accessibility with ARIA attributes and data-testid for testing * Add end-to-end tests for drawing CRUD, export/import, search/sort, and theme toggle functionalities - Implemented E2E tests for drawing creation, editing, and deletion in `drawing-crud.spec.ts`. - Added tests for export and import features, including JSON and SQLite formats in `export-import.spec.ts`. - Created tests for searching and sorting drawings by name and date in `search-and-sort.spec.ts`. - Developed tests for theme toggle functionality to ensure persistence across sessions in `theme-toggle.spec.ts`. * Update backend/src/__tests__/testUtils.ts --------- Co-authored-by: Zimeng Xiong <zxzimeng@gmail.com> * version bump 0.1.8 * fix(ci): consolidate E2E server startup to prevent shell isolation issues Background processes started with & in separate GitHub Actions run steps can terminate when those steps complete because each step creates a new shell. This caused the backend and frontend servers to die before the E2E tests could run. Fixed by consolidating server startup and test execution into a single shell step with: - Proper PID tracking for cleanup - Health check loops instead of fixed sleep times - All processes run in the same shell session * fix(ci): use absolute database path for E2E tests * fix(backend): use resolved DATABASE_URL path for export/import endpoints --------- Co-authored-by: Adrian Acala <adrianacala017@gmail.com>
144 lines
4.0 KiB
TypeScript
144 lines
4.0 KiB
TypeScript
import { APIRequestContext, expect } from "@playwright/test";
|
|
|
|
// Default ports match the Playwright config
|
|
const DEFAULT_BACKEND_PORT = 8000;
|
|
|
|
export const API_URL = process.env.API_URL || `http://localhost:${DEFAULT_BACKEND_PORT}`;
|
|
|
|
export interface DrawingRecord {
|
|
id: string;
|
|
name: string;
|
|
collectionId: string | null;
|
|
preview?: string | null;
|
|
version?: number;
|
|
createdAt?: number | string;
|
|
updatedAt?: number | string;
|
|
elements?: any[];
|
|
appState?: Record<string, any> | null;
|
|
files?: Record<string, any>;
|
|
}
|
|
|
|
export interface CollectionRecord {
|
|
id: string;
|
|
name: string;
|
|
createdAt?: number | string;
|
|
}
|
|
|
|
export interface CreateDrawingOptions {
|
|
name?: string;
|
|
elements?: any[];
|
|
appState?: Record<string, any>;
|
|
files?: Record<string, any>;
|
|
preview?: string | null;
|
|
collectionId?: string | null;
|
|
}
|
|
|
|
export interface ListDrawingsOptions {
|
|
search?: string;
|
|
collectionId?: string | null;
|
|
includeData?: boolean;
|
|
}
|
|
|
|
const defaultDrawingPayload = () => ({
|
|
name: `E2E Drawing ${Date.now()}`,
|
|
elements: [],
|
|
appState: { viewBackgroundColor: "#ffffff" },
|
|
files: {},
|
|
preview: null,
|
|
collectionId: null as string | null,
|
|
});
|
|
|
|
export async function createDrawing(
|
|
request: APIRequestContext,
|
|
overrides: CreateDrawingOptions = {}
|
|
): Promise<DrawingRecord> {
|
|
const payload = { ...defaultDrawingPayload(), ...overrides };
|
|
const response = await request.post(`${API_URL}/drawings`, {
|
|
headers: { "Content-Type": "application/json" },
|
|
data: payload,
|
|
});
|
|
if (!response.ok()) {
|
|
const text = await response.text();
|
|
throw new Error(`Failed to create drawing: ${response.status()} ${text}`);
|
|
}
|
|
return (await response.json()) as DrawingRecord;
|
|
}
|
|
|
|
export async function getDrawing(
|
|
request: APIRequestContext,
|
|
id: string
|
|
): Promise<DrawingRecord> {
|
|
const response = await request.get(`${API_URL}/drawings/${id}`);
|
|
expect(response.ok()).toBe(true);
|
|
return (await response.json()) as DrawingRecord;
|
|
}
|
|
|
|
export async function deleteDrawing(
|
|
request: APIRequestContext,
|
|
id: string
|
|
): Promise<void> {
|
|
const response = await request.delete(`${API_URL}/drawings/${id}`);
|
|
if (!response.ok()) {
|
|
// Ignore not found to keep cleanup idempotent
|
|
if (response.status() !== 404) {
|
|
const text = await response.text();
|
|
throw new Error(`Failed to delete drawing ${id}: ${response.status()} ${text}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function listDrawings(
|
|
request: APIRequestContext,
|
|
options: ListDrawingsOptions = {}
|
|
): Promise<DrawingRecord[]> {
|
|
const params = new URLSearchParams();
|
|
if (options.search) params.set("search", options.search);
|
|
if (options.collectionId !== undefined) {
|
|
params.set(
|
|
"collectionId",
|
|
options.collectionId === null ? "null" : String(options.collectionId)
|
|
);
|
|
}
|
|
if (options.includeData) params.set("includeData", "true");
|
|
|
|
const query = params.toString();
|
|
const response = await request.get(
|
|
`${API_URL}/drawings${query ? `?${query}` : ""}`
|
|
);
|
|
expect(response.ok()).toBe(true);
|
|
return (await response.json()) as DrawingRecord[];
|
|
}
|
|
|
|
export async function createCollection(
|
|
request: APIRequestContext,
|
|
name: string
|
|
): Promise<CollectionRecord> {
|
|
const response = await request.post(`${API_URL}/collections`, {
|
|
headers: { "Content-Type": "application/json" },
|
|
data: { name },
|
|
});
|
|
expect(response.ok()).toBe(true);
|
|
return (await response.json()) as CollectionRecord;
|
|
}
|
|
|
|
export async function listCollections(
|
|
request: APIRequestContext
|
|
): Promise<CollectionRecord[]> {
|
|
const response = await request.get(`${API_URL}/collections`);
|
|
expect(response.ok()).toBe(true);
|
|
return (await response.json()) as CollectionRecord[];
|
|
}
|
|
|
|
export async function deleteCollection(
|
|
request: APIRequestContext,
|
|
id: string
|
|
): Promise<void> {
|
|
const response = await request.delete(`${API_URL}/collections/${id}`);
|
|
if (!response.ok()) {
|
|
if (response.status() !== 404) {
|
|
const text = await response.text();
|
|
throw new Error(`Failed to delete collection ${id}: ${response.status()} ${text}`);
|
|
}
|
|
}
|
|
}
|