Files
automaker/apps/ui/tests/global-setup.ts
gsxdsm 1c0e460dd1 Add orphaned features management routes and UI integration (#819)
* test(copilot): add edge case test for error with code field

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Changes from fix/bug-fixes-1-0

* refactor(auto-mode): enhance orphaned feature detection and improve project initialization

- Updated detectOrphanedFeatures method to accept preloaded features, reducing redundant disk reads.
- Improved project initialization by creating required directories and files in parallel for better performance.
- Adjusted planning mode handling in UI components to clarify approval requirements for different modes.
- Added refresh functionality for file editor tabs to ensure content consistency with disk state.

These changes enhance performance, maintainability, and user experience across the application.

* feat(orphaned-features): add orphaned features management routes and UI integration

- Introduced new routes for managing orphaned features, including listing, resolving, and bulk resolving.
- Updated the UI to include an Orphaned Features section in project settings and navigation.
- Enhanced the execution service to support new orphaned feature functionalities.

These changes improve the application's capability to handle orphaned features effectively, enhancing user experience and project management.

* fix: Normalize line endings and resolve stale dirty states in file editor

* chore: Update .gitignore and enhance orphaned feature handling

- Added a blank line in .gitignore for better readability.
- Introduced a hash to worktree paths in orphaned feature resolution to prevent conflicts.
- Added validation for target branch existence during orphaned feature resolution.
- Improved prompt formatting in execution service for clarity.
- Enhanced error handling in project selector for project initialization failures.
- Refactored orphaned features section to improve state management and UI responsiveness.

These changes improve code maintainability and user experience when managing orphaned features.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-27 22:14:41 -08:00

145 lines
4.7 KiB
TypeScript

/**
* Global setup for all e2e tests
* This runs once before all tests start.
* It authenticates with the backend and saves the session state so that
* all workers/tests can reuse it (avoiding per-test login overhead).
*/
import { chromium, FullConfig } from '@playwright/test';
import fs from 'fs';
import path from 'path';
import {
cleanupLeftoverFixtureWorkerDirs,
cleanupLeftoverTestDirs,
} from './utils/cleanup-test-dirs';
const TEST_PORT = process.env.TEST_PORT || '3107';
const TEST_SERVER_PORT = process.env.TEST_SERVER_PORT || '3108';
const reuseServer = process.env.TEST_REUSE_SERVER === 'true';
const API_BASE_URL = `http://127.0.0.1:${TEST_SERVER_PORT}`;
const WEB_BASE_URL = `http://127.0.0.1:${TEST_PORT}`;
const AUTH_DIR = path.join(__dirname, '.auth');
const AUTH_STATE_PATH = path.join(AUTH_DIR, 'storage-state.json');
async function globalSetup(config: FullConfig) {
// Clean up leftover test dirs and fixture worker copies from previous runs (aborted, crashed, etc.)
cleanupLeftoverTestDirs();
cleanupLeftoverFixtureWorkerDirs();
// Note: Server killing is handled by the pretest script in package.json
// GlobalSetup runs AFTER webServer starts, so we can't kill the server here
if (reuseServer) {
const baseURL = `http://127.0.0.1:${TEST_PORT}`;
try {
const res = await fetch(baseURL, { signal: AbortSignal.timeout(3000) });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
} catch {
throw new Error(
`TEST_REUSE_SERVER is set but nothing is listening at ${baseURL}. ` +
'Start the UI and server first (e.g. from apps/ui: TEST_PORT=3107 TEST_SERVER_PORT=3108 pnpm dev; from apps/server: PORT=3108 pnpm run dev:test) or run tests without TEST_REUSE_SERVER.'
);
}
}
// Authenticate once and save state for all workers
await authenticateAndSaveState(config);
console.log('[GlobalSetup] Setup complete');
}
/**
* Authenticate with the backend and save browser storage state.
* All test workers will load this state to skip per-test authentication.
*/
async function authenticateAndSaveState(_config: FullConfig) {
// Ensure auth directory exists
fs.mkdirSync(AUTH_DIR, { recursive: true });
const apiKey = process.env.AUTOMAKER_API_KEY || 'test-api-key-for-e2e-tests';
// Wait for backend to be ready (exponential backoff: 250ms → 500ms → 1s → 2s)
const start = Date.now();
let backoff = 250;
let healthy = false;
while (Date.now() - start < 30000) {
try {
const health = await fetch(`${API_BASE_URL}/api/health`, {
signal: AbortSignal.timeout(3000),
});
if (health.ok) {
healthy = true;
break;
}
} catch {
// Retry
}
await new Promise((r) => setTimeout(r, backoff));
backoff = Math.min(backoff * 2, 2000);
}
if (!healthy) {
throw new Error(`Backend health check timed out after 30s for ${API_BASE_URL}`);
}
// Launch a browser to get a proper context for login
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
try {
// Navigate to the app first (needed for cookies to bind to the correct domain)
await page.goto(WEB_BASE_URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
// Login via API
const loginResponse = await page.request.post(`${API_BASE_URL}/api/auth/login`, {
data: { apiKey },
headers: { 'Content-Type': 'application/json' },
timeout: 15000,
});
const response = (await loginResponse.json().catch(() => null)) as {
success?: boolean;
token?: string;
} | null;
if (!response?.success || !response.token) {
throw new Error(
'[GlobalSetup] Login failed - cannot proceed without authentication. ' +
'Check that the backend is running and AUTOMAKER_API_KEY is set correctly.'
);
}
// Set the session cookie
await context.addCookies([
{
name: 'automaker_session',
value: response.token,
domain: '127.0.0.1',
path: '/',
httpOnly: true,
sameSite: 'Lax',
},
]);
// Verify auth works
const statusRes = await page.request.get(`${API_BASE_URL}/api/auth/status`, {
timeout: 5000,
});
const statusJson = (await statusRes.json().catch(() => null)) as {
authenticated?: boolean;
} | null;
if (!statusJson?.authenticated) {
throw new Error(
'[GlobalSetup] Auth verification failed - session cookie was set but status check returned unauthenticated.'
);
}
// Save storage state for all workers to reuse
await context.storageState({ path: AUTH_STATE_PATH });
} finally {
await browser.close();
}
}
export default globalSetup;