CI workflow (nrwl/nx)
The CI workflow from nrwl/nx, explained and optimized by Latchkey.
CI health: C - fair
Point runs-on at Latchkey and get run de-duplication, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the CI workflow from the nrwl/nx repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: CI
on:
push:
branches:
- master
- '[0-9]+.[0-9]+.x'
pull_request:
branches:
- "**"
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
NX_CLOUD_ENABLE_METRICS_COLLECTION: 'true'
PNPM_HOME: ~/.pnpm
# Pin corepack to the pnpm version from packageManager. Without this, corepack
# falls back to "latest" in directories that have no packageManager field
# (e.g. e2e temp dirs), pulling pnpm 11 and breaking install.
COREPACK_DEFAULT_TO_LATEST: '0'
jobs:
main-linux:
runs-on: ubuntu-latest
env:
NX_BATCH_MODE: 'true'
NX_E2E_CI_CACHE_KEY: e2e-github-linux
NX_DAEMON: 'true'
NX_PERF_LOGGING: 'false'
NX_VERBOSE_LOGGING: 'false'
NX_NATIVE_LOGGING: 'false'
NX_E2E_RUN_E2E: 'true'
NX_CI_EXECUTION_ENV: 'linux'
NX_CLOUD_NO_TIMEOUTS: 'true'
NX_ALLOW_NON_CACHEABLE_DTE: 'true'
NX_CLOUD_EXPERIMENTAL_POLLING: 'true'
NX_CLOUD_CONTINUOUS_ASSIGNMENT: 'true'
NX_CLOUD_VERBOSE_LOGGING: 'true'
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
filter: tree:0
- name: Set verbose logging from debug mode
if: runner.debug == '1'
run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"
- name: Fetch Master
run: git fetch origin master:master
if: ${{ github.event_name == 'pull_request' }}
- name: Set SHAs
uses: nrwl/nx-set-shas@310288c04d90696f9f1bc27c5e3caea6642b53d4 # v5.0.0
with:
main-branch-name: 'master'
- name: Start CI Run
run: npx nx-cloud@next start-ci-run --distribute-on="./.nx/workflows/dynamic-changesets.yaml" --stop-agents-after="e2e"
- name: Setup dev tools with mise
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
- name: Enable corepack and install pnpm
run: |
corepack enable
corepack prepare --activate
- name: Get pnpm store directory
id: pnpm-cache
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@48b5f213c81028ace310571dc5ec0fbbca0b2947 # v4.4.3
- name: Install project dependencies
run: pnpm install --frozen-lockfile
- name: Restore .NET analyzer projects
run: dotnet restore nx.sln
- name: Nx Report
run:
pnpm nx report
- name: Run Checks/Lint/Test/Build
run: |
pids=()
pnpm nx record -- nx format:check &
pids+=($!)
pnpm nx record -- nx sync:check
pids+=($!)
pnpm nx build workspace-plugin && pnpm nx record -- pnpm nx-cloud conformance:check
pids+=($!)
pnpm nx run-many -t check-imports check-lock-files check-codeowners --parallel=1 --no-dte &
pids+=($!)
pnpm nx affected --targets=lint,test,build,e2e,e2e-ci,format-native,lint-native,gradle:build-ci,vale,run &
pids+=($!)
for pid in "${pids[@]}"; do
wait "$pid"
done
timeout-minutes: 100
- name: Fix CI
run: pnpm nx fix-ci
if: failure()
main-macos:
runs-on: macos-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}${{ contains(github.event_name, 'push') && format('-{0}', github.sha) || '' }}
cancel-in-progress: true
env:
NX_E2E_CI_CACHE_KEY: e2e-github-macos
NX_PERF_LOGGING: 'false'
NX_CI_EXECUTION_ENV: 'macos'
SELECTED_PM: 'npm'
steps:
- name: Log concurrency info
run: |
echo "Concurrency group: ${{ github.workflow }}-${{ github.ref }}${{ contains(github.event_name, 'push') && format('-{0}', github.sha) || '' }}"
echo "Concurrency cancel-in-progress: ${{ !contains(github.event_name, 'push') }}"
echo "Concurrency cancel-event-name: ${{ github.event_name }}"
if: always()
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
filter: tree:0
- name: Set verbose logging from debug mode
if: runner.debug == '1'
run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV"
- name: Fetch Master
run: git fetch origin master:master
if: ${{ github.event_name == 'pull_request' }}
- name: Setup dev tools with mise
uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3
- name: Enable corepack and install pnpm
run: |
corepack enable
corepack prepare --activate
- name: Set SHAs
uses: nrwl/nx-set-shas@310288c04d90696f9f1bc27c5e3caea6642b53d4 # v5.0.0
with:
main-branch-name: 'master'
- name: Check for React Native changes
id: check-changes
run: |
HAS_CHANGED=$(node ./scripts/check-react-native-changes.js $NX_BASE $NX_HEAD);
if $HAS_CHANGED; then
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "React Native projects are affected, will run macOS tests"
else
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "No React Native projects affected, skipping macOS tests"
fi
- name: Restore Homebrew packages
if: steps.check-changes.outputs.has_changes == 'true'
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: |
/opt/homebrew
~/Library/Caches/Homebrew
key: nrwl-nx-homebrew-packages
- name: Configure Detox Environment, Install applesimutils
if: steps.check-changes.outputs.has_changes == 'true'
run: |
# Ensure Xcode command line tools are installed and configured
xcode-select --print-path || sudo xcode-select --reset
sudo xcode-select -s /Applications/Xcode.app
# Install or update applesimutils with error handling
if ! brew list applesimutils &>/dev/null; then
echo "Installing applesimutils..."
HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null
# Homebrew now refuses to load formulae from third-party taps unless trusted
brew trust wix/brew
HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils >/dev/null || {
echo "Failed to install applesimutils, retrying with update..."
brew update
brew trust wix/brew
HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils
}
else
echo "Updating applesimutils..."
HOMEBREW_NO_AUTO_UPDATE=1 brew upgrade applesimutils || true
fi
# Verify applesimutils installation
applesimutils --version || (echo "applesimutils installation failed" && exit 1)
# Configure environment for M-series Mac
echo "DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer" >> $GITHUB_ENV
echo "PLATFORM_NAME=iOS Simulator" >> $GITHUB_ENV
# Set additional environment variables for better debugging
echo "DETOX_DISABLE_TELEMETRY=1" >> $GITHUB_ENV
echo "DETOX_LOG_LEVEL=trace" >> $GITHUB_ENV
# Verify Xcode installation
xcodebuild -version
# List available simulators
xcrun simctl list devices available
timeout-minutes: 10
continue-on-error: false
- name: Reset iOS Simulators
if: steps.check-changes.outputs.has_changes == 'true'
id: reset-simulators
run: |
echo "Resetting iOS Simulators..."
# Kill simulator processes
sudo killall -9 com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
killall "Simulator" 2>/dev/null || true
killall "iOS Simulator" 2>/dev/null || true
# Wait for processes to terminate
sleep 3
# Shutdown and erase all simulators (ignore failures)
xcrun simctl shutdown all 2>/dev/null || true
sleep 5
xcrun simctl erase all 2>/dev/null || true
# If erase failed, try the nuclear option
if xcrun simctl list devices | grep -q "Booted" 2>/dev/null; then
echo "Standard reset failed, using nuclear option..."
rm -rf ~/Library/Developer/CoreSimulator/Devices/* 2>/dev/null || true
launchctl remove com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true
sleep 3
fi
# Clean up additional directories
rm -rf ~/Library/Developer/CoreSimulator/Caches/* 2>/dev/null || true
rm -rf ~/Library/Logs/CoreSimulator/* 2>/dev/null || true
rm -rf ~/Library/Developer/Xcode/DerivedData/* 2>/dev/null || true
echo "Simulator reset completed"
timeout-minutes: 5
continue-on-error: true
- name: Verify Simulator Reset
if: steps.check-changes.outputs.has_changes == 'true' && steps.reset-simulators.outcome == 'success'
run: |
# Verify CoreSimulator service restarted
pgrep -fl "CoreSimulator" || (echo "CoreSimulator service not running" && exit 1)
# Check simulator list is clean
xcrun simctl list devices
# Verify simulator runtime paths exist and are writable
test -d ~/Library/Developer/CoreSimulator/Devices || (echo "Simulator devices directory missing" && exit 1)
touch ~/Library/Developer/CoreSimulator/Devices/test || (echo "Simulator devices directory not writable" && exit 1)
rm ~/Library/Developer/CoreSimulator/Devices/test
timeout-minutes: 5
- name: Diagnose Simulator Reset Failure
if: steps.check-changes.outputs.has_changes == 'true' && steps.reset-simulators.outcome == 'failure'
run: |
echo "Simulator reset failed. Collecting diagnostic information..."
xcrun simctl list
echo "Checking simulator logs..."
ls -la ~/Library/Logs/CoreSimulator/ || echo "No simulator logs found"
- name: Save Homebrew Cache
if: steps.check-changes.outputs.has_changes == 'true'
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: |
/opt/homebrew
~/Library/Caches/Homebrew
key: nrwl-nx-homebrew-packages
- name: Get pnpm store directory
if: steps.check-changes.outputs.has_changes == 'true'
id: pnpm-cache-macos
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
if: steps.check-changes.outputs.has_changes == 'true'
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ${{ steps.pnpm-cache-macos.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install project dependencies
if: steps.check-changes.outputs.has_changes == 'true'
run: |
pnpm install --frozen-lockfile
pnpm playwright install --with-deps
- name: Restore .NET packages
if: steps.check-changes.outputs.has_changes == 'true'
run: dotnet restore nx.sln
- name: Run E2E Tests for macOS
if: steps.check-changes.outputs.has_changes == 'true'
run: |
pnpm nx affected -t e2e-macos-local --parallel=1 --base=$NX_BASE --head=$NX_HEAD
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: CI on: push: branches: - master - '[0-9]+.[0-9]+.x' pull_request: branches: - "**" env: NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }} NX_CLOUD_ENABLE_METRICS_COLLECTION: 'true' PNPM_HOME: ~/.pnpm # Pin corepack to the pnpm version from packageManager. Without this, corepack # falls back to "latest" in directories that have no packageManager field # (e.g. e2e temp dirs), pulling pnpm 11 and breaking install. COREPACK_DEFAULT_TO_LATEST: '0' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: main-linux: timeout-minutes: 30 runs-on: latchkey-small env: NX_BATCH_MODE: 'true' NX_E2E_CI_CACHE_KEY: e2e-github-linux NX_DAEMON: 'true' NX_PERF_LOGGING: 'false' NX_VERBOSE_LOGGING: 'false' NX_NATIVE_LOGGING: 'false' NX_E2E_RUN_E2E: 'true' NX_CI_EXECUTION_ENV: 'linux' NX_CLOUD_NO_TIMEOUTS: 'true' NX_ALLOW_NON_CACHEABLE_DTE: 'true' NX_CLOUD_EXPERIMENTAL_POLLING: 'true' NX_CLOUD_CONTINUOUS_ASSIGNMENT: 'true' NX_CLOUD_VERBOSE_LOGGING: 'true' steps: - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 filter: tree:0 - name: Set verbose logging from debug mode if: runner.debug == '1' run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV" - name: Fetch Master run: git fetch origin master:master if: ${{ github.event_name == 'pull_request' }} - name: Set SHAs uses: nrwl/nx-set-shas@310288c04d90696f9f1bc27c5e3caea6642b53d4 # v5.0.0 with: main-branch-name: 'master' - name: Start CI Run run: npx nx-cloud@next start-ci-run --distribute-on="./.nx/workflows/dynamic-changesets.yaml" --stop-agents-after="e2e" - name: Setup dev tools with mise uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3 - name: Enable corepack and install pnpm run: | corepack enable corepack prepare --activate - name: Get pnpm store directory id: pnpm-cache run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm store uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Setup Gradle uses: gradle/actions/setup-gradle@48b5f213c81028ace310571dc5ec0fbbca0b2947 # v4.4.3 - name: Install project dependencies run: pnpm install --frozen-lockfile - name: Restore .NET analyzer projects run: dotnet restore nx.sln - name: Nx Report run: pnpm nx report - name: Run Checks/Lint/Test/Build run: | pids=() pnpm nx record -- nx format:check & pids+=($!) pnpm nx record -- nx sync:check pids+=($!) pnpm nx build workspace-plugin && pnpm nx record -- pnpm nx-cloud conformance:check pids+=($!) pnpm nx run-many -t check-imports check-lock-files check-codeowners --parallel=1 --no-dte & pids+=($!) pnpm nx affected --targets=lint,test,build,e2e,e2e-ci,format-native,lint-native,gradle:build-ci,vale,run & pids+=($!) for pid in "${pids[@]}"; do wait "$pid" done timeout-minutes: 100 - name: Fix CI run: pnpm nx fix-ci if: failure() main-macos: timeout-minutes: 30 runs-on: macos-latest concurrency: group: ${{ github.workflow }}-${{ github.ref }}${{ contains(github.event_name, 'push') && format('-{0}', github.sha) || '' }} cancel-in-progress: true env: NX_E2E_CI_CACHE_KEY: e2e-github-macos NX_PERF_LOGGING: 'false' NX_CI_EXECUTION_ENV: 'macos' SELECTED_PM: 'npm' steps: - name: Log concurrency info run: | echo "Concurrency group: ${{ github.workflow }}-${{ github.ref }}${{ contains(github.event_name, 'push') && format('-{0}', github.sha) || '' }}" echo "Concurrency cancel-in-progress: ${{ !contains(github.event_name, 'push') }}" echo "Concurrency cancel-event-name: ${{ github.event_name }}" if: always() - name: Checkout uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 filter: tree:0 - name: Set verbose logging from debug mode if: runner.debug == '1' run: echo "NX_VERBOSE_LOGGING=true" >> "$GITHUB_ENV" - name: Fetch Master run: git fetch origin master:master if: ${{ github.event_name == 'pull_request' }} - name: Setup dev tools with mise uses: jdx/mise-action@146a28175021df8ca24f8ee1828cc2a60f980bd5 # v3 - name: Enable corepack and install pnpm run: | corepack enable corepack prepare --activate - name: Set SHAs uses: nrwl/nx-set-shas@310288c04d90696f9f1bc27c5e3caea6642b53d4 # v5.0.0 with: main-branch-name: 'master' - name: Check for React Native changes id: check-changes run: | HAS_CHANGED=$(node ./scripts/check-react-native-changes.js $NX_BASE $NX_HEAD); if $HAS_CHANGED; then echo "has_changes=true" >> $GITHUB_OUTPUT echo "React Native projects are affected, will run macOS tests" else echo "has_changes=false" >> $GITHUB_OUTPUT echo "No React Native projects affected, skipping macOS tests" fi - name: Restore Homebrew packages if: steps.check-changes.outputs.has_changes == 'true' uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: | /opt/homebrew ~/Library/Caches/Homebrew key: nrwl-nx-homebrew-packages - name: Configure Detox Environment, Install applesimutils if: steps.check-changes.outputs.has_changes == 'true' run: | # Ensure Xcode command line tools are installed and configured xcode-select --print-path || sudo xcode-select --reset sudo xcode-select -s /Applications/Xcode.app # Install or update applesimutils with error handling if ! brew list applesimutils &>/dev/null; then echo "Installing applesimutils..." HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null # Homebrew now refuses to load formulae from third-party taps unless trusted brew trust wix/brew HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils >/dev/null || { echo "Failed to install applesimutils, retrying with update..." brew update brew trust wix/brew HOMEBREW_NO_AUTO_UPDATE=1 brew install applesimutils } else echo "Updating applesimutils..." HOMEBREW_NO_AUTO_UPDATE=1 brew upgrade applesimutils || true fi # Verify applesimutils installation applesimutils --version || (echo "applesimutils installation failed" && exit 1) # Configure environment for M-series Mac echo "DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer" >> $GITHUB_ENV echo "PLATFORM_NAME=iOS Simulator" >> $GITHUB_ENV # Set additional environment variables for better debugging echo "DETOX_DISABLE_TELEMETRY=1" >> $GITHUB_ENV echo "DETOX_LOG_LEVEL=trace" >> $GITHUB_ENV # Verify Xcode installation xcodebuild -version # List available simulators xcrun simctl list devices available timeout-minutes: 10 continue-on-error: false - name: Reset iOS Simulators if: steps.check-changes.outputs.has_changes == 'true' id: reset-simulators run: | echo "Resetting iOS Simulators..." # Kill simulator processes sudo killall -9 com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true killall "Simulator" 2>/dev/null || true killall "iOS Simulator" 2>/dev/null || true # Wait for processes to terminate sleep 3 # Shutdown and erase all simulators (ignore failures) xcrun simctl shutdown all 2>/dev/null || true sleep 5 xcrun simctl erase all 2>/dev/null || true # If erase failed, try the nuclear option if xcrun simctl list devices | grep -q "Booted" 2>/dev/null; then echo "Standard reset failed, using nuclear option..." rm -rf ~/Library/Developer/CoreSimulator/Devices/* 2>/dev/null || true launchctl remove com.apple.CoreSimulator.CoreSimulatorService 2>/dev/null || true sleep 3 fi # Clean up additional directories rm -rf ~/Library/Developer/CoreSimulator/Caches/* 2>/dev/null || true rm -rf ~/Library/Logs/CoreSimulator/* 2>/dev/null || true rm -rf ~/Library/Developer/Xcode/DerivedData/* 2>/dev/null || true echo "Simulator reset completed" timeout-minutes: 5 continue-on-error: true - name: Verify Simulator Reset if: steps.check-changes.outputs.has_changes == 'true' && steps.reset-simulators.outcome == 'success' run: | # Verify CoreSimulator service restarted pgrep -fl "CoreSimulator" || (echo "CoreSimulator service not running" && exit 1) # Check simulator list is clean xcrun simctl list devices # Verify simulator runtime paths exist and are writable test -d ~/Library/Developer/CoreSimulator/Devices || (echo "Simulator devices directory missing" && exit 1) touch ~/Library/Developer/CoreSimulator/Devices/test || (echo "Simulator devices directory not writable" && exit 1) rm ~/Library/Developer/CoreSimulator/Devices/test timeout-minutes: 5 - name: Diagnose Simulator Reset Failure if: steps.check-changes.outputs.has_changes == 'true' && steps.reset-simulators.outcome == 'failure' run: | echo "Simulator reset failed. Collecting diagnostic information..." xcrun simctl list echo "Checking simulator logs..." ls -la ~/Library/Logs/CoreSimulator/ || echo "No simulator logs found" - name: Save Homebrew Cache if: steps.check-changes.outputs.has_changes == 'true' uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: | /opt/homebrew ~/Library/Caches/Homebrew key: nrwl-nx-homebrew-packages - name: Get pnpm store directory if: steps.check-changes.outputs.has_changes == 'true' id: pnpm-cache-macos run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Cache pnpm store if: steps.check-changes.outputs.has_changes == 'true' uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: ${{ steps.pnpm-cache-macos.outputs.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- - name: Install project dependencies if: steps.check-changes.outputs.has_changes == 'true' run: | pnpm install --frozen-lockfile pnpm playwright install --with-deps - name: Restore .NET packages if: steps.check-changes.outputs.has_changes == 'true' run: dotnet restore nx.sln - name: Run E2E Tests for macOS if: steps.check-changes.outputs.has_changes == 'true' run: | pnpm nx affected -t e2e-macos-local --parallel=1 --base=$NX_BASE --head=$NX_HEAD
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cancel superseded runs when a branch or PR gets a newer push.
- Add a job timeout so a hung step cannot burn hours of runner time.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Dependency installs
- End-to-end and browser tests
This workflow runs 2 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.