tomato·bazeldocs v0 · latest
Docs/Reference/Modules/rules_chrome

rules_chrome

Bazel rules for Chrome for Testing. Hermetic, sha256-pinned chrome + chromedriver per platform; launchers + opt-in Playwright (py + js) macros with Bazel-managed user-data-dirs.

Latest0.1.1
Versions2
CategoryBazel rules
MaintainersMatt Marshall
Registryhttps://registry.tbzl.dev/modules/rules_chrome/
Sourcegithub.com/tomato-bazel/rules_chrome
MODULE.bazelstarlark
bazel_dep(name = "rules_chrome", version = "0.1.1")

View source & releases on GitHub ↗

Bazel rules for Chrome for Testing. Fetches the prebuilt chrome + matching chromedriver hermetically and exposes them through a Bazel toolchain plus thin bazel run launchers tuned for test automation.

  • module extension: chrome — auto-creates @chrome + @chromedriver external repos at a pinned version. See docs/extensions.md.

  • toolchain: chrome_toolchain — wraps the chrome binary + chromedriver; resolved via @rules_chrome//chrome:toolchain_type. See docs/toolchains.md.

  • rules:

    • chrome_runbazel run //path:target → launches Chrome for Testing with a managed --user-data-dir and the standard automation flags.
    • chromedriver_runbazel run //path:target → launches chromedriver on a configurable port; consume from selenium / playwright / pytest.

    See docs/defs.md.

  • playwright sub-module (opt-in): playwright_chrome_py_test and playwright_chrome_js_test macros that wire @chrome into a Playwright launchPersistentContext against a Bazel-managed user-data-dir. See docs/playwright_py.md and docs/playwright_js.md. Because a macro’s load() resolves against the defining module, rules_chrome declares aspect_rules_js + rules_python as non-dev bazel_deps (so the macros are loadable by consumers — see 0.1.1 in the CHANGELOG); they enter the module graph but the chrome/chromedriver toolchain itself needs neither.

Install

Add the registry to your .bazelrc:

common --registry=https://registry.fastverk.com/
common --registry=https://bcr.bazel.build/

In your MODULE.bazel:

bazel_dep(name = "rules_chrome", version = "0.1.0")

chrome = use_extension("@rules_chrome//chrome:extensions.bzl", "chrome")
use_repo(chrome, "chrome", "chromedriver")
register_toolchains("@chrome//:chrome_toolchain_def")

Override the version if needed:

chrome.toolchain(version = "148.0.7778.167")

The default tracks the upstream Stable channel as of the last tools/refresh_versions.py run. The chromedriver version is locked to the chrome version — they ship as a matched pair from upstream.

Quick start

A smoke check that the binaries resolve and launch:

# BUILD.bazel
load("@rules_chrome//chrome:defs.bzl", "chrome_run", "chromedriver_run")

chrome_run(
    name = "browser",
    headless = True,
    extra_args = ["--disable-gpu"],
)

chromedriver_run(
    name = "driver",
    port = 9515,
)
bazel run //:browser -- https://example.com
bazel run //:driver

Pass any chrome flag after --; they’re appended after the rules_chrome defaults so they always win. The launcher provisions an ephemeral --user-data-dir under $TMPDIR and cleans it up on exit; flip user_data_dir_mode = "workspace" to persist cookies / extensions across bazel run sessions.

For the bare binaries (Selenium grids, custom test rules, screenshot tools) depend directly on @chrome//:chrome and @chromedriver//:chromedriver — both are executable targets with the rest of the bundle in their runfiles:

sh_test(
    name = "page_loads",
    srcs = ["page_loads.sh"],
    data = [
        "@chrome//:chrome",
        "@chromedriver//:chromedriver",
    ],
)

How it works

The chrome module extension downloads two zips per build:

RepoSource
@chromehttps://storage.googleapis.com/chrome-for-testing-public/{v}/{p}/chrome-{p}.zip
@chromedriver…/chromedriver-{p}.zip

{v} is the pinned version and {p} is one of linux64, mac-arm64, mac-x64, win32, win64, resolved from rctx.os.name / rctx.os.arch. The extracted bundle stays intact inside its repo — on macOS that means the full .app wrapper, because chrome’s dyld load commands resolve Frameworks/ relative to the executable. The :chrome target is a thin launcher script that exec’s the real bundle binary by absolute path so @executable_path points at Contents/MacOS/ (not the repo root).

