Skip to content

PLAYWRIGHT_BROWSERS_PATH literal ${RUNNER_TEMP} not expanded by install step, but is by playwright-core at launch — browser install/launch disagree on path #60871

Description

@vadzim-z

Description

playwrightBrowsersPath (pkg/workflow/playwright_cli.go) is compiled as the literal string ${RUNNER_TEMP}/gh-aw/playwright-browsers and set via env: on both the "Install Playwright browsers" step and the agent-execution step. GitHub Actions env: map values are never shell-expanded, so ${RUNNER_TEMP} reaches each process as a literal, unexpanded string — but the two steps end up disagreeing on where the browser is:

  • Install step (plain run: bash install_playwright_browsers.sh chromium, no shell macro-expansion available): the browser is written under a directory literally named ${RUNNER_TEMP} (a relative path, since it doesn't start with /), rooted at the job's cwd. Observed in the real log:
    Chrome for Testing 152.0.7977.8 (playwright chromium v1237) downloaded to
    /home/runner/work/<repo>/<repo>/${RUNNER_TEMP}/gh-aw/playwright-browsers/chromium-1237
    
  • Agent step (Claude + playwright-cli open): playwright-core's own throwIfExecutableMissing resolves the same nominal path to the real, expanded runner temp dir:
    Error: Browser "chrome-for-testing" is not installed; expected executable at
    /home/runner/work/_temp/gh-aw/playwright-browsers/chromium-1237/chrome-linux64/chrome
    

Because the browser was actually installed at the first (bogus, unexpanded) path and looked for at the second (real) path, playwright-cli open always fails with "Browser ... is not installed", even though the install step reported success. The agent correctly refuses to self-heal (per its own "never install at runtime" instruction) and reports a noop instead — so the failure is silent from a CI-green/red perspective, just quietly produces nothing.

Reproduced on agent-pr-diagram.md (Playwright CLI mode, tools.playwright: with no mode:, i.e. default) pinned to v0.88.7. Confirmed playwrightBrowsersPath is unchanged in HEAD (checked through v0.89.13) — not fixed by a version bump.

I believe this is why ${{ runner.temp }} isn't used here (per #54295, that trips a CodeQL code-injection alert on the interpolated ${{ }}), but the literal-string fallback isn't actually expanded anywhere in the plain-bash install step, only in whatever resolves paths inside playwright-core at launch time. The two need to agree.

Repro

  1. Workflow with tools.playwright: (CLI mode) and a step later in the same job that runs playwright-cli open ....
  2. Compile + run on a GitHub-hosted runner.
  3. Install step log shows the browser downloaded under a directory literally named ${RUNNER_TEMP} (relative to cwd).
  4. The later playwright-cli open fails with Browser "chrome-for-testing" is not installed; expected executable at <real runner.temp>/gh-aw/playwright-browsers/....

Suggested fix

Don't rely on the literal ${RUNNER_TEMP} string being expanded consistently by whatever happens to read PLAYWRIGHT_BROWSERS_PATH. Either:

  • have install_playwright_browsers.sh (and any other consumer) explicitly export PLAYWRIGHT_BROWSERS_PATH="${RUNNER_TEMP}/gh-aw/playwright-browsers" itself at the top of the script (real bash expansion, since run: script bodies do get $VAR-expanded, unlike env: map values), rather than trusting the value handed in via env:; or
  • resolve playwrightBrowsersPath to a real absolute path at compile time some other CodeQL-safe way (e.g. a small composite/js step that reads process.env.RUNNER_TEMP and re-exports the resolved value via $GITHUB_ENV, rather than a literal ${VAR} string in a YAML env: map).

Evidence

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions