Skip to main content

foundation-test-migration

Migrating unit tests to foundation unit tests using TestUniverse and devtools_foundation_module. Use when moving tests away from DOM-heavy helpers like describeWithEnvironment or describeWithMockConnection.

Quellinformationen

Repository
ChromeDevTools/devtools-frontend
Letzte Quellaktivität
13. Oktober 2026 um 11:20
Erkannte Sprache von SKILL.md
Englisch
Sterne
4.085
Forks
733

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
foundation-test-migration
description
Migrating unit tests to foundation unit tests using TestUniverse and devtools_foundation_module. Use when moving tests away from DOM-heavy helpers like describeWithEnvironment or describeWithMockConnection.
# Foundation Test Migration This skill provides guidance on migrating DevTools unit tests to the "foundation" pattern, which is lighter, avoids global singletons, and is compatible with both Node and Browser runtimes (Isomorphic). ## Core Concepts ### devtools_foundation_module (BUILD.gn) Use this template for modules that should be platform-agnostic. - **Enforcement**: It type-checks the code against both Browser and Node APIs. - **Constraint**: Avoid direct DOM access (like `FileReader` or layout metrics) or heavy DevTools dependencies. Use `Universe` to access services. ### TestUniverse `TestUniverse` is the preferred way to setup a DevTools-like environment for tests without global singletons. - **Lazy**: Dependencies (targetManager, settings, workspace, etc.) are only created when accessed via getters. - **Scoped**: Does not install instances as globals (avoids `Common.Settings.Settings.instance()`). - **Explicit**: Uses `DevToolsContext` to manage dependencies. ## Migration Guide ### 1. Replace Heavy Helpers Avoid `describeWithEnvironment` or `describeWithMockConnection`. Instead, use standard `describe` and initialize environment hooks at the top-level `describe` block. **Crucial:** Without `setupRuntimeHooks`, tests creating SDK models will crash due to uninitialized experiments (e.g. `capture-node-creation-stacks`). ```ts import {setupLocaleHooks} from '../../testing/LocaleHelpers.js'; import {setupSettingsHooks} from '../../testing/SettingsHelpers.js'; import {setupRuntimeHooks} from '../../testing/RuntimeHelpers.js'; import {TestUniverse} from '../../testing/TestUniverse.js'; describe('MyComponent', () => { setupLocaleHooks(); setupSettingsHooks(); setupRuntimeHooks(); let universe: TestUniverse; beforeEach(() => { universe = new TestUniverse(); }); }); ``` ### 2. Access Dependencies via Universe Instead of using `SDK.TargetManager.TargetManager.instance()`, use `universe.targetManager`. Use `universe.createTarget()` instead of the global `createTarget`. ```ts // OLD const target = createTarget(); const targetManager = SDK.TargetManager.TargetManager.instance(); // NEW const target = universe.createTarget({url: urlString`http://example.com/`}); const targetManager = universe.targetManager; ``` #### Mocking CDP Traffic If the test used `describeWithMockConnection` and global `setMockConnectionResponseHandler` to stub CDP traffic, you can migrate this by passing a `MockCDPConnection` to `universe.createTarget()`. This allows stubbing out CDP traffic scoped to a specific target tree rather than globally. ```ts import {MockCDPConnection} from '../../testing/MockCDPConnection.js'; const cdpConnection = new MockCDPConnection([ { method: 'Network.getResponseBody', response: () => ({body: 'mocked body', base64Encoded: false}), } ]); const target = universe.createTarget({connection: cdpConnection}); ``` > [!TIP] > **Legacy Target URLs**: `EnvironmentHelpers.createTarget()` defaults to `http://example.com/`. `TestUniverse.createTarget()` defaults to `about:blank`. If your test asserts against specific URLs, remember to pass the URL explicitly. ### 3. Dealing with Legacy Singletons & Helpers For large integration tests, you may encounter code that strictly calls `SomeModule.instance()` or uses complex legacy helpers (like `createWorkspaceProject`). **Do not use `setUpEnvironment()`** as it will create disconnected singletons. Instead, wire the singletons to your `TestUniverse` and stub the globals to bridge legacy helpers: ```ts beforeEach(async () => { universe = new TestUniverse(); const {targetManager, workspace, settings} = universe; // 1. Stub globals so legacy helpers use TestUniverse components sinon.stub(Workspace.Workspace.WorkspaceImpl, 'instance').returns(workspace); sinon.stub(SDK.TargetManager.TargetManager, 'instance').returns(targetManager); sinon.stub(Common.Settings.Settings, 'instance').returns(settings); // 2. Initialize interdependent singletons in the correct order SDK.NetworkManager.MultitargetNetworkManager.instance({forceNew: true, targetManager}); // 3. Now safe to use legacy helpers that rely on the above instances await createWorkspaceProject(urlString`file:///path/to/overrides`, [...]); }); ``` ## Pitfalls & Troubleshooting ### Strict Equality in Protocol Responses (`assert.deepEqual`) Protocol requests/responses dynamically generated by Chrome (like Network conditions) can vary slightly (e.g., adding `connectionType`, `urlPattern`). - **Problem**: `assert.deepEqual(rules, [{...}])` will flake if unrequested fields are present. - **Solution**: Use `sinon.spy()` for handlers and assert with `sinon.assert.calledOnceWithMatch`: ```ts const emulateSpy = sinon.spy(); connection.setSuccessHandler('Network.emulateNetworkConditionsByRule', request => { emulateSpy(request); return {ruleIds: []}; }); // Matches only the fields you care about, ignoring extra protocol fields sinon.assert.calledOnceWithMatch(emulateSpy, { offline: false, matchedNetworkConditions: [sinon.match({ downloadThroughput: 1000 })], }); ``` ### DOM Globals (`ReferenceError: FileReader is not defined`) Foundation tests run in Node.js where `window`, `FileReader`, and certain DOM string encodings don't exist. - **Fix**: Use isomorphic equivalents (e.g. `btoa()`, `Uint8Array`). - **Fix**: Abstract the APIs that are different between Node.js and Browser via `front_end/core/platform/api/HostRuntime.ts`. ### Initialization Order Lockups If a test times out (5000ms exceeded), it is usually an unhandled promise caused by a missing singleton. Double-check the constructor of the failing manager to see which `instance()` it listens to, and ensure that dependent singleton was created *first*. ## BUILD.gn Changes When a module and its tests are ready, update `BUILD.gn`: 1. Change `devtools_module` to `devtools_foundation_module` for both the module and its unittests. 2. Ensure the tests are grouped under a `foundation_unittests` target in the parent `BUILD.gn`. ```gn # front_end/my_module/BUILD.gn devtools_foundation_module("my_module") { sources = [ "MyModule.ts" ] ts_deps = [ "../../core/common:bundle" ] } devtools_foundation_module("unittests") { testonly = true sources = [ "MyModule.test.ts" ] ts_deps = [ ":my_module", "../../testing", ] } ``` ## Verification Foundation tests must pass in both Node.js and Browser runtimes. ### Run in Node.js ```bash ./dt test front_end/core/sdk/NetworkManager.test.ts --node-unit-tests ``` ### Run in Browser ```bash ./dt test front_end/core/sdk/NetworkManager.test.ts ```
Auf GitHub ansehen