# Reusable workflow: Desktop E2E tests (Linux + Windows)
# Call with workflow_call from ci.yml or e2e-desktop-comment.yml
name: E2E Desktop
on:
workflow_call:
inputs:
ref:
description: Git ref to checkout (e.g. PR head SHA or github.sha)
required: true
type: string
secrets:
TAURI_SIGNING_PRIVATE_KEY:
required: false
TAURI_SIGNING_PRIVATE_KEY_PASSWORD:
required: false
env:
PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig
jobs:
e2e-desktop:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
- os: windows-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- name: Install Linux deps
if: matrix.os == 'ubuntu-latest'
uses: ./.github/actions/install-linux-deps
with:
e2e: 'true'
- uses: dtolnay/rust-toolchain@stable
env:
PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.os }}-e2e-desktop
shared-key: rust-${{ matrix.os == 'ubuntu-latest' && 'x86_64-unknown-linux-gnu' || 'x86_64-pc-windows-msvc' }}
cache-on-failure: true
env:
PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
# Cache pnpm store for faster installs
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: pnpm-store-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
pnpm-store-${{ runner.os }}-
- name: Cache tauri-driver
uses: actions/cache@v4
id: tauri-driver-cache
with:
path: ~/.cargo/bin/tauri-driver*
key: tauri-driver-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: Install tauri-driver
if: steps.tauri-driver-cache.outputs.cache-hit != 'true'
run: cargo install tauri-driver --locked
env:
PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig
# Cache Tauri CLI binary (avoid recompiling on every run)
- name: Cache Tauri CLI
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/cargo-tauri*
~/.cargo/bin/tauri*
key: tauri-cli-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
tauri-cli-${{ runner.os }}-
- run: pnpm install --frozen-lockfile
- name: Build app (Linux)
if: matrix.os == 'ubuntu-latest'
run: pnpm --filter @mcpmux/desktop exec tauri build --bundles deb,rpm,updater
env:
PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
- name: Build app (Windows)
if: matrix.os == 'windows-latest'
run: pnpm build
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
- name: Run desktop E2E tests (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
# Start dbus session and unlock gnome-keyring with a dummy password for CI.
# Two-step process: unlock creates the login keyring, start exports env vars.
# Use eval to robustly export GNOME_KEYRING_CONTROL so the keyring stays
# accessible across all test specs (avoids "unlock prompt was dismissed").
dbus-run-session -- bash -c '
echo "test" | gnome-keyring-daemon --unlock --components=secrets
eval "$(gnome-keyring-daemon --start --components=secrets)"
echo "GNOME_KEYRING_CONTROL=$GNOME_KEYRING_CONTROL"
sleep 1
xvfb-run --auto-servernum pnpm test:e2e
'
- name: Install matching EdgeDriver (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
# Tauri uses the WebView2 runtime, not Edge directly.
# msedgedriver must match the WebView2 runtime version, not the Edge browser version.
$wv2Key = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}'
if (-not (Test-Path $wv2Key)) { $wv2Key = 'HKLM:\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' }
$wv2Version = (Get-ItemProperty $wv2Key).pv
Write-Host "WebView2 runtime version: $wv2Version"
$driverUrl = "https://msedgewebdriverstorage.blob.core.windows.net/edgewebdriver/$wv2Version/edgedriver_win64.zip"
$zipPath = "$env:TEMP\edgedriver.zip"
$extractDir = "$env:TEMP\edgedriver"
Invoke-WebRequest -Uri $driverUrl -OutFile $zipPath
Expand-Archive -Path $zipPath -DestinationPath $extractDir -Force
$driverDir = "$extractDir\edgedriver_win64"
if (-not (Test-Path "$driverDir\msedgedriver.exe")) { $driverDir = $extractDir }
Write-Host "EdgeDriver path: $driverDir"
echo "$driverDir" | Out-File -Append -Encoding utf8 $env:GITHUB_PATH
- name: Run desktop E2E tests (Windows)
if: matrix.os == 'windows-latest'
run: pnpm test:e2e
# Upload test results and artifacts
- name: Upload E2E test results
uses: actions/upload-artifact@v4
if: always()
with:
name: e2e-desktop-results-${{ matrix.os }}
path: |
tests/e2e/reports/
tests/e2e/screenshots/
tests/e2e/videos/
retention-days: 7
# Publish E2E Desktop test results with separate checks per OS
e2e-report:
needs: [e2e-desktop]
if: always()
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
checks: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Download all E2E test results
uses: actions/download-artifact@v4
with:
pattern: e2e-desktop-results-*
path: test-results
# Remove malformed/empty JUnit XMLs that crash dorny/test-reporter.
# This can happen when tauri-driver crashes mid-spec and the JUnit
# reporter writes incomplete XML (missing testsuites or test names).
- name: Sanitize JUnit XML reports
if: always()
run: |
for f in test-results/e2e-desktop-results-*/reports/*.xml; do
[ -f "$f" ] || continue
if ! grep -q '<testsuites' "$f" 2>/dev/null; then
echo "Removing malformed JUnit XML: $f"
rm "$f"
fi
done
- name: 'Report: E2E Desktop Tests (Linux)'
uses: dorny/test-reporter@v2
if: always()
with:
name: '🖥️ E2E Desktop Tests (Linux)'
path: test-results/e2e-desktop-results-ubuntu-latest/**/*.xml
reporter: java-junit
fail-on-error: false
- name: 'Report: E2E Desktop Tests (Windows)'
uses: dorny/test-reporter@v2
if: always()
with:
name: '🖥️ E2E Desktop Tests (Windows)'
path: test-results/e2e-desktop-results-windows-latest/**/*.xml
reporter: java-junit
fail-on-error: false