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
47 changes: 47 additions & 0 deletions .github/workflows/ci-quality-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: CI Quality Gate

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

jobs:
quality-gate:
runs-on: ubuntu-latest
timeout-minutes: 30

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

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Build frontend workspaces
run: |
npm run build --workspace @flix/web
npm run build --workspace @flix/admin

- name: Install Playwright browser
run: npx playwright install --with-deps chromium

- name: Run browser smoke gate
run: npm run e2e:smoke:gate

- name: Upload Playwright diagnostics
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-diagnostics
path: |
playwright-report
test-results
if-no-files-found: ignore

5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,8 @@ Thumbs.db

# Local database artifacts
services/api/.data/
.data/

# E2E artifacts
playwright-report/
test-results/
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ npm run dev:admin
npm run dev:api
```

## Quality Gates

```bash
npm run ci:local
npm run e2e:smoke
npm run e2e:smoke:gate
npm run release:readiness
```

## Documentation

- `docs/design-system-extraction.md` (what can be migrated from legacy DS)
Expand Down
58 changes: 43 additions & 15 deletions docs/stories/8.3.story.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Story 8.3: Browser E2E Smoke and CI Quality Gate Expansion

## Status
Draft
Review

## Executor Assignment
executor: "@devops"
Expand All @@ -19,15 +19,15 @@ quality_gate_tools: ["manual-review"]
3. Gate fails explicitly on critical flow breakage with actionable diagnostics.

## Tasks / Subtasks
- [ ] Task 1 (AC: 1) E2E smoke suite covers core admin and learner happy paths in browser runtime.
- [ ] Implement technical changes required for AC 1
- [ ] Add validation/tests for AC 1
- [ ] Task 2 (AC: 2) CI/release gate includes frontend build checks and E2E smoke execution.
- [ ] Implement technical changes required for AC 2
- [ ] Add validation/tests for AC 2
- [ ] Task 3 (AC: 3) Gate fails explicitly on critical flow breakage with actionable diagnostics.
- [ ] Implement technical changes required for AC 3
- [ ] Add validation/tests for AC 3
- [x] Task 1 (AC: 1) E2E smoke suite covers core admin and learner happy paths in browser runtime.
- [x] Implement technical changes required for AC 1
- [x] Add validation/tests for AC 1
- [x] Task 2 (AC: 2) CI/release gate includes frontend build checks and E2E smoke execution.
- [x] Implement technical changes required for AC 2
- [x] Add validation/tests for AC 2
- [x] Task 3 (AC: 3) Gate fails explicitly on critical flow breakage with actionable diagnostics.
- [x] Implement technical changes required for AC 3
- [x] Add validation/tests for AC 3

## Dev Notes
### Previous Story Insights
Expand Down Expand Up @@ -79,16 +79,44 @@ quality_gate_tools: ["manual-review"]

## Dev Agent Record
### Agent Model Used
_To be populated by dev agent_
GPT-5 Codex

### Debug Log References
- _To be populated by dev agent_
- `npm run e2e:smoke`
- `npm run e2e:smoke:gate`
- `npm run build --workspace @flix/web`
- `npm run build --workspace @flix/admin`
- `npm run release:readiness`

### Completion Notes List
- _To be populated by dev agent_
- Added Playwright browser smoke setup with runtime orchestration for API + admin + learner apps.
- Added learner smoke scenario covering catalog-to-playback critical journey.
- Added admin smoke scenario covering browser login and event creation.
- Added CI workflow (`.github/workflows/ci-quality-gate.yml`) executing frontend builds and E2E smoke gate on PR/push to `main`.
- Added explicit E2E gate script with actionable diagnostics (`playwright-report`, `test-results`, rerun command) on failure.
- Expanded release readiness gate to enforce:
- browser smoke gate;
- frontend production builds;
- API integration regression checks.

### File List
- _To be populated by dev agent_
- `.github/workflows/ci-quality-gate.yml`
- `.gitignore`
- `e2e/playwright.config.mjs`
- `e2e/tests/admin-smoke.spec.mjs`
- `e2e/tests/learner-smoke.spec.mjs`
- `package.json`
- `package-lock.json`
- `scripts/e2e-smoke-gate.mjs`
- `scripts/release-readiness-gate.mjs`
- `README.md`
- `docs/stories/8.3.story.md`

## QA Results
_To be populated after implementation_
PASS

- Browser smoke suite passed (`2` tests, chromium):
- admin login + create event
- learner catalog + playback navigation
- Frontend builds passed for both apps (`@flix/web`, `@flix/admin`).
- `release:readiness` gate passed with final status `GO`.
54 changes: 54 additions & 0 deletions e2e/playwright.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { defineConfig, devices } from '@playwright/test';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const isCi = Boolean(process.env.CI);
const configDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(configDir, '..');
const dbFilePath = `${repoRoot}/services/api/.data/flix.e2e.sqlite`;
const dbUrl = `file:${dbFilePath}`;

export default defineConfig({
testDir: './tests',
fullyParallel: false,
forbidOnly: isCi,
retries: isCi ? 1 : 0,
workers: isCi ? 1 : undefined,
timeout: 45_000,
reporter: [['list'], ['html', { open: 'never', outputFolder: 'playwright-report' }]],
use: {
baseURL: 'http://127.0.0.1:4173',
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
webServer: [
{
command:
`sh -c "cd .. && DATABASE_URL=${dbUrl} npm run db:reset --workspace @flix/api && DATABASE_URL=${dbUrl} API_PORT=3001 PERSISTENCE_ADAPTER=sqlite CORS_ORIGIN=http://127.0.0.1:4173,http://127.0.0.1:4174,http://localhost:4173,http://localhost:4174 node services/api/src/server.js"`,
url: 'http://127.0.0.1:3001/health',
reuseExistingServer: !isCi,
timeout: 120_000,
},
{
command:
'sh -c "cd .. && VITE_API_BASE_URL=http://127.0.0.1:3001 npm run dev --workspace @flix/admin -- --host 127.0.0.1 --port 4174"',
url: 'http://127.0.0.1:4174/login',
reuseExistingServer: !isCi,
timeout: 120_000,
},
{
command:
'sh -c "cd .. && VITE_API_BASE_URL=http://127.0.0.1:3001 npm run dev --workspace @flix/web -- --host 127.0.0.1 --port 4173"',
url: 'http://127.0.0.1:4173/events/flix-mvp-launch-event',
reuseExistingServer: !isCi,
timeout: 120_000,
},
],
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
21 changes: 21 additions & 0 deletions e2e/tests/admin-smoke.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect, test } from '@playwright/test';

test('admin happy path smoke: login and create event', async ({ page }) => {
await page.goto('http://127.0.0.1:4174/login');

await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('heading', { name: 'Admin Content Operations' })).toBeVisible();

const randomSuffix = `${Date.now()}`;
await page.getByPlaceholder('Event title').fill(`E2E Event ${randomSuffix}`);
await page.getByRole('textbox', { name: 'Slug', exact: true }).fill(`e2e-event-${randomSuffix}`);
await page.getByPlaceholder('Description').fill('Smoke test event created via browser runtime');
await page.getByPlaceholder('Hero title').fill(`Hero ${randomSuffix}`);
await page.getByPlaceholder('Hero subtitle').fill('Subtitle for smoke validation');
await page.getByPlaceholder('Hero CTA text').fill('Start now');

await page.getByRole('button', { name: 'Create event' }).click();
await expect(page.getByText('Event created successfully.')).toBeVisible();

await expect(page.getByRole('button', { name: new RegExp(`E2E Event ${randomSuffix}`) })).toBeVisible();
});
28 changes: 28 additions & 0 deletions e2e/tests/learner-smoke.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { expect, test } from '@playwright/test';

test('learner happy path smoke: catalog to playback', async ({ page }) => {
await page.goto('/events/flix-mvp-launch-event');

await expect(page.getByRole('heading', { name: 'Flix Learner' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Catalog Access' })).toBeVisible();

const lessonLink = page.locator('a[href="/events/flix-mvp-launch-event/lessons/kickoff-do-mvp"]');
for (let attempt = 1; attempt <= 3; attempt += 1) {
await page.getByRole('button', { name: 'Load catalog' }).click();
try {
await expect(lessonLink).toBeVisible({ timeout: 8_000 });
break;
} catch (error) {
if (attempt === 3) {
throw error;
}
await page.waitForTimeout(1_000);
}
}

await lessonLink.click();
await expect(page.getByRole('heading', { name: 'Lesson Playback' })).toBeVisible();

await expect(page.getByRole('heading', { name: 'Lesson Materials' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Lesson Quiz' })).toBeVisible();
});
68 changes: 67 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@
"typecheck": "npm run typecheck --workspaces --if-present",
"dev:web": "npm --workspace @flix/web run dev",
"dev:admin": "npm --workspace @flix/admin run dev",
"dev:api": "npm --workspace @flix/api run dev"
,
"release:readiness": "node scripts/release-readiness-gate.mjs"
"dev:api": "npm --workspace @flix/api run dev",
"release:readiness": "node scripts/release-readiness-gate.mjs",
"e2e:smoke": "playwright test --config e2e/playwright.config.mjs",
"e2e:smoke:gate": "node scripts/e2e-smoke-gate.mjs",
"ci:local": "node scripts/ci-local-gate.mjs"
},
"devDependencies": {
"@playwright/test": "^1.51.1"
}
}
Loading