>[!warning]- Info
> Author:
[email protected]
> Publish date: 2026/07/02
> Last update: 2026/07/02
> Status: WIP
> Revision: 1
> Visibility: Public
>[!note]- AI Assistance
> This document was drafted with the assistance of [Claude](https://claude.ai) (Anthropic). The technical content reflects work done by the author; Claude helped structure and articulate it.
# Modularizing `chrome/test`
A phased approach to decoupling `chrome/test` from its monolithic `BUILD.gn`, following the Bedrock modularization pattern.
> [!summary] TL;DR
> - **The problem:** `chrome/test/BUILD.gn` is ~11,000 lines — one of the largest files in the tree, aggregating sources from 26 subdirectories directly.
> - **The goal:** Give each logical subdirectory of `chrome/test` its own `BUILD.gn`, mirroring the ~105-file pattern already achieved in `chrome/browser/ui`.
> - **The approach:** Three phases — start with leaf modules that have soft boundaries and no dependents within `chrome/test` (`interaction`, `user_education`, `permissions`), then tackle the large `base/` infrastructure split, then complete the remaining specialized directories.
> - **Biggest win:** `chrome/test/base/` — 131 source files, no `BUILD.gn` today; extracting it unlocks downstream modularization of the entire tree.
## Background
Project Bedrock's primary mandate is fixing the `Browser` god-object in `chrome/browser`. But the structural half of that work — giving each subdirectory its own `BUILD.gn` and `OWNERS`, enforcing visibility, breaking circular deps — applies directly to `chrome/test`.
The pattern, as established by the bedrock team's recent CLs, is straightforward: instead of one massive `BUILD.gn` that reaches into child directories via relative source paths like `"base/testing_profile.cc"`, each subdirectory owns a target (`static_library` or `source_set`) and the parent `BUILD.gn` references it via a GN dep.
> [!info] Key Bedrock BUILD.gn principles
> Each module directory gets: its own `BUILD.gn` + `OWNERS`; targets declared with `testonly = true`; explicit `public_deps` for transitive headers; `visibility` scoped to known consumers to prevent accidental coupling.
### Two complementary efforts, one goal
There are two distinct but related modularization efforts in flight:
| Effort | What it extracts | Code movement? |
|--------|-----------------|----------------|
| **This work — `chrome/test/` modularization** | Test *support* targets (`InProcessBrowserTest`, `TestingProfile`, `InteractiveBrowserTestApi`…) into per-subdirectory `BUILD.gn` files | None — `BUILD.gn` reorganization only |
| **Bedrock team — `chrome/browser/` modularization** | Test *case* source declarations (files physically in `chrome/browser/<sub>/`) out of `chrome/test/BUILD.gn` and into the module's own `:unit_tests`/`:browser_tests` targets | None — declarations move, files stay |
Both efforts shrink `chrome/test/BUILD.gn`'s line count from opposite ends: this work removes test *support* declarations; `chrome/browser` modularization removes test *case* declarations. They are independent and can proceed in parallel.
---
## Current State
The directory has 26 subdirectories. Only 8 already have their own `BUILD.gn`. The rest have their sources directly listed in the 11,000-line root `BUILD.gn`.
```
chrome/test/
├── BUILD.gn ← 10,986 lines — the target
├── base/ ← 131 files, 2 MB, no BUILD.gn
├── interaction/ ← no BUILD.gn
├── user_education/ ← no BUILD.gn
├── enterprise/ ← no BUILD.gn
├── permissions/ ← no BUILD.gn
├── views/ ← no BUILD.gn
├── gpu/ ← no BUILD.gn
├── v8/ ← no BUILD.gn
├── accessibility/ ← no BUILD.gn
├── webapps/ ← no BUILD.gn
├── android/ ← BUILD.gn exists but 8 APK/Java sources still in root
├── chromedriver/ ← BUILD.gn exists (standalone binary)
├── fuzzing/ ← BUILD.gn exists
├── media_router/ ← BUILD.gn exists
├── mini_installer/ ← BUILD.gn exists
├── payments/ ← BUILD.gn exists
├── supervised_user/ ← BUILD.gn exists
├── variations/ ← BUILD.gn exists
└── data/ (static assets, 427 MB, no build)
```
> [!tip] Reference point
> `chrome/browser/ui/` — a fully modularized area — has **105 BUILD.gn files** across its subdirectories. `chrome/test` has 8 subdirectories with a `BUILD.gn`, but only 7 are verified fully modularized (0 sources remaining in root). `android/` is partial. The gap is the work.
---
## Code Nature Audit
Before planning the `BUILD.gn` reorganization, it's worth confirming: are files in `chrome/test/<subfolder>/` reusable test helpers and harnesses, or are they module-specific test cases that should actually live in `chrome/browser/<subfolder>/` alongside the code they test?
**The answer: all directories audited contain reusable test infrastructure.** No code movement to `chrome/browser/` is needed. The modularization work here is purely a `BUILD.gn` reorganization.
| Directory | `.cc` files | Helpers | Self-ref cases | Verdict |
|-----------|-------------|---------|----------------|---------|
| `base/` | 70 | 61 | 9 | Stay — core harness. Self-tests cover `InProcessBrowserTest`, `WebUIMocha`, etc. |
| `interaction/` | 20 | 9 | 11 | Stay — interactive test framework. Test cases test the framework itself, not `chrome/browser` features. |
| `user_education/` | 5 | 4 | 1 | Stay — reusable base classes + mocks for user education feature tests. |
| `permissions/` | 1 | 1 | 0 | Stay — `PermissionRequestManagerTestApi` helper only. |
| `views/` | 3 | 3 | 0 | Stay — `ChromeViewsTestBase`, `ChromeTestWidget`, `AccessibilityChecker`. |
| `enterprise/`, `webapps/` | 0 (Python) | — | — | No C++ sources. Python test scripts only. |
> [!info] Self-referential test cases vs. module-specific tests
> Files like `interactive_browser_test_browsertest.cc` in `chrome/test/interaction/` are test cases, but they test the **test harness itself** — analogous to a library's own unit tests. They belong in `chrome/test/interaction/` for the same reason library tests live alongside the library. They are not testing a `chrome/browser/` feature and should not be moved there.
>
> Contrast this with a file like `chrome/browser/accessibility/accessibility_labels_service_browsertest.cc`, which tests a specific browser feature. That file physically lives in `chrome/browser/accessibility/` and is only listed in `chrome/test/BUILD.gn` as a declaration — the `/modularize-chrome-browser` skill handles moving that declaration into `chrome/browser/accessibility/BUILD.gn:browser_tests`.
---
## Subdirectory Inventory & Priority
| Subdirectory | Status | Est. Files | Priority | Notes |
|---|---|---|---|---|
| `base/` | ❌ No `BUILD.gn` | 131 | P1 | Core test harness (`InProcessBrowserTest`, `TestingProfile`, `UiTestUtils`). Biggest win; unlocks everything else. |
| `interaction/` | ❌ No `BUILD.gn` | 20 .cc | P1 | Interactive test framework (9 helpers + 11 self-tests). Well-bounded, few external dependents. |
| `user_education/` | ❌ No `BUILD.gn` | 5 .cc | P1 | `InteractiveFeaturePromoTestApi` base class + mocks. Clear ownership boundary. |
| `enterprise/` | ❌ No `BUILD.gn` | Python only | P2 | No C++ sources — Python e2e test scripts. `BUILD.gn` will be minimal; include for completeness. |
| `permissions/` | ❌ No `BUILD.gn` | 1 .cc | P2 | `PermissionRequestManagerTestApi` — single helper file, trivial to extract. |
| `webapps/` | ❌ No `BUILD.gn` | Python only | P2 | No C++ sources — Python test scripts. Same situation as `enterprise/`. |
| `views/` | ❌ No `BUILD.gn` | 3 .cc | P2 | `ChromeViewsTestBase`, `ChromeTestWidget`, `AccessibilityChecker`. Non-Android only. |
| `accessibility/` | ❌ No `BUILD.gn` | ~10 | P3 | A11y test utilities. Lower coupling risk. |
| `gpu/` | ❌ No `BUILD.gn` | ~8 | P3 | GPU-specific test infra. Specialized but isolated. |
| `v8/` | ❌ No `BUILD.gn` | ~5 | P3 | V8 integration test support. Small, low risk. |
| `android/` | ⚠️ Partial | 100+ | P3 | Has `BUILD.gn` but 8 Java/APK source entries remain in root `BUILD.gn`. Needs completion. |
| `chromedriver/` | ✅ Done | 63 | — | Verified: 0 sources remain in root `BUILD.gn`. Fully self-contained standalone binary. |
| `payments/`, `supervised_user/`, `variations/`, `media_router/`, `fuzzing/`, `mini_installer/` | ✅ Done | 5–15 each | — | Verified: 0 sources remain in root `BUILD.gn` for any of these. Truly modularized. |
---
## Ordering Rationale
The priority table above is a topological ordering of the dependency graph within `chrome/test` — start at the leaves, work toward the root. Five criteria drove the ranking:
**1. Reverse dependency count (the key signal).** A subdirectory is a "leaf" if nothing else inside `chrome/test` includes its headers. `interaction/` and `user_education/` depend on `base/`, but `base/` does not depend on them. Extracting a leaf only requires updating one place — the root `BUILD.gn` — with no ripple into sibling directories. In contrast, `base/` is the opposite extreme: almost every other subdir depends on it, so it must be extracted after its consumers are settled.
**2. Feature coherence.** Directories containing test support for a single Chrome feature area (`interaction/`, `user_education/`, `permissions/`) have natural module boundaries — their contents are cohesive and their dep lists are narrow. Directories like `enterprise/`, whose tests touch multiple subsystems and carry heavy platform conditionals, are more complex to split cleanly and are deferred to a later phase.
**3. Dependency fan-out.** Some directories pull in a wide range of `//chrome/browser` subtargets. The narrower the fan-out, the easier the `BUILD.gn` deps section is to write correctly, and the less likely a `gn check` error will block the CL.
**4. Platform complexity.** Subdirectories with sources conditioned on many platforms (`is_android`, `is_chromeos`, `is_mac`, `is_win`) require more careful `BUILD.gn` authoring — each condition must be reproduced correctly in the new child file. `enterprise/` is the main example: it has significant per-platform variation.
**5. External consumer breadth (affects visibility design).** A header-inclusion audit across `chrome/browser/` reveals how broadly each subdirectory is consumed outside `chrome/test/`. This directly determines what `visibility` to set in each new `BUILD.gn`. Key finding: `base/` headers are included by hundreds of files across virtually every feature area — restricting its visibility would require an impossibly long allowlist. `interaction/` is consumed by many `*_interactive_uitest.cc` files across `chrome/browser/ui/views/`. `user_education/` and `permissions/` are narrower.
> [!info] Soft boundary vs. hard boundary
> A *soft boundary* is a directory that is already organized as a logical unit in the source tree (its files belong to one feature area, include a coherent set of headers) but has no `BUILD.gn`. A *hard boundary* is one already formalized with its own `BUILD.gn`. The modularization work is converting soft boundaries into hard ones, in leaf-to-root order.
---
## Proposed Phased Approach
> [!tip] No code movement
> This work is `BUILD.gn` reorganization only. No source files are moved or modified. Every file currently in `chrome/test/<subfolder>/` belongs there — the audit confirms they are all reusable test infrastructure, not module-specific test cases.
### Phase 1 — Leaf modules (interaction, user_education, permissions, views, webapps)
These subdirectories contain self-contained test support code for a single feature area. They have few or no reverse dependencies within `chrome/test` itself, making them the safest starting point.
- Create `BUILD.gn` per subdirectory with a `source_set("test_support")` or `static_library("test_support")` target
- Move source file declarations out of root `BUILD.gn` into each child
- Update root `BUILD.gn` to add `"//chrome/test/interaction:test_support"` etc. to deps
- Add `OWNERS` files, pointing to feature area owners
- Set `visibility = [ "//chrome/test:*", "//chrome/browser/ui/..." ]` appropriately
### Phase 2 — `chrome/test/base/` (the core infrastructure split)
131 files, 2 MB; `InProcessBrowserTest`, `TestingProfile`, `UiTestUtils`. This is the highest-impact and highest-risk step. `chrome/test/base/` is depended upon by nearly everything in `chrome/test` and many places outside it.
- **2a — Audit deps:** Map which files in `base/` depend on `//chrome/browser` vs. `//content` vs. platform — to spot and break any pre-existing circular deps before moving build targets
- **2b — Create `base/BUILD.gn`** with logical internal sub-targets:
- `source_set("browser_test_base")` — `in_process_browser_test.{cc,h}`
- `source_set("testing_profile")` — `testing_profile.{cc,h}`, mock browser process
- `source_set("ui_test_utils")` — `ui_test_utils.{cc,h}`
- `source_set("test_support")` — umbrella target, pulls in the above
- Platform guards (`is_android`, `is_chromeos`, `is_mac`) remain per-target inside `base/BUILD.gn`
> [!warning] Watch for circular deps
> If any `chrome/browser` non-test target pulls in headers from `chrome/test/base/`, that must be fixed (split into separate `:module` and `:impl` sub-targets, or the offending include removed) before the `BUILD.gn` split will pass `gn check`.
### Phase 3 — Enterprise, accessibility, gpu, v8
- Same pattern as Phase 1: one `BUILD.gn` per directory, a single `test_support` target
- `enterprise/` will need careful handling of policy mocking and conditional platform sources
- `gpu/` and `v8/` are small enough for a single `source_set`
- After this phase, the root `BUILD.gn` should be primarily a thin aggregator of `group()` targets — no direct source file paths into subdirs
---
## BUILD.gn Patterns to Follow
Canonical pattern for a new per-directory `BUILD.gn`, using `chrome/test/interaction/` as the example:
```gn
# chrome/test/interaction/BUILD.gn
import("//build/config/features.gni")
source_set("test_support") {
testonly = true
sources = [
"interactive_browser_test.cc",
"interactive_browser_test.h",
# ... rest of sources formerly listed in chrome/test/BUILD.gn
# as "interaction/interactive_browser_test.cc" etc.
]
public_deps = [
# Headers that consumers will include transitively
"//ui/base/interaction:interaction",
]
deps = [
"//chrome/test/base:browser_test_base",
"//chrome/browser/ui",
"//content/public/test:test_support",
]
# testonly=true prevents production code from depending on this
# regardless of visibility.
visibility = [
"//chrome/browser/*",
"//chrome/test/*",
]
}
```
Visibility declarations must reflect the actual consumer landscape established by the header-inclusion audit:
| Subdirectory | Consumer breadth | Recommended visibility |
|---|---|---|
| `base/` | Everywhere | Omit (public) — a restrictive allowlist would be unmaintainable |
| `interaction/` | Broad | `["//chrome/browser/*", "//chrome/test/*"]` |
| `user_education/` | Narrow | `["//chrome/browser/ui/*", "//chrome/test/*"]` |
| `permissions/` | Narrow | `["//chrome/browser/*", "//chrome/test/*"]` |
| `views/` | Moderate | `["//chrome/browser/ui/views/*", "//chrome/test/*"]` |
Then in the root `chrome/test/BUILD.gn`, the existing inline source list is replaced by a dep:
```gn
# chrome/test/BUILD.gn — after modularization
static_library("test_support") {
testonly = true
# Before: "interaction/interactive_browser_test.cc", ...
# After: a dep on the child target
public_deps = [
"//chrome/test/base:test_support", # Phase 2
"//chrome/test/interaction:test_support", # Phase 1
"//chrome/test/user_education:test_support", # Phase 1
# ...
]
}
```
> [!tip] GN validation
> After each subdirectory's `BUILD.gn` is created, run `gn check out/Default //chrome/test/...` to catch missing deps and circular dependencies before uploading a CL. Run `gn desc out/Default //chrome/test:test_support deps` to verify the dep graph looks correct.
---
## Risks & Constraints
| Risk | Likelihood | Mitigation |
|---|---|---|
| Circular deps in `base/` | High | Run `gn check` early. Audit includes in `base/*.h` before moving targets. Some headers may need to be split into `:module` and `:impl` sub-targets. |
| Platform conditionals scattered across root `BUILD.gn` | High | The root file has heavy `if (is_android)`, `if (is_chromeos)` guards. Each child `BUILD.gn` must carry the relevant subset — map them out before splitting. |
| External callers depending on `//chrome/test:test_support` | Medium | Keep the umbrella `test_support` target in the root `BUILD.gn` as a compatibility shim; it just lists `public_deps` on the new child targets. Do not break external consumers immediately. |
| `testonly` leaking into non-test targets | Low | All new targets must have `testonly = true`. GN will error if a non-testonly target tries to depend on them, which is the desired behavior. |
| Large CL review burden | Medium | Structure CLs as: (1) add new `BUILD.gn` — no behavior change, (2) move source references — still no behavior change, (3) update `OWNERS`. Three small CLs are easier to review than one massive one. |
---
## Suggested First CL
The lowest-risk, highest-signal first step is `chrome/test/interaction/`: it's a coherent feature area, its sources are clearly grouped, and it has no reverse dependents within `chrome/test/base/` (dependency goes the other way). A CL that:
1. Creates `chrome/test/interaction/BUILD.gn` with a `source_set("test_support")`
2. Moves the `interaction/*.{cc,h}` source declarations from the root `BUILD.gn` into it
3. Adds `"//chrome/test/interaction:test_support"` to `chrome/test:test_support`'s deps
4. Adds an `OWNERS` file
...would be the canonical first CL in this effort, immediately reviewable and non-breaking.
After landing that, `chrome/test/user_education/` follows the exact same pattern and can go in the same week. Then `base/` — after a dep audit — is the Phase 2 centerpiece CL.
> [!note] Coordinate with the bedrock team
> As `chrome/test` sources are extracted into per-subdirectory targets, the Bedrock team's `/modularize-chrome-browser` work will increasingly find that `//chrome/test:<subdir>:test_support` is the correct dep to add (rather than the monolithic `//chrome/test:test_support`). Aligning on naming conventions for the child targets early avoids a later sweep to rename them.
---
## Progress
| Subdirectory | CL 1 (test cases) | CL 2 (test support) |
|---|---|---|
| `interaction/` | ✅ landed | ⬜ planned |
| `user_education/` | ⬜ planned | ⬜ planned |
| `permissions/` | ⬜ planned | ⬜ planned |
| `views/` | ⬜ planned | ⬜ planned |
| `base/` | ⬜ Phase 2 | ⬜ Phase 2 |
---
## References
- [Project Bedrock design doc](https://chromium.googlesource.com/chromium/src/+/main/docs/modularization.md)
- `chrome/test/interaction/BUILD.gn` — created as the first CL in this effort
- `chrome/test/BUILD.gn` — modified to dep on new per-subdir targets