Compare commits

..

15 Commits

5 changed files with 67 additions and 53 deletions

View File

@@ -1,5 +1,26 @@
FROM node:20-bookworm-slim FROM node:20-bookworm-slim
RUN apt-get update && apt-get install -y \
libglib2.0-0 \
libnss3 \
libnspr4 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libdbus-1-3 \
libxkbcommon0 \
libxcomposite1 \
libxdamage1 \
libxfixes3 \
libxrandr2 \
libgbm1 \
libpango-1.0-0 \
libcairo2 \
libasound2 \
libatspi2.0-0 \
&& rm -rf /var/lib/apt/lists/*
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
ENV CHROME_DEBUGGING_PORT=9222 ENV CHROME_DEBUGGING_PORT=9222

View File

@@ -6,6 +6,7 @@
"test:headed": "playwright test --headed" "test:headed": "playwright test --headed"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.40.0" "@playwright/test": "^1.40.0",
"axios": "^1.6.0"
} }
} }

View File

@@ -1,8 +1,7 @@
import { defineConfig, devices } from '@playwright/test'; import { defineConfig, devices } from '@playwright/test';
const baseURL = process.env.BASE_URL || 'http://127.0.0.1:3000'; const baseURL = process.env.FRONTEND_URL || 'http://frontend:3000';
const webServerURL = process.env.WEB_SERVER_URL || baseURL; const backendURL = process.env.BACKEND_URL || 'http://backend:8000';
const chromeDebuggingPort = process.env.CHROME_DEBUGGING_PORT || '9222';
export default defineConfig({ export default defineConfig({
testDir: './tests', testDir: './tests',
@@ -14,22 +13,16 @@ export default defineConfig({
use: { use: {
baseURL, baseURL,
trace: 'on-first-retry', trace: 'on-first-retry',
launchOptions: {
args: [`--remote-debugging-port=${chromeDebuggingPort}`],
},
}, },
projects: [ projects: [
{ {
name: 'chromium', name: 'chromium',
use: { use: { ...devices['Desktop Chrome'] },
...devices['Desktop Chrome'],
channel: 'chrome',
},
}, },
], ],
webServer: { webServer: {
command: 'npm run dev', command: 'npm run dev',
url: webServerURL, url: baseURL,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
}, },
}); });

View File

@@ -1,6 +0,0 @@
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/HarborForge/);
});

View File

@@ -1,45 +1,50 @@
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
const WIZARD_URL = process.env.WIZARD_URL || 'http://127.0.0.1:18080'; const FRONTEND_URL = process.env.FRONTEND_URL || 'http://frontend:3000';
const BACKEND_URL = process.env.BACKEND_URL || 'http://127.0.0.1:8000';
test.describe('Setup Wizard', () => { test.describe('Setup Wizard', () => {
test('complete wizard flow', async ({ page }) => { test('complete wizard flow through frontend', async ({ page }) => {
// Go to frontend which should redirect to wizard // Navigate to frontend - should redirect to wizard since not configured
await page.goto('/'); await page.goto(FRONTEND_URL);
await page.waitForLoadState('networkidle');
// Step 0: Welcome - Click "Connect to Wizard" // Step 0: Welcome - should see wizard page
await expect(page.locator('h1')).toContainText('HarborForge Setup Wizard'); await expect(page.locator('h1')).toContainText('HarborForge', { timeout: 10000 });
await page.click('button:has-text("Connect to Wizard")');
// Wait for wizard health check - should proceed to step 1 // Check if we're on wizard page (look for Connect to Wizard button)
await page.waitForTimeout(1000); const connectButton = page.locator('button:has-text("Connect to Wizard")');
if (await connectButton.isVisible().catch(() => false)) {
// We're on wizard page, proceed with wizard flow
await connectButton.click();
// Step 1: Database - Click Next with defaults // Wait for step 1: Database
if (await page.locator('h2:has-text("Database configuration")').isVisible()) { await page.waitForSelector('h2:has-text("Database configuration")', { timeout: 10000 });
await page.click('button:has-text("Next")'); await page.fill('input[name="host"]', 'mysql');
} await page.fill('input[name="port"]', '3306');
await page.fill('input[name="user"]', 'harborforge');
// Step 2: Admin - Fill in admin credentials await page.fill('input[name="password"]', 'harborforge_pass');
await page.fill('input[placeholder="Set admin password"]', 'admin123'); await page.fill('input[name="database"]', 'harborforge');
await page.fill('input[type="email"]', 'admin@test.com');
await page.fill('input[value="Admin"]', 'Test Admin');
await page.click('button:has-text("Next")'); await page.click('button:has-text("Next")');
// Step 3: Project - Configure backend and project // Wait for step 2: Admin
await page.fill('input[placeholder="http://127.0.0.1:8000"]', BACKEND_URL); await page.waitForSelector('h2:has-text("Admin account")', { timeout: 10000 });
await page.fill('input[value="Default"]', 'Test Project'); await page.fill('input[name="password"]', 'admin123');
await page.fill('input[value="Default project"]', 'Test Project Description'); await page.fill('input[name="email"]', 'admin@test.com');
await page.fill('input[name="full_name"]', 'Test Admin');
await page.click('button:has-text("Next")');
// Wait for step 3: Project
await page.waitForSelector('h2:has-text("Default project")', { timeout: 10000 });
await page.fill('input[name="backend_url"]', 'http://backend:8000');
await page.fill('input[name="name"]', 'Test Project');
await page.fill('input[name="description"]', 'Test Project Description');
await page.click('button:has-text("Finish setup")'); await page.click('button:has-text("Finish setup")');
// Step 4: Complete // Wait for step 4: Complete
await expect(page.locator('h2')).toContainText('Setup complete!'); await expect(page.locator('h2')).toContainText('Setup complete!', { timeout: 10000 });
await expect(page.locator('code')).toContainText('docker compose restart'); } else {
}); // Wizard was already configured, verify we're on main page
await expect(page.locator('h1')).toContainText('HarborForge');
test('wizard health check', async ({ request }) => { }
const response = await request.get(`${WIZARD_URL}/health`);
// Wizard might return 200 or 404 if not initialized
expect([200, 404]).toContain(response.status());
}); });
}); });