From 1f213a48213f29872e9c20302af5832e5af1c160 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 22:19:45 +0000 Subject: [PATCH 1/4] Initial plan From ec23ac742277eb946777326d9578ba6b2b7e210b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 22:28:46 +0000 Subject: [PATCH 2/4] Fix E2E test timeout: use small CI model, add WebGPU flags, support E2E_DTYPE Co-authored-by: devlux76 <86517969+devlux76@users.noreply.github.com> Agent-Logs-Url: https://github.com/devlux76/q2/sessions/3ac97559-4d4e-424f-9282-36e2a2e2f589 --- .github/workflows/ci.yml | 9 +++++++++ e2e/chat.spec.ts | 3 ++- e2e/model-loading.spec.ts | 7 ++++--- playwright.config.ts | 13 +++++++++++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd23eba..7f4cd2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,8 +22,17 @@ jobs: - run: bun run test e2e: + # GPU runners accelerate WebGPU/WebNN inference in transformers.js. + # WASM-only runners (ubuntu-latest) time out on models >200 MB. + # Use a GPU-enabled runner when available; fall back to ubuntu-latest. runs-on: ubuntu-latest timeout-minutes: 30 + env: + # Use a small 135 M-parameter ONNX instruct model so the download + + # WASM inference completes well within the CI timeout. On a real GPU + # runner you can override this with a larger model. + E2E_MODEL: onnx-community/SmolLM2-135M-Instruct + E2E_DTYPE: q4 steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 diff --git a/e2e/chat.spec.ts b/e2e/chat.spec.ts index b141c95..7e8d918 100644 --- a/e2e/chat.spec.ts +++ b/e2e/chat.spec.ts @@ -13,6 +13,7 @@ import { test, expect } from '@playwright/test'; test.setTimeout(480_000); const MODEL_ID = process.env.E2E_MODEL ?? 'onnx-community/Qwen3.5-0.8B-ONNX'; +const MODEL_DTYPE = process.env.E2E_DTYPE ?? 'q4'; test.describe('Real chat interaction', () => { test('sends a message and receives a streamed response', async ({ page }, testInfo) => { @@ -24,7 +25,7 @@ test.describe('Real chat interaction', () => { // ── Step 1: Load the model ───────────────────────────────────────────── await page.click('#tab-settings'); await page.fill('#model-custom-id', MODEL_ID); - await page.selectOption('#model-dtype', 'q4'); + await page.selectOption('#model-dtype', MODEL_DTYPE); await page.click('#load-btn'); // Wait for model to finish loading. diff --git a/e2e/model-loading.spec.ts b/e2e/model-loading.spec.ts index 86a73da..4b3b499 100644 --- a/e2e/model-loading.spec.ts +++ b/e2e/model-loading.spec.ts @@ -7,7 +7,7 @@ * ONNX Runtime WASM session, and the UI transitions to "ready". * * The default model is onnx-community/Qwen3.5-0.8B-ONNX (q4). Override with - * the E2E_MODEL environment variable for faster CI runs. + * the E2E_MODEL and E2E_DTYPE environment variables for faster CI runs. */ import { test, expect } from '@playwright/test'; @@ -15,6 +15,7 @@ import { test, expect } from '@playwright/test'; test.setTimeout(300_000); const MODEL_ID = process.env.E2E_MODEL ?? 'onnx-community/Qwen3.5-0.8B-ONNX'; +const MODEL_DTYPE = process.env.E2E_DTYPE ?? 'q4'; test.describe('Real model loading via transformers.js', () => { test('loads a model and transitions to ready state', async ({ page }, testInfo) => { @@ -28,8 +29,8 @@ test.describe('Real model loading via transformers.js', () => { await page.click('#tab-settings'); await page.fill('#model-custom-id', MODEL_ID); - // Ensure q4 dtype is selected (smallest download). - await page.selectOption('#model-dtype', 'q4'); + // Ensure the configured dtype is selected (q4 is the smallest download by default). + await page.selectOption('#model-dtype', MODEL_DTYPE); await page.screenshot({ path: testInfo.outputPath('model-before-load.png'), fullPage: true }); diff --git a/playwright.config.ts b/playwright.config.ts index f28465e..509c7e9 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -27,6 +27,19 @@ export default defineConfig({ screenshot: 'on', trace: 'retain-on-failure', headless: true, + /* Enable WebGPU and hardware-acceleration hints. + * On runners with a real GPU these flags allow WebGPU inference. + * On CPU-only runners they engage SwiftShader software-GL so the + * WebGPU preflight succeeds and WASM is used as final fallback. */ + launchOptions: { + args: [ + '--enable-gpu', + '--ignore-gpu-blocklist', + '--enable-unsafe-webgpu', + '--disable-gpu-sandbox', + '--use-gl=swiftshader', + ], + }, }, projects: [ From 96b2b31072a789a7e95bbbcac053da8c14d26928 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 23:29:17 +0000 Subject: [PATCH 3/4] Add GPU runner support: configurable runs-on, conditional SwiftShader, three-tier CI docs Co-authored-by: devlux76 <86517969+devlux76@users.noreply.github.com> Agent-Logs-Url: https://github.com/devlux76/q2/sessions/0dbd6e2c-0b47-4820-b874-7734ab896901 --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++++-------- playwright.config.ts | 36 +++++++++++++++++++++++-------- 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f4cd2a..1317764 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,17 +22,45 @@ jobs: - run: bun run test e2e: - # GPU runners accelerate WebGPU/WebNN inference in transformers.js. - # WASM-only runners (ubuntu-latest) time out on models >200 MB. - # Use a GPU-enabled runner when available; fall back to ubuntu-latest. - runs-on: ubuntu-latest + # ── Runner configuration ─────────────────────────────────────────────── + # + # This job supports three runner tiers. Set the repository variable + # E2E_RUNNER (Settings → Secrets and variables → Actions → Variables) to + # switch tiers without touching the workflow file: + # + # Tier 1 — CPU / WASM (default, free, no variable needed) + # E2E_RUNNER: (unset — falls back to ubuntu-latest) + # E2E_MODEL: onnx-community/SmolLM2-135M-Instruct (135 M params) + # E2E_DTYPE: q4 + # Notes: SwiftShader software-GL, WASM inference. Fast enough for the + # small CI model; no GPU hardware required. + # + # Tier 2 — GitHub-hosted GPU (Teams / Enterprise plan) + # E2E_RUNNER: ubuntu-latest-gpu (NVIDIA T4, provisioned via + # GitHub org Settings → Actions → Runners) + # E2E_MODEL: onnx-community/Qwen3.5-0.8B-ONNX + # E2E_DTYPE: q4 + # E2E_GPU_AVAILABLE: 1 (disables SwiftShader; Chrome uses real GPU) + # + # Tier 3 — Self-hosted GPU runner + # E2E_RUNNER: self-hosted-gpu (or any label you assigned when + # registering the runner; e.g. [self-hosted,linux,gpu]) + # E2E_MODEL: onnx-community/Qwen3.5-0.8B-ONNX + # E2E_DTYPE: q4 + # E2E_GPU_AVAILABLE: 1 + # + # ── Default (Tier 1) ────────────────────────────────────────────────── + runs-on: ${{ vars.E2E_RUNNER || 'ubuntu-latest' }} timeout-minutes: 30 env: - # Use a small 135 M-parameter ONNX instruct model so the download + - # WASM inference completes well within the CI timeout. On a real GPU - # runner you can override this with a larger model. - E2E_MODEL: onnx-community/SmolLM2-135M-Instruct - E2E_DTYPE: q4 + # Small 135 M-parameter instruct model for WASM/CPU Tier-1 runs. + # On a GPU runner (Tier 2/3) set E2E_MODEL to a larger model via the + # E2E_MODEL repository variable or override here. + E2E_MODEL: ${{ vars.E2E_MODEL || 'onnx-community/SmolLM2-135M-Instruct' }} + E2E_DTYPE: ${{ vars.E2E_DTYPE || 'q4' }} + # Set to '1' on GPU runners so playwright.config.ts drops --use-gl=swiftshader + # and lets Chromium use the actual hardware GPU for WebGPU inference. + E2E_GPU_AVAILABLE: ${{ vars.E2E_GPU_AVAILABLE || '' }} steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 diff --git a/playwright.config.ts b/playwright.config.ts index 509c7e9..fa93b06 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -6,6 +6,30 @@ import { defineConfig } from '@playwright/test'; * Serves the built application via a lightweight static server and runs real * browser tests against it — no mocks, no fakes, no fake DOM. */ + +/** + * Chromium launch args for WebGPU support in CI. + * + * On CPU-only runners (ubuntu-latest, no GPU hardware): + * --use-gl=swiftshader forces Chromium to use the Mesa/SwiftShader software + * rasterizer. WebGPU then runs through the Vulkan-over-SwiftShader adapter, + * which is slow but always available. WASM is the final inference fallback. + * + * On GPU runners (ubuntu-latest-gpu / self-hosted with NVIDIA): + * Drop --use-gl=swiftshader so Chromium can use the real hardware GPU via + * Vulkan/ANGLE. Set the E2E_GPU_AVAILABLE=1 repository variable (see + * ci.yml comments) to activate this path. + */ +const gpuArgs = [ + '--enable-gpu', + '--ignore-gpu-blocklist', + '--enable-unsafe-webgpu', + '--disable-gpu-sandbox', + // Use SwiftShader (software GL) only on CPU-only runners. On a real GPU + // runner set E2E_GPU_AVAILABLE=1 to let Chrome use hardware rendering. + ...( process.env.E2E_GPU_AVAILABLE ? [] : ['--use-gl=swiftshader'] ), +]; + export default defineConfig({ testDir: './e2e', outputDir: './e2e-results', @@ -29,16 +53,10 @@ export default defineConfig({ headless: true, /* Enable WebGPU and hardware-acceleration hints. * On runners with a real GPU these flags allow WebGPU inference. - * On CPU-only runners they engage SwiftShader software-GL so the - * WebGPU preflight succeeds and WASM is used as final fallback. */ + * On CPU-only runners SwiftShader software-GL is used instead + * (see gpuArgs above). */ launchOptions: { - args: [ - '--enable-gpu', - '--ignore-gpu-blocklist', - '--enable-unsafe-webgpu', - '--disable-gpu-sandbox', - '--use-gl=swiftshader', - ], + args: gpuArgs, }, }, From ba01c92c8a50a26a5df1b8cd53cfca77abf36d8d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 21 Mar 2026 01:07:56 +0000 Subject: [PATCH 4/4] Move E2E tests to local Docker pre-push workflow; remove from CI Co-authored-by: devlux76 <86517969+devlux76@users.noreply.github.com> Agent-Logs-Url: https://github.com/devlux76/q2/sessions/cf642d47-b83d-4618-a569-3d7f3c7a70bf --- .github/workflows/ci.yml | 64 ++++++---------------------------------- .husky/pre-push | 16 ++++++++++ e2e/Dockerfile | 32 ++++++++++++++++++++ e2e/run-local.sh | 63 +++++++++++++++++++++++++++++++++++++++ package.json | 1 + playwright.config.ts | 27 ++++++++--------- 6 files changed, 134 insertions(+), 69 deletions(-) create mode 100755 .husky/pre-push create mode 100644 e2e/Dockerfile create mode 100755 e2e/run-local.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1317764..304e81b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,58 +21,12 @@ jobs: - run: bun run check - run: bun run test - e2e: - # ── Runner configuration ─────────────────────────────────────────────── - # - # This job supports three runner tiers. Set the repository variable - # E2E_RUNNER (Settings → Secrets and variables → Actions → Variables) to - # switch tiers without touching the workflow file: - # - # Tier 1 — CPU / WASM (default, free, no variable needed) - # E2E_RUNNER: (unset — falls back to ubuntu-latest) - # E2E_MODEL: onnx-community/SmolLM2-135M-Instruct (135 M params) - # E2E_DTYPE: q4 - # Notes: SwiftShader software-GL, WASM inference. Fast enough for the - # small CI model; no GPU hardware required. - # - # Tier 2 — GitHub-hosted GPU (Teams / Enterprise plan) - # E2E_RUNNER: ubuntu-latest-gpu (NVIDIA T4, provisioned via - # GitHub org Settings → Actions → Runners) - # E2E_MODEL: onnx-community/Qwen3.5-0.8B-ONNX - # E2E_DTYPE: q4 - # E2E_GPU_AVAILABLE: 1 (disables SwiftShader; Chrome uses real GPU) - # - # Tier 3 — Self-hosted GPU runner - # E2E_RUNNER: self-hosted-gpu (or any label you assigned when - # registering the runner; e.g. [self-hosted,linux,gpu]) - # E2E_MODEL: onnx-community/Qwen3.5-0.8B-ONNX - # E2E_DTYPE: q4 - # E2E_GPU_AVAILABLE: 1 - # - # ── Default (Tier 1) ────────────────────────────────────────────────── - runs-on: ${{ vars.E2E_RUNNER || 'ubuntu-latest' }} - timeout-minutes: 30 - env: - # Small 135 M-parameter instruct model for WASM/CPU Tier-1 runs. - # On a GPU runner (Tier 2/3) set E2E_MODEL to a larger model via the - # E2E_MODEL repository variable or override here. - E2E_MODEL: ${{ vars.E2E_MODEL || 'onnx-community/SmolLM2-135M-Instruct' }} - E2E_DTYPE: ${{ vars.E2E_DTYPE || 'q4' }} - # Set to '1' on GPU runners so playwright.config.ts drops --use-gl=swiftshader - # and lets Chromium use the actual hardware GPU for WebGPU inference. - E2E_GPU_AVAILABLE: ${{ vars.E2E_GPU_AVAILABLE || '' }} - steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - with: - bun-version: 'latest' - - run: bun install - - run: bun run build - - run: npx playwright install --with-deps chromium - - run: npx playwright test - - uses: actions/upload-artifact@v4 - if: always() - with: - name: e2e-screenshots - path: e2e-results/ - retention-days: 30 + # E2E tests are NOT run in CI — they require GPU/Vulkan to run transformers.js + # models in WebGPU mode within a reasonable time budget, and GitHub Actions + # Pro does not offer GPU-enabled runners. + # + # Run E2E tests locally before pushing: + # bun run test:e2e:local (builds a Docker image with Mesa Vulkan + Playwright) + # + # See e2e/Dockerfile and e2e/run-local.sh for details. + # The pre-push git hook runs this automatically. diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..053b1a4 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,16 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +# Run Playwright E2E tests before pushing. +# Requires Docker (see e2e/run-local.sh). +# +# Skip when Docker is unavailable or for a quick push: +# git push --no-verify + +if ! command -v docker > /dev/null 2>&1; then + echo "⚠ Docker not found — skipping E2E tests. Install Docker to enable local E2E." + exit 0 +fi + +echo "▶ Running E2E tests before push (skip with: git push --no-verify)..." +bun run test:e2e:local diff --git a/e2e/Dockerfile b/e2e/Dockerfile new file mode 100644 index 0000000..0d237aa --- /dev/null +++ b/e2e/Dockerfile @@ -0,0 +1,32 @@ +# syntax=docker/dockerfile:1 +# +# E2E local test image for Q². +# +# Built on the official Playwright Ubuntu Jammy image — Chromium and all of +# its system-level dependencies are pre-installed and version-matched. +# Mesa Vulkan drivers are layered on top so that Chromium can use WebGPU via: +# • Intel ANV (Intel integrated / discrete GPU) +# • AMD RADV (AMD GPU via /dev/dri passthrough) +# • lavapipe (Mesa software Vulkan — unconditional fallback, no GPU needed) +# +# Build: docker build -t q2-e2e -f e2e/Dockerfile . +# Run: bun run test:e2e:local (or directly: ./e2e/run-local.sh) +# +# Update this tag when you bump @playwright/test in package.json. +# Available tags: https://mcr.microsoft.com/v2/playwright/tags/list +FROM mcr.microsoft.com/playwright:v1.50.1-jammy + +# ── Vulkan support ──────────────────────────────────────────────────────── +# mesa-vulkan-drivers installs Intel ANV, AMD RADV, and lavapipe in one shot. +# libvulkan1 is the Vulkan loader (required by both drivers and Chromium). +RUN apt-get update && apt-get install -y --no-install-recommends \ + libvulkan1 \ + mesa-vulkan-drivers \ + && rm -rf /var/lib/apt/lists/* + +# ── Bun ─────────────────────────────────────────────────────────────────── +# Bun is the project's build tool and package manager. +RUN curl -fsSL https://bun.sh/install | bash +ENV PATH="/root/.bun/bin:$PATH" + +WORKDIR /app diff --git a/e2e/run-local.sh b/e2e/run-local.sh new file mode 100755 index 0000000..b07242c --- /dev/null +++ b/e2e/run-local.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Run Playwright E2E tests locally inside a Docker container. +# +# • Builds the q2-e2e Docker image on first run (one-time ~3 min). +# • Caches node_modules in a named Docker volume so subsequent bun installs +# are near-instant. +# • Passes /dev/dri to the container for Intel/AMD GPU acceleration when +# available; falls back to Mesa lavapipe (software Vulkan) otherwise. +# • The pre-installed Playwright browsers in the image are reused — no +# 200 MB chromium download on each run. +# +# Usage: +# ./e2e/run-local.sh +# E2E_MODEL=onnx-community/SmolLM2-135M-Instruct E2E_DTYPE=q4 ./e2e/run-local.sh +# +# Rebuild the image after updating the Playwright version in package.json: +# docker build -t q2-e2e -f e2e/Dockerfile . + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +IMAGE="q2-e2e:local" +NM_VOLUME="q2-e2e-node-modules" + +# ── Require Docker ──────────────────────────────────────────────────────── +if ! command -v docker > /dev/null 2>&1; then + echo "✖ Docker is not installed or not in PATH." >&2 + echo " Install Docker Desktop: https://docs.docker.com/get-docker/" >&2 + exit 1 +fi + +# ── Build image if it does not exist yet ───────────────────────────────── +if ! docker image inspect "$IMAGE" > /dev/null 2>&1; then + echo "▶ Building E2E Docker image (first-run setup — ~3 min)..." + docker build -t "$IMAGE" -f "$SCRIPT_DIR/Dockerfile" "$REPO_ROOT" +fi + +# ── GPU passthrough ─────────────────────────────────────────────────────── +GPU_FLAGS=() +GPU_AVAILABLE="" +if [ -d /dev/dri ]; then + GPU_FLAGS+=(--device /dev/dri) + GPU_AVAILABLE="1" + echo "▶ /dev/dri found — Intel/AMD GPU passthrough enabled (WebGPU)." +else + echo "▶ /dev/dri not found — using Mesa lavapipe (software Vulkan fallback)." +fi + +# ── Run tests ───────────────────────────────────────────────────────────── +echo "▶ Running E2E tests..." +exec docker run --rm \ + --ipc=host \ + ${GPU_FLAGS[@]+"${GPU_FLAGS[@]}"} \ + -v "$REPO_ROOT:/app" \ + -v "$NM_VOLUME:/app/node_modules" \ + -w /app \ + -e CI=true \ + -e E2E_GPU_AVAILABLE="${E2E_GPU_AVAILABLE:-$GPU_AVAILABLE}" \ + -e E2E_MODEL="${E2E_MODEL:-}" \ + -e E2E_DTYPE="${E2E_DTYPE:-}" \ + "$IMAGE" \ + sh -c "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 bun install --frozen-lockfile && bun run build && npx playwright test" diff --git a/package.json b/package.json index 9744c5c..d6bf492 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "test": "vitest run --coverage", "pretest:e2e": "bun run build", "test:e2e": "playwright test", + "test:e2e:local": "bash e2e/run-local.sh", "test:browser": "vitest run --browser", "coverage": "vitest run --coverage", "deploy": "bun run build" diff --git a/playwright.config.ts b/playwright.config.ts index fa93b06..d740f9c 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -8,25 +8,25 @@ import { defineConfig } from '@playwright/test'; */ /** - * Chromium launch args for WebGPU support in CI. + * Chromium launch args for WebGPU support. * - * On CPU-only runners (ubuntu-latest, no GPU hardware): - * --use-gl=swiftshader forces Chromium to use the Mesa/SwiftShader software - * rasterizer. WebGPU then runs through the Vulkan-over-SwiftShader adapter, - * which is slow but always available. WASM is the final inference fallback. + * Local Docker runs (e2e/run-local.sh): + * /dev/dri is passed to the container for Intel/AMD GPU access. The run + * script sets E2E_GPU_AVAILABLE=1 automatically when /dev/dri is present, + * which drops --use-gl=swiftshader and lets Chromium use hardware Vulkan + * (Intel ANV / AMD RADV). * - * On GPU runners (ubuntu-latest-gpu / self-hosted with NVIDIA): - * Drop --use-gl=swiftshader so Chromium can use the real hardware GPU via - * Vulkan/ANGLE. Set the E2E_GPU_AVAILABLE=1 repository variable (see - * ci.yml comments) to activate this path. + * Without GPU (lavapipe fallback): + * --use-gl=swiftshader forces the Mesa software Vulkan rasterizer. + * WebGPU still works; inference falls back to WASM. */ const gpuArgs = [ '--enable-gpu', '--ignore-gpu-blocklist', '--enable-unsafe-webgpu', '--disable-gpu-sandbox', - // Use SwiftShader (software GL) only on CPU-only runners. On a real GPU - // runner set E2E_GPU_AVAILABLE=1 to let Chrome use hardware rendering. + // Use SwiftShader (software GL) when no GPU is available. + // Dropped when E2E_GPU_AVAILABLE=1 (set by run-local.sh on /dev/dri systems). ...( process.env.E2E_GPU_AVAILABLE ? [] : ['--use-gl=swiftshader'] ), ]; @@ -52,9 +52,8 @@ export default defineConfig({ trace: 'retain-on-failure', headless: true, /* Enable WebGPU and hardware-acceleration hints. - * On runners with a real GPU these flags allow WebGPU inference. - * On CPU-only runners SwiftShader software-GL is used instead - * (see gpuArgs above). */ + * On systems with Intel/AMD GPU: hardware Vulkan via /dev/dri passthrough. + * Without GPU: Mesa lavapipe (software Vulkan) via SwiftShader flag. */ launchOptions: { args: gpuArgs, },