426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
301 lines
13 KiB
TypeScript
301 lines
13 KiB
TypeScript
/**
|
||
* Playwright configuration for the Playwright Ui Smoke app test lane,
|
||
* including browser projects and app-server wiring.
|
||
*/
|
||
import { mkdirSync, writeFileSync } from "node:fs";
|
||
import path from "node:path";
|
||
import { fileURLToPath } from "node:url";
|
||
import { defineConfig, devices } from "@playwright/test";
|
||
// The committed source of truth for the known-phrase audio is the data-URL .ts
|
||
// (a real omnivoice.cpp speech clip). Binary .wav fixtures are gitignored, so
|
||
// derive the on-disk WAV from it for Chromium's --use-file-for-fake-audio-capture.
|
||
import { KNOWN_PHRASE_WAV_DATA_URL } from "../ui/src/voice/voice-selftest/fixtures/known-phrase";
|
||
import {
|
||
ASSERTION_GRADE_DASHBOARD_SPECS,
|
||
DASHBOARD_E2E_DEVICE_MATRIX,
|
||
} from "./test/ui-smoke/device-matrix";
|
||
|
||
const appDir = path.dirname(fileURLToPath(import.meta.url));
|
||
const repoRoot = path.resolve(appDir, "../..");
|
||
const uiSmokeLiveStack = path.join(
|
||
repoRoot,
|
||
"packages",
|
||
"app-core",
|
||
"scripts",
|
||
"playwright-ui-live-stack.ts",
|
||
);
|
||
const uiSmokeApiPort = Number(process.env.ELIZA_UI_SMOKE_API_PORT || "31337");
|
||
const uiSmokePort = Number(process.env.ELIZA_UI_SMOKE_PORT || "2138");
|
||
const reuseExistingServer = process.env.ELIZA_UI_SMOKE_REUSE_SERVER === "1";
|
||
const nodeExecutable =
|
||
process.env.ELIZA_NODE_PATH?.trim() ||
|
||
process.env.npm_node_execpath?.trim() ||
|
||
process.execPath;
|
||
const chromiumExecutablePath =
|
||
process.env.ELIZA_UI_SMOKE_CHROMIUM_EXECUTABLE?.trim();
|
||
// Real audio fed to the browser mic for the voice button-press e2e: Chromium
|
||
// plays this WAV file as the fake capture device so the REAL local-ASR recorder
|
||
// (getUserMedia + WAV encode + POST) runs end-to-end with no human/microphone.
|
||
// Materialized from the committed data-URL fixture (no gitignored binary).
|
||
const fakeAudioWav = path.join(
|
||
appDir,
|
||
"test-results",
|
||
".voice",
|
||
"known-phrase.wav",
|
||
);
|
||
mkdirSync(path.dirname(fakeAudioWav), { recursive: true });
|
||
writeFileSync(
|
||
fakeAudioWav,
|
||
Buffer.from(KNOWN_PHRASE_WAV_DATA_URL.split(",")[1] ?? "", "base64"),
|
||
);
|
||
const VOICE_MIC_SPEC = /(voice-realaudio|transcript-realaudio)\.spec\.ts/;
|
||
// WebKit (Safari engine) pointer/focus/text-input lane. iOS/iPadOS ship Safari's
|
||
// WebKit, but every default lane above is Chromium-only, so pointer, focus, and
|
||
// text-input regressions specific to WebKit go uncaught. This lane re-runs the
|
||
// chat pointer/focus/composer specs plus the plugin-views keyboard focus-order
|
||
// audit on WebKit. Scoped to keyless, stub-backed specs that need no
|
||
// Chromium-only permissions (clipboard/microphone) or fake-media launch flags,
|
||
// so they run green on WebKit (chat-message-actions and wallet-inventory grant
|
||
// clipboard permissions WebKit does not support and are intentionally excluded).
|
||
// CDP-touch specs (Input.dispatchTouchEvent is Chromium-only) stay on Chromium.
|
||
// plugin-views-visual is included for its Tab-order keyboard-navigation audit:
|
||
// the registered plugin views ship in the iOS/macOS WKWebView, so a WebKit focus
|
||
// or sequential-focus-navigation divergence must be caught here, not only on
|
||
// Chromium. Opt-in via PLAYWRIGHT_WEBKIT=1: WebKit is a separate browser download
|
||
// (`playwright install webkit`) not present on every machine, so gating keeps the
|
||
// default lane from reddening where WebKit is absent.
|
||
const WEBKIT_POINTER_FOCUS_SPEC =
|
||
/(chat-overlay-controls-interactions|conversation-management|slash-commands|plugin-views-visual)\.spec\.ts/;
|
||
const webkitLaneEnabled = process.env.PLAYWRIGHT_WEBKIT === "1";
|
||
// The all-views aesthetic audit (#8796) walks ~50 views × 2 viewports; it is a
|
||
// dedicated tool run via `audit:app`, not part of the default e2e smoke.
|
||
const AUDIT_APP_SPEC = /all-views-aesthetic-audit\.spec\.ts/;
|
||
// The cloud-surface aesthetic audit (#10725/#11342) walks every registered
|
||
// cloud route (packages/ui/src/cloud/register-all.ts) at desktop + mobile; a
|
||
// dedicated tool run via `audit:cloud`, not part of the default e2e smoke.
|
||
const AUDIT_CLOUD_SPEC = /cloud-surfaces-aesthetic-audit\.spec\.ts/;
|
||
// Focused light/dark contrast proof for the Applications dropdown/select
|
||
// popovers (#14232): renders the two touched surfaces in BOTH themes with the
|
||
// SelectContent popover open. Run via `--project=audit-app-dropdown`; kept out
|
||
// of the default e2e lane like the other dedicated aesthetic-audit tools.
|
||
const AUDIT_APP_DROPDOWN_SPEC = /applications-dropdown-contrast\.spec\.ts/;
|
||
// The WebKit lane (#10104/#10722): the assertion-grade dashboard specs, the
|
||
// core shell smoke, and the input-modality spec on a real Desktop Safari
|
||
// engine. WebKit-only behavior differences are real (for example,
|
||
// foreignObject canvas uploads can taint in WebKit while current Chromium
|
||
// accepts them), so the shipped Capacitor iOS WebView / desktop WKWebView
|
||
// engine must run in CI, not only Chromium wearing a Safari viewport.
|
||
const WEBKIT_SMOKE_SPECS =
|
||
/(browser-workspace|character-editor|wallet-inventory|workflow-editor|ui-smoke|input-modality)\.spec\.ts/;
|
||
const recording = !!process.env.E2E_RECORD;
|
||
const videoMode =
|
||
process.env.ELIZA_UI_SMOKE_DISABLE_VIDEO === "1"
|
||
? "off"
|
||
: recording
|
||
? "on"
|
||
: "retain-on-failure";
|
||
const humanSlowMo = Number.parseInt(
|
||
process.env.ELIZA_UI_PLAYWRIGHT_SLOWMO || "0",
|
||
10,
|
||
);
|
||
const slowMoLaunchOptions =
|
||
Number.isFinite(humanSlowMo) && humanSlowMo > 0
|
||
? { slowMo: humanSlowMo }
|
||
: {};
|
||
|
||
function withLaunchOptions(options: Record<string, unknown> = {}): {
|
||
launchOptions?: Record<string, unknown>;
|
||
} {
|
||
const launchOptions = { ...slowMoLaunchOptions, ...options };
|
||
return Object.keys(launchOptions).length > 0 ? { launchOptions } : {};
|
||
}
|
||
|
||
function withChromiumLaunchOptions(options: Record<string, unknown> = {}): {
|
||
launchOptions?: Record<string, unknown>;
|
||
} {
|
||
return withLaunchOptions({
|
||
...options,
|
||
...(chromiumExecutablePath
|
||
? { executablePath: chromiumExecutablePath }
|
||
: {}),
|
||
});
|
||
}
|
||
|
||
// Keep the app's API port env aligned with the live stack when the suite runs
|
||
// on non-default ports.
|
||
if (!process.env.ELIZA_API_PORT) {
|
||
process.env.ELIZA_API_PORT = String(uiSmokeApiPort);
|
||
}
|
||
|
||
export default defineConfig({
|
||
testDir: "./test/ui-smoke",
|
||
timeout: 180_000,
|
||
expect: {
|
||
timeout: 15_000,
|
||
},
|
||
fullyParallel: false,
|
||
retries: 0,
|
||
workers: 1,
|
||
reporter: "list",
|
||
outputDir: recording
|
||
? path.resolve(appDir, "../../e2e-recordings/app/test-results")
|
||
: "./test-results",
|
||
use: {
|
||
baseURL: `http://127.0.0.1:${uiSmokePort}`,
|
||
trace: recording ? "on" : "retain-on-failure",
|
||
video: videoMode,
|
||
screenshot: recording ? "on" : "only-on-failure",
|
||
},
|
||
projects: [
|
||
{
|
||
name: "chromium",
|
||
// The voice button-press spec needs the fake-audio launch flags; it runs
|
||
// in the dedicated `chromium-voice-mic` project below, not here. The
|
||
// all-views aesthetic audit runs only via the `audit:app` project; the
|
||
// cloud-surface audit only via `audit:cloud`.
|
||
testIgnore: [
|
||
VOICE_MIC_SPEC,
|
||
AUDIT_APP_SPEC,
|
||
AUDIT_CLOUD_SPEC,
|
||
AUDIT_APP_DROPDOWN_SPEC,
|
||
],
|
||
use: {
|
||
...devices["Desktop Chrome"],
|
||
...withChromiumLaunchOptions(),
|
||
},
|
||
},
|
||
...DASHBOARD_E2E_DEVICE_MATRIX.map((viewport) => ({
|
||
name: `dashboard-${viewport.id}`,
|
||
testMatch: ASSERTION_GRADE_DASHBOARD_SPECS,
|
||
use: {
|
||
...devices["Desktop Chrome"],
|
||
viewport: viewport.viewport,
|
||
isMobile: viewport.isMobile,
|
||
hasTouch: viewport.hasTouch,
|
||
...withChromiumLaunchOptions(),
|
||
},
|
||
})),
|
||
{
|
||
name: "chromium-voice-mic",
|
||
testMatch: VOICE_MIC_SPEC,
|
||
use: {
|
||
...devices["Desktop Chrome"],
|
||
permissions: ["microphone"],
|
||
launchOptions: {
|
||
...slowMoLaunchOptions,
|
||
args: [
|
||
"--use-fake-ui-for-media-stream",
|
||
"--use-fake-device-for-media-stream",
|
||
`--use-file-for-fake-audio-capture=${fakeAudioWav}`,
|
||
"--autoplay-policy=no-user-gesture-required",
|
||
],
|
||
...(chromiumExecutablePath
|
||
? { executablePath: chromiumExecutablePath }
|
||
: {}),
|
||
},
|
||
},
|
||
},
|
||
{
|
||
name: "mobile-chromium",
|
||
// Mobile-viewport (Pixel 7, hasTouch) lane: the decomposed
|
||
// personal-assistant domain views plus the real-touch chat gesture specs
|
||
// and the input-modality spec (its real-touch tests need hasTouch + CDP),
|
||
// so each surface is exercised at the same WebView viewport that ships on
|
||
// Capacitor iOS/Android.
|
||
testMatch:
|
||
/(apps-personal-assistant-decomposed-interactions|chat-clear-swipe|chat-send-voice-newchat-fuzz|gesture-matrix|input-modality|launcher-gesture-loop)\.spec\.ts/,
|
||
use: { ...devices["Pixel 7"], ...withLaunchOptions() },
|
||
},
|
||
// WebKit cross-engine lane (opt-in). Only added when PLAYWRIGHT_WEBKIT=1 so a
|
||
// machine without the WebKit browser download never reds the default lane.
|
||
...(webkitLaneEnabled
|
||
? [
|
||
{
|
||
name: "webkit",
|
||
testMatch: WEBKIT_POINTER_FOCUS_SPEC,
|
||
use: {
|
||
...devices["Desktop Safari"],
|
||
...withLaunchOptions(),
|
||
// Same parity as the desktop-webkit lane below: the PROD renderer
|
||
// registers /sw.js (skipWaiting + clients.claim), and WebKit —
|
||
// unlike Chromium — does NOT bypass a controlling service worker
|
||
// when page.route interception is active. Once the SW claims the
|
||
// page, every /api/* fetch silently goes AROUND the per-spec
|
||
// route fixtures to the real stub server (verified via an
|
||
// in-page probe: a route-fulfilled /api/conversations returned
|
||
// the stub server's conversations instead of the fixture's), so
|
||
// e.g. the conversation-persistence reload rehydrated a foreign
|
||
// thread and timed out (#11112 finding 2).
|
||
serviceWorkers: "block" as const,
|
||
},
|
||
},
|
||
]
|
||
: []),
|
||
{
|
||
name: "desktop-webkit",
|
||
// Real WebKit (Desktop Safari device profile) over the dashboard +
|
||
// shell-smoke + input-modality specs. This is the only lane where the
|
||
// engine that ships in every iOS WebView and macOS WKWebView actually
|
||
// executes the dashboard flows; Chromium-only skips inside these specs
|
||
// must carry a written engine-difference justification.
|
||
testMatch: WEBKIT_SMOKE_SPECS,
|
||
use: {
|
||
...devices["Desktop Safari"],
|
||
...withLaunchOptions(),
|
||
// Parity with the Chromium lanes, not an app change: Chromium
|
||
// force-bypasses the registered service worker whenever page.route
|
||
// interception is active; WebKit does not, so the app SW would
|
||
// silently serve /api/* AROUND every helpers.ts fixture stub (verified:
|
||
// with the SW active, a route-fulfilled /api/conversations list came
|
||
// back with the stub server's conversations instead of the fixture).
|
||
serviceWorkers: "block",
|
||
},
|
||
},
|
||
{
|
||
// All-views aesthetic audit (#8796) — run with `audit:app`
|
||
// (`--project=audit-app`). Walks every view at desktop + mobile internally.
|
||
name: "audit-app",
|
||
testMatch: AUDIT_APP_SPEC,
|
||
use: {
|
||
...devices["Desktop Chrome"],
|
||
...withChromiumLaunchOptions(),
|
||
},
|
||
},
|
||
{
|
||
// Cloud-surface aesthetic audit (#10725/#11342) — run with `audit:cloud`
|
||
// (`--project=audit-cloud`). Walks every registered cloud route at
|
||
// desktop + mobile internally. Requires a renderer built with
|
||
// VITE_PLAYWRIGHT_TEST_AUTH=true; the runner invalidates dist for this
|
||
// project so a cached non-auth build cannot skip the local auth shell.
|
||
name: "audit-cloud",
|
||
testMatch: AUDIT_CLOUD_SPEC,
|
||
use: {
|
||
...devices["Desktop Chrome"],
|
||
...withChromiumLaunchOptions(),
|
||
},
|
||
},
|
||
{
|
||
// Applications dropdown/select light+dark contrast proof (#14232), run
|
||
// via `--project=audit-app-dropdown`. Like audit-cloud it needs the
|
||
// renderer built with VITE_PLAYWRIGHT_TEST_AUTH=true so the Steward auth
|
||
// shell serves the app-detail route.
|
||
name: "audit-app-dropdown",
|
||
testMatch: AUDIT_APP_DROPDOWN_SPEC,
|
||
use: {
|
||
...devices["Desktop Chrome"],
|
||
...withChromiumLaunchOptions(),
|
||
},
|
||
},
|
||
],
|
||
webServer: {
|
||
command: `${JSON.stringify(nodeExecutable)} ${JSON.stringify(path.join(repoRoot, "packages", "app-core", "scripts", "run-node-tsx.mjs"))} ${JSON.stringify(uiSmokeLiveStack)}`,
|
||
cwd: repoRoot,
|
||
url: `http://127.0.0.1:${uiSmokePort}`,
|
||
reuseExistingServer,
|
||
// A cold renderer build transforms ~3000 modules (~12 min) before the smoke
|
||
// harness can bind the port; the live stack caps the build at 18 min, so the
|
||
// outer wait must exceed that (was 7 min, which killed every cold build).
|
||
timeout: 1_200_000,
|
||
},
|
||
});
|