diff --git a/.github/workflows/ci-cookbook.yml b/.github/workflows/ci-cookbook.yml new file mode 100644 index 00000000..af73bea0 --- /dev/null +++ b/.github/workflows/ci-cookbook.yml @@ -0,0 +1,41 @@ +name: CI cookbook-v2 + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + - run: corepack enable + - run: yarn install + + # Build gl-react packages (needed by cookbook-v2) + - name: Build gl-react packages + run: | + for d in packages/gl-react packages/gl-react-dom; do + yarn workspace gl-react-dev exec babel --root-mode upward --source-maps --extensions '.ts,.tsx' -d "$d/lib" "$d/src" + done + + # Install Playwright browsers + - name: Install Playwright + run: yarn workspace gl-react-cookbook-v2 e2e:install + + # Run Playwright tests + - name: Run Playwright tests + run: yarn workspace gl-react-cookbook-v2 e2e + + # Upload test results on failure + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: cookbook-v2-test-results + path: packages/cookbook-v2/test-results/ + retention-days: 7 diff --git a/packages/cookbook-v2/e2e/examples.spec.ts b/packages/cookbook-v2/e2e/examples.spec.ts index c744fe43..921702d8 100644 --- a/packages/cookbook-v2/e2e/examples.spec.ts +++ b/packages/cookbook-v2/e2e/examples.spec.ts @@ -1,6 +1,5 @@ import { test, expect } from "@playwright/test"; -// All example IDs from the registry const exampleIds = [ "hellogl", "helloblue", @@ -11,6 +10,7 @@ const exampleIds = [ "saturation", "colorscale", "mergechannels", + "mergechannelsfun", "diamondcrop", "diamondhello", "diamondanim", @@ -20,60 +20,77 @@ const exampleIds = [ "blurmap", "blurmapdyn", "blurmapmouse", - "distortion", + "blurimgtitle", + "blurvideo", + "blurfeedback", "demotunnel", "demodesert", + "demodesertcrt", "sdf1", "gol", "golglider", "golrot", + "golrotscu", + "golwebcam", + "distortion", "glsledit", - "transitions", - "textanimated", - "textfunky", + "paint", + "pixeleditor", "animated", "reactmotion", + "textanimated", + "textfunky", + "video", + "webcam", + "webcampersistence", + "transitions", + "behindasteroids", + "ibex", ]; -test("examples page loads with all examples listed", async ({ page }) => { +function filterWebGLErrors(errors: string[]): string[] { + return errors.filter( + (e) => + !e.includes("WebGL") && + !e.includes("GL_") && + !e.includes("getUserMedia") && + !e.includes("Failed to create WebGL context") && + !e.includes("no-webgl-context") + ); +} + +test("homepage loads", async ({ page }) => { const errors: string[] = []; page.on("pageerror", (err) => errors.push(err.message)); + await page.goto("/"); + await page.waitForLoadState("networkidle"); + await expect(page.locator("text=gl-react")).toBeVisible(); + expect(filterWebGLErrors(errors)).toEqual([]); +}); +test("examples page loads", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); await page.goto("/examples"); await page.waitForLoadState("networkidle"); - - // Check page loaded - await expect(page.locator("h1")).toHaveText(/Examples/); - - // No JS errors - expect(errors).toEqual([]); + await expect(page.locator("text=Examples")).toBeVisible(); + expect(filterWebGLErrors(errors)).toEqual([]); }); for (const id of exampleIds) { - test(`example "${id}" loads without JS errors`, async ({ page }) => { + test(`example "${id}" loads without errors`, async ({ page }) => { const errors: string[] = []; page.on("pageerror", (err) => errors.push(err.message)); await page.goto(`/examples/${id}`); await page.waitForLoadState("networkidle"); - // Wait for lazy-loaded component - await page.waitForTimeout(2000); - - // Title should be visible - await expect(page.locator("h1")).toBeVisible(); + // Wait for lazy-loaded component to render (canvas visible) + await expect(page.locator("canvas").first()).toBeVisible({ timeout: 10000 }).catch(() => {}); - // Report errors but don't fail on WebGL context issues (CI may not have GPU) - const criticalErrors = errors.filter( - (e) => - !e.includes("WebGL") && - !e.includes("GL_") && - !e.includes("getUserMedia") - ); + // Ensure the example loaded (not the "not found" fallback) + await expect(page.locator("text=Example not found")).toHaveCount(0); - if (criticalErrors.length > 0) { - console.log(`[${id}] JS errors:`, criticalErrors); - } - expect(criticalErrors).toEqual([]); + expect(filterWebGLErrors(errors)).toEqual([]); }); } diff --git a/packages/cookbook-v2/package.json b/packages/cookbook-v2/package.json index efefa53c..592a3244 100644 --- a/packages/cookbook-v2/package.json +++ b/packages/cookbook-v2/package.json @@ -9,7 +9,9 @@ "preview": "vite preview", "type-check": "tsc --noEmit", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "lint:fix": "eslint . --ext ts,tsx --fix" + "lint:fix": "eslint . --ext ts,tsx --fix", + "e2e": "playwright test", + "e2e:install": "playwright install --with-deps chromium" }, "dependencies": { "@heroicons/react": "^2.2.0",