>[!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