Skip to content

fix(test): throw on legacy test signature; case-insensitive PSModuleP… #9562

fix(test): throw on legacy test signature; case-insensitive PSModuleP…

fix(test): throw on legacy test signature; case-insensitive PSModuleP… #9562

Workflow file for this run

name: Test
on:
push:
branches:
- master
- release/v*
pull_request:
workflow_call:
inputs:
rebuild-docker-images-call:
description: "Force rebuild of Docker images?"
required: false
type: boolean
default: false
workflow_dispatch:
inputs:
rebuild-docker-images:
description: "Force rebuild of Docker images?"
required: false
type: boolean
default: false
reset-vitest-smart-cache:
description: "Reset Vitest smart cache (will cause all tests to run without caching for this run, then cache will be repopulated for future runs)"
required: false
type: boolean
default: false
collect-coverage:
description: "Collect v8 code coverage during the run and publish a merged coverage report (adds per-shard overhead; off by default)"
required: false
type: boolean
default: true
# concurrency:
# group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
# cancel-in-progress: true
permissions:
contents: read
env:
TEST_IMAGE_NODE_MAJOR_VERSION: 22
jobs:
check-if-docker-build:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
force-docker-build: ${{ steps.changes.outputs.docker == 'true' || steps.changes.outputs.workflow == 'true' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Determine if Dockerfiles changed
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
with:
filters: |
docker:
- 'docker/*/Dockerfile'
- 'docker/build.sh'
workflow:
- '.github/workflows/docker-build.yml'
run-docker-build:
needs: check-if-docker-build
if: |
always() &&
(needs.check-if-docker-build.outputs.force-docker-build == 'true' ||
inputs.rebuild-docker-images == 'true' || inputs.rebuild-docker-images == true ||
inputs.rebuild-docker-images-call == 'true' || inputs.rebuild-docker-images-call == true)
uses: ./.github/workflows/docker-build.yml
# -------------------------
# Linting + Shard Planning (unified job outputting map for all platforms)
# -------------------------
validate-and-shard:
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
linux: ${{ steps.plan.outputs.linux }}
windows: ${{ steps.plan.outputs.windows }}
macos: ${{ steps.plan.outputs.macos }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Tests
uses: ./.github/actions/pretest
with:
cache-key: v-39.8.10-linux-electron
cache-path: ~/.cache/electron
reset-vitest-smart-cache: ${{ inputs.reset-vitest-smart-cache }}
collect-coverage: ${{ inputs.collect-coverage }}
- name: Validate files
run: pnpm ci:validate
- name: Lint files
run: pnpm lint
- name: Output shard plan
run: pnpm ci:test:count
- id: plan
run: |
# Get shard counts for all platforms as JSON
SHARD_JSON=$(pnpm --silent ci:test:count | tail -n1)
# Extract individual platform arrays
LINUX_SHARDS=$(echo "$SHARD_JSON" | jq -c '.linux')
WINDOWS_SHARDS=$(echo "$SHARD_JSON" | jq -c '.win32')
MACOS_SHARDS=$(echo "$SHARD_JSON" | jq -c '.darwin')
# Set outputs
echo "linux=$LINUX_SHARDS" >> $GITHUB_OUTPUT
echo "windows=$WINDOWS_SHARDS" >> $GITHUB_OUTPUT
echo "macos=$MACOS_SHARDS" >> $GITHUB_OUTPUT
# Debug output
echo "Planned shards:"
echo " Linux: $LINUX_SHARDS"
echo " Windows: $WINDOWS_SHARDS"
echo " macOS: $MACOS_SHARDS"
# -------------------------
# Unified Test Jobs (one per OS, sharding within)
# -------------------------
test-linux:
name: Test Linux (Shard ${{ matrix.shard }})
needs: [check-if-docker-build, run-docker-build, validate-and-shard]
if: |
always() &&
needs.check-if-docker-build.result == 'success' &&
(needs.run-docker-build.result == 'success' || needs.run-docker-build.result == 'skipped')
strategy:
fail-fast: false
matrix:
shard: ${{ fromJson(needs.validate-and-shard.outputs.linux) }}
include:
- platform: linux
runner: ubuntu-latest
cache-path: ~/.cache/electron
cache-key: v-39.8.10-linux-electron
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 2
persist-credentials: false
- name: Cleanup runner
uses: ./.github/actions/cleanup-runner
- name: Setup Tests
uses: ./.github/actions/pretest
with:
cache-path: ${{ matrix.cache-path }}
cache-key: ${{ matrix.cache-key }}
reset-vitest-smart-cache: ${{ inputs.reset-vitest-smart-cache }}
collect-coverage: ${{ inputs.collect-coverage }}
- name: Download test-runner if exists
if: needs.run-docker-build.result == 'success'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: electron-builder-all-${{ env.TEST_IMAGE_NODE_MAJOR_VERSION }}
path: ${{ runner.temp }}
- name: Load test runner image if needed
if: needs.run-docker-build.result == 'success'
run: |
docker image load --input ${{ runner.temp }}/electron-builder-all-${{ env.TEST_IMAGE_NODE_MAJOR_VERSION }}.tar
docker image ls -a
- name: Write Docker secrets env file
run: |
printf 'CSC_KEY_PASSWORD=%s\n' "$CSC_KEY_PASSWORD_SECRET" > "${{ runner.temp }}/docker-secrets.env"
chmod 600 "${{ runner.temp }}/docker-secrets.env"
env:
CSC_KEY_PASSWORD_SECRET: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
- name: Run shard in docker
run: |
echo $TEST_RUNNER_IMAGE_TAG
pnpm test:linux
env:
ADDITIONAL_DOCKER_ARGS: "--env-file ${{ runner.temp }}/docker-secrets.env -e VITEST_SHARD_INDEX=${{ matrix.shard }} -e VITEST_SMART_CACHE_FILE=./test/vitest-scripts/vitest-config/_vitest-smart-cache.json -e RESET_VITEST_SHARD_CACHE=${{ inputs.reset-vitest-smart-cache }} -e VITEST_COVERAGE=${{ inputs.collect-coverage }}"
TEST_RUNNER_IMAGE_TAG: electronuserland/builder:${{ env.TEST_IMAGE_NODE_MAJOR_VERSION }}-wine-mono
- name: Upload test artifacts
if: always()
uses: ./.github/actions/upload-test-artifacts
with:
name: ${{ matrix.platform }}-${{ matrix.shard }}
test-windows:
name: Test Windows (Shard ${{ matrix.shard }})
needs: validate-and-shard
strategy:
fail-fast: false
matrix:
shard: ${{ fromJson(needs.validate-and-shard.outputs.windows) }}
include:
- platform: windows
runner: windows-latest
cache-path: ~\AppData\Local\electron\Cache
cache-key: v-39.8.10-windows-electron
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Tests
uses: ./.github/actions/pretest
with:
cache-key: ${{ matrix.cache-key }}
cache-path: ${{ matrix.cache-path }}
reset-vitest-smart-cache: ${{ inputs.reset-vitest-smart-cache }}
collect-coverage: ${{ inputs.collect-coverage }}
- name: Pre-initialize NuGet IsolatedStorage
shell: pwsh
run: |
# NuGet 2.x (bundled in the Squirrel.Windows vendor) uses IsolatedStorageFile.GetUserStoreForAssembly()
# for user settings. On fresh Windows CI runners this directory may not exist, causing the first
# NuGet invocation to fail with IsolatedStorageException (0x80131468). Pre-creating it lets
# the CLR create its hashed subdirectory without error.
New-Item -ItemType Directory -Force -Path "$env:LOCALAPPDATA\IsolatedStorage" | Out-Null
- name: Run shard
run: pnpm ci:test
env:
CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
VITEST_SHARD_INDEX: ${{ matrix.shard }}
VITEST_SMART_CACHE_FILE: ${{ github.workspace }}\test\vitest-scripts\vitest-config\_vitest-smart-cache.json
- name: Upload test artifacts
if: always()
uses: ./.github/actions/upload-test-artifacts
with:
name: ${{ matrix.platform }}-${{ matrix.shard }}
test-macos:
name: Test macOS (Shard ${{ matrix.shard }})
needs: validate-and-shard
strategy:
fail-fast: false
matrix:
shard: ${{ fromJson(needs.validate-and-shard.outputs.macos) }}
include:
- platform: macos
runner: macos-26
cache-path: ~/Library/Caches/electron
cache-key: v-39.8.10-macos-electron
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Tests
uses: ./.github/actions/pretest
with:
cache-path: ${{ matrix.cache-path }}
cache-key: ${{ matrix.cache-key }}
reset-vitest-smart-cache: ${{ inputs.reset-vitest-smart-cache }}
collect-coverage: ${{ inputs.collect-coverage }}
- name: Silence the damn GH runner warnings (temporary until GH updates their runners to a newer macOS base toolset bundle)
run: |
brew uninstall bicep || true # Not actually a dependency, but GH runners have it preinstalled and it prints annoying warnings about missing .NET workloads
brew untap aws/tap azure/bicep || true
- name: Install toolset via brew
run: |
brew install rpm
brew install snapcraft
- name: Run shard
run: pnpm ci:test
env:
VITEST_SHARD_INDEX: ${{ matrix.shard }}
VITEST_SMART_CACHE_FILE: ${{ github.workspace }}/test/vitest-scripts/vitest-config/_vitest-smart-cache.json
- name: Upload test artifacts
if: always()
uses: ./.github/actions/upload-test-artifacts
with:
name: ${{ matrix.platform }}-${{ matrix.shard }}
# -------------------------
# Special test jobs
# -------------------------
# Need to separate from other tests because logic is specific to when TOKEN env vars are set
test-updater:
name: Test Updater
needs: [check-if-docker-build, run-docker-build]
if: |
always() &&
needs.check-if-docker-build.result == 'success' &&
(needs.run-docker-build.result == 'success' || needs.run-docker-build.result == 'skipped')
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Cleanup runner
uses: ./.github/actions/cleanup-runner
- name: Setup Tests
uses: ./.github/actions/pretest
with:
cache-path: ~/.cache/electron
cache-key: v-39.8.10-update-electron
reset-vitest-smart-cache: ${{ inputs.reset-vitest-smart-cache }}
collect-coverage: ${{ inputs.collect-coverage }}
- name: Verify Docs Generation
run: pnpm generate:all
- name: Verify Site Generation
run: pnpm docs:all
- name: Download test-runner if exists
if: needs.run-docker-build.result == 'success'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: electron-builder-all-${{ env.TEST_IMAGE_NODE_MAJOR_VERSION }}
path: ${{ runner.temp }}
- name: Load test runner image if needed
if: needs.run-docker-build.result == 'success'
run: |
docker image load --input ${{ runner.temp }}/electron-builder-all-${{ env.TEST_IMAGE_NODE_MAJOR_VERSION }}.tar
docker image ls -a
# The updater tests no longer hit live provider endpoints, so no provider tokens are injected.
# The file must always exist (even empty) because docker --env-file fails on a missing file
# and the R2 append step below is skipped on forks.
- name: Create Docker secrets env file
run: |
: > "${{ runner.temp }}/docker-secrets.env"
chmod 600 "${{ runner.temp }}/docker-secrets.env"
# Gated on the upstream repository so forks cannot substitute their own R2 credentials;
# without these vars the R2 live smoke test is skipped (test.ifEnv).
- name: Add R2 secrets to Docker secrets env file
if: github.repository == 'electron-userland/electron-builder'
run: |
printf 'CF_R2_ACCESS_KEY_ID=%s\nCF_R2_SECRET_ACCESS_KEY=%s\nCF_R2_ACCOUNT_ID=%s\nCF_R2_BUCKET=%s\nCF_R2_PUBLIC_URL=%s\n' \
"$CF_R2_ACCESS_KEY_ID_SECRET" "$CF_R2_SECRET_ACCESS_KEY_SECRET" "$CF_R2_ACCOUNT_ID_SECRET" "$CF_R2_BUCKET_SECRET" "$CF_R2_PUBLIC_URL_SECRET" \
>> "${{ runner.temp }}/docker-secrets.env"
env:
CF_R2_ACCESS_KEY_ID_SECRET: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
CF_R2_SECRET_ACCESS_KEY_SECRET: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
CF_R2_ACCOUNT_ID_SECRET: ${{ secrets.CF_R2_ACCOUNT_ID }}
CF_R2_BUCKET_SECRET: ${{ secrets.CF_R2_BUCKET }}
CF_R2_PUBLIC_URL_SECRET: ${{ secrets.CF_R2_PUBLIC_URL }}
- name: Test
run: |
echo $TEST_RUNNER_IMAGE_TAG
pnpm test:linux
env:
TEST_FILES: nsisUpdaterTest,PublishManagerTest,differentialUpdateTest,GitlabPublisherTest,R2PublisherTest
TEST_RUNNER_IMAGE_TAG: electronuserland/builder:${{ env.TEST_IMAGE_NODE_MAJOR_VERSION }}-wine-mono
ADDITIONAL_DOCKER_ARGS: "--env-file ${{ runner.temp }}/docker-secrets.env -e VITEST_SMART_CACHE_FILE=./test/vitest-scripts/vitest-config/_vitest-smart-cache.json -e RESET_VITEST_SHARD_CACHE=${{ inputs.reset-vitest-smart-cache }} -e VITEST_COVERAGE=${{ inputs.collect-coverage }}"
- name: Upload test artifacts
if: always()
uses: ./.github/actions/upload-test-artifacts
with:
name: updater
test-snap:
name: Test Snap (${{ matrix.core }})
runs-on: ubuntu-24.04
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
core: [core18, core20, core22, core24]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Tests
uses: ./.github/actions/pretest
with:
cache-path: ~/.cache/electron
cache-key: v-39.8.10-native-electron
reset-vitest-smart-cache: ${{ inputs.reset-vitest-smart-cache }}
collect-coverage: ${{ inputs.collect-coverage }}
- name: Test snap ${{ matrix.core }} (Docker)
run: SNAP_CORE=${{ matrix.core }} ./test/src/linux/test-snap.sh
env:
VITEST_SMART_CACHE_FILE: ${{ github.workspace }}/test/vitest-scripts/vitest-config/_vitest-smart-cache.json
RESET_VITEST_SHARD_CACHE: ${{ inputs.reset-vitest-smart-cache }}
# Native install+launch test — core24 only; requires snapcraft + unsquashfs on the runner.
- name: Disable background apt services (prevent apt lock contention)
if: matrix.core == 'core24'
run: |
sudo systemctl stop unattended-upgrades apt-daily.service apt-daily-upgrade.service || true
sudo systemctl disable apt-daily.timer apt-daily-upgrade.timer || true
sudo systemctl kill --kill-who=all apt-daily.service apt-daily-upgrade.service || true
while sudo fuser /var/lib/apt/lists/lock /var/lib/dpkg/lock /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do
echo "Waiting for apt lock to be released…"
sleep 2
done
- name: Install snapcraft natively
if: matrix.core == 'core24'
run: |
sudo apt-get update
sudo apt-get install -y squashfs-tools xvfb
# Pin to the 8.x track: snapcraft 9.0.0 (latest/stable) intermittently hangs while
# apt-fetching destructive-mode stagePackages, causing the core24 test to time out.
sudo snap install snapcraft --classic --channel=8.x/stable
- name: Test snap core24 native (install + launch)
if: matrix.core == 'core24'
run: pnpm ci:test
env:
TEST_FILES: snapHeavyTest
SNAP_TEST_CORES: core24
SNAPCRAFT_BUILD_ENVIRONMENT: host
RUN_SNAP_TESTS: "true"
VITEST_SMART_CACHE_FILE: ${{ github.workspace }}/test/vitest-scripts/vitest-config/_vitest-smart-cache.json
RESET_VITEST_SHARD_CACHE: ${{ inputs.reset-vitest-smart-cache }}
- name: Upload test artifacts
if: always()
uses: ./.github/actions/upload-test-artifacts
with:
name: snap-${{ matrix.core }}
test-e2e:
name: Test e2e Auto Updater Linux
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
test_config:
- stem: archlinux
env:
PACKAGE_MANAGER_TO_TEST: pacman
- stem: rpm
env:
PACKAGE_MANAGER_TO_TEST: dnf
- stem: rpm
env:
PACKAGE_MANAGER_TO_TEST: zypper
- stem: rpm
env:
PACKAGE_MANAGER_TO_TEST: yum
- stem: rpm
env:
PACKAGE_MANAGER_TO_TEST: rpm
- stem: debian
env:
PACKAGE_MANAGER_TO_TEST: apt
- stem: debian
env:
PACKAGE_MANAGER_TO_TEST: dpkg
- stem: appimage
env:
RUN_APP_IMAGE_TEST: true
steps:
- name: Checkout code repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Tests
uses: ./.github/actions/pretest
with:
cache-path: ~/.cache/electron
cache-key: v-39.8.10-linux-e2e-electron
reset-vitest-smart-cache: ${{ inputs.reset-vitest-smart-cache }}
collect-coverage: ${{ inputs.collect-coverage }}
- name: docker build
run: ./test/src/updater/test-specific-platforms.sh ${{ matrix.test_config.stem }} build
- name: e2e Linux Updater tests (install, auto-update, uninstall)
run: ./test/src/updater/test-specific-platforms.sh ${{ matrix.test_config.stem }} run
env:
ADDITIONAL_DOCKER_ARGS: >-
-e PACKAGE_MANAGER_TO_TEST=${{ matrix.test_config.env.PACKAGE_MANAGER_TO_TEST || '' }}
-e RUN_APP_IMAGE_TEST=${{ matrix.test_config.env.RUN_APP_IMAGE_TEST || false }}
-e VITEST_SMART_CACHE_FILE=./test/vitest-scripts/vitest-config/_vitest-smart-cache.json
-e RESET_VITEST_SHARD_CACHE=${{ inputs.reset-vitest-smart-cache }} -e VITEST_COVERAGE=${{ inputs.collect-coverage }}
- name: Upload test artifacts
if: always()
uses: ./.github/actions/upload-test-artifacts
with:
name: e2e-${{ matrix.test_config.env.PACKAGE_MANAGER_TO_TEST || matrix.test_config.stem }}
# -------------------------
# Merge Smart Cache Artifacts
# -------------------------
merge-smart-cache:
needs: [test-linux, test-windows, test-macos, test-e2e, test-updater, test-snap]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: ./.github/actions/pnpm
- name: Download all smart cache artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: vitest-smart-cache-*
path: smart-cache-artifacts
continue-on-error: true
- name: Merge cache files
run: pnpm ci:test:merge-cache
# -------------------------
# Aggregate per-shard test reports → run summary + combined downloadable report
# -------------------------
- name: Download all test report artifacts
if: always()
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: vitest-report-*
path: report-artifacts
continue-on-error: true
# Per-test / per-shard / total durations + a combined list of every failed test, written to the
# run's Summary page (GITHUB_STEP_SUMMARY) and the console. Never fails the job — it is reporting.
- name: Write test summary
if: always()
continue-on-error: true
run: pnpm ci:test:summary --results-dir report-artifacts
- name: Gather blob reports
if: always()
run: |
mkdir -p vitest-blobs
# Prefix each blob with its artifact name (report-artifacts/<artifact>/vitest-blobs/blob-*.json)
# so two shards that happen to share a blob filename (same platform+shard+pid across different
# runners) can't overwrite each other. vitest --merge-reports accepts arbitrary blob filenames.
find report-artifacts -type f -name 'blob-*.json' | while read -r f; do
art=$(basename "$(dirname "$(dirname "$f")")")
cp "$f" "vitest-blobs/${art}__$(basename "$f")"
done
echo "Collected $(ls vitest-blobs 2>/dev/null | wc -l) blob report(s)"
# Combined @vitest/ui HTML report (+ merged v8 coverage when collect-coverage is on) from every
# shard's blob. Runs from the repo root so blob paths and reporter resolution line up.
- name: Merge reports into combined HTML report
if: always()
continue-on-error: true
run: pnpm ci:test:merge-reports
env:
VITEST_COVERAGE: ${{ inputs.collect-coverage }}
- name: Upload combined test report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vitest-combined-report
path: |
html-report/
merged-test-report.json
merged-coverage/
retention-days: 14
if-no-files-found: warn
- name: Delete individual cache artifacts
uses: geekyeggo/delete-artifact@176a747ab7e287e3ff4787bf8a148716375ca118 # v6.0.0
with:
name: |
vitest-smart-cache-*
vitest-report-*
failOnError: false
continue-on-error: true
- name: Copy merged cache to canonical path
if: always()
run: cp merged-cache/_vitest-smart-cache.json test/vitest-scripts/vitest-config/_vitest-smart-cache.json
- name: Compute Vitest smart cache ref
if: always()
run: |
PR_NUM="${{ github.event.pull_request.number }}"
if [ -n "$PR_NUM" ]; then
echo "VITEST_CACHE_REF=PR-${PR_NUM}" >> "$GITHUB_ENV"
else
# Artifact names cannot contain "/", so sanitize slashes in branch
# names like "release/v26" -> "release-v26" (keep in sync with detectRef in fetch-smart-cache.ts).
echo "VITEST_CACHE_REF=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_ENV"
fi
- name: Upload vitest smart cache artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: vitest-smart-cache-${{ env.VITEST_CACHE_REF }}
path: merged-cache/_vitest-smart-cache.json
retention-days: 90
overwrite: true