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
9 changes: 8 additions & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ jobs:
# Card 4eb58505: smoke de prod en pushes a main — el stack real self-hosted
# (Coolify en jokerserver: frontend :3010, backend :8010). El runner
# coolify-deploy comparte host con el stack, por eso 127.0.0.1. Aquí sí
# corren los tests de API (E2E_API_URL): la BD real tiene el usuario Joker.
# corren los tests de API (E2E_API_URL).
# Card 64763175: E2E_ENV=production hace que el test de docs afirme el 404
# (docs deshabilitados en prod). Login tests usan E2E_AUTH_USERNAME/
# E2E_AUTH_PASSWORD: si los secrets no están configurados, los tests hacen
# skip explícito en vez de fallar con credenciales inexistentes.
# Los PRs se saltan este job (el preview E2E vive en pr-deploy-coolify.yml).
e2e-prod-smoke:
name: E2E Prod Smoke (self-hosted)
Expand All @@ -88,6 +92,9 @@ jobs:
env:
E2E_BASE_URL: http://127.0.0.1:3010
E2E_API_URL: http://127.0.0.1:8010
E2E_ENV: production
E2E_AUTH_USERNAME: ${{ secrets.E2E_AUTH_USERNAME }}
E2E_AUTH_PASSWORD: ${{ secrets.E2E_AUTH_PASSWORD }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
38 changes: 32 additions & 6 deletions dashboard/frontend/e2e/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,26 @@ import { test, expect } from '@playwright/test';
// skipped otherwise: fresh preview backends boot with an empty database
// (create_all, no seed), so login-dependent checks only run against the
// real prod stack in the `e2e-prod-smoke` job.
//
// Card 64763175: no hardcoded credentials either.
// - Login-dependent tests read E2E_AUTH_USERNAME / E2E_AUTH_PASSWORD from
// the environment and SKIP with a clear reason when they are absent.
// The old 'Joker'/'Joker123!' literals were preview/staging-era values
// that do not exist in the production database.
// - E2E_ENV=production flips the docs test to expect 404: the backend
// disables /api/v1/docs when settings.is_production (main.py, PR #147),
// and asserting that response is the point of the test in prod.
const BACKEND_URL = process.env.E2E_API_URL ?? '';
const hasApi = BACKEND_URL !== '';

const E2E_USERNAME = process.env.E2E_AUTH_USERNAME ?? '';
const E2E_PASSWORD = process.env.E2E_AUTH_PASSWORD ?? '';
const hasAuth = E2E_USERNAME !== '' && E2E_PASSWORD !== '';
const AUTH_SKIP_REASON =
'E2E_AUTH_USERNAME / E2E_AUTH_PASSWORD not set — configure them (CI secrets or local env) to run login tests against this environment';

const IS_PRODUCTION = (process.env.E2E_ENV ?? '') === 'production';

test.describe('QA-FRAMEWORK E2E Tests', () => {

test('Backend health check', async ({ request }) => {
Expand All @@ -24,14 +41,21 @@ test.describe('QA-FRAMEWORK E2E Tests', () => {
test('Backend API docs accessible', async ({ request }) => {
test.skip(!hasApi, 'E2E_API_URL not set — requires a deployed backend');
const response = await request.get(`${BACKEND_URL}/api/v1/docs`);
expect(response.ok()).toBeTruthy();
if (IS_PRODUCTION) {
// Production deliberately disables the docs endpoints
// (docs_url=None if settings.is_production) — 404 IS the secure posture.
expect(response.status()).toBe(404);
} else {
expect(response.ok()).toBeTruthy();
}
});

test('Login API works', async ({ request }) => {
test.skip(!hasApi, 'E2E_API_URL not set — requires a deployed backend');
test.skip(!hasAuth, AUTH_SKIP_REASON);
const response = await request.post(`${BACKEND_URL}/api/v1/auth/login`, {
headers: { 'Content-Type': 'application/json' },
data: { username: 'Joker', password: 'Joker123!' }
data: { username: E2E_USERNAME, password: E2E_PASSWORD }
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
Expand All @@ -41,10 +65,11 @@ test.describe('QA-FRAMEWORK E2E Tests', () => {

test('Get user info with token', async ({ request }) => {
test.skip(!hasApi, 'E2E_API_URL not set — requires a deployed backend');
test.skip(!hasAuth, AUTH_SKIP_REASON);
// First login
const loginResponse = await request.post(`${BACKEND_URL}/api/v1/auth/login`, {
headers: { 'Content-Type': 'application/json' },
data: { username: 'Joker', password: 'Joker123!' }
data: { username: E2E_USERNAME, password: E2E_PASSWORD }
});
const loginData = await loginResponse.json();
const token = loginData.access_token;
Expand All @@ -55,7 +80,7 @@ test.describe('QA-FRAMEWORK E2E Tests', () => {
});
expect(meResponse.ok()).toBeTruthy();
const userData = await meResponse.json();
expect(userData.username).toBe('Joker');
expect(userData.username).toBe(E2E_USERNAME);
});

test('Frontend loads', async ({ page }) => {
Expand All @@ -73,13 +98,14 @@ test.describe('QA-FRAMEWORK E2E Tests', () => {
});

test('Full login flow', async ({ page }) => {
test.skip(!hasAuth, AUTH_SKIP_REASON);
await page.goto('/login');
await page.waitForLoadState('networkidle');

// Fill login form using placeholder or type
const inputs = page.locator('input');
await inputs.nth(0).fill('Joker');
await inputs.nth(1).fill('Joker123!');
await inputs.nth(0).fill(E2E_USERNAME);
await inputs.nth(1).fill(E2E_PASSWORD);

// Submit
await page.click('button:has-text("Login")');
Expand Down
Loading