Skip to main content

Interactor

An Interactor provides the low level operations used by component drivers to manipulate and query the UI. Drivers delegate every action such as clicking, entering text or reading an attribute to an interactor. By swapping the interactor implementation, the same driver code works in different environments like unit tests running in JSDOM or end‑to‑end tests with Playwright.

Available interactors​

The project ships with several interactors:

  • DOMInteractor – runs against a DOM environment using @testing-library utilities. This is used for unit/integration tests.
  • ReactInteractor from @atomic-testing/react-core and the versioned React adapters – extensions of DOMInteractor that wrap interactions in React's act() helper so state updates are flushed correctly when testing React 18 or later.
  • VueInteractor from @atomic-testing/vue-3 – an extension of DOMInteractor that calls Vue's nextTick() after every interaction so reactive state settles before the next assertion.
  • AngularInteractor from @atomic-testing/angular-core (used by the versioned @atomic-testing/angular-20, -21 and -22 adapters) – an extension of DOMInteractor that awaits the app's ApplicationRef.whenStable() after every interaction, so change detection has settled before the next assertion. This works under both zone.js and zoneless change detection.
  • StorybookInteractor from @atomic-testing/storybook – an extension of DOMInteractor for driving component drivers inside a real-browser Storybook: play functions and stories running under @storybook/addon-vitest. Unlike the React/Vue/Angular interactors, it has no framework act()/nextTick() to hook into, so it settles after every interaction with a macrotask plus two animation frames instead, and dispatches through Storybook's instrumented userEvent so interactions show up in the Interactions panel.
  • PlaywrightInteractor – drives a Playwright Page object to execute tests in a real browser.

Building an interactor​

To build your own interactor, implement the Interactor interface from the core package. Most custom interactors extend an existing one and override only the behaviour that differs. The snippet below logs every click before delegating to DOMInteractor:

import { ClickOption, PartLocator } from '@atomic-testing/core';
import { DOMInteractor } from '@atomic-testing/dom-core';

export class LoggingInteractor extends DOMInteractor {
async click(locator: PartLocator, option?: Partial<ClickOption>): Promise<void> {
console.log('clicking', await this.innerHTML(locator));
await super.click(locator, option);
}
}

None of the framework createTestEngine factories β€” @atomic-testing/react-18/ react-19, @atomic-testing/vue-3, @atomic-testing/angular-20/-21/-22, @atomic-testing/playwright, or @atomic-testing/dom-core β€” accept a custom interactor; each one constructs its own hardcoded Interactor subclass internally (see e.g. packages/dom-core/src/createTestEngine.ts). To use a custom interactor, construct TestEngine (packages/core/src/TestEngine.ts) directly instead of calling createTestEngine. For a DOM-only interactor, that means reproducing what @atomic-testing/dom-core's createTestEngine does internally, with your interactor swapped in for DOMInteractor:

import { TestEngine } from '@atomic-testing/core';

import { LoggingInteractor } from './LoggingInteractor';

// Same shape as @atomic-testing/dom-core's own createTestEngine, with
// LoggingInteractor swapped in for DOMInteractor. `element` is whatever root
// node your component was already rendered into.
const testEngine = new TestEngine([], new LoggingInteractor(element), { parts: partDefinitions });

The cleanup callback (TestEngine's 4th constructor argument) is optional and defaults to a no-op if omitted. If your custom interactor needs React's act() or Vue's nextTick() wrapping too, extend ReactInteractor / VueInteractor instead of DOMInteractor, and mirror the locator/mount/cleanup logic from that framework's own createTestEngine (packages/react-core/src/createTestEngine.ts, packages/vue-3/src/createTestEngine.ts).