diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9eb8b37d..2b4051a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -161,6 +161,75 @@ jobs: test-results/ retention-days: 7 + update-screenshots: + runs-on: ubuntu-latest + # Only on develop pushes; skip bot commits to avoid an infinite loop + if: github.event_name == 'push' && github.ref == 'refs/heads/develop' && !contains(github.event.head_commit.message, 'docs: refresh screenshots') + needs: [ backend, frontend ] + permissions: + contents: write + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install root dependencies (Playwright) + run: npm ci + + - name: Install backend dependencies + working-directory: ./backend + run: npm ci + + - name: Build backend + working-directory: ./backend + run: npm run build + + - name: Install frontend dependencies + working-directory: ./frontend + run: npm ci + + - name: Create compose directory + run: mkdir -p /tmp/compose + + - name: Start backend + working-directory: ./backend + run: node dist/index.js & + env: + JWT_SECRET: ci-test-secret-key-not-for-production + COMPOSE_DIR: /tmp/compose + PORT: 3000 + NODE_ENV: test + + - name: Start frontend dev server + working-directory: ./frontend + run: npm run dev & + + - name: Wait for services to be ready + run: npx wait-on http://localhost:3000/api/health http://localhost:5173 --timeout 30000 + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Capture screenshots + run: npx playwright test e2e/screenshots.spec.ts --project=chromium + env: + E2E_USERNAME: admin + E2E_PASSWORD: password123 + + - name: Commit updated screenshots + run: | + git config user.email "docs-bot@sencho.io" + git config user.name "Sencho Docs Bot" + git add docs/images/ + git commit -m "docs: refresh screenshots" || echo "No changes to commit" + git push + sync-docs: runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref == 'refs/heads/develop' diff --git a/CHANGELOG.md b/CHANGELOG.md index 604d4447..42e703fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - feat: automated documentation pipeline with Mintlify sync +- feat: CI job to auto-refresh doc screenshots on every develop push ### Fixed - **COOP header console warning on HTTP deployments:** Helmet sends `Cross-Origin-Opener-Policy: same-origin` by default, which browsers silently ignore over HTTP but log as a console error. Disabled via `crossOriginOpenerPolicy: false` — same rationale as the existing HSTS and COEP disables. diff --git a/e2e/screenshots.spec.ts b/e2e/screenshots.spec.ts new file mode 100644 index 00000000..a3842f1a --- /dev/null +++ b/e2e/screenshots.spec.ts @@ -0,0 +1,52 @@ +/** + * Docs screenshot capture. + * + * Takes canonical screenshots of key UI views and writes them to docs/images/. + * Run via: npx playwright test e2e/screenshots.spec.ts + * + * The CI `update-screenshots` job runs this on every push to develop and + * commits any changed images back to the repo so sync-docs picks them up. + */ +import * as fs from 'fs'; +import * as path from 'path'; +import { test } from '@playwright/test'; +import { loginAs } from './helpers'; + +const DOCS_IMAGES = path.resolve(__dirname, '../docs/images'); + +test.use({ + viewport: { width: 1280, height: 800 }, + // Always capture — this spec exists solely to produce screenshots + screenshot: 'on', +}); + +test.beforeAll(() => { + fs.mkdirSync(DOCS_IMAGES, { recursive: true }); +}); + +test('login page', async ({ page }) => { + await page.context().clearCookies(); + await page.goto('/'); + await page.waitForTimeout(600); + await page.screenshot({ path: path.join(DOCS_IMAGES, 'login.png'), fullPage: true }); +}); + +test('dashboard', async ({ page }) => { + await loginAs(page); + // Wait for stats widgets to settle + await page.waitForTimeout(1_000); + await page.screenshot({ path: path.join(DOCS_IMAGES, 'dashboard.png'), fullPage: true }); +}); + +test('stacks', async ({ page }) => { + await loginAs(page); + await page.getByRole('button', { name: 'Create Stack' }).waitFor({ timeout: 10_000 }); + await page.screenshot({ path: path.join(DOCS_IMAGES, 'stacks.png'), fullPage: true }); +}); + +test('resources', async ({ page }) => { + await loginAs(page); + await page.getByRole('link', { name: /resources/i }).click(); + await page.waitForTimeout(800); + await page.screenshot({ path: path.join(DOCS_IMAGES, 'resources.png'), fullPage: true }); +});