playwright-ci-caching

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Caching Playwright Browsers in CI/CD

在CI/CD中缓存Playwright浏览器

When to Use This Skill

何时使用该技巧

Use this skill when:
  • Setting up CI/CD for a project with Playwright E2E tests
  • Build times are slow due to browser downloads (~400MB, 1-2 minutes)
  • You want automatic cache invalidation when Playwright version changes
  • Using GitHub Actions or Azure DevOps pipelines
在以下场景使用该技巧:
  • 为包含Playwright E2E测试的项目搭建CI/CD流水线
  • 浏览器下载导致构建速度缓慢(约400MB,耗时1-2分钟)
  • 希望在Playwright版本变更时自动失效缓存
  • 使用GitHub Actions或Azure DevOps流水线

The Problem

问题背景

Playwright browsers (~400MB) must be downloaded on every CI run by default. This:
  • Adds 1-2 minutes to every build
  • Wastes bandwidth
  • Can fail on transient network issues
  • Slows down PR feedback loops
默认情况下,每次CI运行都必须下载Playwright浏览器(约400MB),这会:
  • 为每次构建增加1-2分钟的耗时
  • 浪费带宽
  • 可能因临时网络问题导致构建失败
  • 拉慢PR反馈周期

Core Pattern

核心实现模式

  1. Extract Playwright version from
    Directory.Packages.props
    (CPM) to use as cache key
  2. Cache browser binaries using platform-appropriate paths
  3. Conditional install - only download on cache miss
  4. Automatic cache bust - key includes version, so package upgrades invalidate cache
  1. 提取Playwright版本:从
    Directory.Packages.props
    (CPM)中提取版本号作为缓存键
  2. 缓存浏览器二进制文件:使用对应平台的路径进行缓存
  3. 条件性安装:仅在缓存未命中时才下载浏览器
  4. 自动缓存失效:缓存键包含版本号,因此升级包版本会自动使缓存失效

Cache Paths by OS

各操作系统对应的缓存路径

OSPath
Linux
~/.cache/ms-playwright
macOS
~/Library/Caches/ms-playwright
Windows
%USERPROFILE%\AppData\Local\ms-playwright
操作系统路径
Linux
~/.cache/ms-playwright
macOS
~/Library/Caches/ms-playwright
Windows
%USERPROFILE%\AppData\Local\ms-playwright

GitHub Actions

GitHub Actions配置

yaml
- name: Get Playwright Version
  shell: pwsh
  run: |
    $propsPath = "Directory.Packages.props"
    [xml]$props = Get-Content $propsPath
    $version = $props.Project.ItemGroup.PackageVersion |
      Where-Object { $_.Include -eq "Microsoft.Playwright" } |
      Select-Object -ExpandProperty Version
    echo "PlaywrightVersion=$version" >> $env:GITHUB_ENV

- name: Cache Playwright Browsers
  id: playwright-cache
  uses: actions/cache@v4
  with:
    path: ~/.cache/ms-playwright
    key: ${{ runner.os }}-playwright-${{ env.PlaywrightVersion }}

- name: Install Playwright Browsers
  if: steps.playwright-cache.outputs.cache-hit != 'true'
  shell: pwsh
  run: ./build/playwright.ps1 install --with-deps
yaml
- name: Get Playwright Version
  shell: pwsh
  run: |
    $propsPath = "Directory.Packages.props"
    [xml]$props = Get-Content $propsPath
    $version = $props.Project.ItemGroup.PackageVersion |
      Where-Object { $_.Include -eq "Microsoft.Playwright" } |
      Select-Object -ExpandProperty Version
    echo "PlaywrightVersion=$version" >> $env:GITHUB_ENV

- name: Cache Playwright Browsers
  id: playwright-cache
  uses: actions/cache@v4
  with:
    path: ~/.cache/ms-playwright
    key: ${{ runner.os }}-playwright-${{ env.PlaywrightVersion }}

- name: Install Playwright Browsers
  if: steps.playwright-cache.outputs.cache-hit != 'true'
  shell: pwsh
  run: ./build/playwright.ps1 install --with-deps

Multi-OS GitHub Actions

多操作系统的GitHub Actions配置

For workflows that run on multiple operating systems:
yaml
- name: Cache Playwright Browsers
  id: playwright-cache
  uses: actions/cache@v4
  with:
    path: |
      ~/.cache/ms-playwright
      ~/Library/Caches/ms-playwright
      ~/AppData/Local/ms-playwright
    key: ${{ runner.os }}-playwright-${{ env.PlaywrightVersion }}