Hermeticity

LayerPinned by
chrome binarysha256 in chrome/private/known_versions.bzl per (version, platform)
chromedriversame table
Profile stateephemeral --user-data-dir per chrome_run invocation

Unpinned versions still download — a warning is emitted at load time but the build proceeds. To lock a new version:

tools/refresh_versions.py --version 148.0.7778.167

The tool pulls the upstream last-known-good-versions-with-downloads.json endpoint (or {version}.json for a specific build), downloads every (binary, platform) zip, hashes it, and rewrites known_versions.bzl in place. Stdlib-only — no pip install needed.

Playwright integration (opt-in)

If you drive chrome through Playwright, the chrome/playwright sub-module gives you idiomatic Bazel macros that compose correctly with launch_persistent_context:

# MODULE.bazel — add rules_python and your own pip hub
bazel_dep(name = "rules_python", version = "2.0.1")

pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
    hub_name = "my_pip",
    python_version = "3.12",
    requirements_lock = "//:requirements_lock.txt",   # must include playwright
)
use_repo(pip, "my_pip")
# BUILD.bazel
load("@rules_chrome//chrome/playwright:py.bzl", "playwright_chrome_py_test")
load("@my_pip//:requirements.bzl", "requirement")

playwright_chrome_py_test(
    name = "browser_test",
    srcs = ["browser_test.py"],
    user_data_dir_mode = "workspace",  # persistent profile under bazel run
    deps = [requirement("playwright")],
)
# browser_test.py
from rules_chrome_playwright import chrome_context

def test_thing():
    with chrome_context() as ctx:           # BrowserContext, not Browser
        page = ctx.new_page()
        page.goto("https://example.com")
        # cookies/extensions/local-storage survive across `bazel run` in workspace mode

The Node side mirrors this — playwright_chrome_js_test from @rules_chrome//chrome/playwright:js.bzl, with Playwright pulled through aspect_rules_js. See examples/smoke for runnable versions of both.

As of 0.1.1, aspect_rules_js + rules_python are non-dev bazel_deps on rules_chrome (a macro’s load() resolves against the defining module, so they must be visible to consumers of the sub-module). They therefore appear in your module graph even if you never load the sub-module — but the chrome/chromedriver toolchain itself pulls in neither at fetch time. rules_nodejs stays dev-only.

Scope and non-goals

This module intentionally stays small. It provides the generally reusable piece — fetching chrome + chromedriver hermetically, launching them through the toolchain with sane automation defaults. Things that stay in your repo:

  • Selenium / Playwright / Puppeteer integrations — install the client of your choice via rules_python / rules_js, and point it at the chromedriver binary or the chromedriver_run target.
  • Extension loading — pass --load-extension=... through extra_args or the bazel run CLI.
  • Profile bootstrapping (preset bookmarks, signed-in cookies) — drop a pre-populated profile under .cache/rules_chrome/... and run chrome_run(user_data_dir_mode = "workspace").
  • chrome-headless-shell — upstream ships this as a separate artifact; add it to tools/refresh_versions.py’s BINARIES tuple plus a new repo rule in chrome/extensions.bzl to expose it as @chrome_headless_shell.

Compatibility

  • Bazel: 7.4+, bzlmod required.
  • Chrome for Testing: 148.0.7778.167 pinned by default. Bump via tools/refresh_versions.py.
  • Platforms: linux64, mac-arm64, mac-x64, win64 (win32 available but untested in CI).

On Linux runners chrome needs a handful of shared libs that ubuntu-latest doesn’t ship by default — the bundled CI workflow installs libnss3, libnspr4, libatk1.0-0, libatk-bridge2.0-0, libcups2, libdrm2, libxkbcommon0, libxcomposite1, libxdamage1, libxfixes3, libxrandr2, libgbm1, libpango-1.0-0, libcairo2, and libasound2t64. If your own CI launches chrome (not just --version), mirror that list.

Contributing

Reference docs (docs/defs.md, docs/extensions.md, docs/toolchains.md) are stardoc-generated from the .bzl docstrings and committed to source. After editing a rule docstring:

bazel run //docs:update

CI gates this via bazel test //docs/... (diff_test against the committed output) and the smoke targets in examples/smoke/:

