Skip to content
Merged
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
218 changes: 207 additions & 11 deletions .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
name: E2E Tests
runs-on: ubuntu-latest
needs: lint-and-test
timeout-minutes: 20

services:
postgres:
Expand Down Expand Up @@ -100,9 +101,33 @@ jobs:
working-directory: ./frontend
run: npm ci

- name: Verify PostgreSQL is ready
run: |
echo "🐘 Checking PostgreSQL connection..."
for i in {1..30}; do
if pg_isready -h localhost -p 5432 -U postgres > /dev/null 2>&1; then
echo "✅ PostgreSQL is ready!"
break
fi
if [ $i -eq 30 ]; then
echo "❌ PostgreSQL failed to become ready"
exit 1
fi
echo "Waiting for PostgreSQL... ($i/30)"
sleep 1
done

echo "📊 PostgreSQL status:"
psql -h localhost -U postgres -d stackshare_test -c "SELECT version();" || echo "Failed to query PostgreSQL"
env:
PGPASSWORD: postgres

- name: Install Playwright Browsers
working-directory: ./frontend
run: npx playwright install --with-deps
run: |
echo "🎭 Installing Playwright browsers..."
npx playwright install --with-deps
echo "✅ Playwright browsers installed"

- name: Restore backend dependencies
working-directory: ./backend
Expand All @@ -112,28 +137,188 @@ jobs:
working-directory: ./backend
run: dotnet build --no-restore

- name: Start backend services
- name: Start backend services with detailed logging
working-directory: ./backend
run: |
dotnet run --project src/StackShare.API &
sleep 30 # Wait for backend to start
echo "🚀 Starting backend API..."
echo "Environment: ASPNETCORE_ENVIRONMENT=Testing"
echo "Database: stackshare_test on port 5432"

# Start API in background with logging
nohup dotnet run --project src/StackShare.API > ../api.log 2>&1 &
API_PID=$!
echo "📊 API started with PID: $API_PID"

# Wait and check for API startup
echo "⏳ Waiting for API to start (up to 60 seconds)..."
for i in {1..60}; do
echo "Attempt $i/60: Checking API health..."

if curl -f -s http://localhost:5095/api/health > /dev/null 2>&1; then
echo "✅ API is healthy and ready!"
echo "📋 API logs (last 20 lines):"
tail -20 ../api.log || echo "No logs available yet"
break
fi

if [ $i -eq 60 ]; then
echo "❌ API failed to start within 60 seconds"
echo "📋 Full API logs:"
cat ../api.log || echo "No logs found"
echo "🔍 Checking if process is still running:"
ps aux | grep dotnet | grep -v grep || echo "No dotnet processes found"
echo "🔍 Checking port 5095:"
netstat -tlnp | grep 5095 || echo "Port 5095 not in use"
exit 1
fi

sleep 1
done

echo "🎯 Final API health check:"
curl -v http://localhost:5095/api/health || true
env:
ASPNETCORE_ENVIRONMENT: Testing
ConnectionStrings__DefaultConnection: "Host=localhost;Port=5432;Database=stackshare_test;Username=postgres;Password=postgres"

- name: Build frontend
- name: Build frontend with logging
working-directory: ./frontend
run: npm run build
run: |
echo "🏗️ Building frontend..."
npm run build
echo "✅ Frontend build completed"
echo "📂 Build output:"
ls -la dist/ || echo "No dist folder found"

- name: Start frontend
- name: Start frontend with detailed monitoring
working-directory: ./frontend
run: |
npm run preview &
sleep 10 # Wait for frontend to start
echo "🌐 Starting frontend preview server..."

# Start preview in background with logging
nohup npm run preview > ../frontend.log 2>&1 &
FRONTEND_PID=$!
echo "📊 Frontend started with PID: $FRONTEND_PID"

# Wait and check for frontend startup
echo "⏳ Waiting for frontend to start (up to 30 seconds)..."
for i in {1..30}; do
echo "Attempt $i/30: Checking frontend availability..."

if curl -f -s http://localhost:4173 > /dev/null 2>&1; then
echo "✅ Frontend is ready!"
echo "📋 Frontend logs (last 10 lines):"
tail -10 ../frontend.log || echo "No logs available yet"
break
fi

if [ $i -eq 30 ]; then
echo "❌ Frontend failed to start within 30 seconds"
echo "📋 Full frontend logs:"
cat ../frontend.log || echo "No logs found"
echo "🔍 Checking if process is still running:"
ps aux | grep "npm run preview" | grep -v grep || echo "No preview process found"
echo "🔍 Checking port 4173:"
netstat -tlnp | grep 4173 || echo "Port 4173 not in use"
exit 1
fi

sleep 1
done

echo "🎯 Final frontend check:"
curl -I http://localhost:4173 || true

- name: Pre-test system diagnostics
run: |
echo "🔍 System diagnostics before running tests:"
echo "Memory usage:"
free -h
echo "Disk usage:"
df -h
echo "Running processes:"
ps aux | head -20
echo "Network connections:"
netstat -tlnp | grep -E "(4173|5095|5432)"
echo "Environment variables:"
env | grep -E "(CI|NODE_|ASPNETCORE_)" | sort