针对在多操作系统上运行的工作流:
yaml
- name: Cache Playwright Browsers
  id: playwright-cache
  uses: actions/cache@v4
  with:
    path: |
      ~/.cache/ms-playwright
      ~/Library/Caches/ms-playwright
      ~/AppData/Local/ms-playwright
    key: ${{ runner.os }}-playwright-${{ env.PlaywrightVersion }}

Azure DevOps

Azure DevOps配置

yaml
- task: PowerShell@2
  displayName: 'Get Playwright Version'
  inputs:
    targetType: 'inline'
    script: |
      [xml]$props = Get-Content "Directory.Packages.props"
      $version = $props.Project.ItemGroup.PackageVersion |
        Where-Object { $_.Include -eq "Microsoft.Playwright" } |
        Select-Object -ExpandProperty Version
      Write-Host "##vso[task.setvariable variable=PlaywrightVersion]$version"

- task: Cache@2
  displayName: 'Cache Playwright Browsers'
  inputs:
    key: 'playwright | "$(Agent.OS)" | $(PlaywrightVersion)'
    path: '$(HOME)/.cache/ms-playwright'
    cacheHitVar: 'PlaywrightCacheHit'

- task: PowerShell@2
  displayName: 'Install Playwright Browsers'
  condition: ne(variables['PlaywrightCacheHit'], 'true')
  inputs:
    filePath: 'build/playwright.ps1'
    arguments: 'install --with-deps'
yaml
- task: PowerShell@2
  displayName: 'Get Playwright Version'
  inputs:
    targetType: 'inline'
    script: |
      [xml]$props = Get-Content "Directory.Packages.props"
      $version = $props.Project.ItemGroup.PackageVersion |
        Where-Object { $_.Include -eq "Microsoft.Playwright" } |
        Select-Object -ExpandProperty Version
      Write-Host "##vso[task.setvariable variable=PlaywrightVersion]$version"

- task: Cache@2
  displayName: 'Cache Playwright Browsers'
  inputs:
    key: 'playwright | "$(Agent.OS)" | $(PlaywrightVersion)'
    path: '$(HOME)/.cache/ms-playwright'
    cacheHitVar: 'PlaywrightCacheHit'

- task: PowerShell@2
  displayName: 'Install Playwright Browsers'
  condition: ne(variables['PlaywrightCacheHit'], 'true')
  inputs:
    filePath: 'build/playwright.ps1'
    arguments: 'install --with-deps'

Helper Script: playwright.ps1

辅助脚本:playwright.ps1

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.
powershell
undefined
创建
build/playwright.ps1
脚本,用于查找并运行Playwright CLI。该脚本可抽象Playwright CLI的位置(不同项目结构中位置可能不同)。
powershell
undefined

build/playwright.ps1

build/playwright.ps1

Discovers Microsoft.Playwright.dll and runs the bundled Playwright CLI

查找Microsoft.Playwright.dll并运行捆绑的Playwright CLI

param( [Parameter(ValueFromRemainingArguments = $true)] [string[]]$Arguments )
param( [Parameter(ValueFromRemainingArguments = $true)] [string[]]$Arguments )

Find the Playwright DLL (after dotnet build/restore)

查找Playwright DLL(需先执行dotnet build/restore)

$playwrightDll = Get-ChildItem -Path . -Recurse -Filter "Microsoft.Playwright.dll" -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $playwrightDll) { Write-Error "Microsoft.Playwright.dll not found. Run 'dotnet build' first." exit 1 }
$playwrightDir = $playwrightDll.DirectoryName
$playwrightDll = Get-ChildItem -Path . -Recurse -Filter "Microsoft.Playwright.dll" -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $playwrightDll) { Write-Error "未找到Microsoft.Playwright.dll,请先执行'dotnet build'。" exit 1 }
$playwrightDir = $playwrightDll.DirectoryName

Find the playwright CLI (path varies by OS and node version)

查找playwright CLI(路径因操作系统和node版本而异)

