From c1d4aa61fc92db2bd38d31e99811bc3c027c34aa Mon Sep 17 00:00:00 2001 From: Steven Enamakel's Droid Date: Thu, 25 Jun 2026 21:38:33 -0700 Subject: [PATCH] ci(e2e): allow selected Appium spec dispatch (#4137) --- .github/workflows/e2e-reusable.yml | 64 +++++++++++++++++++++--------- .github/workflows/e2e.yml | 16 +++++++- app/scripts/e2e-run-session.sh | 12 ++++-- app/src-tauri/src/lib.rs | 20 ++++++---- 4 files changed, 81 insertions(+), 31 deletions(-) diff --git a/.github/workflows/e2e-reusable.yml b/.github/workflows/e2e-reusable.yml index a9385ba42..0efcc2d1e 100644 --- a/.github/workflows/e2e-reusable.yml +++ b/.github/workflows/e2e-reusable.yml @@ -2,7 +2,7 @@ # Reusable E2E workflow — single source of truth for the desktop E2E recipe. # # Callers: -# - `.github/workflows/e2e.yml` — PR/push, all-OS mega-flow gate. +# - `.github/workflows/e2e.yml` — PR/push, all-OS selected-spec gate. # - `.github/workflows/release-staging.yml` — pretest gate, all 3 OS, full suite. # - `.github/workflows/release-production.yml` — pretest gate, all 3 OS, full suite. # @@ -50,17 +50,25 @@ on: full: description: When true, run the entire spec suite via `e2e-run-session.sh` (no - spec arg). When false, run the desktop full-flow lane only - (`mega-flow.spec.ts`). Releases set this to true. + spec arg). When false, run the selected desktop spec. Releases set + this to true. type: boolean default: false + spec_path: + description: Spec path to run when full is false. + type: string + default: test/e2e/specs/mega-flow.spec.ts + spec_label: + description: Log label for the selected spec when full is false. + type: string + default: mega-flow permissions: contents: read packages: read jobs: - # Mega-flow gate for PR/push (full=false). The full-suite path lives in + # Selected-spec gate for PR/push (full=false). The full-suite path lives in # `e2e-linux-full` below, which fans out across 4 parallel shards via # `e2e-run-all-flows.sh --suite=`. Splitting the two prevents the # smoke job from paying matrix overhead for a 2-spec run. @@ -115,7 +123,7 @@ jobs: path: | ~/.appium /usr/local/lib/node_modules/appium - key: appium3-chromium-${{ runner.os }}-v1 + key: appium3-chromium-${{ runner.os }}-v2 - name: Install JS dependencies run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile @@ -130,6 +138,9 @@ jobs: if ! command -v appium >/dev/null 2>&1; then bash scripts/ci-cancel-aware.sh npm install -g appium@3 fi + # appium-chromium-driver is loaded from ~/.appium and imports the + # appium package from that tree at runtime. + bash scripts/ci-cancel-aware.sh npm install --prefix "$HOME/.appium" appium@3 # `appium driver list --installed` can miss cached installs on some # Appium builds; install idempotently and ignore "already installed". bash scripts/ci-cancel-aware.sh appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true @@ -137,12 +148,12 @@ jobs: - name: Build E2E app run: bash scripts/ci-cancel-aware.sh pnpm --filter openhuman-app test:e2e:build - - name: Run E2E (mega-flow) + - name: Run E2E (${{ inputs.spec_label }}) if: ${{ !inputs.full }} run: | bash scripts/ci-cancel-aware.sh \ xvfb-run -a --server-args="-screen 0 1280x960x24" \ - bash app/scripts/e2e-run-session.sh test/e2e/specs/mega-flow.spec.ts mega-flow + bash app/scripts/e2e-run-session.sh "${{ inputs.spec_path }}" "${{ inputs.spec_label }}" - name: Upload E2E failure artifacts if: failure() @@ -292,7 +303,7 @@ jobs: path: | ~/.appium /usr/local/lib/node_modules/appium - key: appium3-chromium-${{ runner.os }}-v1 + key: appium3-chromium-${{ runner.os }}-v2 - name: Install JS dependencies (for test harness only) run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile @@ -302,6 +313,7 @@ jobs: if ! command -v appium >/dev/null 2>&1; then bash scripts/ci-cancel-aware.sh npm install -g appium@3 fi + bash scripts/ci-cancel-aware.sh npm install --prefix "$HOME/.appium" appium@3 bash scripts/ci-cancel-aware.sh appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true - name: Download build artifact @@ -475,7 +487,7 @@ jobs: with: path: | ~/.appium - key: appium3-chromium-${{ runner.os }}-v1 + key: appium3-chromium-${{ runner.os }}-v2 - name: Install JS dependencies run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile @@ -490,6 +502,9 @@ jobs: if ! command -v appium >/dev/null 2>&1; then bash scripts/ci-cancel-aware.sh npm install -g appium@3 fi + # appium-chromium-driver is loaded from ~/.appium and imports the + # appium package from that tree at runtime. + bash scripts/ci-cancel-aware.sh npm install --prefix "$HOME/.appium" appium@3 # `appium driver list --installed` can miss cached installs on some # Appium builds; install idempotently and ignore "already installed". bash scripts/ci-cancel-aware.sh appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true @@ -506,9 +521,9 @@ jobs: codesign --verify --deep --verbose=2 \ app/src-tauri/target/debug/bundle/macos/OpenHuman.app - - name: Run E2E (mega-flow) + - name: Run E2E (${{ inputs.spec_label }}) run: | - bash scripts/ci-cancel-aware.sh bash app/scripts/e2e-run-session.sh test/e2e/specs/mega-flow.spec.ts mega-flow + bash scripts/ci-cancel-aware.sh bash app/scripts/e2e-run-session.sh "${{ inputs.spec_path }}" "${{ inputs.spec_label }}" # Artifact uploads intentionally omitted — see e2e-linux for the # reusable-workflow-is-also-used-by-releases rationale. @@ -516,7 +531,11 @@ jobs: e2e-windows: if: inputs.run_windows && !inputs.full name: E2E (Windows / Appium Chromium) - runs-on: windows-latest + # Pin to 2022 for the CEF/Appium harness. As of June 2026, + # windows-latest resolves to Windows Server 2025 / VS 2026, where + # whisper-rs-sys' CMake build exits with 0xc0000142 before the harness can + # launch. + runs-on: windows-2022 timeout-minutes: 30 steps: - name: Checkout code @@ -562,7 +581,7 @@ jobs: with: path: | ~/.appium - key: appium3-chromium-${{ runner.os }}-v1 + key: appium3-chromium-${{ runner.os }}-v2 - name: Install JS dependencies run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile @@ -579,6 +598,9 @@ jobs: if ! command -v appium >/dev/null 2>&1; then bash scripts/ci-cancel-aware.sh npm install -g appium@3 fi + # appium-chromium-driver is loaded from ~/.appium and imports the + # appium package from that tree at runtime. + bash scripts/ci-cancel-aware.sh npm install --prefix "$HOME/.appium" appium@3 # `appium driver list --installed` can miss cached installs on some # Appium builds; install idempotently and ignore "already installed". bash scripts/ci-cancel-aware.sh appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true @@ -586,10 +608,10 @@ jobs: - name: Build E2E app run: bash scripts/ci-cancel-aware.sh pnpm --filter openhuman-app test:e2e:build - - name: Run E2E (mega-flow) + - name: Run E2E (${{ inputs.spec_label }}) shell: bash run: | - bash scripts/ci-cancel-aware.sh bash app/scripts/e2e-run-session.sh test/e2e/specs/mega-flow.spec.ts mega-flow + bash scripts/ci-cancel-aware.sh bash app/scripts/e2e-run-session.sh "${{ inputs.spec_path }}" "${{ inputs.spec_label }}" # Artifact uploads intentionally omitted — see e2e-linux for the # reusable-workflow-is-also-used-by-releases rationale. @@ -727,7 +749,7 @@ jobs: with: path: | ~/.appium - key: appium3-chromium-${{ runner.os }}-v1 + key: appium3-chromium-${{ runner.os }}-v2 - name: Install JS dependencies (for test harness only) run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile @@ -737,6 +759,7 @@ jobs: if ! command -v appium >/dev/null 2>&1; then bash scripts/ci-cancel-aware.sh npm install -g appium@3 fi + bash scripts/ci-cancel-aware.sh npm install --prefix "$HOME/.appium" appium@3 bash scripts/ci-cancel-aware.sh appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true - name: Download build artifact @@ -807,7 +830,8 @@ jobs: build-windows-full: if: inputs.run_windows && inputs.full name: Build (Windows full) - runs-on: windows-latest + # Keep the full-suite build on the same stable image as the smoke lane. + runs-on: windows-2022 timeout-minutes: 30 steps: - name: Checkout code @@ -890,7 +914,8 @@ jobs: if: inputs.run_windows && inputs.full needs: build-windows-full name: E2E (Windows full / ${{ matrix.shard.name }}) - runs-on: windows-latest + # Keep full-suite shards on the same stable image as the build job. + runs-on: windows-2022 timeout-minutes: 30 strategy: fail-fast: false @@ -924,7 +949,7 @@ jobs: with: path: | ~/.appium - key: appium3-chromium-${{ runner.os }}-v1 + key: appium3-chromium-${{ runner.os }}-v2 - name: Install JS dependencies (for test harness only) run: bash scripts/ci-cancel-aware.sh pnpm install --frozen-lockfile @@ -935,6 +960,7 @@ jobs: if ! command -v appium >/dev/null 2>&1; then bash scripts/ci-cancel-aware.sh npm install -g appium@3 fi + bash scripts/ci-cancel-aware.sh npm install --prefix "$HOME/.appium" appium@3 bash scripts/ci-cancel-aware.sh appium driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true - name: Download build artifact diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 934cf42ea..c135d3404 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,7 +1,9 @@ --- # PR/push E2E gate. # -# Desktop full-flow lane (mega-flow) across Linux, macOS, and Windows. +# Desktop E2E lane across Linux, macOS, and Windows. Manual dispatch defaults +# to mega-flow but can target a narrower spec, such as the Appium harness smoke +# spec, without changing the reusable workflow recipe. # The browser-hosted Playwright suite lives in its own standalone workflow so # it can be run on demand without gating every push / PR. name: E2E @@ -24,9 +26,17 @@ on: full: description: When true, run the entire spec suite (sharded). When false, run the - desktop full-flow lane only (mega-flow.spec.ts). + selected desktop spec only. type: boolean default: false + spec_path: + description: Spec path to run when full is false. + type: string + default: test/e2e/specs/mega-flow.spec.ts + spec_label: + description: Log label for the selected spec when full is false. + type: string + default: mega-flow permissions: contents: read @@ -45,3 +55,5 @@ jobs: run_macos: ${{ inputs.run_macos }} run_windows: ${{ inputs.run_windows }} full: ${{ inputs.full }} + spec_path: ${{ inputs.spec_path }} + spec_label: ${{ inputs.spec_label }} diff --git a/app/scripts/e2e-run-session.sh b/app/scripts/e2e-run-session.sh index e2c8e546e..abeb1976c 100755 --- a/app/scripts/e2e-run-session.sh +++ b/app/scripts/e2e-run-session.sh @@ -48,6 +48,7 @@ SPEC_ARG="${SPEC_ARGS[0]:-}" E2E_MOCK_PORT="${E2E_MOCK_PORT:-18473}" CEF_CDP_PORT="${CEF_CDP_PORT:-19222}" APPIUM_PORT="${APPIUM_PORT:-4723}" +E2E_CDP_READY_TIMEOUT_SECONDS="${E2E_CDP_READY_TIMEOUT_SECONDS:-180}" OS="$(uname)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -440,7 +441,7 @@ APP_PID=$! echo "[runner] Waiting for CDP at http://127.0.0.1:${CEF_CDP_PORT}/json/version ..." CDP_VERSION_JSON="" -for i in $(seq 1 60); do +for i in $(seq 1 "$E2E_CDP_READY_TIMEOUT_SECONDS"); do CDP_VERSION_JSON="$(curl -sf "http://127.0.0.1:${CEF_CDP_PORT}/json/version" 2>/dev/null || true)" if [ -n "$CDP_VERSION_JSON" ]; then echo "[runner] CDP is ready." @@ -453,8 +454,8 @@ for i in $(seq 1 60); do echo "----- end log -----" >&2 exit 1 fi - if [ "$i" -eq 60 ]; then - echo "ERROR: CDP did not come up within 60s. App log follows:" >&2 + if [ "$i" -eq "$E2E_CDP_READY_TIMEOUT_SECONDS" ]; then + echo "ERROR: CDP did not come up within ${E2E_CDP_READY_TIMEOUT_SECONDS}s. App log follows:" >&2 echo "----- $APP_LOG -----" >&2 cat "$APP_LOG" >&2 || true echo "----- end log -----" >&2 @@ -587,6 +588,11 @@ source "$SCRIPT_DIR/e2e-resolve-node-appium.sh" # exits non-zero on parse errors in some Appium versions, so just attempt the # install and ignore "already installed" output. echo "[runner] Ensuring Appium chromium driver is installed..." +APPIUM_HOME_DIR="${APPIUM_HOME:-$HOME/.appium}" +if [ ! -d "$APPIUM_HOME_DIR/node_modules/appium" ]; then + echo "[runner] Installing Appium into $APPIUM_HOME_DIR for chromium driver peer resolution..." + npm install --prefix "$APPIUM_HOME_DIR" appium@3 >/dev/null +fi "$APPIUM_BIN" driver install --source=npm appium-chromium-driver >/dev/null 2>&1 || true APPIUM_LOG="$LOG_DIR/appium-e2e-${LOG_SUFFIX}.log" diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index 16e11c1bc..d10eb4a1f 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -2655,10 +2655,13 @@ pub fn run() { // manager. Both are no-ops on Windows/Linux, so safe to always set. // // CDP attach goes through the in-process channel only — see - // `app/src-tauri/src/cdp/in_process.rs`. The legacy - // `--remote-debugging-port` flag is no longer passed: every + // `app/src-tauri/src/cdp/in_process.rs`. Production builds do + // not pass the legacy `--remote-debugging-port` flag: every // scanner attaches via `Webview::send_dev_tools_message` and - // there is no remaining loopback DevTools listener. + // there is no remaining loopback DevTools listener. The E2E + // test-support build can opt into a loopback port below because + // the Appium Chromium harness still attaches through + // `debuggerAddress`. // // NOTE: flags must be prefixed with `--`. The runtime's // `on_before_command_line_processing` dispatch (in @@ -2763,10 +2766,13 @@ pub fn run() { args.push(("--use-fake-ui-for-media-stream", None)); args.push(("--use-file-for-fake-video-capture", Some(path))); } - // CDP attach runs entirely through the in-process channel; the - // `--remote-debugging-port` flag is intentionally NOT passed so - // no loopback DevTools listener is bound for the lifetime of - // the embedded browser. + #[cfg(feature = "e2e-test-support")] + if std::env::var("OPENHUMAN_E2E_MODE").ok().as_deref() == Some("1") { + let port = std::env::var("CEF_CDP_PORT").unwrap_or_else(|_| "19222".to_string()); + let leaked_port: &'static str = Box::leak(port.into_boxed_str()); + log::info!("[cef-startup] e2e remote-debugging-port enabled port={leaked_port}"); + args.push(("--remote-debugging-port", Some(leaked_port))); + } let force_gpu_env = std::env::var("OPENHUMAN_FORCE_GPU").ok(); append_platform_cef_gpu_workarounds( &mut args,