Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/chromatic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: 'Chromatic'

on: push

jobs:
chromatic:
name: Run Chromatic
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
- name: Install dependencies
# ⚠️ See your package manager's documentation for the correct command to install dependencies in a CI environment.
run: pnpm install --frozen-lockfile
- name: Run Chromatic
uses: chromaui/action@latest
with:
# ⚠️ Make sure to configure a `CHROMATIC_PROJECT_TOKEN` repository secret
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
exitOnceUploaded: true
63 changes: 63 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: E2E Tests

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9.1.1

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Generate Prisma Client
run: pnpm prisma generate

- name: Install Playwright browsers
run: pnpm playwright install --with-deps

- name: Build Docker image for testing
run: docker build -t fresco:test .

- name: Run E2E tests
run: pnpm test:e2e
env:
CI: true
TEST_IMAGE_NAME: fresco:test

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: |
tests/e2e/playwright-report/
tests/e2e/test-results/
retention-days: 30

- name: Upload test videos
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-videos
path: tests/e2e/test-results/**/*.webm
retention-days: 7
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,12 @@ yarn-error.log*
*.tsbuildinfo
*storybook.log
storybook-static

# e2e testing
/tests/e2e/playwright-report/
/tests/e2e/test-results/
/tests/e2e/screenshots/
/tests/e2e/.auth/
/playwright-report/
/test-results/
/playwright/.cache/
Binary file modified .serena/cache/typescript/document_symbols_cache_v23-06-25.pkl
Binary file not shown.
8 changes: 1 addition & 7 deletions actions/appSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,7 @@ import { type AppSetting, appSettingsSchema } from '~/schemas/appSettings';
import { requireApiAuth } from '~/utils/auth';
import { prisma } from '~/utils/db';
import { ensureError } from '~/utils/ensureError';

// Convert string | boolean | Date to string
const getStringValue = (value: string | boolean | Date) => {
if (typeof value === 'boolean') return value.toString();
if (value instanceof Date) return value.toISOString();
return value;
};
import { getStringValue } from '~/utils/getStringValue';

export async function setAppSetting<
Key extends AppSetting,
Expand Down
131 changes: 131 additions & 0 deletions app/api/health/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { type NextRequest, NextResponse } from 'next/server';

type HealthStatus = 'healthy' | 'degraded' | 'unhealthy';

type HealthCheck = {
name: string;
status: HealthStatus;
duration: number;
error?: string;
details?: Record<string, unknown>;
};

type HealthResponse = {
status: HealthStatus;
timestamp: string;
uptime: number;
version?: string;
checks: HealthCheck[];
};

async function checkBasicHealth(): Promise<HealthCheck> {

Check failure on line 21 in app/api/health/route.ts

View workflow job for this annotation

GitHub Actions / lint

Async function 'checkBasicHealth' has no 'await' expression
const start = performance.now();

try {
// Basic health check - just verify the service is running
const nodeVersion = process.version;
const duration = performance.now() - start;

return {
name: 'basic',
status: 'healthy',
duration: Math.round(duration),
details: {
nodeVersion,
environment: process.env.NODE_ENV,

Check failure on line 35 in app/api/health/route.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected use of process.env
uptime: Math.round(process.uptime()),
},
};
} catch (error) {
const duration = performance.now() - start;

return {
name: 'basic',
status: 'unhealthy',
duration: Math.round(duration),
error: error instanceof Error ? error.message : 'Basic health check failed',
};
}
}

function getOverallStatus(checks: HealthCheck[]): HealthStatus {
const hasUnhealthy = checks.some((check) => check.status === 'unhealthy');
const hasDegraded = checks.some((check) => check.status === 'degraded');

if (hasUnhealthy) return 'unhealthy';
if (hasDegraded) return 'degraded';
return 'healthy';
}

function getStatusCode(status: HealthStatus): number {
switch (status) {
case 'healthy':
return 200;
case 'degraded':
return 200; // Still operational
case 'unhealthy':
return 503; // Service Unavailable
}
}