TargetExercises
chrome_version_test@chrome launcher → chrome --version exits 0
chromedriver_version_test@chromedriver launcher → chromedriver --version exits 0
playwright_smoke_test (py)Python Playwright → executable_path=@chrome → about:blank + JS eval
playwright_node_smoke_test (js)Node Playwright → same shape, exercises the primary CDP code path
playwright_module_py_testplaywright_chrome_py_test macro + rules_chrome_playwright helper
playwright_module_js_testplaywright_chrome_js_test macro + helper, end-to-end

The Playwright tests pull playwright==1.59.0 hermetically — rules_python + smoke_pip for the Python side, aspect_rules_js + smoke_npm for Node. Both are scoped as dev-only MODULE.bazel extensions; consumers of rules_chrome never see them.

To pull a newer Chrome for Testing release:

tools/refresh_versions.py                       # latest Stable
tools/refresh_versions.py --channel Beta        # latest Beta
tools/refresh_versions.py --version 149.0.7800.0   # specific build

…then bump MODULE.bazel’s version and commit both.

License

MIT.

Usage#

Real usage, taken from the module’s examples/.

examples/smoke/BUILD.bazel

load("@aspect_rules_js//js:defs.bzl", "js_test")
load("@rules_chrome//chrome:defs.bzl", "chrome_run", "chromedriver_run")
load("@rules_chrome//chrome/playwright:js.bzl", "playwright_chrome_js_test")
load("@rules_chrome//chrome/playwright:py.bzl", "playwright_chrome_py_test")
load("@rules_python//python:py_test.bzl", "py_test")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
load("@smoke_npm//:defs.bzl", "npm_link_all_packages")
load("@smoke_pip//:requirements.bzl", "requirement")

# `bazel run //examples/smoke:chrome` — launches headless chrome with a
# fresh ephemeral profile. Verifies the toolchain resolves and the
# binary executes on the host.
chrome_run(
    name = "chrome",
    extra_args = ["--disable-gpu"],
    headless = True,
)

# `bazel run //examples/smoke:chromedriver` — launches chromedriver on
# the OS-assigned port. Read it from chromedriver's stdout.
chromedriver_run(
    name = "chromedriver",
)

# Smoke test: `chrome --version` exits 0 and prints a version string.
sh_test(
    name = "chrome_version_test",
    srcs = ["version_smoke.sh"],
    args = ["$(rootpath @chrome//:chrome)"],
    data = ["@chrome"],
)

# Smoke test: `chromedriver --version` exits 0 and prints a version string.
sh_test(
    name = "chromedriver_version_test",
    srcs = ["version_smoke.sh"],
    args = ["$(rootpath @chromedriver//:chromedriver)"],
    data = ["@chromedriver"],
)

# Playwright integration: launches headless chrome via the @chrome
# launcher, drives it over CDP, evaluates a trivial JS expression.
# Confirms our wrapper survives Playwright's subprocess-spawn /
# CDP-handshake path (chromedriver isn't in this loop — Playwright
# speaks CDP directly).
py_test(
    name = "playwright_smoke_test",
    srcs = ["playwright_smoke.py"],
    args = ["$(rootpath @chrome//:chrome)"],
    data = ["@chrome"],
    # Playwright's launch spawns a subprocess and refuses to find its
    # bundled node driver if PATH is empty under sandboxing — let it
    # inherit the host PATH. Same goes for HOME (used by tmpdir).
    env_inherit = [
        "PATH",
        "HOME",
    ],
    main = "playwright_smoke.py",
    deps = [requirement("playwright")],
)

# Materialize the @smoke_npm/* labels under this package so js_test can
# depend on `//examples/smoke:node_modules/playwright`. The macro
# expands per pnpm-lock entry — no per-package boilerplate.
npm_link_all_packages(name = "node_modules")

# Playwright (Node) integration: same shape as the py_test, but
# exercises Playwright's primary (Node) bindings rather than the Python
# wrapper. Covers the launcher under a different subprocess-spawn path.
js_test(
    name = "playwright_node_smoke_test",
    args = ["$(rootpath @chrome//:chrome)"],
    data = [
        "playwright_smoke.js",
        ":node_modules/playwright",
        "@chrome",
    ],
    entry_point = "playwright_smoke.js",
    env = {
        # Skip the post-install chromium fetch. We point Playwright at
        # @chrome via executablePath; its bundled browser is dead weight.
        "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD": "1",
    },
)

