Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1a205eb
Tunes e2e CI job for manual debugging on testing branch
nikolay-1986 Jul 14, 2026
e009930
Removes stale gitlens_commit_composer MCP tool from e2e tests
nikolay-1986 Jul 14, 2026
9c50373
Fixes flaky Fetch popover e2e by opening via hover not focus
nikolay-1986 Jul 14, 2026
ee06277
Restores default e2e worker count for CI run
nikolay-1986 Jul 15, 2026
8223e8f
Adds multi-editor e2e matrix (VS Code, Windsurf, Kiro, Cursor)
nikolay-1986 Jul 15, 2026
d91f114
Fixes fork provisioning in e2e CI matrix
nikolay-1986 Jul 15, 2026
5c792eb
Makes IPC discovery scheme assertion editor-aware in e2e
nikolay-1986 Jul 15, 2026
0bb08a7
Makes e2e resilient to Cursor and Kiro editor quirks
nikolay-1986 Jul 15, 2026
97af520
Fixes lint errors in e2e multi-editor harness
nikolay-1986 Jul 15, 2026
b98b551
Restructures e2e editor selection into per-editor Playwright projects
nikolay-1986 Jul 15, 2026
0e905a0
Hardens graph e2e specs against fork webview render races
nikolay-1986 Jul 16, 2026
5e45754
Retries editor launch/connect to survive parallel-launch contention
nikolay-1986 Jul 16, 2026
7ea1b4d
Fixes e2e CI running all projects per editor job + never uploading ar…
nikolay-1986 Jul 16, 2026
1f9e999
Addresses PR review: harden e2e harness robustness
nikolay-1986 Jul 16, 2026
2bcea20
Fails fast on login-walled forks and reaps orphaned editor processes
nikolay-1986 Jul 17, 2026
b12c13c
Excludes login-walled Cursor from the CI e2e matrix
nikolay-1986 Jul 17, 2026
d21fb75
Stabilizes Positron e2e: launch retry budget, treeView graph clicks, …
nikolay-1986 Jul 17, 2026
6c4d079
Hardens graph conflict-resolution WIP-row selection against overlay i…
nikolay-1986 Jul 17, 2026
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
130 changes: 123 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,55 @@ jobs:
- name: Run unit tests
run: xvfb-run -a pnpm run test

derive-matrix:
name: Derive editor matrix
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
timeout-minutes: 5
outputs:
editors: ${{ steps.gen.outputs.editors }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Calc tools versions
id: tools-versions
run: |
NODE_VERSION=$(jq -r '.engines.node | gsub("[^0-9.]"; "")' package.json)
echo "node-version=$NODE_VERSION" >> $GITHUB_OUTPUT

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ steps.tools-versions.outputs.node-version }}

# Single source of truth for the editor matrix is tests/e2e/editors.ts; read it directly
# (Node strips the TS types) so the CI matrix never drifts from the Playwright projects.
- name: Generate matrix
Comment thread
nikolay-1986 marked this conversation as resolved.
id: gen
run: |
editors=$(node --experimental-strip-types -e "import('./tests/e2e/editors.ts').then(m => console.log(JSON.stringify(m.editors)))")
# Heredoc-delimited output is robust even if the value ever spans lines — this is the
# workflow's source of truth, and a bare `echo name=$value` would corrupt fromJSON().
{
echo "editors<<EOF"
echo "$editors"
echo "EOF"
} >> "$GITHUB_OUTPUT"

e2e-tests:
name: E2E Tests
name: E2E Tests (${{ matrix.editor.name }})
needs: derive-matrix
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
timeout-minutes: 20
timeout-minutes: 25
# Experimental forks have known editor-specific UI gaps; keep them informational so they don't
# block CI, while non-experimental editors (VS Code, Windsurf) remain required.
continue-on-error: ${{ matrix.editor.experimental }}
strategy:
fail-fast: false
matrix:
editor: ${{ fromJSON(needs.derive-matrix.outputs.editors) }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -158,21 +202,93 @@ jobs:
- name: Build (production with DEBUG for simulator)
run: pnpm run bundle:e2e

# Provisions a VS Code-compatible editor binary for forks and exposes its path via the
# editor's env var (matrix.editor.envVar), which the Playwright project reads. VS Code
# itself is downloaded by the harness, so no path is emitted for it.
- name: Provision editor
run: |
set -euo pipefail
id="${{ matrix.editor.id }}"
exe=""
case "$id" in
vscode)
: # Harness downloads VS Code via @vscode/test-electron
;;
windsurf)
url=$(curl -fsSL "https://windsurf-stable.codeium.com/api/update/linux-x64/stable/latest" | jq -r '.url')
echo "Downloading Windsurf: $url"
curl -fsSL "$url" -o /tmp/windsurf.tar.gz
mkdir -p /tmp/windsurf && tar -xzf /tmp/windsurf.tar.gz -C /tmp/windsurf
# Windsurf's Electron binary is named after its codename (devin-desktop)
exe=$(find /tmp/windsurf -maxdepth 2 -type f \( -name devin-desktop -o -name windsurf \) | head -n1)
;;
kiro)
url=$(curl -fsSL "https://prod.download.desktop.kiro.dev/stable/metadata-linux-x64-stable.json" | jq -r '.releases[].updateTo.url | select(endswith(".tar.gz"))' | head -n1)
echo "Downloading Kiro: $url"
curl -fsSL "$url" -o /tmp/kiro.tar.gz
mkdir -p /tmp/kiro && tar -xzf /tmp/kiro.tar.gz -C /tmp/kiro
exe=$(find /tmp/kiro -maxdepth 2 -type f -name kiro | head -n1)
;;
cursor)
url=$(curl -fsSL "https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable" | jq -r '.downloadUrl')
echo "Downloading Cursor: $url"
curl -fsSL "$url" -o /tmp/cursor.AppImage
chmod +x /tmp/cursor.AppImage
( cd /tmp && ./cursor.AppImage --appimage-extract >/dev/null )
exe=$(find /tmp/squashfs-root -type f -path '*/usr/share/cursor/cursor' | head -n1)
[ -z "$exe" ] && exe=$(find /tmp/squashfs-root -maxdepth 2 -type f -name cursor | head -n1)
;;
positron)
# Positron ships no GitHub release assets; binaries live on Posit's CDN.
# releases.json carries the current version's direct .deb URL (no version hardcode).
url=$(curl -fsSL "https://cdn.posit.co/positron/releases/deb/x86_64/releases.json" | jq -r '.url')
echo "Downloading Positron: $url"
curl -fsSL "$url" -o /tmp/positron.deb
mkdir -p /tmp/positron && dpkg-deb -x /tmp/positron.deb /tmp/positron
# Electron binary is /usr/share/positron/positron (code-oss layout); the bin/ wrapper is deeper
exe=$(find /tmp/positron/usr/share -maxdepth 2 -type f -name positron | head -n1)
;;
*)
echo "::error::Unknown editor id '$id'"; exit 1
;;
esac
if [ "$id" != "vscode" ]; then
if [ -z "$exe" ] || [ ! -f "$exe" ]; then
echo "::error::Could not resolve $id executable"; exit 1
fi
chmod +x "$exe"
echo "Resolved $id executable: $exe"
# Expose the path via the editor's env var so the Playwright project picks it up.
echo "${{ matrix.editor.envVar }}=$exe" >> "$GITHUB_ENV"
fi

# Invoke Playwright directly via `pnpm exec` — `pnpm run test:e2e -- --project=<id>`
# keeps the literal `--`, which Playwright treats as a positional filter (not the
# `--project` option), so the filter is ignored and EVERY registered project runs
# (e.g. vscode + windsurf → ~2x the tests). Passing --project straight to the binary
# applies it correctly, keeping each matrix job scoped to its own editor.
Comment on lines +267 to +271
- name: Run E2E tests
run: xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x16" pnpm run test:e2e
run: >
xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x16"
pnpm exec playwright test -c tests/e2e/playwright.config.ts --project=${{ matrix.editor.id }}

# Upload on both pass and fail (skip only on cancel) so results/report are available to
# inspect even when the job is green — flaky-retry traces (trace: on-first-retry) land in
# out/test-results/ but would be dropped by an `if: failure()` gate on a passing run.
- name: Upload test results
if: failure()
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: playwright-results
name: playwright-results-${{ matrix.editor.id }}
path: out/test-results/
retention-days: 7
if-no-files-found: ignore

- name: Upload Playwright report
if: failure()
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: playwright-report
name: playwright-report-${{ matrix.editor.id }}
path: playwright-report/
retention-days: 7
if-no-files-found: ignore
18 changes: 16 additions & 2 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,24 @@ pnpm run test:e2e -- --grep "wizard"
# Run in headed mode (useful for debugging)
pnpm run test:e2e -- --headed

# Run with specific project (Electron desktop)
pnpm run test:e2e -- --project=electron
# Run against a specific editor (one Playwright project per editor; see tests/e2e/editors.ts).
# VS Code is the default; a fork project only registers when its binary path env var is set.
pnpm run test:e2e -- --project=vscode
WINDSURF_E2E_PATH=/path/to/windsurf pnpm run test:e2e -- --project=windsurf
```

Editors that lack the UI surface a given spec needs (e.g. some forks) can opt that spec out with the
`@no-fork` tag — fork projects run with `grepInvert: /@no-fork/`, while `vscode` runs everything:

```ts
// @no-fork: <editor> lacks a stable point to drive this flow
test('...', { tag: '@no-fork' }, async ({ vscode }) => {
/* ... */
});
```

Only tag genuine editor incompatibilities (missing UI), never functional failures — those get fixed.

## Interpreting Test Output

- PASS = test passed
Expand Down
Loading
Loading