What is happening?
On macOS, installing by channel name (chrome-version: stable) caches the contents of Google Chrome for Testing.app into the tool cache, so the resulting chrome-path has no .app component:
$RUNNER_TOOL_CACHE/setup-chrome/chrome/stable/arm64/Contents/MacOS/Google Chrome for Testing
Installing by explicit version (or a snapshot) keeps the bundle:
$RUNNER_TOOL_CACHE/setup-chrome/chrome/152.0.7977.75/arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing
The browser starts either way, but launchers that need a real app bundle break on the first layout. In our case Cypress 16 launches it, connects to CDP, and then never finds the initial page, because the page target's url stays empty:
Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 50 seconds.
Error: Could not find url target in browser about:blank.
Targets were [{"type":"page","title":"","url":"", …}, {"type":"browser_ui", …}, {"type":"browser_ui", …}]
I isolated it to the path alone — same bytes, only the .app component differs:
| Executable path |
Cypress run |
…/chrome/stable/arm64/Contents/MacOS/Google Chrome for Testing (channel install) |
fails, 2/2 |
…/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/… (straight from the zip) |
passes, 1/1 |
…/Google Chrome for Testing.app/Contents/MacOS/… where the .app is a symlink to the cached folder above |
passes, 1/1 |
The third row is the interesting one: it is the exact same directory as the failing case, reached through a .app-named symlink, and that is enough to make it work. So it is not the copy, the code signature or the download — only the absence of .app in the invoked path.
I could not reduce this to a Cypress-free repro: launching either layout manually with --headless --remote-debugging-port=… about:blank and reading /json/list gives a normal about:blank page target in both cases. So Cypress's specific launch flags are involved too, and I don't want to overstate the mechanism — but the .app component is reliably the deciding factor.
Version where you found the bug
v2 (v2.2.0)
Runner specifics
Self-hosted macOS 26.6.2, arm64 (Apple silicon). Also reproduced locally on the same OS with a hand-made copy of the two layouts, so it is not runner-specific.
Action workflow configuration
- name: Install chrome
id: install-chrome
uses: browser-actions/setup-chrome@v2
with:
chrome-version: stable
- uses: cypress-io/github-action@v7
with:
browser: ${{ steps.install-chrome.outputs.chrome-path }}
Relevant log output
Browser: Custom Chrome for Testing 152 (headless)
Still waiting to connect to Custom chrome for testing, retrying in 1 second (attempt 62/62)
Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 50 seconds.
This usually indicates there was a problem opening the Custom chrome for testing browser.
Error: Could not find url target in browser about:blank. Targets were [{"targetId":"…","type":"page","title":"","url":"","attached":true,"canAccessOpener":false,"browserContextId":"…"},{"targetId":"…","type":"browser_ui","title":"","url":"","attached":true,"canAccessOpener":false,"browserContextId":"…"},{"targetId":"…","type":"browser_ui","title":"","url":"","attached":true,"canAccessOpener":false,"browserContextId":"…"}]
Where it comes from
MacOSChannelInstaller caches the .app directory itself, so its contents land directly in <cache>/chrome/<channel>/<arch>/:
// src/channel_macos.ts
const extAppRoot = path.join(extPath, `chrome-${this.versionResolver.platformString}`, "Google Chrome for Testing.app");
const root = await cache.cacheDir(extAppRoot, "chrome", version);
return { root, bin: "Contents/MacOS/Google Chrome for Testing" };
The other two installers cache the parent directory and keep the bundle in bin:
// src/version_installer.ts
const extAppRoot = path.join(extPath, `chrome-${this.versionResolver.platformString}`);
const root = await cache.cacheDir(extAppRoot, "chrome", resolved.version);
// bin, DARWIN: "Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"
// src/snapshot_installer.ts
// bin, DARWIN: "Chromium.app/Contents/MacOS/Chromium"
So aligning channel_macos.ts with them — cache chrome-${platformString} and return bin: "Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" — would make channel installs consistent with version and snapshot installs. It changes the cached layout, so existing chrome/<channel>/<arch> cache entries on long-lived self-hosted runners would need to be invalidated.
The same applies to the chromedriver path in that file if it has the equivalent shape.
Workaround
For anyone hitting this: pin an explicit version instead of a channel name, which routes through VersionInstaller and keeps the .app:
with:
chrome-version: 152.0.7977.75
Or symlink a .app name over the cached folder and launch through that:
bin_name="$(basename "$CHROME_PATH")"
app_root="$(cd "$(dirname "$CHROME_PATH")/../.." && pwd)"
bundle="$RUNNER_TEMP/browser/$bin_name.app"
mkdir -p "$RUNNER_TEMP/browser"; rm -rf "$bundle"; ln -s "$app_root" "$bundle"
echo "path=$bundle/Contents/MacOS/$bin_name" >> "$GITHUB_OUTPUT"
Thanks for the action — happy to test a patch if that helps.
What is happening?
On macOS, installing by channel name (
chrome-version: stable) caches the contents ofGoogle Chrome for Testing.appinto the tool cache, so the resultingchrome-pathhas no.appcomponent:Installing by explicit version (or a snapshot) keeps the bundle:
The browser starts either way, but launchers that need a real app bundle break on the first layout. In our case Cypress 16 launches it, connects to CDP, and then never finds the initial page, because the page target's
urlstays empty:I isolated it to the path alone — same bytes, only the
.appcomponent differs:…/chrome/stable/arm64/Contents/MacOS/Google Chrome for Testing(channel install)…/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/…(straight from the zip)…/Google Chrome for Testing.app/Contents/MacOS/…where the.appis a symlink to the cached folder aboveThe third row is the interesting one: it is the exact same directory as the failing case, reached through a
.app-named symlink, and that is enough to make it work. So it is not the copy, the code signature or the download — only the absence of.appin the invoked path.I could not reduce this to a Cypress-free repro: launching either layout manually with
--headless --remote-debugging-port=… about:blankand reading/json/listgives a normalabout:blankpage target in both cases. So Cypress's specific launch flags are involved too, and I don't want to overstate the mechanism — but the.appcomponent is reliably the deciding factor.Version where you found the bug
v2 (v2.2.0)
Runner specifics
Self-hosted macOS 26.6.2, arm64 (Apple silicon). Also reproduced locally on the same OS with a hand-made copy of the two layouts, so it is not runner-specific.
Action workflow configuration
Relevant log output
Where it comes from
MacOSChannelInstallercaches the.appdirectory itself, so its contents land directly in<cache>/chrome/<channel>/<arch>/:The other two installers cache the parent directory and keep the bundle in
bin:So aligning
channel_macos.tswith them — cachechrome-${platformString}and returnbin: "Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"— would make channel installs consistent with version and snapshot installs. It changes the cached layout, so existingchrome/<channel>/<arch>cache entries on long-lived self-hosted runners would need to be invalidated.The same applies to the
chromedriverpath in that file if it has the equivalent shape.Workaround
For anyone hitting this: pin an explicit version instead of a channel name, which routes through
VersionInstallerand keeps the.app:Or symlink a
.appname over the cached folder and launch through that:Thanks for the action — happy to test a patch if that helps.