Skip to content

Patchright Release Build and Publish #1914

Patchright Release Build and Publish

Patchright Release Build and Publish #1914

name: Patchright Release Build and Publish
on:
# enabling manual trigger
workflow_dispatch:
inputs:
version:
description: 'Playwright Version (e.g. v1.58.0 or 1.58.0)'
default: ''
patchright_release:
description: 'Patchright Release Version (e.g. v1.58.0 or 1.58.0) on npm'
default: ''
# running every hour
schedule:
- cron: '0 * * * *'
env:
REPO: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
patchright_release: ${{ github.event.inputs.patchright_release || '' }}
jobs:
check-release-version:
name: Check Release Version
runs-on: ubuntu-latest
outputs:
proceed: ${{ steps.version_check.outputs.proceed == 'true' && (github.event_name != 'schedule' || steps.existing_failure_issue.outputs.skip_due_to_existing_issue != 'true') }}
playwright_version: ${{ steps.version_check.outputs.playwright_version }}
previous_playwright_version: ${{ steps.version_check.outputs.previous_playwright_version }}
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Check Release Version
id: version_check
run: |
if [ -n "${{ github.event.inputs.version }}" ]; then
raw_version="${{ github.event.inputs.version }}"
normalized_version="v${raw_version#v}"
response=$(curl --silent "https://api.github.com/repos/${REPO}/releases/latest")
previous_playwright_version=$(echo "$response" | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
echo "proceed=true" >>$GITHUB_OUTPUT
echo "playwright_version=${normalized_version}" >>$GITHUB_OUTPUT
echo "previous_playwright_version=${previous_playwright_version:-v0.0.0}" >>$GITHUB_OUTPUT
echo "playwright_version=${normalized_version}" >> $GITHUB_ENV
elif [ -n "${{ github.event.inputs.patchright_release }}" ]; then
raw_patchright_release="${{ github.event.inputs.patchright_release }}"
normalized_patchright_release="${raw_patchright_release#v}"
response=$(curl --silent "https://api.github.com/repos/microsoft/playwright/releases/latest")
playwright_version=$(echo "$response" | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
response=$(curl --silent "https://api.github.com/repos/${REPO}/releases/latest")
previous_playwright_version=$(echo "$response" | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
echo "proceed=true" >>$GITHUB_OUTPUT
echo "playwright_version=${playwright_version}" >>$GITHUB_OUTPUT
echo "previous_playwright_version=${previous_playwright_version:-v0.0.0}" >>$GITHUB_OUTPUT
echo "playwright_version=${playwright_version}" >> $GITHUB_ENV
echo "patchright_release=${normalized_patchright_release}" >> $GITHUB_ENV
else
chmod +x utils/release_version_check.sh
utils/release_version_check.sh
fi
- name: Check Existing Failure Issue For Scheduled Run
id: existing_failure_issue
if: github.event_name == 'schedule' && steps.version_check.outputs.proceed == 'true'
uses: actions/github-script@v8
env:
TARGET_VERSION: ${{ steps.version_check.outputs.playwright_version }}
with:
script: |
const rawTargetVersion = process.env.TARGET_VERSION || 'unknown';
const normalizedTargetVersion = rawTargetVersion.startsWith('v') ? rawTargetVersion.slice(1) : rawTargetVersion;
const targetTitle = `Release Failed: v${normalizedTargetVersion} - Tests Failed`;
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'release-failed,upstream-break',
per_page: 100,
});
const existingIssue = issues.find((issue) =>
!issue.pull_request && issue.title === targetTitle && issue.user?.login === 'github-actions[bot]'
);
core.setOutput('skip_due_to_existing_issue', existingIssue ? 'true' : 'false');
check-patch-impact:
name: Check Patch Impact
needs: [check-release-version, run-patchright-tests]
if: always() && needs.check-release-version.outputs.proceed == 'true'
permissions:
contents: read
issues: write
uses: ./.github/workflows/check_patch_impact.yml
with:
old_version: ${{ needs.check-release-version.outputs.previous_playwright_version }}
new_version: ${{ needs.check-release-version.outputs.playwright_version }}
create_issue: false
run-patchright-tests:
name: Run Patchright Tests
needs: check-release-version
if: needs.check-release-version.outputs.proceed == 'true'
uses: ./.github/workflows/patchright_tests.yml
with:
playwright_version: ${{ needs.check-release-version.outputs.playwright_version }}
notify-test-failure:
name: Open Issue On Test Failure
needs: [check-release-version, run-patchright-tests, check-patch-impact]
if: always() && needs.check-release-version.outputs.proceed == 'true' && (needs.run-patchright-tests.outputs.test_outcome == 'failure' || needs.run-patchright-tests.result == 'failure')
runs-on: ubuntu-latest
permissions:
actions: read
contents: write
issues: write
pull-requests: write
steps:
- name: Download Patch Impact Report Artifact
continue-on-error: true
uses: actions/download-artifact@v6
with:
name: report.json
path: artifacts
- name: Open Test Failure Issue
uses: actions/github-script@v8
env:
OLD_VERSION: ${{ needs.check-release-version.outputs.previous_playwright_version }}
NEW_VERSION: ${{ needs.check-release-version.outputs.playwright_version }}
with:
script: |
const fs = require('node:fs');
const owner = context.repo.owner;
const repo = context.repo.repo;
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const commitSha = context.sha;
const oldVersion = process.env.OLD_VERSION || 'unknown';
const rawNewVersion = process.env.NEW_VERSION || 'unknown';
const normalizedNewVersion = rawNewVersion.startsWith('v') ? rawNewVersion.slice(1) : rawNewVersion;
const issueTitle = `Release Failed: v${normalizedNewVersion} - Tests Failed`;
let reportJsonText = 'Report artifact could not be read.';
let affectedTable = 'No affected symbols were found in the patch impact report.';
try {
const reportPath = 'artifacts/report.json';
const reportRaw = fs.readFileSync(reportPath, 'utf8');
reportJsonText = reportRaw;
const report = JSON.parse(reportRaw);
const affected = Array.isArray(report.affected) ? report.affected : [];
if (affected.length > 0) {
const header = '| Symbol | Kind | Change Type | Playwright File | Patch File |';
const divider = '|--------|------|-------------|-----------------|------------|';
const rows = affected.map((row) => {
const symbol = row.symbol ?? '';
const kind = row.kind ?? '';
const changeType = row.changeType ?? '';
const playwrightFile = row.playwrightFile ?? '';
const patchFile = row.patchFile ?? '';
return `| ${symbol} | ${kind} | ${changeType} | ${playwrightFile} | ${patchFile} |`;
});
affectedTable = [header, divider, ...rows].join('\n');
}
} catch (error) {
affectedTable = 'Patch impact report artifact could not be read.';
}
let pageTestsStepLink = 'Unavailable';
let libraryTestsStepLink = 'Unavailable';
try {
const jobsResponse = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
per_page: 100,
});
const testsJob = jobsResponse.data.jobs.find((job) =>
job.name.includes('Run Patchright Tests') ||
job.name.toLowerCase().includes('run-patchright-tests')
);
if (testsJob?.html_url) {
const pageStep = (testsJob.steps || []).find((step) => step.name === 'Run Page Tests');
const libraryStep = (testsJob.steps || []).find((step) => step.name === 'Run Library Tests');
if (pageStep?.number)
pageTestsStepLink = `[Run Page Tests](${testsJob.html_url}#step:${pageStep.number}:1)`;
if (libraryStep?.number)
libraryTestsStepLink = `[Run Library Tests](${testsJob.html_url}#step:${libraryStep.number}:1)`;
}
} catch (error) {
// Keep defaults when job/step links are unavailable.
}
const issueBody = [
`## Release Failed: Playwright ${oldVersion} → ${rawNewVersion}`,
'',
'The automated release workflow failed at the test step.',
'',
'Failure context:',
`- Workflow run: ${runUrl}`,
`- Commit: ${commitSha}`,
'',
'Test step logs:',
`- ${pageTestsStepLink}`,
`- ${libraryTestsStepLink}`,
'',
'The following patched APIs changed between these versions and may be the cause:',
'',
affectedTable,
'',
'Full `check_patch_impact` report (`report.json`):',
'```json',
reportJsonText,
'```',
'',
'cc @Vinyzu',
].join('\n');
const existingIssues = await github.rest.issues.listForRepo({
owner,
repo,
state: 'open',
labels: 'release-failed,upstream-break',
per_page: 100,
});
const existingIssue = existingIssues.data.find((issue) =>
!issue.pull_request && issue.title === issueTitle && issue.user?.login === 'github-actions[bot]'
);
if (existingIssue) {
await github.rest.issues.update({
owner,
repo,
issue_number: existingIssue.number,
title: issueTitle,
body: issueBody,
labels: ['release-failed', 'upstream-break'],
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: existingIssue.number,
body: `Failure reproduced in workflow run ${runUrl} on commit ${commitSha}.`,
});
} else {
const { data: createdIssue } = await github.rest.issues.create({
owner,
repo,
title: issueTitle,
body: issueBody,
labels: ['release-failed', 'upstream-break'],
});
const branchName = `fix-v${normalizedNewVersion}-${context.runId}`;
const { data: baseBranch } = await github.rest.repos.getBranch({
owner,
repo,
branch: 'main',
});
const baseSha = baseBranch.commit.sha;
const baseTreeSha = baseBranch.commit.commit.tree.sha;
await github.rest.git.createRef({
owner,
repo,
ref: `refs/heads/${branchName}`,
sha: baseSha,
});
const { data: scaffoldCommit } = await github.rest.git.createCommit({
owner,
repo,
message: `chore: scaffold ${branchName}`,
tree: baseTreeSha,
parents: [baseSha],
});
await github.rest.git.updateRef({
owner,
repo,
ref: `heads/${branchName}`,
sha: scaffoldCommit.sha,
});
const { data: pullRequest } = await github.rest.pulls.create({
owner,
repo,
title: `fix: investigate Playwright ${rawNewVersion} release failure`,
head: branchName,
base: 'main',
draft: true,
body: [
`Closes #${createdIssue.number}`,
'',
`Auto-created from failed release workflow run: ${runUrl}`,
].join('\n'),
});
console.log(`Draft fix PR created: ${pullRequest.html_url}`);
}
patchright-workflow:
name: "Patchright Release: Install, Patch, Build and Publish Patchright Driver"
needs: [check-release-version, check-patch-impact, run-patchright-tests]
if: needs.check-release-version.outputs.proceed == 'true' && needs.run-patchright-tests.result == 'success' && needs.run-patchright-tests.outputs.test_outcome == 'success'
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Configure Fast APT Mirror
uses: vegardit/fast-apt-mirror.sh@v1
- name: Setup Node v24
uses: actions/setup-node@v6
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
- name: Install NPM Dependencies
run: npm install
- name: Install Playwright Driver
run: |
git clone https://github.com/microsoft/playwright --branch ${{ needs.check-release-version.outputs.playwright_version }}
cd playwright
npm ci
- name: Patch Playwright Driver
run: |
cd playwright
git -C .. submodule update --init --recursive --remote patchright-nodejs
cd .. && npm run patch
- name: Generate Playwright Channels
# Ignore the error exit code, as the script exits 1 when a file is modified.
continue-on-error: true
run: |
cd playwright
node utils/generate_channels.js
- name: Build Patchright Driver
run: |
cd playwright
npm run build
npx playwright install-deps
chmod +x utils/build/build-playwright-driver.sh
utils/build/build-playwright-driver.sh
- name: Rebrand Patchright Package
run: |
cd playwright
npx tsx ../patchright-nodejs/patchright_rebranding.ts
- name: Publish Patchright-Core Package
run: |
cd playwright/packages/patchright-core/
package_name=$(node -p "require('./package.json').name")
package_version=$(node -p "require('./package.json').version")
if npm view "${package_name}@${package_version}" version >/dev/null 2>&1; then
echo "Skipping ${package_name}@${package_version}: already published."
else
npm publish --access=public --provenance
fi
- name: Publish Patchright Package
run: |
cd playwright/packages/patchright/
package_name=$(node -p "require('./package.json').name")
package_version=$(node -p "require('./package.json').version")
if npm view "${package_name}@${package_version}" version >/dev/null 2>&1; then
echo "Skipping ${package_name}@${package_version}: already published."
else
npm publish --access=public --provenance
fi
- name: Publish Patchright Driver
if: env.patchright_release == '' || needs.check-release-version.outputs.previous_playwright_version != needs.check-release-version.outputs.playwright_version
env:
PLAYWRIGHT_VERSION: ${{ needs.check-release-version.outputs.playwright_version }}
run: |
chmod +x utils/release_driver.sh
utils/release_driver.sh