>[!warning]- Info
> Author:
[email protected]
> Publish date: 2026/07/02
> Last update: 2026/08/07
> Status: WIP
> Revision: 19
> 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 (`interaction`, `user_education`, `permissions`…), tackle the large `base/` infrastructure split, then complete remaining specialized directories.
> - **Biggest win:** `chrome/test/base/` — 131 source files, no `BUILD.gn` historically; extracting it unlocks downstream modularization of the entire tree.
## Background
Project Bedrock's primary mandate is fixing the `Browser` god-object in `chrome/browser`. 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: instead of one massive `BUILD.gn` reaching into child directories via relative source paths like `"base/testing_profile.cc"`, each subdirectory owns a target and the parent references it via a GN dep.
There are two complementary efforts:
| Effort | What it extracts | Code movement? |
|--------|-----------------|----------------|
| **This work — `chrome/test/` modularization** | Test *support* targets (`InProcessBrowserTest`, `TestingProfile`…) into per-subdirectory `BUILD.gn` files | None — `BUILD.gn` reorganization only |
| **Bedrock team — `chrome/browser/` modularization** | Test *case* source declarations out of `chrome/test/BUILD.gn` into the module's own `:browser_tests` targets | None — declarations move, files stay |
---
## Current State
```
chrome/test/
├── BUILD.gn ← monolith, shrinking
├── base/ ← Phase 2 (current — see progress table)
├── interaction/ ← ✅ done
├── user_education/ ← ✅ done
├── permissions/ ← ✅ done
├── views/ ← ✅ done
├── gpu/ ← ✅ done
├── v8/ ← ✅ done
├── enterprise/ ← Python-only, skip
├── accessibility/ ← standalone tool, skip
├── webapps/ ← test data only, skip
├── delayload/ ← Windows-only, defer
├── android/ ← BUILD.gn exists; 8 APK/Java sources remain in root
├── chromedriver/ ← BUILD.gn exists (standalone binary)
└── (fuzzing, mini_installer, payments, etc.) ← BUILD.gn exists, fully modularized
```
### Progress
| Phase | Status | Notes |
|---|---|---|
| Phase 1 — leaf modules (`interaction`, `user_education`, `permissions`, `views`, `gpu`, `v8`) | ✅ All landed | All temp monolith deps resolved |
| Phase 2a — `base/` thin targets (`:in_process_browser_test`, `:ui_test_utils`, `:test_launcher_utils`) + subdir dep cleanup | ✅ Landed | CLs 0–3 |
| Phase 2b — `:in_process_browser_test_mixin`, `:chrome_test_launcher` | ✅ Landed | |
| Phase 2b — `:testing_profile` thin target | ✅ Landed | [CL4](https://chromium-review.googlesource.com/8089948) |
| Phase 2b — `:testing_profile` full + `:testing_browser_process` | ✅ Landed | [CL5](https://chromium-review.googlesource.com/8089949); uses `allow_circular_includes_from` — see below |
| Phase 2b — `:testing_profile` Pattern A split (header+impl) | ✅ Landed | [CL6](https://chromium-review.googlesource.com/8094642); breaks extensions cycle; cros ash cycle deferred |
| Phase 2b — Forward-declare `ScopedCrosSettingsTestHelper` in testing_profile.h | ✅ Landed | [CL7](https://chromium-review.googlesource.com/8094905); fully removes ash/settings dep from header target |
| Phase 2c — `:profile_lifecycle_test_utils` | ✅ Landed | [CL8](https://chromium-review.googlesource.com/8094906); BUILD.gn-only; profile_waiter + profile_deletion_observer + profile_destruction_waiter |
| Phase 2c — `:browser_window_waiters` | ✅ Landed | [CL9](https://chromium-review.googlesource.com/8094907); browser_closed_waiter + browser_created_waiter |
| Phase 2c — `:browser_feature_test_waiters`, `:chrome_test_scoped_utils`; finalize `:test_launcher_utils` | ✅ Landed | [CL10](https://chromium-review.googlesource.com/8100982); autocomplete_change_observer, find_result_waiter, search_test_utils, scoped_browser_locale; `.cc` added to thin test_launcher_utils; `tracing` kept in monolith per reviewer; later extracted separately as CL11b |
| Phase 2c — `test_switches` + `test_theme_provider` relocated to `//chrome/test/base/` | ✅ Landed | [CL11a](https://chromium-review.googlesource.com/8140005); BUILD.gn-only |
| Phase 2c — `:tracing`, `:web_feature_histogram_tester`, `:web_ui_test_data_source`, `:scoped_channel_override` | ✅ Landed | [CL11b](https://chromium-review.googlesource.com/8138555)–[11e](https://chromium-review.googlesource.com/8138558); reland of 11e fixed missing `branding_buildflags` dep |
| Phase 2c — `:platform_browser_test`, `:scoped_metrics_service_for_synthetic_trials`, `:chrome_render_view_host_test_harness`, `:devtools_coverage`, `:chrome_test_utils` thin header | ✅ Landed | CL12a–13a; BUILD.gn-only; Windows validated clean |
| Phase 2c — `:test_browser_window` + `:dialog_test_browser_window` + `:test_browser_window_aura` (CL14b); BWI refactor (CL14a) | ✅ Landed | BUILD.gn-only; non-android |
| Phase 2c — `:web_ui_mocha_browser_test` + `:android_ui_test_utils` (CL15a) | 📬 CQ dry run | android_ui_test_utils split as own target to preserve dep chain |
| Phase 2c — `:save_desktop_snapshot` (CL16a); `:test_chrome_web_ui_controller_factory` (CL16b) | 🔨 local | Both from `test_support_ui`; BUILD.gn-only; webrtc headers accessible via //content/public/browser transitively |
| Phase 2c — remaining `base/` sources | ⬜ | `interactive_test_utils` cluster, `in_process_browser_test` full impl, platform files, `chrome_render_view_test` (blocked), `chrome_test_suite`/`chrome_unit_test_suite` (deferred) |
| Phase 3 — remaining dirs (`android/` leftovers…) | ⬜ | After `base/` complete |
| Phase 3a — Monolith → thin aggregator | ⬜ | After ALL sources extracted |
| Phase 3b — Consumer migration + monolith deletion | ⬜ | After 3a; requires coordination |
> [!tip] Phase 2a thin targets
> `:in_process_browser_test` and `:ui_test_utils` are currently header-only. Their `.cc` implementations remain in the monolith until the pre-existing cycle `//chrome/browser/ui:test_support → //chrome/test:test_support_ui` is broken. `:test_launcher_utils` is now fully extracted (CL10).
---
## chrome/test/base — Extraction Status
### Already extracted
| Target | Files | Notes |
|--------|-------|-------|
| `:test_launcher_utils` | `test_launcher_utils.{cc,h}` | Full (CL10); previously header-only |
| `:in_process_browser_test` | `in_process_browser_test.h` | Thin header-only (non-android); `.cc` pending cycle fix |
| `:ui_test_utils` | `ui_test_utils.h` | Thin header-only (non-android); `.cc` pending cycle fix |
| `:in_process_browser_test_mixin` | `in_process_browser_test_mixin.{cc,h}` | Full |
| `:chrome_test_launcher` | `chrome_test_launcher.{cc,h}` | Full |
| `:testing_profile` | `testing_profile.h`, `fake_profile_manager.h`, `testing_profile_key.h`, `testing_profile_manager.h` | Header-only; `allow_circular_includes_from` entries in TBP pending removal (see below) |
| `:testing_profile_impl` | `testing_profile.cc`, `fake_profile_manager.cc`, `testing_profile_key.cc`, `testing_profile_manager.cc` | Full impl |
| `:testing_browser_process` | `testing_browser_process.{cc,h}`, `testing_browser_process_platform_part.{cc,h}` | Full; has 3 remaining `allow_circular_includes_from` (see below) |
| `:profile_lifecycle_test_utils` | `profile_{waiter,deletion_observer,destruction_waiter}.{cc,h}` | Full |
| `:browser_window_waiters` | `browser_{closed,created}_waiter.{cc,h}` | Full; see improvement note below |
| `:browser_feature_test_waiters` | `autocomplete_change_observer.{cc,h}`, `find_result_waiter.{cc,h}`, `search_test_utils.{cc,h}` | Full (CL10) |
| `:chrome_test_scoped_utils` | `scoped_browser_locale.{cc,h}` | Full (CL10) |
| `:test_switches` | `test_switches.{cc,h}` | Full (CL11a); relocated from `//chrome/test:test_switches` |
| `:test_theme_provider` | `test_theme_provider.{cc,h}` | Full (CL11a); relocated from `//chrome/test:test_theme_provider`; toolkit_views only |
| `:tracing` | `tracing.{cc,h}` | Full (CL11b) |
| `:web_feature_histogram_tester` | `web_feature_histogram_tester.{cc,h}` | Full (CL11c) |
| `:web_ui_test_data_source` | `web_ui_test_data_source.{cc,h}` | Full (CL11d) |
| `:scoped_channel_override` | `scoped_channel_override.h` + platform `.cc` files | Full (CL11e); chrome-branded; mac/win/posix-non-android-non-cros |
| `:platform_browser_test` | `platform_browser_test.h` | Thin header-only (CL12a); non-android; aliases `:in_process_browser_test` |
| `:scoped_metrics_service_for_synthetic_trials` | `scoped_metrics_service_for_synthetic_trials.{cc,h}` | Full (CL12b) |
| `:chrome_render_view_host_test_harness` | `chrome_render_view_host_test_harness.{cc,h}` | Full (CL12c) |
| `:devtools_coverage` | `devtools_agent_coverage_observer.{cc,h}`, `devtools_listener.{cc,h}` | Full (CL12d); non-android |
| `:chrome_test_utils` | `chrome_test_utils.h` | Thin header-only (CL13a); `.cc` stays in monolith — TBP dep cycle; deferred full extraction |
| `:test_browser_window` | `test_browser_window.{cc,h}`, `dialog_test_browser_window.{cc,h}` | Full (CL14b); non-android; preceded by BWI refactor CL14a |
| `:test_browser_window_aura` | `test_browser_window_aura.{cc,h}` | Full (CL14b); use_aura only |
| `:web_ui_mocha_browser_test` | `web_ui_mocha_browser_test.{cc,h}` | Full (CL15a); android/desktop conditional public_deps |
| `:android_ui_test_utils` | `android/android_ui_test_utils.{cc,h}` | Full (CL15a); android only; split as own target to fix dep chain |
| `:save_desktop_snapshot` | `save_desktop_snapshot.{cc,h}` | Full (CL16a); non-android; webrtc headers accessible via `//content/public/browser` transitively |
| `:test_chrome_web_ui_controller_factory` | `test_chrome_web_ui_controller_factory.{cc,h}` | Full (CL16b); non-android |
### Progress estimate (2026-08-07, after CL16a+16b local)
| Metric | Value |
|--------|-------|
| Total `.cc/.h/.mm` files in `chrome/test/base/` | 153 |
| Still referenced in monolith (`chrome/test/BUILD.gn`) | ~101 |
| Browsertest / entry-point files (stay in monolith) | ~22 |
| Deferred (`chrome_test_suite` group) | ~5 |
| Blocked by deferred | ~4 pairs |
| **Practically extractable, remaining** | **~35–40 pairs** |
| **Overall extraction progress** | **~54%** |
| Estimated remaining CL-units before Phase 3 | **10–16** |
The most complex remaining work is the `interactive_test_utils` cluster (desktop + android variants) and the full implementations of `in_process_browser_test`, `mixin_based_in_process_browser_test`, and `ui_test_utils`.
### Phase 2c — cluster map (audited 2026-07-14)
Dep analysis done by reading all remaining `.h`/`.cc` files and cross-referencing with `gn gen --ide=json` project.json for precise header→target resolution.
#### Future improvement: `:browser_window_waiters` → separate `:test_browser_window` target
`test_browser_window.{cc,h}` and `dialog_test_browser_window.{cc,h}` also include `browser_collection_observer.h` and thus dep on `//chrome/browser/ui/browser_window`, same as the two waiters. However, `test_browser_window.h` carries **much heavier** additional deps:
- `chrome/browser/ui/browser.h` — full `Browser` class
- `chrome/browser/ui/autofill/test/test_autofill_bubble_handler.h`
- `chrome/browser/ui/dialogs/browser_dialogs.h`
- `chrome/browser/ui/location_bar/location_bar.h`
- `chrome/browser/ui/translate/partial_translate_bubble_model.h`
And `dialog_test_browser_window.h` includes `test_browser_window.h` directly. Both are non-android only.
**Decision:** keep `:browser_window_waiters` lightweight (all-platform, waiter-only). Extract `test_browser_window` + `dialog_test_browser_window` as a **separate** `:test_browser_window` target (non-android, heavier Browser+UI dep footprint). Do not merge into `:browser_window_waiters` — that would force the heavy Browser class on consumers that only need the waiters.
#### Forced cluster: `:test_suites`
Files: `chrome_test_suite.{cc,h}` + `chrome_unit_test_suite.{cc,h}`
Reason: `chrome_unit_test_suite.h` inherits from `ChromeTestSuite` and includes `chrome_test_suite.h` directly — tightly coupled.
Heavy deps: `//chrome/app:test_support`, `//chrome/browser:browser_process`, `//chrome/browser/ui/webui:configs`, `//chrome/browser/ui:ui`, `//chrome/browser/profiles:profile_manager`, `//chrome/browser/lifetime`, `//chrome/common:constants`, `//chrome/test/base:testing_browser_process`, and more. Biggest lift remaining.
#### Separate targets despite similar names: `chrome_render_view_*`
- `chrome_render_view_host_test_harness` — browser-side, wraps `TestingProfile`; deps: `:testing_profile` + `//content/test:test_support`
- `chrome_render_view_test` — renderer-side, no `Profile`; deps: `//content/test:test_support`, `//services/service_manager/public/cpp:cpp`, `//third_party/blink/public/common:headers`
Different process boundary, different dep profiles → extract as separate targets.
#### Clean standalones (deps fully audited)
✅ CL10: `autocomplete_change_observer`, `find_result_waiter`, `search_test_utils` → `:browser_feature_test_waiters`; `scoped_browser_locale` → `:chrome_test_scoped_utils`.
✅ CL11b–11c: `tracing` → `:tracing`; `web_feature_histogram_tester` → `:web_feature_histogram_tester`.
✅ CL12a–13a: `platform_browser_test` (thin header), `scoped_metrics_service_for_synthetic_trials`, `chrome_render_view_host_test_harness`, `devtools_coverage`, `chrome_test_utils` (thin header; `.cc` deferred — TBP dep cycle).
#### ~~Complete existing thin target: `:test_launcher_utils`~~ ✅ Done in CL10
Added `test_launcher_utils.cc` — deps: `//chrome/common`, `//components/os_crypt/common:switches`, `//components/password_manager/core/browser:password_switches`, `//content/public/common`, `//ui/display`; aura-only: `//ui/wm`.
#### ~~Blocked: `chrome_test_utils`~~ ✅ Done in CL13a (thin header)
`chrome_test_utils.h` extracted as thin header-only target. The `.cc` stays in the monolith until the TBP dep-graph cycle is resolved via Pattern A split.
#### Suggested extraction order
1. ✅ `:browser_window_waiters` — [CL9](https://chromium-review.googlesource.com/8094907)
2. ✅ `:browser_feature_test_waiters` + `:chrome_test_scoped_utils` + `:test_launcher_utils` (complete) — [CL10](https://chromium-review.googlesource.com/8100982)
3. ✅ `:tracing` — [CL11b](https://chromium-review.googlesource.com/8138555); extracted as standalone (not merged into `:chrome_test_scoped_utils`)
4. ✅ `:web_feature_histogram_tester` — [CL11c](https://chromium-review.googlesource.com/8138556)
5. ✅ `:platform_browser_test` thin header — CL12a; unblocked `chrome_test_utils`
6. ✅ `:scoped_metrics_service_for_synthetic_trials` — CL12b
7. ✅ `:chrome_render_view_host_test_harness` — CL12c
8. ✅ `:devtools_coverage` — CL12d
9. ✅ `:chrome_test_utils` thin header — CL13a; `.cc` deferred (TBP dep cycle)
10. `chrome_render_view_test` — renderer-side; no Profile dep
11. `:test_browser_window` — non-android; `test_browser_window.{cc,h}` + `dialog_test_browser_window.{cc,h}`; heavier deps (Browser class, autofill, dialogs, translate)
12. `web_ui_mocha_browser_test` — blocked; needs `android_ui_test_utils` investigation
13. `:test_suites` (heaviest)
### Deferred — TBP `allow_circular_includes_from` cleanup
Three entries remain in `testing_browser_process`'s `allow_circular_includes_from` that need structural fixes before removal:
| Entry | Root cause | Fix |
|-------|-----------|-----|
| `testing_profile_impl` | `testing_profile_manager.cc` includes `testing_browser_process.h` | Remove/forward-declare TBP.h in `testing_profile_manager.cc` |
| `extensions:test_support` | `extensions:test_support` sources include `testing_browser_process.h` | TBP Pattern A split (thin header-only + impl) |
| `ash/settings:test_support`, `ash/system_web_apps/test_support` | Same — include `testing_browser_process.h` on cros | TBP Pattern A split |
The Pattern A split for TBP mirrors what was done for `testing_profile` in CL6-CL7: produce a thin `testing_browser_process` target with only the header deps it needs, and a `testing_browser_process_impl` that carries the heavy ash/extensions deps. The three targets that currently include `testing_browser_process.h` can then dep on the thin target directly, eliminating the cycles.
### Phase 2c — remaining `base/` sources to audit and extract
These files still live in `//chrome/test:test_support` sources. Each needs a dep audit before extraction.
| Files | Likely complexity | Notes |
|-------|------------------|-------|
| `chrome_test_utils.cc` | Low–medium | `.cc` still in monolith; blocked by TBP dep cycle; deferred until TBP Pattern A split |
| `chrome_render_view_test.{cc,h}` | Medium | Renderer-side; no Profile dep; next in line |
| `test_browser_window.{cc,h}`, `dialog_test_browser_window.{cc,h}` | Medium | Non-android; heavy Browser+UI deps |
| `web_ui_mocha_browser_test.{cc,h}` | Medium | Blocked; needs `android_ui_test_utils` investigation |
| `chrome_test_suite.{cc,h}`, `chrome_unit_test_suite.{cc,h}` | High | Test suite infrastructure; many deps; own planning session |
| `in_process_browser_test.cc`, `ui_test_utils.cc` | Blocked | Blocked by `//chrome/browser/ui:test_support → //chrome/test:test_support_ui` cycle; thin targets already extracted |
### Long-term blocker — `browser/ui:test_support` cycle
`:test_launcher_utils` was unblocked in CL10 — its `wm_core_switches.h` include is Aura-guarded, so `//ui/wm` could be added as a conditional dep with no cycle. The two remaining thin targets (`:in_process_browser_test`, `:ui_test_utils`) still have their `.cc` impls stuck in the monolith because moving them would require resolving:
```
//chrome/browser/ui:test_support → //chrome/test:test_support_ui → (would dep on) //chrome/browser/ui
```
Breaking this requires either a Pattern A split of `//chrome/browser/ui:test_support` or a pre-extraction of whatever `//chrome/browser/ui:test_support` needs from `test_support_ui`. Deferred until the Bedrock team's `chrome/browser/ui` modularization creates a natural opportunity.
---
## BUILD.gn Patterns
### New per-directory `BUILD.gn`
```gn
# chrome/test/interaction/BUILD.gn
source_set("test_support") {
testonly = true
sources = [
"interactive_browser_test.cc",
"interactive_browser_test.h",
# ...
]
public_deps = [
"//ui/base/interaction:interaction",
]
deps = [
"//chrome/test/base:in_process_browser_test",
"//chrome/browser/ui",
"//content/public/test:test_support",
]
# testonly=true prevents production code from depending here regardless
# of visibility.
visibility = [
"//chrome/browser/*",
"//chrome/test/*",
]
}
```
### Root `chrome/test/BUILD.gn` after modularization
```gn
static_library("test_support") {
testonly = true
# Before: "interaction/interactive_browser_test.cc", ...
# After: dep on the child target
public_deps = [
"//chrome/test/base:test_support",
"//chrome/test/interaction:test_support",
"//chrome/test/user_education:test_support",
# ...
]
}
```
### Validation commands
```sh
gn check out/linux # desktop
gn check out/cros # ChromeOS (catches ash/extensions include cycles)
gn check out/android # Android
```
Always run all three before uploading — platform-specific deps can hide gn check
failures that only surface in one config. Windows (`out/win-arm64-dbg`) has been
separately validated for CL11x–13a and found clean; no Windows-specific deps or
conditional guards were missing.
---
## Handling Circular Dependencies
### Background: dep-graph cycle vs. include cycle
GN cycle detection operates on the **dep graph** (`deps`/`public_deps` entries),
not on `#include` lines. Two targets can mutually include each other's headers at
compile time while keeping the dep graph a DAG — as long as only one dep edge exists
and `allow_circular_includes_from` is declared.
> [!info] Key insight
> `allow_circular_includes_from` is a **header-include permission** — invisible to the dep graph. If target A has B in its direct deps, A can declare `allow_circular_includes_from = ["//B"]` to permit B's sources to `#include` A's headers. GN sees a DAG, `gn check` stays green.
### Case 1 — `interaction:interaction` / `test_support_ui` (include cycle)
`test_support_ui` deps on `interaction:interaction` (via `public_deps`), but
interaction source files include headers still compiled inside `test_support_ui`
(`in_process_browser_test.h`, etc.). A naïve reading suggests a cycle — but it
isn't, because `interaction:interaction` has **no dep edge back** to `test_support_ui`.
```
test_support_ui ──public_deps──► interaction:interaction
test_support_ui ──allow_circular_includes_from──► interaction:interaction
↑ interaction's files may #include test_support_ui headers
```
A related issue: `interaction:interaction` needed `test_switches.h` from `test_support`.
Adding `//chrome/test:test_support` as a dep would close a true dep-graph cycle
(`test_support_ui → interaction → test_support → test_support_ui`). Fix: extract
`test_switches.{h,cc}` into a thin `//chrome/test:test_switches` with zero deps.
> [!warning]
> `allow_circular_includes_from` cannot rescue a **true dep-graph cycle** — GN errors regardless. True cycles require structural fixes (pre-extraction of a thin target, Pattern A split, etc.).
### Case 2 — `testing_profile` / `testing_browser_process` extraction (reverse-dep problem)
When extracting `testing_profile` and `testing_browser_process` into standalone
targets, `gn check` failed on three ash targets and one extensions target whose
source files include `testing_profile.h` or `testing_browser_process.h`:
| Target | Includes |
|---|---|
| `//chrome/browser/ash/settings:test_support` | both `testing_profile.h` and `testing_browser_process.h` |
| `//chrome/browser/ash/system_web_apps/test_support` | `testing_browser_process.h` (×2 files) |
| `//chrome/browser/extensions:test_support` | `testing_browser_process.h` |
Adding a direct dep from these targets to the new targets would create a dep-graph
cycle because `testing_profile` and `testing_browser_process` *already depend on*
those ash/extensions targets.
**Interim fix:** `allow_circular_includes_from` in the extracted targets:
```gn
source_set("testing_profile") {
allow_circular_includes_from = [] # initialize; extended in blocks below
if (enable_extensions_core) {
deps += [ "//chrome/browser/extensions:test_support" ]
# TODO(crbug.com/353332589): Remove with Pattern A split.
allow_circular_includes_from += [ "//chrome/browser/extensions:test_support" ]
}
if (is_chromeos) {
public_deps += [ "//chrome/browser/ash/settings:test_support" ]
# TODO(crbug.com/353332589): Remove with Pattern A split.
allow_circular_includes_from += [ "//chrome/browser/ash/settings:test_support" ]
}
}
source_set("testing_browser_process") {
allow_circular_includes_from = [ "//chrome/test/base:testing_profile" ] # existing
if (enable_extensions_core) {
deps += [
"//chrome/browser/extensions",
"//chrome/browser/extensions:test_support", # new direct dep (was only transitive)
]
# TODO(crbug.com/353332589): Remove with Pattern A split.
allow_circular_includes_from += [ "//chrome/browser/extensions:test_support" ]
}
if (is_chromeos) {
deps += [
"//chrome/browser/ash/policy/core",
"//chrome/browser/media_galleries",
"//chrome/browser/ash/settings:test_support", # new
"//chrome/browser/ash/system_web_apps/test_support", # new
]
# TODO(crbug.com/353332589): Remove with Pattern A split.
allow_circular_includes_from += [
"//chrome/browser/ash/settings:test_support",
"//chrome/browser/ash/system_web_apps/test_support",
]
}
}
```
**Key constraint:** The target listed in `allow_circular_includes_from` must be
a **DIRECT** dep of the declaring target. If it was only transitive before, add it
as a direct dep first.
**CL6 — Pattern A split (`chromod/chrome-test-base-6`):** Splits `testing_profile` into:
- `testing_profile` — thin header-only (`.h` only; on cros, still needs `ash/settings:test_support` because `testing_profile.h` has a `ScopedCrosSettingsTestHelper` by-value member)
- `testing_profile_impl` — full `.cc` + heavy deps (ash, extensions)
Result: `extensions:test_support` can now dep directly on `testing_profile` (header-only) — the extensions `allow_circular_includes_from` entry in `testing_profile` is removed. The cros case (`ash/settings:test_support`) still requires `allow_circular_includes_from` because `testing_profile.h` directly includes from `ash/settings:test_support` (preventing `ash/settings` from deping back).
**Remaining deferred:**
- *cros*: Forward-declare `ScopedCrosSettingsTestHelper` in `testing_profile.h`, use `unique_ptr` for the member → header dep on `ash/settings:test_support` moves to `.cc` → `ash/settings:test_support` can dep on `testing_profile` directly → remove `allow_circular_includes_from` from `testing_profile` and TBP.
- *all platforms*: Remove `testing_profile_manager.cc`'s include of `testing_browser_process.h` → TBP can drop `allow_circular_includes_from = ["testing_profile_impl"]`.
---
## Explicit Dep Audit (required for "done")
After extracting a new module, a required follow-up step is a dep scope audit:
find every external consumer of the new module's headers and narrow `public_deps`
to `deps` wherever safe. Without this step, the new module leaks transitively
through the monolith just as the original sources did.
### Checklist
1. For each header in the new module, run `gn refs` to find all external GN targets.
2. For each depending target: check whether any of its *own* public headers include
the new module's headers. If not, move the new module from `public_deps` to `deps`.
3. Where it must stay in `public_deps`, add a `TODO(crbug.com/353332589)` naming
the specific public header that blocks the cleanup.
### Example: `chrome/test/views`
`test_support_ui`: `//chrome/test/views` moved `public_deps` → `deps`. Safe because
`in_process_browser_test.h` (the public header) includes no views headers.
`test_support`: `//chrome/test/views` stays in `public_deps` — blocked by
`browser_with_test_window_test.h`, which uses `ChromeTestViewsDelegate<>` as a
concrete member field (full definition required). Unblocks when that header is
componentized in Phase 2c.
---
## References
- [Project Bedrock design doc](https://chromium.googlesource.com/chromium/src/+/main/docs/modularization.md)
- `chrome/test/interaction/BUILD.gn` — first CL in this effort; reference for BUILD.gn pattern
- `chrome/test/base/BUILD.gn` — active extraction target
- `chrome/test/BUILD.gn` — monolith, being reduced
- Bug tracker: [crbug.com/353332589](https://crbug.com/353332589)