Skip to content

docs: correct capture examples and RAW conversion semantics #413

docs: correct capture examples and RAW conversion semantics

docs: correct capture examples and RAW conversion semantics #413

Workflow file for this run

name: CI
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
# Harden npm installs against transient registry network blips (e.g.
# ECONNRESET) seen intermittently on hosted runners.
NPM_CONFIG_FETCH_RETRIES: 5
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: 20000
NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: 120000
jobs:
lint-and-type-check:
name: Code Quality (Strict)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm install --legacy-peer-deps # Need --legacy-peer-deps for react-native-windows@0.76.17
- name: Check formatting (no prettier diff allowed)
run: npm run format:check
- name: Run TypeScript type check (strict)
run: npm run type-check
- name: Run linter (strict - no warnings allowed)
run: npm run lint:ci
- name: Build project
run: npm run build
- name: Run unit tests
run: npm test
build-android:
name: Build Android APK
runs-on: ubuntu-latest
needs: lint-and-type-check
steps:
- name: Checkout code
uses: actions/checkout@v4
# The Android assembleDebug build (RN new arch) exhausts the runner's
# ~14 GB of free disk. Reclaim space by removing large preinstalled
# toolchains we don't use, while keeping the Android SDK/NDK. Runs
# before Node/Java setup so their tool caches are untouched.
- name: Free up disk space
run: |
echo "Disk before cleanup:"; df -h /
sudo rm -rf /usr/share/dotnet \
/opt/ghc /usr/local/.ghcup \
/opt/hostedtoolcache/CodeQL \
/usr/local/share/boost \
/usr/local/share/powershell \
/usr/local/lib/node_modules || true
sudo docker image prune --all --force || true
sudo swapoff -a || true
sudo rm -f /mnt/swapfile || true
sudo apt-get clean || true
echo "Disk after cleanup:"; df -h /
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Setup Java JDK
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Install dependencies
run: npm install --legacy-peer-deps # Need --legacy-peer-deps for react-native-windows@0.76.17
- name: Build library
run: npm run build
- name: Install example dependencies
working-directory: example
run: npm ci --legacy-peer-deps
- name: Run library Java unit tests
working-directory: example/android
run: |
echo "🧪 Running library JUnit tests..."
./gradlew :react-native-view-shot:testDebugUnitTest --no-daemon --stacktrace
- name: Build Android APK
working-directory: example/android
run: |
echo "🔨 Building Android APK..."
./gradlew assembleDebug --no-daemon --stacktrace -PnewArchEnabled=true
- name: Upload Android APK
uses: actions/upload-artifact@v4
with:
name: android-apk
path: example/android/app/build/outputs/apk/debug/app-debug.apk
lint-podspec:
name: Lint CocoaPods Podspec
runs-on: macos-15
needs: lint-and-type-check
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup CocoaPods
uses: maxim-lobanov/setup-cocoapods@v1
with:
version: latest
- name: Lint podspec
run: pod lib lint --allow-warnings --quick
build-ios:
name: Build iOS App
runs-on: macos-15 # Use latest macOS with Xcode 16.1+
needs: lint-and-type-check
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Cache CocoaPods
uses: actions/cache@v4
with:
path: example/ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Install dependencies
run: npm install --legacy-peer-deps # Need --legacy-peer-deps for react-native-windows@0.76.17
- name: Build library
run: npm run build
- name: Install example dependencies
working-directory: example
run: npm ci --legacy-peer-deps
- name: Setup CocoaPods
uses: maxim-lobanov/setup-cocoapods@v1
with:
version: latest
- name: Pod install
working-directory: example/ios
run: pod install --verbose
- name: Build iOS
working-directory: example
run: |
xcodebuild -workspace ios/ViewShotExample.xcworkspace \
-scheme ViewShotExample \
-configuration Debug \
-destination 'generic/platform=iOS Simulator' \
build CODE_SIGNING_ALLOWED=NO
- name: Run iOS unit tests
working-directory: example
run: |
# Pick the first available iPhone simulator and pass its UDID
# (not its name) to xcodebuild. We use UDID because simulator
# display names can contain parentheses (e.g. "iPhone SE
# (3rd generation)"), and parsing them with a name-based regex
# is brittle: stripping the trailing "(...)" loses version
# disambiguation, and quoting parentheses on the
# `-destination name=...` form is fragile across shells.
# UDIDs are opaque and stable, so `id=$UDID` always resolves
# exactly one device regardless of name shape. We also avoid
# hard-coding e.g. "iPhone 16" which may disappear when Xcode
# updates on macos-15.
UDID=$(xcrun simctl list devices available -j | \
jq -r '.devices | to_entries[]
| select(.key | startswith("com.apple.CoreSimulator.SimRuntime.iOS"))
| .value[]
| select(.name | startswith("iPhone "))
| .udid' | head -1)
if [ -z "$UDID" ]; then
echo "::error::No iPhone simulator available on the runner"
xcrun simctl list devices available
exit 1
fi
echo "Using simulator UDID: $UDID"
xcrun simctl list devices available | grep "$UDID" || true
xcodebuild test \
-workspace ios/ViewShotExample.xcworkspace \
-scheme ViewShotExampleTests \
-destination "platform=iOS Simulator,id=$UDID" \
CODE_SIGNING_ALLOWED=NO
- name: Upload iOS Build Logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: ios-build-logs
path: ~/Library/Logs/DiagnosticReports/
build-ios-new-arch:
name: Build iOS App (New Architecture)
runs-on: macos-15
needs: lint-and-type-check
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Cache CocoaPods (new arch)
uses: actions/cache@v4
with:
path: example/ios/Pods
key: ${{ runner.os }}-pods-newarch-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-newarch-
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Build library
run: npm run build
- name: Install example dependencies
working-directory: example
run: npm ci --legacy-peer-deps
- name: Setup CocoaPods
uses: maxim-lobanov/setup-cocoapods@v1
with:
version: latest
- name: Pod install (new arch)
working-directory: example/ios
env:
RCT_NEW_ARCH_ENABLED: '1'
run: pod install --verbose
- name: Build iOS (new arch)
working-directory: example
run: |
xcodebuild -workspace ios/ViewShotExample.xcworkspace \
-scheme ViewShotExample \
-configuration Debug \
-destination 'generic/platform=iOS Simulator' \
build CODE_SIGNING_ALLOWED=NO
- name: Upload iOS Build Logs (new arch)
if: failure()
uses: actions/upload-artifact@v4
with:
name: ios-build-logs-new-arch
path: ~/Library/Logs/DiagnosticReports/
test-windows-unit:
name: Run Windows unit tests
runs-on: ubuntu-latest
needs: lint-and-type-check
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('windows/RNViewShot.Tests/RNViewShot.Tests.csproj') }}
restore-keys: ${{ runner.os }}-nuget-
- name: Run dotnet tests
run: |
dotnet test windows/RNViewShot.Tests/RNViewShot.Tests.csproj \
--nologo --verbosity normal \
--logger "trx;LogFileName=test-results.trx" \
--results-directory TestResults
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: windows-unit-test-results
path: TestResults/
build-windows:
name: Build Windows
# Pinned to windows-2022 (VS 2022). GitHub migrated windows-latest to
# Visual Studio 2026 (18.x) in June 2026, which react-native-windows 0.76
# does not support — its CLI only accepts VS in range [17.11.0, 18.0).
runs-on: windows-2022
needs: lint-and-type-check
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: |
package-lock.json
example-windows/package-lock.json
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Ensure Windows SDK 10.0.22621
shell: pwsh
run: |
$sdkPath = "C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0"
if (Test-Path $sdkPath) {
Write-Host "Windows SDK 10.0.22621 already installed"
exit 0
}
Write-Host "Installed SDKs:"
Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\Include" -ErrorAction SilentlyContinue | Select-Object Name | ForEach-Object { Write-Host " $($_.Name)" }
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$info = & $vswhere -latest -prerelease -products * -format json | ConvertFrom-Json
if (-not $info) { throw "vswhere returned no VS install" }
Write-Host "VS productId: $($info.productId)"
$vsInstaller = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vs_installer.exe"
$args = @(
'modify',
'--channelId', $info.channelId,
'--productId', $info.productId,
'--add', 'Microsoft.VisualStudio.Component.Windows11SDK.22621',
'--quiet', '--norestart'
)
Write-Host "Running: $vsInstaller $($args -join ' ')"
$proc = Start-Process -FilePath $vsInstaller -ArgumentList $args -Wait -PassThru
Write-Host "vs_installer exit code: $($proc.ExitCode)"
if (-not (Test-Path $sdkPath)) {
throw "Windows SDK 10.0.22621 still missing after install attempt"
}
Write-Host "Windows SDK 10.0.22621 installed"
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-build-${{ hashFiles('**/packages.lock.json') }}
restore-keys: ${{ runner.os }}-nuget-build-
- name: Install root dependencies
run: npm install --legacy-peer-deps
- name: Build library
run: npm run build
- name: Install example-windows dependencies
working-directory: example-windows
run: npm ci --legacy-peer-deps
- name: Run Windows autolinking
working-directory: example-windows
run: npx --no-install react-native autolink-windows --no-telemetry
- name: Build example-windows
working-directory: example-windows
shell: pwsh
run: |
npx --no-install react-native run-windows `
--arch x64 `
--no-launch `
--no-deploy `
--no-packager `
--no-autolink `
--logging `
--buildLogDirectory buildlogs `
--no-telemetry
- name: Verify build artifacts
working-directory: example-windows
shell: pwsh
run: |
$exe = "windows/ViewShotWindowsExample/bin/x64/Debug/ViewShotWindowsExample.exe"
$rnviewshot = "windows/ViewShotWindowsExample/bin/x64/Debug/RNViewShot.winmd"
if (-not (Test-Path $exe)) {
Write-Error "Missing $exe"; exit 1
}
if (-not (Test-Path $rnviewshot)) {
Write-Error "Missing $rnviewshot — RNViewShot module did not link"; exit 1
}
Write-Host "OK: $exe and RNViewShot.winmd present"
- name: Upload Windows app package
uses: actions/upload-artifact@v4
with:
name: windows-app
path: example-windows/windows/ViewShotWindowsExample/AppPackages/**
if-no-files-found: error
- name: Upload Windows build logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: windows-build-logs
path: |
example-windows/buildlogs/**
example-windows/windows/**/*.log
example-windows/windows/**/*.err
# Temporarily disabled - Android Detox setup needs native library conflict resolution
# The Android build works fine, but Detox test APK has libfbjni.so conflicts
# Using functional E2E tests instead (test-all-features.js)
# test-e2e-android:
# name: E2E Tests (Android)
# runs-on: ubuntu-latest
# needs: build-android
#
# steps:
# - name: Checkout code
# uses: actions/checkout@v4
#
# - name: Setup Node.js
# uses: actions/setup-node@v4
# with:
# node-version: '20'
# cache: 'npm'
#
# - name: Setup Java JDK
# uses: actions/setup-java@v4
# with:
# distribution: 'temurin'
# java-version: '17'
#
# - name: Enable KVM group perms
# run: |
# echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
# sudo udevadm control --reload-rules
# sudo udevadm trigger --name-match=kvm
#
# - name: Cache Gradle
# uses: actions/cache@v4
# with:
# path: |
# ~/.gradle/caches
# ~/.gradle/wrapper
# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
# restore-keys: |
# ${{ runner.os }}-gradle-
#
# - name: Cache AVD
# uses: actions/cache@v4
# id: avd-cache
# with:
# path: |
# ~/.android/avd/*
# ~/.android/adb*
# key: avd-34
#
# - name: Create AVD and generate snapshot for caching
# if: steps.avd-cache.outputs.cache-hit != 'true'
# uses: reactivecircus/android-emulator-runner@v2
# with:
# api-level: 34
# target: google_apis
# arch: x86_64
# profile: Nexus 6
# force-avd-creation: false
# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
# disable-animations: false
# script: echo "Generated AVD snapshot for caching."
#
# - name: Install dependencies
# run: npm install --legacy-peer-deps
#
# - name: Build library
# run: npm run build
#
# - name: Install example dependencies
# working-directory: example
# run: npm ci --legacy-peer-deps
#
# - name: Build Android APK for E2E
# working-directory: example/android
# run: ./gradlew assembleDebug
#
# - name: Run Functional E2E Tests (Android)
# uses: reactivecircus/android-emulator-runner@v2
# with:
# api-level: 34
# target: google_apis
# arch: x86_64
# profile: Nexus 6
# force-avd-creation: false
# emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
# disable-animations: true
# script: |
# echo "🚀 Starting Functional E2E Tests for Android"
# cd example
#
# # Start Metro bundler in background
# npm start &
# METRO_PID=$!
#
# # Wait for Metro to start
# sleep 30
#
# # Run our comprehensive feature tests
# echo "📱 Running comprehensive ViewShot feature tests..."
# node test-all-features.js
#
# # Kill Metro
# kill $METRO_PID || true
#
# - name: Upload E2E Test Results
# if: always()
# uses: actions/upload-artifact@v4
# with:
# name: e2e-test-results-android
# path: |
# example/test-*.png
# example/test-report.json
test-detox-ios:
name: Detox E2E Tests (iOS)
runs-on: macos-15
needs: build-ios
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Cache CocoaPods
uses: actions/cache@v4
with:
path: example/ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Install applesimutils for iOS Detox
run: |
# Try to install via brew tap first. Homebrew's tap-trust gate
# refuses to load formulae from community taps by default, so
# the tap must be explicitly trusted or `brew install` fails.
if brew tap wix/brew && brew trust wix/brew && brew install applesimutils 2>/dev/null; then
echo "✅ Installed applesimutils via brew tap"
else
echo "⚠️ Brew install failed, trying manual compilation..."
# Download source and compile
curl -L https://github.com/wix/AppleSimulatorUtils/archive/0.9.6.tar.gz | tar -xz
cd AppleSimulatorUtils-0.9.6
xcodebuild -project applesimutils.xcodeproj -scheme applesimutils -configuration Release -derivedDataPath ./build clean build
sudo cp ./build/Build/Products/Release/applesimutils /usr/local/bin/
echo "✅ Installed applesimutils via compilation"
fi
- name: Build library
run: npm run build
- name: Install example dependencies
working-directory: example
run: npm ci --legacy-peer-deps
- name: Setup CocoaPods
uses: maxim-lobanov/setup-cocoapods@v1
with:
version: latest
- name: Pod install
working-directory: example/ios
run: pod install --verbose
- name: Build iOS for Detox
working-directory: example
run: npm run build:e2e:ios
- name: Start Metro Bundler
working-directory: example
run: |
npm start &
echo $! > metro.pid
sleep 30
- name: Run Detox Tests
working-directory: example
run: npm run test:e2e:ios
continue-on-error: true
id: detox_ios_tests
- name: Stop Metro Bundler
if: always()
working-directory: example
run: |
if [ -f metro.pid ]; then
kill $(cat metro.pid) || true
rm metro.pid
fi
- name: Upload Detox Screenshots
if: always()
uses: actions/upload-artifact@v4
with:
name: detox-screenshots-ios
path: example/artifacts/**/*.png
- name: Upload Detox Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: detox-test-results-ios
path: |
example/e2e/test-results/**/*
example/artifacts/**/*.xml
- name: Report iOS test results
if: always()
# Non-blocking: Jest SummaryReporter crashes (isSet TypeError) and the
# snapshot-content-container / style-filters suites are known-failing.
run: |
if [ "${{ steps.detox_ios_tests.outcome }}" == "failure" ]; then
echo "⚠️ Detox exited with error (may be Jest reporter crash, check artifacts for actual results)"
else
echo "✅ Detox iOS tests completed"
fi
# Android Detox is disabled on CI: native library conflicts not yet
# reproduced outside the runner. Track via the PR description.
# test-detox-android:
# name: Detox E2E Tests (Android)
# runs-on: ubuntu-latest
# needs: build-android
# timeout-minutes: 60
build-web-example:
name: Build Web Example
runs-on: ubuntu-latest
needs: lint-and-type-check
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install root dependencies
run: npm install --legacy-peer-deps
- name: Build library
run: npm run build
- name: Install web example dependencies
working-directory: example-web
run: npm ci
- name: Build web example (production)
working-directory: example-web
run: npm run build
- name: Verify build output
working-directory: example-web
run: |
echo "✅ Web build completed successfully"
ls -lh dist/
du -sh dist/
echo ""
echo "Build contains:"
ls -1 dist/
- name: Upload web build artifacts
uses: actions/upload-artifact@v4
with:
name: web-build
path: example-web/dist/
retention-days: 7
- name: Build summary
working-directory: example-web
run: |
echo "### Web Build Summary 🌐" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ Production build successful" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Bundle size:** $(du -sh dist/ | cut -f1)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Files:**" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
ls -lh dist/ >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
test-web-example:
name: Test Web Example (Playwright)
runs-on: ubuntu-latest
needs: build-web-example
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install root dependencies
run: npm install --legacy-peer-deps
- name: Build library
run: npm run build
- name: Install web example dependencies
working-directory: example-web
run: npm ci
- name: Install Playwright Browsers
working-directory: example-web
run: npx playwright install chromium --with-deps
- name: Start web development server
working-directory: example-web
run: |
npx webpack serve --mode development --port 3000 &
echo $! > web-server.pid
sleep 30
# Wait for server to be ready
for i in {1..30}; do
if curl -s http://localhost:3000 > /dev/null; then
echo "✅ Web server is ready"
break
fi
echo "⏳ Waiting for web server... ($i/30)"
sleep 2
done
- name: Run Playwright tests
working-directory: example-web
run: npm run test:e2e
id: playwright_tests
- name: Stop web development server
if: always()
working-directory: example-web
run: |
if [ -f web-server.pid ]; then
kill $(cat web-server.pid) || true
rm web-server.pid
fi
- name: Check for snapshot differences
if: failure() && steps.playwright_tests.outcome == 'failure'
working-directory: example-web
run: |
if [ -d "test-results" ]; then
echo "## 📸 Visual Regression Detected" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Some visual snapshots have changed. This could be due to:" >> $GITHUB_STEP_SUMMARY
echo "- UI changes in the web example" >> $GITHUB_STEP_SUMMARY
echo "- Different rendering between platforms (Linux CI vs local macOS)" >> $GITHUB_STEP_SUMMARY
echo "- Font or styling differences" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**To update snapshots:**" >> $GITHUB_STEP_SUMMARY
echo "1. Download the \`web-snapshots-actual\` artifact (uploaded below)" >> $GITHUB_STEP_SUMMARY
echo "2. Take the \`*-actual.png\` files and copy them over the matching baselines in \`example-web/e2e/snapshots/reference/viewshot.spec.ts-snapshots/\` (drop the \`-actual\` suffix)" >> $GITHUB_STEP_SUMMARY
echo "3. Commit the updated snapshots" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Diff files available in artifacts:**" >> $GITHUB_STEP_SUMMARY
find test-results -name "*-diff.png" | head -5 | while read file; do
echo "- \`$(basename "$file")\`" >> $GITHUB_STEP_SUMMARY
done
fi
- name: Upload generated snapshots (actual from test-results)
if: always()
uses: actions/upload-artifact@v4
with:
name: web-snapshots-actual
path: |
example-web/test-results/**/*-actual.png
example-web/test-results/**/*-expected.png
example-web/test-results/**/*-diff.png
retention-days: 30
if-no-files-found: ignore
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: example-web/playwright-report/
retention-days: 7
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-test-results
path: example-web/test-results/
retention-days: 7
- name: Fail if tests failed
if: steps.playwright_tests.outcome == 'failure'
run: exit 1