# `playwright_chrome_py_test` macro smoke: exercises the rules_chrome
# sub-module abstraction (env wiring + helper module) end-to-end.
# Ephemeral mode → profile lives under $TEST_TMPDIR.
playwright_chrome_py_test(
    name = "playwright_module_py_test",
    srcs = ["playwright_module_smoke.py"],
    deps = [requirement("playwright")],
)

# Same shape, JS side — `playwright_chrome_js_test` macro.
playwright_chrome_js_test(
    name = "playwright_module_js_test",
    data = [":node_modules/playwright"],
    entry_point = "playwright_module_smoke.js",
)

# Workspace-mode coverage: fakes BUILD_WORKSPACE_DIRECTORY so the
# launch_persistent_context path that's normally only reachable via
# `bazel run` (not `bazel test`) gets CI coverage too. Also exercises
# the helper's clear-error behavior when the env var is missing.
playwright_chrome_py_test(
    name = "playwright_workspace_mode_test",
    srcs = ["workspace_mode_smoke.py"],
    deps = [requirement("playwright")],
)

Rules & providers#

Generated with Stardoc from the module's .bzl sources.

from docs/defs.md

User-facing Bazel rules for rules_chrome.

Exports two bazel run-friendly launchers:

  • chrome_run: launches Chrome for Testing with an isolated user-data-dir and the standard testing flags pre-applied. Reusable across smoke tests, screenshot tools, scripted browser sessions.
  • chromedriver_run: launches chromedriver on a configurable port; used as the WebDriver backend for selenium / playwright / pytest-selenium.

Both rules resolve their binaries through @rules_chrome//chrome:toolchain_type, so consumers can swap in a locally-built or alternate-channel chrome via register_toolchains(...) without editing target attributes.

For the bare binaries (without launcher ergonomics) consumers can depend directly on @chrome//:chrome and @chromedriver//:chromedriver.

chrome_run

load("@rules_chrome//chrome:defs.bzl", "chrome_run")

chrome_run(name, extra_args, headless, user_data_dir, user_data_dir_mode)

Run Chrome for Testing via bazel run, with a managed user-data-dir and the standard automation flags pre-applied. Additional CLI arguments are forwarded to chrome verbatim.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
extra_argsExtra command-line flags appended after the rules_chrome defaults but before any args passed on the CLI. Use this to bake in test-specific flags (e.g. --remote-debugging-port=9222).List of stringsoptional[]
headlessAppend --headless=new to the command line.BooleanoptionalFalse
user_data_dirWorkspace-relative profile path used when user_data_dir_mode = "workspace". Defaults to .cache/rules_chrome/<target name>.Stringoptional""
user_data_dir_modeHow to seed --user-data-dir: * ephemeral (default): a fresh $TMPDIR/chrome_run.XXXXXX per invocation, cleaned on exit. Safe for tests and parallel runs. * workspace: a persistent directory under $BUILD_WORKSPACE_DIRECTORY/<user_data_dir> (cookies, extensions, signed-in state survive across bazel run). Requires bazel run. * system: omit --user-data-dir entirely. Chrome uses the real per-user profile — almost never what you want for automation.Stringoptional"ephemeral"

chromedriver_run

load("@rules_chrome//chrome:defs.bzl", "chromedriver_run")

chromedriver_run(name, extra_args, port)

Run chromedriver via bazel run. The matching driver version is resolved through the registered chrome toolchain; extra CLI arguments are forwarded to chromedriver verbatim.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
extra_argsExtra command-line flags appended after the configured --port and before any args passed on the CLI.List of stringsoptional[]
portListen port. 0 (default) lets chromedriver pick — read the chosen port from its first line of stdout.Integeroptional0

from docs/extensions.md

Module extension for rules_chrome.

Auto-fetches the prebuilt Chrome for Testing + chromedriver binaries for the host platform. Versions are pinned by sha256 in private/known_versions.bzl. Consumers can override the version via the toolchain tag class.

Default usage (pulls the default-pinned Chrome + chromedriver):

chrome = use_extension("@rules_chrome//chrome:extensions.bzl", "chrome")
use_repo(chrome, "chrome", "chromedriver")
register_toolchains("@chrome//:chrome_toolchain_def")

Pin a specific Chrome for Testing version:

