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
104 changes: 104 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: E2E

on:
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: e2e-${{ github.ref }}
cancel-in-progress: true

jobs:
# Offline UI tier — no secrets, no external network. Always runs.
core-e2e:
name: Core E2E (3 engines × 4 viewports)
runs-on: ubuntu-latest
env:
CI: "true"
TZ: UTC
steps:
- uses: actions/checkout@v4

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

- name: Install dependencies
run: npm ci

- name: Cache Playwright browsers
id: pw-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: pw-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium firefox webkit

- name: Run core specs
run: npx playwright test --grep-invert @live

- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-core-${{ github.run_id }}
path: |
playwright-report
test-results
if-no-files-found: warn
retention-days: 14

# Live tier — drives the real Talk2View API (chat + model switching).
# Skipped on forks (no secret access); in-test guards skip if secrets are
# unset, so this never fails red when credentials are absent.
live-e2e:
name: Live E2E (chat + model switch)
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
env:
CI: "true"
TZ: UTC
T2V_TEST_USER_EMAIL: ${{ secrets.T2V_TEST_USER_EMAIL }}
T2V_TEST_USER_PASSWORD: ${{ secrets.T2V_TEST_USER_PASSWORD }}
steps:
- uses: actions/checkout@v4

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

- name: Install dependencies
run: npm ci

- name: Cache Playwright browsers
id: pw-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: pw-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium firefox webkit

- name: Run live specs
run: npx playwright test --grep @live

- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-live-${{ github.run_id }}
path: |
playwright-report
test-results
if-no-files-found: warn
retention-days: 14
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ dist-ssr
.idea
*.log
.playwright-mcp/

# Playwright e2e output
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
100 changes: 100 additions & 0 deletions package-lock.json

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

13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
"preview": "vite preview",
"test:e2e": "playwright test",
"test:e2e:core": "playwright test --grep-invert @live",
"test:e2e:live": "playwright test --grep @live",
"test:e2e:mobile": "playwright test --grep @mobile",
"test:e2e:chromium": "playwright test --project=chromium-laptop",
"test:e2e:ui": "playwright test --ui",
"test:e2e:report": "playwright show-report",
"test:e2e:install": "playwright install --with-deps"
},
"dependencies": {
"@talk2view/sdk": "^0.5.0",
Expand All @@ -20,7 +28,10 @@
"react-dom": "^18.3.1"
},
"devDependencies": {
"@playwright/test": "^1.49.1",
"@types/node": "^22.10.5",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.6.3",
"vite": "^6.0.5"
}
}
75 changes: 75 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { defineConfig, devices } from "@playwright/test";

// Viewport profiles. The app's only responsive breakpoint is JS-driven
// (isPhone = innerWidth < 480), so we drive responsiveness purely by
// overriding viewport width on a desktop engine — no touch/mobile-UA
// descriptor needed.
const SIZES = {
phone: { width: 390, height: 844 },
tablet: { width: 820, height: 1180 },
laptop: { width: 1366, height: 768 },
desktop: { width: 1680, height: 1050 },
} as const;

const ENGINES = {
chromium: devices["Desktop Chrome"],
firefox: devices["Desktop Firefox"],
webkit: devices["Desktop Safari"],
} as const;

// 3 engines × 4 sizes = 12 projects named `${engine}-${size}`.
const projects = Object.entries(ENGINES).flatMap(([engine, engineDevice]) =>
Object.entries(SIZES).map(([size, viewport]) => ({
name: `${engine}-${size}`,
use: { ...engineDevice, viewport },
}))
);

// FULL_ARTIFACTS=1 turns on trace/video/screenshot for EVERY test (heavy —
// use for a targeted debug run or nightly). By default the built-in capture
// stays failure-oriented; passing-test artifacts come from the always-on
// screenshot + console-log fixtures in tests/e2e/fixtures/artifacts.ts.
const FULL = !!process.env.FULL_ARTIFACTS;

const PORT = process.env.E2E_DEV ? 5173 : 4173;

export default defineConfig({
testDir: "./tests/e2e/specs",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
timeout: 30_000,
expect: { timeout: 7_500 },
outputDir: "test-results/artifacts-pw",

reporter: [
["list"],
["html", { open: "never", outputFolder: "playwright-report" }],
["junit", { outputFile: "test-results/junit.xml" }],
],

use: {
baseURL: `http://localhost:${PORT}`,
// Pin timezone + locale so the schedule (which reads `new Date()` and
// toLocaleDateString) renders identically on every machine/runner.
timezoneId: "UTC",
locale: "en-US",
actionTimeout: 10_000,
navigationTimeout: 15_000,
screenshot: FULL ? "on" : "only-on-failure",
video: FULL ? "on" : "retain-on-failure",
trace: FULL ? "on" : "on-first-retry",
},

projects,

webServer: {
command: process.env.E2E_DEV
? "npm run dev -- --port 5173 --strictPort"
: "npm run build && npm run preview -- --port 4173 --strictPort",
url: `http://localhost:${PORT}`,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});
Loading
Loading