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