chrome = use_extension("@rules_chrome//chrome:extensions.bzl", "chrome")
chrome.toolchain(version = "148.0.7778.167")
use_repo(chrome, "chrome", "chromedriver")

chrome

chrome = use_extension("@rules_chrome//chrome:extensions.bzl", "chrome")
chrome.toolchain(version)

Sets up @chrome and @chromedriver as Bazel-fetched prebuilt binaries.

TAG CLASSES

toolchain

Attributes

NameDescriptionTypeMandatoryDefault
versionOverride the Chrome for Testing version. Defaults to the value in known_versions.bzl.Stringoptional""

from docs/playwright_js.md

Playwright (Node) wrappers for rules_chrome.

Exports playwright_chrome_js_test, a thin macro over js_test (aspect_rules_js) that wires @chrome into the test’s runfiles and exposes a Bazel-managed user-data-dir through env vars. The runtime helper at @rules_chrome//chrome/playwright:rules_chrome_playwright.js reads those env vars and returns a Playwright BrowserContext via launchPersistentContext.

Consumers bring their own playwright npm dep (so the version isn’t pinned by rules_chrome). The typical layout:

bazel_dep(name = "rules_chrome", version = "...")
bazel_dep(name = "aspect_rules_js", version = "...")
bazel_dep(name = "rules_nodejs", version = "...")

node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node")
node.toolchain(node_version = "22.11.0")

npm = use_extension("@aspect_rules_js//npm:extensions.bzl", "npm")
npm.npm_translate_lock(
    name = "my_npm",
    pnpm_lock = "//:pnpm-lock.yaml",  # must include playwright
)
use_repo(npm, "my_npm")

chrome = use_extension("@rules_chrome//chrome:extensions.bzl", "chrome")
use_repo(chrome, "chrome", "chromedriver")

In your BUILD.bazel:

load("@rules_chrome//chrome/playwright:js.bzl", "playwright_chrome_js_test")
load("@my_npm//:defs.bzl", "npm_link_all_packages")

npm_link_all_packages(name = "node_modules")

playwright_chrome_js_test(
    name = "browser_test",
    entry_point = "browser_test.js",
    data = [":node_modules/playwright"],
)

Then in browser_test.js:

const { withChromeContext } = require(process.env.RULES_CHROME_PLAYWRIGHT_HELPER);

withChromeContext(async (ctx) => {
    const page = await ctx.newPage();
    await page.goto('about:blank');
    if (await page.evaluate(() => 1 + 1) !== 2) process.exit(1);
});

playwright_chrome_js_test

load("@rules_chrome//chrome/playwright:js.bzl", "playwright_chrome_js_test")

playwright_chrome_js_test(name, entry_point, user_data_dir_mode, user_data_dir, headless, data, env,
                          env_inherit, **kwargs)

Define a Node test that drives Chrome for Testing through Playwright.

PARAMETERS

NameDescriptionDefault Value
nameTest target name.none
entry_pointPath to the JS file to execute. The script reads the helper’s runfiles path from RULES_CHROME_PLAYWRIGHT_HELPER.none
user_data_dir_modeOne of "ephemeral" (fresh per test, default — cleaned by Bazel’s sandbox via $TEST_TMPDIR) or "workspace" (persistent under $BUILD_WORKSPACE_DIRECTORY/<user_data_dir>). "workspace" mode requires bazel run — Bazel’s test sandbox doesn’t set BUILD_WORKSPACE_DIRECTORY, so bazel test will fail at runtime."ephemeral"
user_data_dirWorkspace-relative profile path for workspace mode. Defaults to .cache/rules_chrome_playwright/<name>.""
headlessWhether to run chrome in headless mode (default True).True
dataExtra js_test data. @chrome, the helper JS file, and any node_modules deps the consumer adds get appended. The playwright npm package (:node_modules/playwright) must be included here (consumer-supplied).None
envExtra env vars. The macro adds RULES_CHROME_PATH, RULES_CHROME_PLAYWRIGHT_HELPER, RULES_CHROME_PROFILE_REL (workspace mode only), and RULES_CHROME_HEADFUL (when headless = False).None
env_inheritExtra env vars to inherit from the host. PATH and HOME are inherited by default — Playwright needs both.None
kwargsForwarded to js_test.none

from docs/playwright_py.md

Playwright (Python) wrappers for rules_chrome.