$playwrightCmd = Get-ChildItem -Path "$playwrightDir/.playwright/node" -Recurse -Filter "playwright.cmd" -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $playwrightCmd) { # Try Unix executable $playwrightCmd = Get-ChildItem -Path "$playwrightDir/.playwright/node" -Recurse -Filter "playwright" -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq "playwright" } | Select-Object -First 1 }
if (-not $playwrightCmd) { Write-Error "Playwright CLI not found in $playwrightDir/.playwright/node" exit 1 }
Write-Host "Using Playwright CLI: $($playwrightCmd.FullName)" & $playwrightCmd.FullName @Arguments

Usage:
```bash
$playwrightCmd = Get-ChildItem -Path "$playwrightDir/.playwright/node" -Recurse -Filter "playwright.cmd" -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $playwrightCmd) { # 尝试查找Unix可执行文件 $playwrightCmd = Get-ChildItem -Path "$playwrightDir/.playwright/node" -Recurse -Filter "playwright" -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq "playwright" } | Select-Object -First 1 }
if (-not $playwrightCmd) { Write-Error "在$playwrightDir/.playwright/node中未找到Playwright CLI" exit 1 }
Write-Host "使用Playwright CLI:$($playwrightCmd.FullName)" & $playwrightCmd.FullName @Arguments

使用方法:
```bash

Install browsers

安装浏览器

./build/playwright.ps1 install --with-deps
./build/playwright.ps1 install --with-deps

Install specific browser

安装特定浏览器

./build/playwright.ps1 install chromium
./build/playwright.ps1 install chromium

Show installed browsers

查看已安装的浏览器

./build/playwright.ps1 install --dry-run
undefined
./build/playwright.ps1 install --dry-run
undefined

Prerequisites

前提条件

This pattern assumes:
  1. Central Package Management (CPM) with
    Directory.Packages.props
    :
    xml
    <Project>
      <ItemGroup>
        <PackageVersion Include="Microsoft.Playwright" Version="1.40.0" />
      </ItemGroup>
    </Project>
  2. Project has been built before running
    playwright.ps1
    (so DLLs exist)
  3. PowerShell available on CI agents (pre-installed on GitHub Actions and Azure DevOps)
该实现模式基于以下假设:
  1. **使用中央包管理(CPM)**并包含
    Directory.Packages.props
    xml
    <Project>
      <ItemGroup>
        <PackageVersion Include="Microsoft.Playwright" Version="1.40.0" />
      </ItemGroup>
    </Project>
  2. 运行
    playwright.ps1
    前已构建项目
    (确保DLL文件存在)
  3. CI代理上已安装PowerShell(GitHub Actions和Azure DevOps默认已安装)

Why Version-Based Cache Keys Matter

基于版本的缓存键为何重要

Using the Playwright version in the cache key ensures:
  • Automatic invalidation when you upgrade Playwright
  • No stale browser binaries that don't match the SDK version
  • No manual cache clearing needed after version bumps
If you hardcode the cache key (e.g.,
playwright-browsers-v1
), you'll need to manually bump it every time you upgrade Playwright, or you'll get cryptic version mismatch errors.
在缓存键中使用Playwright版本可确保:
  • 自动失效:升级Playwright版本时自动失效缓存
  • 无过期浏览器二进制文件:避免缓存的浏览器与SDK版本不匹配
  • 版本升级后无需手动清理缓存
如果硬编码缓存键(例如
playwright-browsers-v1
),则每次升级Playwright时都需要手动更新缓存键,否则会出现版本不匹配的模糊错误。

Troubleshooting

故障排除

Cache not being used

缓存未被使用

  1. Verify the version extraction step outputs the correct version
  2. Check that the cache path matches your OS
  3. Ensure
    Directory.Packages.props
    exists and has the Playwright package
  1. 验证版本提取步骤是否输出了正确的版本号
  2. 检查缓存路径是否与当前操作系统匹配
  3. 确保
    Directory.Packages.props
    存在且包含Playwright包

"Browser not found" after cache hit

缓存命中后提示“浏览器未找到”

The cached browsers don't match the Playwright SDK version. This happens when:
  • The cache key doesn't include the version
  • The version extraction failed silently
Fix: Ensure the Playwright version is in the cache key.
缓存的浏览器与Playwright SDK版本不匹配,通常由以下原因导致:
  • 缓存键未包含版本号
  • 版本提取步骤静默失败
解决方法:确保缓存键中包含Playwright版本号。

playwright.ps1 can't find the DLL

playwright.ps1无法找到DLL文件

Run
dotnet build
or
dotnet restore
before running the script. The Playwright DLL only exists after NuGet restore.
运行
playwright.ps1
前先执行
dotnet build
dotnet restore
。Playwright DLL仅在NuGet还原后才会存在。

References

参考资料

This pattern is battle-tested in production projects:
该模式已在生产项目中经过验证:

Related Skills

相关技巧

  • dotnet-skills:playwright-blazor
    - Writing Playwright tests for Blazor applications
  • dotnet-skills:project-structure
    - Central Package Management setup
  • dotnet-skills:playwright-blazor
    - 为Blazor应用编写Playwright测试
  • dotnet-skills:project-structure
    - 中央包管理配置