- name: Run Playwright tests
- name: Run Playwright tests with detailed monitoring
working-directory: ./frontend
run: npm run test:e2e
run: |
echo "🎭 Starting Playwright E2E tests..."
echo "Configuration check:"
echo "- CI environment: $CI"
echo "- Base URL will be: http://localhost:4173"
echo "- Workers: 2 (CI mode)"
echo "- Browsers: Chromium only (CI mode)"

# Verify services are still running
echo "🔍 Pre-test service verification:"
if curl -f -s http://localhost:4173 > /dev/null; then
echo "✅ Frontend is responsive"
else
echo "❌ Frontend is not responsive"
curl -I http://localhost:4173 || true
fi

if curl -f -s http://localhost:5095/api/health > /dev/null; then
echo "✅ Backend API is responsive"
else
echo "❌ Backend API is not responsive"
curl -I http://localhost:5095/api/health || true
fi

# Run tests with timeout and progress reporting
echo "🚀 Starting test execution..."
timeout 600 npm run test:e2e || {
EXIT_CODE=$?
echo "❌ Tests failed or timed out (exit code: $EXIT_CODE)"

echo "📋 Recent frontend logs:"
tail -50 ../frontend.log || echo "No frontend logs"

echo "📋 Recent API logs:"
tail -50 ../api.log || echo "No API logs"

echo "🔍 Current system state:"
ps aux | grep -E "(dotnet|npm|node)" | grep -v grep || echo "No relevant processes"
netstat -tlnp | grep -E "(4173|5095)" || echo "Services not listening"

exit $EXIT_CODE
}

echo "✅ All E2E tests completed successfully!"

- name: Collect logs and diagnostics on failure
if: failure()
run: |
echo "📋 Collecting diagnostic information..."

echo "=== FRONTEND LOGS ==="
cat frontend.log 2>/dev/null || echo "No frontend logs found"

echo "=== API LOGS ==="
cat api.log 2>/dev/null || echo "No API logs found"

echo "=== SYSTEM STATE ==="
echo "Processes:"
ps aux | grep -E "(dotnet|npm|node|playwright)" | grep -v grep || echo "No relevant processes"

echo "Network:"
netstat -tlnp | grep -E "(4173|5095|5432)" || echo "No services listening"

echo "Memory:"
free -h

echo "Disk:"
df -h

echo "=== PLAYWRIGHT TEST RESULTS ==="
ls -la frontend/test-results/ 2>/dev/null || echo "No test results found"

# Try to get more info about what failed
find frontend/test-results/ -name "*.txt" -exec echo "--- {} ---" \; -exec cat {} \; 2>/dev/null || true

- name: Upload Playwright report
uses: actions/upload-artifact@v4
Expand All @@ -142,6 +327,17 @@ jobs:
name: playwright-report
path: frontend/playwright-report/
retention-days: 30

- name: Upload test results and logs
uses: actions/upload-artifact@v4
if: always()
with:
name: e2e-logs-and-results
path: |
frontend/test-results/
api.log
frontend.log
retention-days: 7

build-and-push:
name: Build and Push Docker Image
Expand Down
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,19 @@ FakesAssemblies/
.ntvs_analysis.dat
node_modules/

# Frontend build and test artifacts
frontend/dist/
frontend/build/
frontend/.next/
frontend/out/
frontend/coverage/
frontend/playwright-report/
frontend/test-results/
frontend/.env.local
frontend/.env.development.local
frontend/.env.test.local
frontend/.env.production.local

# Visual Studio 6 build log
*.plg

Expand Down
76 changes: 0 additions & 76 deletions frontend/playwright-report/index.html

This file was deleted.

19 changes: 13 additions & 6 deletions frontend/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,27 @@ export default defineConfig({
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Use multiple workers on CI for faster execution */
workers: process.env.CI ? 2 : undefined,
/* Global test timeout */
timeout: process.env.CI ? 60 * 1000 : 30 * 1000,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:5173',
baseURL: process.env.CI ? 'http://localhost:4173' : 'http://localhost:5173',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
projects: process.env.CI ? [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
] : [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
Expand Down Expand Up @@ -62,9 +69,9 @@ export default defineConfig({
],

/* Run your local dev server before starting the tests */
webServer: {
webServer: process.env.CI ? undefined : {
command: 'npm run dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
reuseExistingServer: true,
},
});
51 changes: 51 additions & 0 deletions frontend/src/components/ui/form-hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use client"

import * as React from "react"
import { useFormContext } from "react-hook-form"

type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}

type FormItemContextValue = {
id: string
}

import type {
FieldPath,
FieldValues,
} from "react-hook-form"

export const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)

export const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)

export const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()

const fieldState = getFieldState(fieldContext.name, formState)

if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}

const { id } = itemContext

return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
Loading