Exports playwright_chrome_py_test, a thin macro over py_test that wires @chrome into the test’s runfiles and exposes a Bazel-managed user-data-dir through env vars. The runtime helper at @rules_chrome//chrome/playwright:helper.py reads those env vars and returns a Playwright BrowserContext via launch_persistent_context.

Consumers bring their own playwright pip dep (so the version isn’t pinned by rules_chrome). The typical layout:

bazel_dep(name = "rules_chrome", version = "...")
bazel_dep(name = "rules_python", version = "...")

pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
    hub_name = "my_pip",
    python_version = "3.12",
    requirements_lock = "//:requirements_lock.txt",  # must include playwright
)
use_repo(pip, "my_pip")

chrome = use_extension("@rules_chrome//chrome:extensions.bzl", "chrome")
use_repo(chrome, "chrome", "chromedriver")

In your BUILD.bazel:

load("@rules_chrome//chrome/playwright:py.bzl", "playwright_chrome_py_test")
load("@my_pip//:requirements.bzl", "requirement")

playwright_chrome_py_test(
    name = "browser_test",
    srcs = ["browser_test.py"],
    deps = [requirement("playwright")],
)

Then in browser_test.py:

from rules_chrome_playwright import chrome_context

def test_x():
    with chrome_context() as ctx:
        page = ctx.new_page()
        page.goto("about:blank")
        assert page.evaluate("1+1") == 2

playwright_chrome_py_test

load("@rules_chrome//chrome/playwright:py.bzl", "playwright_chrome_py_test")

playwright_chrome_py_test(name, srcs, main, user_data_dir_mode, user_data_dir, headless, deps, data,
                          env, env_inherit, **kwargs)

Define a Python test that drives Chrome for Testing through Playwright.

PARAMETERS

NameDescriptionDefault Value
nameTest target name.none
srcsPython source files.none
mainEntry-point Python file. Defaults to srcs[0] when a single src is given, so srcs = ["browser_test.py"] Just Works regardless of the target’s name.None
user_data_dir_modeOne of "ephemeral" (fresh per test, default — cleaned by Bazel’s sandbox via $TEST_TMPDIR) or "workspace" (persistent under $BUILD_WORKSPACE_DIRECTORY/<user_data_dir>). "workspace" mode requires bazel run — Bazel’s test sandbox doesn’t set BUILD_WORKSPACE_DIRECTORY, so bazel test will fail at runtime."ephemeral"
user_data_dirWorkspace-relative profile path for workspace mode. Defaults to .cache/rules_chrome_playwright/<name>.""
headlessWhether to run chrome in headless mode (default True).True
depsExtra py_test deps. The playwright pip requirement must be included here (consumer-supplied). The helper py_library is added automatically.None
dataExtra py_test data. @chrome is added automatically.None
envExtra env vars. The macro adds RULES_CHROME_PATH, RULES_CHROME_PROFILE_REL (workspace mode only), and RULES_CHROME_HEADFUL (when headless = False).None
env_inheritExtra env vars to inherit from the host. PATH and HOME are inherited by default — Playwright needs both to spawn its bundled node driver.None
kwargsForwarded to py_test.none

from docs/toolchains.md

Toolchain rule for rules_chrome.

chrome_toolchain wraps a Chrome for Testing binary plus the matching chromedriver as a single Bazel toolchain. Consumers (the chrome_run and chromedriver_run rules) resolve chrome through @rules_chrome//chrome:toolchain_type, so users can register custom chrome builds (locally-built fork, dev/canary channel, …) via register_toolchains(...) without modifying rule attributes.

The module extension at @rules_chrome//chrome:extensions.bzl generates a default toolchain (@chrome//:chrome_toolchain_def) wrapping the prebuilt binaries. Users register it from their MODULE.bazel:

register_toolchains("@chrome//:chrome_toolchain_def")

chrome_toolchain

load("@rules_chrome//chrome:toolchains.bzl", "chrome_toolchain")

chrome_toolchain(name, chrome, chromedriver)

Declare a Chrome for Testing binary + chromedriver as a Bazel toolchain.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
chromeThe chrome executable target (its runfiles carry the rest of the bundle).Labelrequired
chromedriverThe chromedriver executable target. Optional — leave unset for chrome-only setups.LabeloptionalNone

ChromeToolchainInfo

