# QA Studio - Test Reporting Platform ## Project Overview QA Studio is a comprehensive test management and reporting platform inspired by Allure TestOps and TestRail. It enables teams to organize, execute, and track testing activities across projects. ### Core Features (Phase 1) - **Project Management**: Organize testing by projects with unique keys - **Test Organization**: Create hierarchical test suites and test cases - **Test Execution**: Plan and execute test runs across different environments - **Results Tracking**: Record detailed test results with steps, attachments, and metrics - **Milestone Planning**: Track testing progress against release milestones - **Rich Reporting**: Capture screenshots, logs, and stack traces for failures ### Tech Stack - **Frontend**: SvelteKit 2, Svelte 5, Skeleton UI, Tailwind 4 - **Backend**: SvelteKit API routes - **Database**: PostgreSQL with Prisma ORM - **Authentication**: Self-hosted with bcrypt password hashing and secure sessions - **Storage**: File attachments (screenshots, logs, videos) - **Syntax Highlighting**: Shiki (for API documentation) ## TypeScript Best Practices ### Avoid `as any` Type Assertions **IMPORTANT**: Avoid using `as any` type assertions in TypeScript code. Using `as any` defeats the purpose of TypeScript's type system by disabling all type checking. **Why to avoid `as any`:** - Loses all type safety benefits - Hides type errors that could catch bugs - Makes refactoring dangerous (no compiler errors when types change) - Reduces code maintainability and IDE autocomplete support **Better alternatives:** 1. **Use proper types from Prisma/Zod schemas:** ```typescript // ❌ Bad - loses type safety vi.mocked(db.project.findUnique).mockResolvedValue({ id: 'proj_123', name: 'Test' } as any); // ✅ Good - use Prisma types import type { Project } from '$prisma/client'; vi.mocked(db.project.findUnique).mockResolvedValue({ id: 'proj_123', name: 'Test', key: 'TEST', createdBy: 'user_123', teamId: null, createdAt: new Date(), updatedAt: new Date() } satisfies Project); ``` 2. **Use `satisfies` operator for partial mocks:** ```typescript // ✅ Good - type-safe partial mock const mockUser = { id: 'user_123', email: 'test@example.com', teamId: 'team_123' } satisfies Partial; ``` 3. **Create proper type definitions:** ```typescript // ✅ Good - define the exact type you need type MockProject = Pick & { createdAt: Date; updatedAt: Date; }; ``` 4. **Use `unknown` for truly unknown types, then narrow:** ```typescript // ✅ Good - safer than 'any' const result = (await someFunction()) as unknown; if (isValidType(result)) { // Now result is properly typed } ``` **When `any` IS acceptable:** There are legitimate cases where `any` is the correct choice: 1. **Third-party library API contracts** - When a library expects `any` as part of its designed API: ```typescript // ✅ Good - sveltekit-api's Modifier expects 'any' to mutate OpenAPI config export const Modifier = (r: any) => { r.tags = ['Projects']; r.summary = 'Update resource'; return r; }; ``` 2. **Dynamic Prisma updates** - When building update objects conditionally: ```typescript // ✅ Good - Record for dynamic Prisma updates const data: Record = {}; if (updateData.name !== undefined) { data.name = updateData.name; } if (updateData.status !== undefined) { data.status = updateData.status; } await db.resource.update({ where: { id }, data }); ``` Note: `Record` is better than plain `any` because it communicates "object with string keys" and is validated by Prisma at runtime. 3. **Complex mutation patterns** - When mutating objects where the full type would be impractical: ```typescript // ✅ Good - when working with complex OpenAPI/config objects function enhanceConfig(config: any): any { config.metadata = { ...config.metadata, enhanced: true }; return config; } ``` **When `as any` is NOT acceptable:** - Your application's business logic - Data models and entities - Function return types (let TypeScript infer or use proper types) - Test mocks (use `satisfies` instead) **In tests specifically:** - Use Vitest's type utilities: `vi.mocked()` handles most typing needs - Mock with full object shapes that match the actual types - Use `satisfies` to ensure partial mocks are type-safe - Never use `as any` in test data - it hides type errors ## Database Schema The database is organized into logical sections: ### Projects & Organization - **Project**: Top-level container with unique key (e.g., "PROJ") - **Milestone**: Release milestones for tracking progress - **Environment**: Testing environments (Production, Staging, QA, etc.) ### Test Suites & Cases - **TestSuite**: Hierarchical organization of tests (supports nesting) - **TestCase**: Individual test cases with steps, priorities, types, and automation status - Supports multiple test types: Functional, Regression, Smoke, Integration, Performance, Security, UI, API, Unit, E2E - Priorities: Critical, High, Medium, Low - Automation tracking: Automated, Not Automated, Candidate ### Test Runs & Results - **TestRun**: Test execution session linked to project, milestone, and environment - **TestResult**: Individual test case execution results with status, duration, errors - **TestStepResult**: Granular step-by-step execution tracking - **TestStatus**: Passed, Failed, Blocked, Skipped, Retest, Untested ### Attachments - **Attachment**: Store screenshots, logs, videos linked to test cases or results ### Database Access Patterns This project uses custom path aliases for Prisma imports: **Database Instance** (most common): ```typescript import { db } from '$lib/server/db'; ``` Use this to access the singleton Prisma client instance for database queries. **Prisma Client Class**: ```typescript import { PrismaClient } from '$prisma/client'; ``` Use this when you need to instantiate a new Prisma client (rare - usually only in setup/migration scripts). **Prisma Types**: ```typescript import { Prisma } from '$prisma/client'; ``` Use this to access Prisma-generated TypeScript types like `Prisma.SmsMessageWhereInput` for type-safe queries. **Important**: Do NOT use the standard `@prisma/client` import path - this project uses the `$prisma/client` alias. **Standalone Scripts** (outside SvelteKit): For standalone scripts (like migration scripts) that run with `tsx` or `node`, you cannot use SvelteKit path aliases. Instead, use direct imports and initialize Prisma with the PostgreSQL adapter: ```typescript import 'dotenv/config'; import { PrismaClient } from '../src/generated/client/client.js'; import { PrismaPg } from '@prisma/adapter-pg'; import { Pool } from 'pg'; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const adapter = new PrismaPg(pool); const db = new PrismaClient({ adapter }); // ... your script code ... // Always disconnect when done await db.$disconnect(); await pool.end(); ``` **Examples**: ```typescript // ✅ Correct - Query with singleton instance (SvelteKit routes/endpoints) import { db } from '$lib/server/db'; const users = await db.user.findMany(); // ✅ Correct - Type-safe where clause (SvelteKit routes/endpoints) import { db } from '$lib/server/db'; import { Prisma } from '$prisma/client'; const where: Prisma.UserWhereInput = { email: { contains: '@example.com' } }; const users = await db.user.findMany({ where }); // ✅ Correct - Standalone script (scripts/*) import { PrismaClient } from '../src/generated/client/client.js'; import { PrismaPg } from '@prisma/adapter-pg'; import { Pool } from 'pg'; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const adapter = new PrismaPg(pool); const db = new PrismaClient({ adapter }); // ❌ Wrong - Don't use @prisma/client in SvelteKit code import { PrismaClient } from '@prisma/client'; // Wrong path for SvelteKit! ``` ## Writing Standalone Scripts When writing scripts in the `scripts/` directory that run outside of SvelteKit (using `tsx`, `ts-node`, or `node`), you **cannot** use SvelteKit path aliases or environment imports. ### Why Scripts Fail with SvelteKit Imports SvelteKit path aliases like `$lib`, `$app`, `$env`, and `$prisma` are resolved by Vite during the build process. Standalone scripts run directly with Node.js/tsx and don't go through Vite, so these aliases are not available. **Common Errors:** ``` Error [ERR_MODULE_NOT_FOUND]: Cannot find package '$app' Error [ERR_MODULE_NOT_FOUND]: Cannot find package '$lib' Error [ERR_MODULE_NOT_FOUND]: Cannot find package '$prisma/client' ``` ### Script Structure Template Here's the correct pattern for standalone scripts: ```typescript /** * Script description here * * Usage: * npx tsx scripts/my-script.ts * npx tsx scripts/my-script.ts --dry-run */ import 'dotenv/config'; // Load environment variables import { PrismaClient } from '../src/generated/client/client.js'; import { PrismaPg } from '@prisma/adapter-pg'; import { Pool } from 'pg'; // ✅ Environment variables from dotenv const DATABASE_URL = process.env.DATABASE_URL; const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; if (!DATABASE_URL) { console.error('ERROR: DATABASE_URL environment variable is not set'); process.exit(1); } // ✅ Initialize Prisma with PostgreSQL adapter (required for Prisma 7) const pool = new Pool({ connectionString: DATABASE_URL }); const adapter = new PrismaPg(pool); const db = new PrismaClient({ adapter }); // ✅ Parse command-line arguments const isDryRun = process.argv.includes('--dry-run'); async function main() { if (isDryRun) { console.log('🔍 DRY RUN MODE - No changes will be made\n'); } // Your script logic here const users = await db.user.findMany(); console.log(`Found ${users.length} users`); // Example: Copy utility functions inline if needed // Don't import from $lib - copy the function code instead } // ✅ Always cleanup database connections main() .then(async () => { console.log('\nScript finished successfully'); await db.$disconnect(); await pool.end(); process.exit(0); }) .catch(async (error) => { console.error('\nScript failed:', error); await db.$disconnect(); await pool.end(); process.exit(1); }); ``` ### Key Patterns for Scripts **1. Database Access:** ```typescript // ❌ WRONG - Cannot use $lib in scripts import { db } from '$lib/server/db'; // ✅ CORRECT - Use direct import with adapter import { PrismaClient } from '../src/generated/client/client.js'; import { PrismaPg } from '@prisma/adapter-pg'; import { Pool } from 'pg'; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const adapter = new PrismaPg(pool); const db = new PrismaClient({ adapter }); ``` **2. Environment Variables:** ```typescript // ❌ WRONG - Cannot use $env in scripts import { DATABASE_URL } from '$env/static/private'; // ✅ CORRECT - Use dotenv and process.env import 'dotenv/config'; const DATABASE_URL = process.env.DATABASE_URL; ``` **3. Utility Functions:** ```typescript // ❌ WRONG - Cannot import from $lib import { encrypt, decrypt } from '$lib/server/encryption'; // ✅ CORRECT - Copy the function inline or use relative path import { encrypt, decrypt } from '../src/lib/server/encryption.js'; // OR copy the function code directly into the script function decrypt(encryptedText: string): string { // Implementation copied from src/lib/server/encryption.ts const [ivHex, authTagHex, encrypted] = encryptedText.split(':'); // ... rest of implementation } ``` **4. Prisma Types:** ```typescript // ❌ WRONG - Cannot use $prisma alias import { Prisma } from '$prisma/client'; // ✅ CORRECT - Import from generated client import { Prisma } from '../src/generated/client/index.js'; ``` **5. Running the Script:** ```bash # Development/testing npx tsx scripts/my-script.ts --dry-run # Production npx tsx scripts/my-script.ts ``` ### Common Pitfalls 1. **Forgetting to load dotenv**: Always import `'dotenv/config'` at the top 2. **Not initializing Prisma adapter**: Prisma 7 requires the PG adapter for PostgreSQL 3. **Not cleaning up connections**: Always call `db.$disconnect()` and `pool.end()` 4. **Using SvelteKit imports**: Never use `$lib`, `$app`, `$env`, or `$prisma` in scripts 5. **Wrong Prisma import path**: Use `../src/generated/client/client.js`, not `@prisma/client` ### Real-World Example See [scripts/decrypt-sms-account-sids.ts](scripts/decrypt-sms-account-sids.ts) for a complete working example of a migration script that: - Loads environment variables with dotenv - Initializes Prisma with PostgreSQL adapter - Copies encryption utilities inline (avoiding `$lib` imports) - Supports `--dry-run` flag - Properly cleans up database connections ## API Documentation The platform includes a comprehensive, public-facing API documentation page at `/docs`. ### Features - **Interactive Examples**: All code examples use Shiki syntax highlighting with light/dark theme support - **Copy-to-Clipboard**: One-click copying for all code snippets - **Organized by Resource**: Projects, Milestones, Environments, Test Suites, Test Cases, Test Runs, Test Results, Attachments - **Collapsible Responses**: Accordion-based response examples with status codes - **Parameter Tables**: Clear documentation of path, query, and body parameters - **Getting Started Guide**: Quick examples for common use cases ### Maintaining API Docs The API documentation uses a TypeScript-based schema system for type safety and automatic sync with endpoints. See the [API Documentation System](#api-documentation-system) section below for details on adding new endpoints. ## Skeleton + Svelte Styling Guide This project uses Skeleton UI library with Svelte 5 and Tailwind 4. ## Installation & Setup Global stylesheet ([app.css](src/app.css)) should include: ```css @import '@skeletonlabs/skeleton-svelte'; ``` ## Component Architecture Skeleton uses a **composed pattern** with granular components: ```svelte SK ``` ### Key Patterns **Style Props Convention**: All components accept CSS utilities via the `class` attribute. Styles automatically gain precedence over internal defaults through Tailwind's `@base` layer. **Extensible Markup**: Use the `element` snippet to override internal HTML: ```svelte {#snippet element({ attributes })} {/snippet} ``` **Provider Pattern**: Components support providers that expose Zag.js APIs: ```svelte const tooltip = useTooltip({id}); ``` ## Buttons & Interactive Elements Skeleton UI provides preset button styles that work seamlessly with Tailwind CSS. ### Basic Button Usage All buttons use the base `btn` class combined with preset modifiers: ```svelte ``` ### Button Sizes Combine with size utilities: ```svelte ``` ### Button States ```svelte ``` ### Common Button Patterns **Primary Actions:** ```svelte ``` **Destructive Actions:** ```svelte ``` **Cancel/Secondary Actions:** ```svelte ``` **Link Buttons:** ```svelte Go to Settings ``` ### Button Groups ```svelte
``` ### Responsive Buttons ```svelte ``` ### Custom Styling You can combine presets with Tailwind utilities: ```svelte ``` ### Important Notes - Always use the **full preset name** including the color suffix (e.g., `preset-filled-primary-500`, not `preset-filled-primary`) - The `btn` class must always come first, followed by the preset - Presets handle all color, padding, border-radius, and hover states automatically - Use Tailwind utilities for spacing, sizing, and layout adjustments ## Tailwind Integration ### Core Utilities - **Color Classes**: `[property]-[color]-[shade]` maps to `--color-[color]-[shade]` CSS variables - **Color Pairings**: Light/dark mode balancing via `light-dark()` CSS function - **Typography Scale**: Dynamic font sizing with `--text-scaling` variable - **Radius**: `rounded-base` and `rounded-container` for consistent border radius - **Spacing**: Dynamic scaling via `--spacing` CSS variable ### Dark Mode Skeleton supports three strategies: 1. **Media** (default): Uses `prefers-color-scheme` to match OS settings 2. **Selector**: Add `.dark` class to `` element 3. **Data Attribute**: Use `data-mode="dark"` on `` Apply variants in markup: ```html
Content
``` ### Color Scheme Feature Toggle light or dark rendering at any scope: ```svelte
Always light
``` ## Custom Animations Override component animations using the `element` snippet with Svelte transitions: ```svelte {#snippet element(attributes)} {#if !attributes.hidden} {/if} {/snippet} ``` Key steps: spread attributes, override the `hidden` attribute, apply transition directive. ## Form Elements Skeleton requires the **Tailwind Forms Plugin** for semantic form styling. All form inputs automatically inherit theme colors and styles. ## Layout Best Practices ### Semantic HTML Structure Use `
`, `
`, `