$npx -y skills add Aaronontheweb/dotnet-skills --skill playwright-ci-cachingCache Playwright browser binaries in CI/CD pipelines (GitHub Actions, Azure DevOps) to avoid 1-2 minute download overhead on every build.
| 1 | # Caching Playwright Browsers in CI/CD |
| 2 | |
| 3 | ## When to Use This Skill |
| 4 | |
| 5 | Use this skill when: |
| 6 | - Setting up CI/CD for a project with Playwright E2E tests |
| 7 | - Build times are slow due to browser downloads (~400MB, 1-2 minutes) |
| 8 | - You want automatic cache invalidation when Playwright version changes |
| 9 | - Using GitHub Actions or Azure DevOps pipelines |
| 10 | |
| 11 | ## The Problem |
| 12 | |
| 13 | Playwright browsers (~400MB) must be downloaded on every CI run by default. This: |
| 14 | - Adds 1-2 minutes to every build |
| 15 | - Wastes bandwidth |
| 16 | - Can fail on transient network issues |
| 17 | - Slows down PR feedback loops |
| 18 | |
| 19 | ## Core Pattern |
| 20 | |
| 21 | 1. **Extract Playwright version** from `Directory.Packages.props` (CPM) to use as cache key |
| 22 | 2. **Cache browser binaries** using platform-appropriate paths |
| 23 | 3. **Conditional install** - only download on cache miss |
| 24 | 4. **Automatic cache bust** - key includes version, so package upgrades invalidate cache |
| 25 | |
| 26 | ## Cache Paths by OS |
| 27 | |
| 28 | | OS | Path | |
| 29 | |----|------| |
| 30 | | Linux | `~/.cache/ms-playwright` | |
| 31 | | macOS | `~/Library/Caches/ms-playwright` | |
| 32 | | Windows | `%USERPROFILE%\AppData\Local\ms-playwright` | |
| 33 | |
| 34 | ## GitHub Actions |
| 35 | |
| 36 | ```yaml |
| 37 | - name: Get Playwright Version |
| 38 | shell: pwsh |
| 39 | run: | |
| 40 | $propsPath = "Directory.Packages.props" |
| 41 | [xml]$props = Get-Content $propsPath |
| 42 | $version = $props.Project.ItemGroup.PackageVersion | |
| 43 | Where-Object { $_.Include -eq "Microsoft.Playwright" } | |
| 44 | Select-Object -ExpandProperty Version |
| 45 | echo "PlaywrightVersion=$version" >> $env:GITHUB_ENV |
| 46 | |
| 47 | - name: Cache Playwright Browsers |
| 48 | id: playwright-cache |
| 49 | uses: actions/cache@v4 |
| 50 | with: |
| 51 | path: ~/.cache/ms-playwright |
| 52 | key: ${{ runner.os }}-playwright-${{ env.PlaywrightVersion }} |
| 53 | |
| 54 | - name: Install Playwright Browsers |
| 55 | if: steps.playwright-cache.outputs.cache-hit != 'true' |
| 56 | shell: pwsh |
| 57 | run: ./build/playwright.ps1 install --with-deps |
| 58 | ``` |
| 59 | |
| 60 | ### Multi-OS GitHub Actions |
| 61 | |
| 62 | For workflows that run on multiple operating systems: |
| 63 | |
| 64 | ```yaml |
| 65 | - name: Cache Playwright Browsers |
| 66 | id: playwright-cache |
| 67 | uses: actions/cache@v4 |
| 68 | with: |
| 69 | path: | |
| 70 | ~/.cache/ms-playwright |
| 71 | ~/Library/Caches/ms-playwright |
| 72 | ~/AppData/Local/ms-playwright |
| 73 | key: ${{ runner.os }}-playwright-${{ env.PlaywrightVersion }} |
| 74 | ``` |
| 75 | |
| 76 | ## Azure DevOps |
| 77 | |
| 78 | ```yaml |
| 79 | - task: PowerShell@2 |
| 80 | displayName: 'Get Playwright Version' |
| 81 | inputs: |
| 82 | targetType: 'inline' |
| 83 | script: | |
| 84 | [xml]$props = Get-Content "Directory.Packages.props" |
| 85 | $version = $props.Project.ItemGroup.PackageVersion | |
| 86 | Where-Object { $_.Include -eq "Microsoft.Playwright" } | |
| 87 | Select-Object -ExpandProperty Version |
| 88 | Write-Host "##vso[task.setvariable variable=PlaywrightVersion]$version" |
| 89 | |
| 90 | - task: Cache@2 |
| 91 | displayName: 'Cache Playwright Browsers' |
| 92 | inputs: |
| 93 | key: 'playwright | "$(Agent.OS)" | $(PlaywrightVersion)' |
| 94 | path: '$(HOME)/.cache/ms-playwright' |
| 95 | cacheHitVar: 'PlaywrightCacheHit' |
| 96 | |
| 97 | - task: PowerShell@2 |
| 98 | displayName: 'Install Playwright Browsers' |
| 99 | condition: ne(variables['PlaywrightCacheHit'], 'true') |
| 100 | inputs: |
| 101 | filePath: 'build/playwright.ps1' |
| 102 | arguments: 'install --with-deps' |
| 103 | ``` |
| 104 | |
| 105 | ## Helper Script: playwright.ps1 |
| 106 | |
| 107 | Create a `build/playwright.ps1` script that discovers and runs the Playwright CLI. This abstracts away the Playwright CLI location which varies by project structure. |
| 108 | |
| 109 | ```powershell |
| 110 | # build/playwright.ps1 |
| 111 | # Discovers Microsoft.Playwright.dll and runs the bundled Playwright CLI |
| 112 | |
| 113 | param( |
| 114 | [Parameter(ValueFromRemainingArguments = $true)] |
| 115 | [string[]]$Arguments |
| 116 | ) |
| 117 | |
| 118 | # Find the Playwright DLL (after dotnet build/restore) |
| 119 | $playwrightDll = Get-ChildItem -Path . -Recurse -Filter "Microsoft.Playwright.dll" -ErrorAction SilentlyContinue | |
| 120 | Select-Object -First 1 |
| 121 | |
| 122 | if (-not $playwrightDll) { |
| 123 | Write-Error "Microsoft.Playwright.dll not found. Run 'dotnet build' first." |
| 124 | exit 1 |
| 125 | } |
| 126 | |
| 127 | $playwrightDir = $playwrightDll.DirectoryName |
| 128 | |
| 129 | # Find the playwright CLI (path varies by OS and node version) |
| 130 | $playwrightCmd = Get-ChildItem -Path "$playwrightDir/.playwright/node" -Recurse -Filter "playwright.cmd" -ErrorAction SilentlyContinue | |
| 131 | Select-Object -First 1 |
| 132 | |
| 133 | if (-not $playwrightCmd) { |
| 134 | # Try Unix executable |
| 135 | $playwrightCmd = Get-ChildItem -Path "$playwrightDir/.playwright/node" -Recurse -Filter "playwright" -ErrorAction SilentlyContinue | |
| 136 | Where-Object { $_.Name -eq "playwright" } | |
| 137 | Select-Object -First 1 |
| 138 | } |
| 139 | |
| 140 | if (-not $playwrightCmd) { |
| 141 | Write-Error "Playwright CLI not found in $playwrightDir/.playwright/node" |
| 142 | exit 1 |
| 143 | } |
| 144 | |
| 145 | Write-Host "Using Playwright CLI: $($playwrightCmd.FullName)" |
| 146 | & $playwrightCmd.FullName @Arguments |
| 147 | ``` |
| 148 | |
| 149 | Usage: |
| 150 | ```bash |
| 151 | # Install browsers |
| 152 | ./build/playwright.ps1 install --with-deps |
| 153 | |
| 154 | # Install specific browser |
| 155 | ./build/playwright.ps1 install chromium |
| 156 | |
| 157 | # Show installed browsers |
| 158 | ./build/playwright.ps1 install --dry-run |
| 159 | ``` |
| 160 | |
| 161 | ## Prerequisites |
| 162 | |
| 163 | This pattern assumes: |