export async function GET(request: NextRequest): Promise<NextResponse> {

Check failure on line 71 in app/api/health/route.ts

View workflow job for this annotation

GitHub Actions / lint

'request' is defined but never used. Allowed unused args must match /^_/u
const startTime = performance.now();

try {
// Run health checks
const basicCheck = await checkBasicHealth();
const checks = [basicCheck];

const overallStatus = getOverallStatus(checks);
const statusCode = getStatusCode(overallStatus);

const response: HealthResponse = {
status: overallStatus,
timestamp: new Date().toISOString(),
uptime: Math.round(process.uptime()),
version: process.env.npm_package_version || 'unknown',

Check failure on line 86 in app/api/health/route.ts

View workflow job for this annotation

GitHub Actions / lint

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator

Check failure on line 86 in app/api/health/route.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected use of process.env
checks,
};

const totalDuration = Math.round(performance.now() - startTime);

return NextResponse.json(
{
...response,
duration: totalDuration,
},
{
status: statusCode,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache, no-store, must-revalidate',
'X-Health-Check': 'true',
},
},
);
} catch (error) {
// Fallback error response
const response: HealthResponse = {
status: 'unhealthy',
timestamp: new Date().toISOString(),
uptime: Math.round(process.uptime()),
checks: [
{
name: 'health_check',
status: 'unhealthy',
duration: Math.round(performance.now() - startTime),
error: error instanceof Error ? error.message : 'Health check failed',
},
],
};

return NextResponse.json(response, {
status: 503,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache, no-store, must-revalidate',
'X-Health-Check': 'true',
},
});
}
}
32 changes: 32 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Global type definitions for e2e test environment
*/
/* eslint-disable no-var, @typescript-eslint/no-explicit-any */

import { type Protocol } from '@codaco/protocol-validation';
import type { User } from '@prisma/client';
import type { TestEnvironment } from './tests/e2e/fixtures/test-environment';

declare global {
namespace globalThis {
var __TEST_ENVIRONMENT__: TestEnvironment | undefined;
var __INTERVIEWS_TEST_DATA__:
| {
admin: {
user: User;
username: string;
password: string;
};
protocol: Protocol;
participants: any[];
}
| undefined;
var __INTERVIEWS_CONTEXT__:
| {
restoreSnapshot: (name: string) => Promise<void>;
}
| undefined;
}
}

export {};
29 changes: 0 additions & 29 deletions jest.config.js

This file was deleted.

17 changes: 13 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@
"vercel-build": "node ./setup-database.js && node ./initialize.js && next build",
"knip": "knip",
"test": "vitest",
"load-test": "docker run -i grafana/k6 run - <load-test.js",
"test:e2e": "TEST_IMAGE_NAME=fresco-test:latest playwright test --config=tests/e2e/playwright.config.ts",
"test:e2e:ui": "TEST_IMAGE_NAME=fresco-test:latest playwright test --config=tests/e2e/playwright.config.ts --ui",
"test:e2e:debug": "TEST_IMAGE_NAME=fresco-test:latest playwright test --config=tests/e2e/playwright.config.ts --debug",
"typecheck": "tsc --noEmit",
"storybook": "storybook dev -p 6006",
"storybook-build": "storybook build"
"build-storybook": "SKIP_ENV_VALIDATION=true storybook build",
"chromatic": "pnpx chromatic --project-token=chpt_54d6831d4ddd5cc",
"docker-build": "docker build -t fresco-test:latest ."
},
"pnpm": {
"overrides": {
Expand Down Expand Up @@ -48,8 +52,8 @@
"@reduxjs/toolkit": "^2.5.0",
"@tanstack/react-form": "^1.12.3",
"@tanstack/react-table": "^8.21.3",
"@xmldom/xmldom": "^0.9.8",
"@uploadthing/react": "^7.3.2",
"@xmldom/xmldom": "^0.9.8",
"animejs": "^2.2.0",
"archiver": "^7.0.1",
"async": "^3.2.6",
Expand Down Expand Up @@ -111,6 +115,7 @@
"zustand": "^5.0.6"
},
"devDependencies": {
"@playwright/test": "^1.40.0",
"@prisma/client": "^6.10.0",
"@storybook/addon-docs": "^9.0.17",
"@storybook/addon-links": "^9.0.17",
Expand All @@ -123,6 +128,7 @@
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/postcss": "4.1.10",
"@tailwindcss/typography": "^0.5.16",
"@testcontainers/postgresql": "^10.0.0",
"@total-typescript/ts-reset": "^0.6.1",
"@types/archiver": "^6.0.3",
"@types/async": "^3.2.24",
Expand All @@ -141,6 +147,7 @@
"@typescript-eslint/eslint-plugin": "^8.34.1",
"@typescript-eslint/parser": "^8.34.1",
"@vitejs/plugin-react": "^4.5.2",
"chromatic": "^13.1.3",
"eslint": "^8.57.1",
"eslint-config-next": "^15.4.2",
"eslint-config-prettier": "^10.1.5",
Expand All @@ -158,8 +165,10 @@
"storybook": "^9.0.17",
"tailwindcss": "4.1.11",
"tailwindcss-animate": "^1.0.7",
"testcontainers": "^10.0.0",
"typescript": "5.8.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4"
"vitest": "^3.2.4",
"wait-on": "^7.0.0"
}
}
Loading
Loading