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
41 changes: 41 additions & 0 deletions .github/workflows/ci-cookbook.yml
Original file line number Diff line number Diff line change
@@ -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
75 changes: 46 additions & 29 deletions packages/cookbook-v2/e2e/examples.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { test, expect } from "@playwright/test";

// All example IDs from the registry
const exampleIds = [
"hellogl",
"helloblue",
Expand All @@ -11,6 +10,7 @@ const exampleIds = [
"saturation",
"colorscale",
"mergechannels",
"mergechannelsfun",
"diamondcrop",
"diamondhello",
"diamondanim",
Expand All @@ -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");

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The per-example test can pass even when the route doesn't load the intended example (e.g., an invalid id renders the "Example not found" fallback without throwing any JS errors). Add an assertion that the example exists/loaded (for example, assert the "Example not found" UI is not present, or assert a known element like the breadcrumb select/value matches the id) so this test actually validates the example pages are reachable.

Suggested change
// Ensure the example actually loaded and did not fall back to the "Example not found" UI
await expect(page.locator("text=Example not found")).toHaveCount(0);

Copilot uses AI. Check for mistakes.
// 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([]);
});
}
4 changes: 3 additions & 1 deletion packages/cookbook-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading