Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .claude/skills/author-e2e-tests/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: author-e2e-tests
description: Use when writing, debugging, or maintaining Playwright e2e tests for Positron -- new test files, test cases, flaky-test fixes, test infrastructure, or performance/metric tests.
description: Use when writing, debugging, or maintaining Playwright e2e tests for Positron -- new test files, test cases, flaky-test fixes, test infrastructure, or performance/metric tests. EDIT
---

# Positron Playwright E2E Testing
Expand Down
39 changes: 31 additions & 8 deletions .github/workflows/test-e2e-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ on:
description: "Whether to allow tests marked with :soft-fail to fail without failing the job."
type: boolean
default: false
docs_only:
required: false
description: "When true, skip all build/test steps and report success (docs-only change)."
type: boolean
default: false

workflow_dispatch:
inputs:
Expand Down Expand Up @@ -134,12 +139,18 @@ jobs:
DATABRICKS_WORKSPACE: ${{ secrets.DATABRICKS_WORKSPACE }}
DATABRICKS_PAT: ${{ secrets.DATABRICKS_PAT }}
steps:
- name: Docs-only change -- skipping e2e tests
if: ${{ inputs.docs_only }}
run: echo "Docs-only change detected; skipping e2e test suite."

- uses: actions/checkout@v7
if: ${{ !inputs.docs_only }}
with:
fetch-depth: 0
submodules: recursive

- name: Load secret
if: ${{ !inputs.docs_only }}
uses: 1password/load-secrets-action@v4
with:
# Export loaded secrets as environment variables
Expand All @@ -155,17 +166,19 @@ jobs:
POSIT_PASSWORD: "op://Positron/Posit-AI-Login/password"

- name: Transform to Playwright tags $PW_TAGS
if: ${{ !inputs.docs_only }}
run: bash scripts/pr-tags-transform.sh ${{ inputs.project}} "${{ inputs.grep }}"
shell: bash

- name: 📦 Restore caches
id: restore-caches
if: ${{ !inputs.docs_only }}
uses: ./.github/actions/restore-build-caches

- name: Install node dependencies
if: steps.restore-caches.outputs.cache-npm-core-hit != 'true' ||
if: ${{ !inputs.docs_only && (steps.restore-caches.outputs.cache-npm-core-hit != 'true' ||
steps.restore-caches.outputs.cache-npm-extensions-volatile-hit != 'true' ||
steps.restore-caches.outputs.cache-npm-extensions-stable-hit != 'true'
steps.restore-caches.outputs.cache-npm-extensions-stable-hit != 'true') }}
uses: nick-fields/retry@v4
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
Expand All @@ -191,42 +204,49 @@ jobs:
command: bash scripts/install-npm-parallel.sh

- name: Download binaries (Ark, Kallichore)
if: ${{ !inputs.docs_only }}
uses: ./.github/actions/download-binaries
with:
cache-hit: ${{ steps.restore-caches.outputs.cache-npm-extensions-volatile-hit == 'true' ||
steps.restore-caches.outputs.cache-npm-extensions-stable-hit == 'true' }}

- name: Install E2E test dependencies
if: ${{ !inputs.docs_only }}
run: npm --prefix test/e2e ci --prefer-offline --no-audit --no-fund

- name: Compile Positron and Download Electron
if: ${{ !inputs.docs_only }}
run: npm exec -- npm-run-all --max-old-space-size=8192 -p compile "electron x64"

- name: Install Playwright browsers
if: ${{ !inputs.docs_only }}
env:
PLAYWRIGHT_BROWSERS_PATH: .playwright-browsers
run: npx playwright install

# Downloads Builtin Extensions (needed for integration & e2e testing)
- name: Prelaunch
if: ${{ !inputs.docs_only }}
run: npm run prelaunch

# Must run AFTER Prelaunch since preLaunch.ts runs `npm run electron`
# which can re-extract the Electron binary and reset permissions
- name: Set permissions on SUID sandbox helper
if: ${{ !inputs.docs_only }}
run: |
ELECTRON_ROOT=.build/electron
sudo chown root $ELECTRON_ROOT/chrome-sandbox
sudo chmod 4755 $ELECTRON_ROOT/chrome-sandbox
stat $ELECTRON_ROOT/chrome-sandbox

- name: Move Positron License
if: ${{ inputs.install_license }}
if: ${{ !inputs.docs_only && inputs.install_license }}
run: |
mv /positron-license /__w/positron &&
printf "%s" "${{ secrets.POSITRON_DEV_LICENSE }}" > /__w/positron/positron-license/pdol/target/debug/pdol_rsa

- name: Setup E2E Test Environment
if: ${{ !inputs.docs_only }}
uses: ./.github/actions/setup-test-env
with:
aws-role-to-assume: ${{ secrets.QA_AWS_RO_ROLE }}
Expand All @@ -235,18 +255,21 @@ jobs:
# Preloading ensures the Node.js binary is fully built and ready before
# any parallel processes start, preventing runtime conflicts
- name: Preload Node.js Binary
if: ${{ inputs.project == 'e2e-chromium' }}
if: ${{ !inputs.docs_only && inputs.project == 'e2e-chromium' }}
run: npm run gulp node

- name: Send Results to GH Summary
if: ${{ !inputs.docs_only }}
uses: ./.github/actions/gen-report-dir
with:
identifier: ${{ inputs.project }}

- name: Alter AppArmor Restrictions for Playwright
if: ${{ !inputs.docs_only }}
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

- name: 🧪 Run Playwright Tests
if: ${{ !inputs.docs_only }}
shell: bash
env:
PLAYWRIGHT_BROWSERS_PATH: .playwright-browsers
Expand Down Expand Up @@ -296,31 +319,31 @@ jobs:
fi

- name: Upload Playwright Report to S3
if: ${{ success() || failure() }}
if: ${{ !inputs.docs_only && (success() || failure()) }}
continue-on-error: true
uses: ./.github/actions/upload-report-to-s3
with:
role-to-assume: ${{ secrets.AWS_TEST_REPORTS_ROLE }}
report-dir: ${{ env.REPORT_DIR }}

- name: Upload Test Logs
if: ${{ always() && inputs.upload_logs }}
if: ${{ !inputs.docs_only && always() && inputs.upload_logs }}
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.project }}-ubuntu-logs
path: test-logs
if-no-files-found: ignore

- name: Upload Test Results File
if: ${{ always() }}
if: ${{ !inputs.docs_only && always() }}
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.project }}-ubuntu-json-results
path: test-results/ubuntu.json
if-no-files-found: ignore

- name: Upload inspect-ai JSON Responses
if: ${{ always() && inputs.project == 'inspect-ai' }}
if: ${{ !inputs.docs_only && always() && inputs.project == 'inspect-ai' }}
uses: actions/upload-artifact@v7
with:
name: inspect-ai-responses
Expand Down
31 changes: 26 additions & 5 deletions .github/workflows/test-ext-host.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ on:
pull_request:
required: false
type: boolean
docs_only:
required: false
description: "When true, skip all build/test steps and report success (docs-only change)."
type: boolean
default: false
workflow_dispatch:
inputs:
pull_request:
Expand Down Expand Up @@ -54,24 +59,30 @@ jobs:
R_LIBS_USER: /usr/local/lib/R/site-library
RETICULATE_PYTHON: /root/.venv/bin/python
steps:
- name: Docs-only change -- skipping ext-host tests
if: ${{ inputs.docs_only }}
run: echo "Docs-only change detected; skipping extension-host test suite."

- uses: actions/checkout@v7
if: ${{ !inputs.docs_only }}
with:
submodules: recursive

- name: Checkout specific commit
if: ${{ inputs.commit != '' }}
if: ${{ !inputs.docs_only && inputs.commit != '' }}
run: |
git checkout ${{ inputs.commit }}
git submodule update --init --recursive

- name: 📦 Restore caches
id: restore-caches
if: ${{ !inputs.docs_only }}
uses: ./.github/actions/restore-build-caches

- name: Install node dependencies
if: steps.restore-caches.outputs.cache-npm-core-hit != 'true' ||
if: ${{ !inputs.docs_only && (steps.restore-caches.outputs.cache-npm-core-hit != 'true' ||
steps.restore-caches.outputs.cache-npm-extensions-volatile-hit != 'true' ||
steps.restore-caches.outputs.cache-npm-extensions-stable-hit != 'true'
steps.restore-caches.outputs.cache-npm-extensions-stable-hit != 'true') }}
uses: nick-fields/retry@v4
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
Expand Down Expand Up @@ -104,38 +115,45 @@ jobs:
bash scripts/install-npm-parallel.sh

- name: Download binaries (Ark, Kallichore)
if: ${{ !inputs.docs_only }}
uses: ./.github/actions/download-binaries
with:
cache-hit: ${{ steps.restore-caches.outputs.cache-npm-extensions-volatile-hit == 'true' ||
steps.restore-caches.outputs.cache-npm-extensions-stable-hit == 'true' }}

- name: Compile Positron and Download Electron
if: ${{ !inputs.docs_only }}
run: npm exec -- npm-run-all --max-old-space-size=8192 -p compile "electron x64"

# System deps are baked into the container image, so install browsers only.
- name: Install Playwright browsers
if: ${{ !inputs.docs_only }}
env:
PLAYWRIGHT_BROWSERS_PATH: .playwright-browsers
run: npx playwright install

- name: Setup Xvfb
if: ${{ !inputs.docs_only }}
uses: ./.github/actions/setup-xvfb


# Downloads Builtin Extensions (needed for integration & e2e testing)
- name: Prelaunch
if: ${{ !inputs.docs_only }}
run: npm run prelaunch

# Must run AFTER Prelaunch since preLaunch.ts runs `npm run electron`
# which can re-extract the Electron binary and reset permissions
- name: Set permissions on SUID sandbox helper
if: ${{ !inputs.docs_only }}
run: |
ELECTRON_ROOT=.build/electron
sudo chown root $ELECTRON_ROOT/chrome-sandbox
sudo chmod 4755 $ELECTRON_ROOT/chrome-sandbox
stat $ELECTRON_ROOT/chrome-sandbox

