From 5e3e021150279409ccf9b449a164d705a3cabc6e Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 30 Oct 2026 08:57:21 -0700 Subject: [PATCH 1/2] devops(roll): reject a file that becomes a symlink or a submodule, run the roll without GitHub tools --- .github/workflows/roll.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/roll.yml b/.github/workflows/roll.yml index e1e2b99e6..7fa067bce 100644 --- a/.github/workflows/roll.yml +++ b/.github/workflows/roll.yml @@ -121,6 +121,8 @@ jobs: python scripts/upstream.py log "$OLD_COMMIT" > "$RUNNER_TEMP/commits.md" - name: Roll with Copilot CLI id: agent + # No GitHub tools and no URL access: the agent reads upstream only from .upstream/, so text from + # issues and PRs does not reach it. uses: microsoft/playwright/.github/actions/run-agent@main with: prompt: | @@ -134,7 +136,6 @@ jobs: output-dir: ${{ env.OUTPUT_DIR }} result-file: summary.md artifact-name: roll - github-tools: true exec: ${{ env.CI_ENV_EXEC }} max-ai-credits: ${{ inputs.max_ai_credits || '3000' }} # The agent runs scripts/lint.sh last (step 5 of the skill); a roll that does @@ -246,8 +247,10 @@ jobs: echo "The roll changes files outside DRIVER_VERSION, NODE_VERSION, README.md, playwright/, tests/ and python_rules.js." >&2 exit 1 fi - if git diff --cached --summary | grep ' mode 120000 '; then - echo "The roll adds a symbolic link." >&2 + # The new mode of each path, also when a file changes its type: 120000 is a symbolic link, + # 160000 a submodule. + if git diff --cached --raw --no-renames | awk '$2 ~ /^1[26]0000$/ { found = 1; print } END { exit !found }'; then + echo "The roll adds a symbolic link or a submodule." >&2 exit 1 fi # Never execute code or scripts from the roll in the app job. From a23a1d94bc00a37fd1a0d8bc12b620f022173739 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 30 Oct 2026 11:26:59 -0700 Subject: [PATCH 2/2] devops(roll): roll on Mon, Wed and Fri at night, drop the needs-roll label --- .github/workflows/roll.yml | 109 ++++++++----------------------------- AGENTS.md | 4 +- 2 files changed, 26 insertions(+), 87 deletions(-) diff --git a/.github/workflows/roll.yml b/.github/workflows/roll.yml index 7fa067bce..a2295476a 100644 --- a/.github/workflows/roll.yml +++ b/.github/workflows/roll.yml @@ -8,9 +8,10 @@ name: Roll to latest Playwright on: - # Apply needs-roll to an issue to roll to the tip of main; re-apply it to trigger another roll. - issues: - types: [labeled] + # Mon, Wed and Fri at 9:17 UTC: 2:17 at night Pacific time, 1:17 in winter. Not on the hour, when + # GitHub delays scheduled runs. A scheduled roll skips while a roll pull request is open. + schedule: + - cron: "17 9 * * 1,3,5" workflow_dispatch: inputs: ref: @@ -23,39 +24,18 @@ on: permissions: {} jobs: - start: - name: Post the run link on the issue - if: github.repository == 'microsoft/playwright-python' && github.event.label.name == 'needs-roll' - runs-on: ubuntu-latest - permissions: - issues: write - outputs: - comment: ${{ steps.post.outputs.id }} - env: - ISSUE: ${{ github.event.issue.number }} - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - steps: - - name: Post the run link - id: post - run: | - body=$(printf "Hi, I'm the Playwright bot. I started to roll Playwright to the tip of main: [agent run](%s)\n" "$WORKFLOW_URL") - id=$(gh api "repos/$GH_REPO/issues/$ISSUE/comments" -f body="$body" --jq .node_id) - echo "id=$id" >> "$GITHUB_OUTPUT" - versions: name: Find the commit - if: >- - github.repository == 'microsoft/playwright-python' && - (github.event_name == 'workflow_dispatch' || github.event.label.name == 'needs-roll') + if: github.repository == 'microsoft/playwright-python' runs-on: ubuntu-24.04 permissions: contents: read + pull-requests: read outputs: old_commit: ${{ steps.versions.outputs.old_commit }} new_commit: ${{ steps.versions.outputs.new_commit }} new_short: ${{ steps.versions.outputs.new_short }} + open_roll: ${{ steps.open.outputs.pr }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Read the pinned and the new commit @@ -71,16 +51,30 @@ jobs: echo "new_commit=${new_commit}" echo "new_short=${new_commit:0:10}" } >> "$GITHUB_OUTPUT" + # A manual run rolls anyway: a maintainer asked for it. + - name: Look for an open roll pull request + id: open + if: github.event_name == 'schedule' + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + pr=$(gh pr list --state open --author app/microsoft-playwright-automation --json number,headRefName \ + --jq '[.[] | select(.headRefName | startswith("roll/")) | .number] | first // empty') + if [[ -n "$pr" ]]; then + echo "pr=$pr" >> "$GITHUB_OUTPUT" + echo "Skipped: roll pull request #$pr is open." >> "$GITHUB_STEP_SUMMARY" + fi roll: name: Roll to ${{ needs.versions.outputs.new_short }} needs: versions - if: needs.versions.outputs.new_commit != needs.versions.outputs.old_commit + if: >- + needs.versions.outputs.new_commit != needs.versions.outputs.old_commit && + needs.versions.outputs.open_roll == '' # The runner of CI: the skill runs ./test.sh, both suites on all its cores. runs-on: playwright-x64-ubuntu24-64-core timeout-minutes: 240 - # On the job and not on the workflow: every issue label starts this workflow, and a skipped - # run would replace a roll that waits in a group of the workflow. concurrency: group: roll cancel-in-progress: false @@ -153,7 +147,6 @@ jobs: env: COMPLETE: ${{ steps.agent.outputs.outcome == 'success' && steps.check.outcome == 'success' }} TITLE: "chore: roll Playwright to ${{ steps.record.outputs.version }} (${{ needs.versions.outputs.new_short }})" - ISSUE_URL: ${{ github.event.issue.html_url }} COMPARE_URL: https://github.com/microsoft/playwright/compare/${{ needs.versions.outputs.old_commit }}...${{ needs.versions.outputs.new_commit }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | @@ -187,8 +180,6 @@ jobs: commits.append(f"- … and {total - len(commits)} more: {env['COMPARE_URL']}") parts.append(f"
\nUpstream commits ({total})\n\n" + "\n".join(commits) + "\n\n
") parts.append(f"Upstream changes: {env['COMPARE_URL']}") - if env.get("ISSUE_URL"): - parts.append(f"Fixes {env['ISSUE_URL']}") Path(env["PATCH_DIR"], "pr-body.md").write_text("\n\n".join(parts) + "\n", encoding="utf-8") Path(env["PATCH_DIR"], "roll.json").write_text(json.dumps({"title": env["TITLE"]}) + "\n", encoding="utf-8") EOF @@ -264,7 +255,6 @@ jobs: metadata: roll.json body-file: pr-body.md branch: roll/${{ needs.versions.outputs.new_short }}/${{ github.run_id }}-${{ github.run_attempt }} - commit-body: "${{ github.event.issue.html_url && format('Fixes: {0}', github.event.issue.html_url) || '' }}" pull-request: true base: ${{ github.ref_name }} workflow-name: roll workflow @@ -276,54 +266,3 @@ jobs: GH_REPO: ${{ github.repository }} PR: ${{ steps.publish.outputs.number }} run: gh pr ready "$PR" - - comment: - name: Comment on the issue - needs: [start, versions, roll, publish] - # Runs after every run that the label started, including failed ones, and whatever publish did. - if: ${{ !cancelled() && needs.start.result != 'skipped' }} - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - name: Post the result and hide the run link - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - ISSUE: ${{ github.event.issue.number }} - START_COMMENT: ${{ needs.start.outputs.comment }} - VERSIONS_RESULT: ${{ needs.versions.result }} - OLD_COMMIT: ${{ needs.versions.outputs.old_commit }} - NEW_COMMIT: ${{ needs.versions.outputs.new_commit }} - ROLL_RESULT: ${{ needs.roll.result }} - SETUP_OUTCOME: ${{ needs.roll.outputs.setup }} - VERSION: ${{ needs.roll.outputs.version }} - COMPLETE: ${{ needs.roll.outputs.complete }} - PR_URL: ${{ needs.publish.outputs.pr }} - PUBLISH_RESULT: ${{ needs.publish.result }} - RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - run: | - commit="microsoft/playwright@${NEW_COMMIT:0:10}" - if [[ "$VERSIONS_RESULT" != "success" ]]; then - text="I couldn't roll Playwright: finding the commit to roll to failed." - elif [[ "$OLD_COMMIT" == "$NEW_COMMIT" ]]; then - text="Nothing to roll: \`DRIVER_VERSION\` is at the tip of main already, $commit." - elif [[ -n "$PR_URL" && "$COMPLETE" == "true" ]]; then - text="I rolled Playwright to $VERSION, $commit: $PR_URL" - elif [[ -n "$PR_URL" ]]; then - text="I rolled Playwright to $VERSION, $commit, but the roll is not complete: the agent or the checks failed. Draft PR: $PR_URL" - elif [[ "$PUBLISH_RESULT" == "failure" ]]; then - text="I rolled Playwright to $VERSION, $commit, but opening the pull request failed." - elif [[ "$SETUP_OUTCOME" == "failure" ]]; then - text="I couldn't roll Playwright to $commit: pinning, generating the API, building the driver or installing the browsers failed." - elif [[ "$ROLL_RESULT" == "success" ]]; then - text="I couldn't roll Playwright to $commit: the roll changed no file." - else - text="I couldn't roll Playwright to $commit: the roll job ended with result \`$ROLL_RESULT\`, for example after a timeout." - fi - printf "Hi, I'm the Playwright bot. %s\n\nRolled by the Playwright bot - [agent run](%s)\n\n" "$text" "$RUN_URL" > "$RUNNER_TEMP/comment.md" - gh issue comment "$ISSUE" --body-file "$RUNNER_TEMP/comment.md" - if [[ -n "$START_COMMENT" ]]; then - gh api graphql -f query='mutation($id: ID!) { minimizeComment(input: { subjectId: $id, classifier: OUTDATED }) { clientMutationId } }' -f id="$START_COMMENT" \ - || echo "::warning::Could not hide the comment with the run link" - fi diff --git a/AGENTS.md b/AGENTS.md index 394291f2b..5938520ac 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -139,8 +139,8 @@ left). ## Rolls Use the `playwright-roll` skill. On CI, `.github/workflows/roll.yml` runs it and opens the -PR. Apply the `needs-roll` label to an issue to roll to the tip of `main`, or start the -workflow by hand with a branch, a tag or a commit. +PR, on Mon, Wed and Fri at night unless a roll PR is open. To roll at another time, start +the workflow by hand with a branch, a tag or a commit. ## Commits and PRs