load("@rules_chrome//chrome:toolchains.bzl", "ChromeToolchainInfo")

ChromeToolchainInfo(chrome, chromedriver)

The Chrome for Testing binaries, resolved via a toolchain.

FIELDS

NameDescription
chromeTarget: the chrome executable target (carries its bundle as runfiles).
chromedriverTarget: the chromedriver executable target. May be None if the toolchain was registered without driver support.

Conformance#

No gate findings. 17 contested atoms. See how gating works or the full report.

Contested atoms

Third-party modules where this module resolves a different version than others do. Not a violation of anything this module did — it is the actionable form of a registry-level convergence finding, and the sentence a maintainer can act on.

AtomResolved hereElsewhere
apple_support 1.24.2 2.2.0 ×1
aspect_bazel_lib 2.22.5 2.8.1 ×1
aspect_rules_js 3.1.2 2.1.3 ×1
bazel_lib 3.2.2 3.0.0 ×12
bazel_skylib 1.8.2 1.9.0 ×2
gawk 5.3.2.bcr.3 5.3.2.bcr.1 ×12
jq.bzl 0.4.0 0.1.0 ×12
nlohmann_json 3.6.1 3.12.0.bcr.1 ×1
package_metadata 0.0.2 0.0.5 ×3
protobuf 33.4 34.0.bcr.1 ×2
rules_jvm_external 6.7 6.8 ×4
rules_nodejs 6.7.4 6.3.0 ×16.7.3 ×1
rules_python 2.0.1 1.7.0 ×82
rules_swift 3.1.2 3.6.1 ×1
tar.bzl 0.10.4 0.5.1 ×110.6.0 ×1
upb 0.0.0-20220923-a547704 0.0.0-20230516-61a97ef ×1
yq.bzl 0.3.4 0.1.1 ×12

Dependencies#

rules_chrome in the registry graph — what it depends on (left) and what depends on it (right).

Depends on

platforms1.0.0bazel_skylib1.8.2aspect_rules_js3.1.2rules_python2.0.1rules_shell0.6.1devstardoc0.7.2devrules_nodejs6.7.4dev

Used by (1 in the registry)

Versions#

2 published versions, newest first. Each resolves to an immutable, integrity-checked archive.

VersionIntegrity (sha256)Source archive
0.1.1 latest o/xrDi/TImMZ1HAe… tag archive ↗
0.1.0 cvK8idLueySSfWuO… tag archive ↗

Changelog#

All notable changes to rules_chrome. The format is loosely Keep a Changelog — version headers mirror the published bazel-registry entries.

0.1.1 — fix: playwright macros loadable by consumers

  • Fix: the chrome/playwright sub-module macros (playwright_chrome_js_test / playwright_chrome_py_test) were unusable outside this repo. Their load()s — and the chrome/playwright package’s own BUILD.bazel — resolve @aspect_rules_js / @rules_python against rules_chrome’s repo mapping, but both were declared dev_dependency, so a consumer hit No repository visible as '@aspect_rules_js' from '@rules_chrome+' merely by loading js.bzl or referencing any target in the package. They are now non-dev bazel_deps.
  • Note (revises the 0.1.0 “zero-cost default” claim): because a macro’s load() resolves against the defining module, these deps must be visible to consumers, so the default bazel_dep(rules_chrome) now brings aspect_rules_js + rules_python into the module graph. The chrome/chromedriver toolchain still needs neither at fetch time; rules_nodejs remains dev-only (JS consumers bring their own node toolchain, per docs/playwright_js.md).

0.1.0 — initial release

  • Initial release of Bazel rules for [Chrome for Testing]: a module extension that creates pinned @chrome + @chromedriver external repos, a chrome_toolchain (resolved via @rules_chrome//chrome:toolchain_type), and chrome_run / chromedriver_run launchers tuned for test automation.
  • Optional chrome/playwright sub-module: playwright_chrome_py_test and playwright_chrome_js_test macros wire @chrome into a Playwright launchPersistentContext with a Bazel-managed --user-data-dir. The default bazel_dep stays a zero-cost chrome+chromedriver toolchain — rules_python / aspect_rules_js costs only apply when the sub-module is loaded.
  • Pre-tag hardening: bumped pins, added workspace-mode + refresher tests, plus Playwright (py + node) smoke tests against the @chrome launcher.

← All modules