- name: Install Positron License
if: ${{ !inputs.docs_only }}
uses: ./.github/actions/install-license
with:
github-token: ${{ secrets.POSITRON_GITHUB_RO_PAT }}
Expand All @@ -145,13 +163,16 @@ jobs:
# integration test ("Connections pane works for R") reads a DESCRIPTION
# file from the repo root, so stage that from test-files.
- name: Stage R DESCRIPTION file
if: ${{ !inputs.docs_only }}
run: cp test/e2e/test-files/DESCRIPTION DESCRIPTION

- name: Compile Integration Tests
if: ${{ !inputs.docs_only }}
run: npm run --prefix test/integration/browser compile

- name: 🧪 Run Extension Host Tests (Electron)
id: electron-integration-tests
if: ${{ !inputs.docs_only }}
env:
PLAYWRIGHT_BROWSERS_PATH: .playwright-browsers
run: |
Expand All @@ -162,14 +183,14 @@ jobs:
fi

- name: 🧪 Run Extension Host Tests (Remote)
if: ${{ job.status != 'cancelled' && (success() || failure()) }}
if: ${{ !inputs.docs_only && job.status != 'cancelled' && (success() || failure()) }}
id: electron-remote-integration-tests
env:
PLAYWRIGHT_BROWSERS_PATH: .playwright-browsers
run: DISPLAY=:10 ./scripts/test-remote-integration.sh

- name: 🧪 Run Extension Host Tests (Chromium)
if: ${{ job.status != 'cancelled' && (success() || failure()) }}
if: ${{ !inputs.docs_only && job.status != 'cancelled' && (success() || failure()) }}
id: browser-integration-tests
env:
PLAYWRIGHT_BROWSERS_PATH: .playwright-browsers
Expand Down
46 changes: 43 additions & 3 deletions .github/workflows/test-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,46 @@ on:
- "release/**"

jobs:
docs-only:
name: docs-only check
runs-on: ubuntu-latest
outputs:
docs_only: ${{ steps.check.outputs.docs_only }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Compute docs-only flag
id: check
env:
BEFORE: ${{ github.event.before }}
AFTER: ${{ github.event.after }}
run: |
# Skip heavy suites when a push changed only docs. Fails safe: a
# branch-creation / force-push where BEFORE is all-zeros (no valid
# diff base), or any git error, yields docs_only=false -> full CI.
ZERO="0000000000000000000000000000000000000000"
if [ -z "$BEFORE" ] || [ "$BEFORE" = "$ZERO" ]; then
echo "No valid diff base; running full CI."
echo "docs_only=false" >> "$GITHUB_OUTPUT"
exit 0
fi
CHANGED_FILES=$(git diff --name-only "$BEFORE" "$AFTER" || true)
DOCS_ONLY=$(printf '%s' "$CHANGED_FILES" | bash scripts/is-docs-only-change.sh)
echo "docs_only=$DOCS_ONLY"
echo "docs_only=$DOCS_ONLY" >> "$GITHUB_OUTPUT"

setup:
name: setup
needs: [docs-only]
if: ${{ needs.docs-only.outputs.docs_only != 'true' }}
uses: ./.github/workflows/test-e2e-ubuntu-build.yml
secrets: inherit

e2e-electron:
name: e2e
needs: [setup]
needs: [setup, docs-only]
if: ${{ needs.docs-only.outputs.docs_only != 'true' }}
uses: ./.github/workflows/test-e2e-ubuntu-run.yml
with:
grep: ""
Expand All @@ -33,6 +65,8 @@ jobs:

e2e-windows:
name: e2e
needs: [docs-only]
if: ${{ needs.docs-only.outputs.docs_only != 'true' }}
uses: ./.github/workflows/test-e2e-windows-run.yml
secrets: inherit
with:
Expand All @@ -48,7 +82,8 @@ jobs:

e2e-ubuntu-chromium:
name: e2e
needs: [setup]
needs: [setup, docs-only]
if: ${{ needs.docs-only.outputs.docs_only != 'true' }}
uses: ./.github/workflows/test-e2e-ubuntu-run.yml
with:
grep: ""
Expand All @@ -66,22 +101,27 @@ jobs:

unit-tests:
name: test
needs: [docs-only]
if: ${{ needs.docs-only.outputs.docs_only != 'true' }}
uses: ./.github/workflows/test-unit.yml
secrets: inherit
with:
save_cache: true # Saves Ubuntu caches for subsequent runs

ext-host-tests:
name: test
needs: [docs-only]
if: ${{ needs.docs-only.outputs.docs_only != 'true' }}
uses: ./.github/workflows/test-ext-host.yml
secrets: inherit
with:
pull_request: false

slack-notify:
if: always()
if: ${{ always() && needs.docs-only.outputs.docs_only != 'true' }}
needs:
[
docs-only,
setup,
unit-tests,
ext-host-tests,
Expand Down
Loading
Loading