Overview for testing

@angular/cdk/testing provides infrastructure to help with testing Angular components.

A component harness is a class that lets a test interact with a component via a supported API. Each harness's API interacts with a component the same way a user would. By using the harness API, a test insulates itself against updates to the internals of a component, such as changing its DOM structure. The idea for component harnesses comes from the PageObject pattern commonly used for integration testing.

@angular/cdk/testing contains infrastructure for creating and using component test harnesses. You can create test harnesses for any component, ranging from small reusable widgets to full application pages.

The component harness system supports multiple testing environments. You can use the same harness implementation in both unit and end-to-end tests. This means that users only need to learn one API, and component authors don't have to maintain separate unit and end-to-end test implementations.

Common component libraries, in particular, benefit from this infrastructure due to the wide use of their components. Providing a test harness allows the consumers of a component to write tests that avoid dependencies on any private implementation details. By capturing these implementation details in a single place, consumers can more easily update to new library versions.

This document provides guidance for three types of developers:

  1. Test authors
  2. Component harness authors
  3. Harness environment authors

Since many developers fall into only one of these categories, the relevant APIs are broken out by developer type in the sections below.

Test authors are developers using component harnesses written by someone else to test their application. For example, this could be an app developer who uses a third-party menu component and needs to interact with the menu in a unit test.

ComponentHarness is the abstract base class for all component harnesses. Every harness extends this class. All ComponentHarness subclasses have a static property, hostSelector, that matches the harness class to instances of the component in the DOM. Beyond that, the API of any given harness is specific to its corresponding component; refer to the component's documentation to learn how to use a specific harness.

These classes correspond to different implementations of the component harness system with bindings for specific test environments. Any given test must only import one of these classes. Karma-based unit tests should use the TestbedHarnessEnvironment, while Selenium WebDriver-based end-to-end tests should use the SeleniumWebDriverHarnessEnvironment. Additional environments require custom bindings; see API for harness environment authors for more information on alternate test environments.

These classes are primarily used to create a HarnessLoader instance, and in certain cases, to create ComponentHarness instances directly.

TestbedHarnessEnvironment offers the following static methods:

Method Description
loader(fixture: ComponentFixture<unknown>): HarnessLoader Gets a HarnessLoader instance for the given fixture, rooted at the fixture's root element. Should be used to create harnesses for elements contained inside the fixture
documentRootLoader(fixture: ComponentFixture<unknown>): HarnessLoader Gets a HarnessLoader instance for the given fixture, rooted at the HTML document's root element. Can be used to create harnesses for elements that fall outside of the fixture
harnessForFixture<T extends ComponentHarness>(fixture: ComponentFixture<unknown>, harnessType: ComponentHarnessConstructor<T>): Promise<T> Used to create a ComponentHarness instance for the fixture's root element directly. This is necessary when bootstrapping the test with the component you plan to load a harness for, because Angular does not set the proper tag name when creating the fixture.

In most cases, you can create a HarnessLoader in the beforeEach block using TestbedHarnessEnvironment.loader(fixture) and then use that HarnessLoader to create any necessary ComponentHarness instances. The other methods cover special cases as shown in this example:

Consider a reusable dialog-button component that opens a dialog on click, containing the following components, each with a corresponding harness:

  • MyDialogButton (composes the MyButton and MyDialog with a convenient API)
  • MyButton (a simple button component)
  • MyDialog (a dialog appended to document.body by MyDialogButton upon click)

The following code loads harnesses for each of these components:

let fixture: ComponentFixture<MyDialogButton>;
let loader: HarnessLoader;
let rootLoader: HarnessLoader;

beforeEach(() => {
  fixture = TestBed.createComponent(MyDialogButton);
  loader = TestbedHarnessEnvironment.loader(fixture);
  rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
});

it('loads harnesses', async () => {
  // Load a harness for the bootstrapped component with `harnessForFixture`
  dialogButtonHarness =
      await TestbedHarnessEnvironment.harnessForFixture(fixture, MyDialogButtonHarness);

  // The button element is inside the fixture's root element, so we use `loader`.
  const buttonHarness = await loader.getHarness(MyButtonHarness);

  // Click the button to open the dialog
  await buttonHarness.click();

  // The dialog is appended to `document.body`, outside of the fixture's root element,
  // so we use `rootLoader` in this case.
  const dialogHarness = await rootLoader.getHarness(MyDialogHarness);

  // ... make some assertions
});

SeleniumWebDriverHarnessEnvironment has an API that offers a single static method:

Method Description
loader(): HarnessLoader Gets a HarnessLoader instance for the current HTML document, rooted at the document's root element.

Since Selenium WebDriver does not deal with fixtures, the API in this environment is simpler. The HarnessLoader returned by the loader() method should be sufficient for loading all necessary ComponentHarness instances.

Please note that harnesses may not behave exactly the same in all environments. There will always be some difference between the real browser-generated event sequence when a user clicks or types in an element, versus the simulated event sequence generated in unit tests. Instead, the CDK makes a best effort to normalize the behavior and simulate the most important events in the sequence.

Instances of this class correspond to a specific DOM element (the "root element" of the loader) and are used to create ComponentHarness instances for elements under this root element.

HarnessLoader instances have the following methods:

Method Description
getChildLoader(selector: string): Promise<HarnessLoader> Searches for an element matching the given selector below the root element of this HarnessLoader, and returns a new HarnessLoader rooted at the first matching element
getAllChildLoaders(selector: string): Promise<HarnessLoader[]> Acts like getChildLoader, but returns an array of HarnessLoader instances, one for each matching element, rather than just the first matching element
getHarness<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): Promise<T> Searches for an instance of the given ComponentHarness class or HarnessPredicate below the root element of this HarnessLoader and returns an instance of the harness corresponding to the first matching element
getAllHarnesses<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T> | HarnessPredicate<T>): Promise<T[]> Acts like getHarness, but returns an array of harness instances, one for each matching element, rather than just the first matching element

Calls to getHarness and getAllHarnesses can either take ComponentHarness subclass or a HarnessPredicate. HarnessPredicate applies additional restrictions to the search (e.g. searching for a button that has some particular text, etc). The details of HarnessPredicate are discussed in the API for component harness authors; harness authors should provide convenience methods on their ComponentHarness subclass to facilitate the creation of HarnessPredicate instances. However, if the harness author's API is not sufficient, they can be created manually.

By default, test harnesses will run Angular's change detection before reading the state of a DOM element and after interacting with a DOM element. While convenient in most cases, there may be times that you need finer-grained control over change detection. For example, you may want to check the state of a component while an async operation is pending. In these cases you can use the manualChangeDetection function to disable automatic handling of change detection for a block of code. For example:

it('checks state while async action is in progress', async () => {
  const buttonHarness = loader.getHarness(MyButtonHarness);
  await manualChangeDetection(async () => {
    await buttonHarness.click();
    fixture.detectChanges();
    // Check expectations while async click operation is in progress.
    expect(isProgressSpinnerVisible()).toBe(true);
    await fixture.whenStable();
    // Check expectations after async click operation complete.
    expect(isProgressSpinnerVisible()).toBe(false);
  });
});

To support both unit and end-to-end tests, and to insulate tests against changes in asynchronous behavior, almost all harness methods are asynchronous and return a Promise; therefore, the Angular team recommends using ES2017 async/await syntax to improve the test readability.

Note that await statements block the execution of your test until the associated Promise resolves. Occasionally, you may want to perform multiple actions simultaneously and wait until they're all done rather than performing each action sequentially. For example, reading multiple properties off a single component. In these situations use the parallel function to parallelize the operations. The parallel function works similarly to Promise.all, while also optimizing change detection, so it is not run an excessive number of times. The following code demonstrates how you can read multiple properties from a harness with parallel:

it('reads properties in parallel', async () => {
  const checkboxHarness = loader.getHarness(MyCheckboxHarness);
  // Read the checked and intermediate properties simultaneously.
  const [checked, indeterminate] = await parallel(() => [
    checkboxHarness.isChecked(),
    checkboxHarness.isIndeterminate()
  ]);
  expect(checked).toBe(false);
  expect(indeterminate).toBe(true);
});

Component harness authors are developers who maintain some reusable Angular component, and want to create a test harness for it, that users of the component can use in their tests. For example, this could be an author of a third party Angular component library or a developer who maintains a set of common components for a large Angular application.

The abstract ComponentHarness class is the base class for all component harnesses. To create a custom component harness, extend ComponentHarness and implement the static property hostSelector. The hostSelector property identifies elements in the DOM that match this harness subclass. In most cases, the hostSelector should be the same as the selector of the corresponding Component or Directive. For example, consider a simple popup component:

@Component({
  selector: 'my-popup',
  template: `
    <button (click)="toggle()">{{triggerText}}</button>
    <div *ngIf="open" class="my-popup-content"><ng-content></ng-content></div>
  `
})
class MyPopup {
  @Input() triggerText: string;

  open = false;

  toggle() {
    this.open = !this.open;
  }
}

In this case, a minimal harness for the component would look like the following:

class MyPopupHarness extends ComponentHarness {
  static hostSelector = 'my-popup';
}

While ComponentHarness subclasses require only the hostSelector property, most harnesses should also implement a static with method to generate HarnessPredicate instances. The HarnessPredicate section below covers this in more detail.

Each instance of a ComponentHarness subclass represents a particular instance of the corresponding component. You can access the component's host element via the host method from the ComponentHarness base class.

ComponentHarness additionally offers several methods for locating elements within the component's DOM. These methods are locatorFor, locatorForOptional, and locatorForAll. Note, though, that these methods do not directly find elements. Instead, they create functions that find elements. This approach safeguards against caching references to out-of-date elements. For example, when an ngIf hides and then shows an element, the result is a new DOM element; using functions ensures that tests always reference the current state of the DOM.

Method Description
host(): Promise<TestElement> Returns a Promise for the host element of the corresponding component instance.
locatorFor(selector: string): () => Promise<TestElement> Creates a function that returns a Promise for the first element matching the given selector when called. If no matching element is found, the Promise rejects.
locatorForOptional(selector: string): () => Promise<TestElement | null> Creates a function that returns a Promise for the first element matching the given selector when called. If no matching element is found, the Promise is resolved with null.
locatorForAll(selector: string): () => Promise<TestElement[]> Creates a function that returns a Promise for a list of all elements matching the given selector when called.

For example, the MyPopupHarness class discussed above could provide methods to get the trigger and content elements as follows:

class MyPopupHarness extends ComponentHarness {
  static hostSelector = 'my-popup';

  /** Gets the trigger element */
  getTriggerElement = this.locatorFor('button');

  /** Gets the content element. */
  getContentElement = this.locatorForOptional('.my-popup-content');
}

The functions created with the locator methods described above all return TestElement instances. TestElement offers a number of methods to interact with the underlying DOM:

Method Description
blur(): Promise<void> Blurs the element.
clear(): Promise<void> Clears the text in the element (intended for <input> and <textarea> only).
click(relativeX?: number, relativeY?: number): Promise<void> Clicks the element (at the given position relative to the element's top-left corner).
focus(): Promise<void> Focuses the element.
getCssValue(property: string): Promise<string> Gets the computed value of the given CSS property for the element.
hover(): Promise<void> Hovers over the element.
sendKeys(modifiers?: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void> Sends the given list of key presses to the element (with optional modifier keys).
text(): Promise<string> Gets the text content of the element
getAttribute(name: string): Promise<string | null> Gets the value of the given HTML attribute for the element.
hasClass(name: string): Promise<boolean> Checks whether the element has the given class applied.
getDimensions(): Promise<ElementDimensions> Gets the dimensions of the element.
getProperty(name: string): Promise<any> Gets the value of the given JS property for the element.
matchesSelector(selector: string): Promise<boolean> Checks whether the element matches the given CSS selector.
setInputValue(value: string): Promise<void>; Sets the value of a property of an input.
selectOptions(...optionIndexes: number[]): Promise<void>; Selects the options at the specified indexes inside of a native select element.
dispatchEvent(name: string, data?: Record<string, EventData>): Promise<void>; Dispatches an event with a particular name.

TestElement is an abstraction designed to work across different test environments (Karma, Selenium WebDriver, etc). When using harnesses, you should perform all DOM interaction via this interface. Other means of accessing DOM elements (e.g. document.querySelector) will not work in all test environments.

As a best practice, you should not expose TestElement instances to users of a harness unless its an element the component consumer defines directly (e.g. the host element). Exposing TestElement instances for internal elements leads users to depend on a component's internal DOM structure.

Instead, provide more narrow-focused methods for particular actions the end-user will take or particular state they may want to check. For example, MyPopupHarness could provide methods like toggle and isOpen:

class MyPopupHarness extends ComponentHarness {
  static hostSelector = 'my-popup';

  protected getTriggerElement = this.locatorFor('button');
  protected getContentElement = this.locatorForOptional('.my-popup-content');

  /** Toggles the open state of the popup. */
  async toggle() {
    const trigger = await this.getTriggerElement();
    return trigger.click();
  }

  /** Checks if the popup us open. */
  async isOpen() {
    const content = await this.getContentElement();
    return !!content;
  }
}

Larger components often compose smaller components. You can reflect this structure in a component's harness as well. Each of the locatorFor methods on ComponentHarness discussed earlier has an alternate signature that can be used for locating sub-harnesses rather than elements.

Method Description
locatorFor<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>): () => Promise<T> Creates a function that returns a Promise for the first harness matching the given harness type when called. If no matching harness is found, the Promise rejects.
locatorForOptional<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>): () => Promise<T | null> Creates a function that returns a Promise for the first harness matching the given harness type when called. If no matching harness is found, the Promise is resolved with null.
locatorForAll<T extends ComponentHarness>(harnessType: ComponentHarnessConstructor<T>): () => Promise<T[]> Creates a function that returns a Promise for a list of all harnesses matching the given harness type when called.

For example, consider a menu build using the popup shown above:

@Component({
  selector: 'my-menu',
  template: `
    <my-popup>
      <ng-content></ng-content>
    </my-popup>
  `
})
class MyMenu {
  @Input() triggerText: string;

  @ContentChildren(MyMenuItem) items: QueryList<MyMenuItem>;
}

@Directive({
  selector: 'my-menu-item'
})
class MyMenuItem {}

The harness for MyMenu can then take advantage of other harnesses for MyPopup and MyMenuItem:

class MyMenuHarness extends ComponentHarness {
  static hostSelector = 'my-menu';

  protected getPopupHarness = this.locatorFor(MyPopupHarness);

  /** Gets the currently shown menu items (empty list if menu is closed). */
  getItems = this.locatorForAll(MyMenuItemHarness);

  /** Toggles open state of the menu. */
  async toggle() {
    const popupHarness = await this.getPopupHarness();
    return popupHarness.toggle();
  }
}

class MyMenuItemHarness extends ComponentHarness {
  static hostSelector = 'my-menu-item';
}

When a page contains multiple instances of a particular component, you may want to filter based on some property of the component to get a particular component instance. For example, you may want a button with some specific text, or a menu with a specific ID. The HarnessPredicate class can capture criteria like this for a ComponentHarness subclass. While the test author is able to construct HarnessPredicate instances manually, it's easier when the ComponentHarness subclass provides a helper method to construct predicates for common filters.

The recommended approach to providing this helper is to create a static with method on each ComponentHarness subclass that returns a HarnessPredicate for that class. This allows test authors to write easily understandable code, e.g. loader.getHarness(MyMenuHarness.with({selector: '#menu1'})). In addition to the standard selector and ancestor options, the with method should add any other options that make sense for the particular subclass.

Harnesses that need to add additional options should extend the BaseHarnessFilters interface and additional optional properties as needed. HarnessPredicate provides several convenience methods for adding options.

Method Description
static stringMatches(s: string | Promise<string>, pattern: string | RegExp): Promise<boolean> Compares a string or Promise of a string against a string or RegExp and returns a boolean Promise indicating whether it matches.
addOption<O>(name: string, option: O | undefined, predicate: (harness: T, option: O) => Promise<boolean>): HarnessPredicate<T> Creates a new HarnessPredicate that enforces all of the conditions of the current one, plus the new constraint specified by the predicate parameter. If the option parameter is undefined the predicate is considered to be always true.
add(description: string, predicate: (harness: T) => Promise<boolean>): HarnessPredicate<T> Creates a new HarnessPredicate that enforces all of the conditions of the current one, plus the new constraint specified by the predicate parameter.

For example, when working with a menu it would likely be useful to add a way to filter based on trigger text and to filter menu items based on their text:

interface MyMenuHarnessFilters extends BaseHarnessFilters {
  /** Filters based on the trigger text for the menu. */
  triggerText?: string | RegExp;
}

interface MyMenuItemHarnessFilters extends BaseHarnessFilters {
  /** Filters based on the text of the menu item. */
  text?: string | RegExp;
}

class MyMenuHarness extends ComponentHarness {
  static hostSelector = 'my-menu';

  /** Creates a `HarnessPredicate` used to locate a particular `MyMenuHarness`. */
  static with(options: MyMenuHarnessFilters): HarnessPredicate<MyMenuHarness> {
    return new HarnessPredicate(MyMenuHarness, options)
        .addOption('trigger text', options.triggerText,
            (harness, text) => HarnessPredicate.stringMatches(harness.getTriggerText(), text));
  }

  protected getPopupHarness = this.locatorFor(MyPopupHarness);

  /** Gets the text of the menu trigger. */
  async getTriggerText(): Promise<string> {
    const popupHarness = await this.getPopupHarness();
    return popupHarness.getTriggerText();
  }

  ...
}

class MyMenuItemHarness extends ComponentHarness {
  static hostSelector = 'my-menu-item';

  /** Creates a `HarnessPredicate` used to locate a particular `MyMenuItemHarness`. */
  static with(options: MyMenuItemHarnessFilters): HarnessPredicate<MyMenuItemHarness> {
    return new HarnessPredicate(MyMenuItemHarness, options)
        .addOption('text', options.text,
            (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text));
  }

  /** Gets the text of the menu item. */
  async getText(): Promise<string> {
    const host = await this.host();
    return host.text();
  }
}

You can pass a HarnessPredicate in place of a ComponentHarness class to any of the APIs on HarnessLoader, LocatorFactory, or ComponentHarness. This allows test authors to easily target a particular component instance when creating a harness instance. It also allows the harness author to leverage the same HarnessPredicate to enable more powerful APIs on their harness class. For example, consider the getItems method on the MyMenuHarness shown above. This can now easily be expanded to allow users of the harness to search for particular menu items:

class MyMenuHarness extends ComponentHarness {
  static hostSelector = 'my-menu';

  /** Gets a list of items in the menu, optionally filtered based on the given criteria. */
  async getItems(filters: MyMenuItemHarnessFilters = {}): Promise<MyMenuItemHarness[]> {
    const getFilteredItems = this.locatorForAll(MyMenuItemHarness.with(filters));
    return getFilteredItems();
  }

  ...
}

Some components use <ng-content> to project additional content into the component's template. When creating a harness for such a component, you can give the harness user a HarnessLoader instance scoped to the element containing the <ng-content>. This allows the user of the harness to load additional harnesses for whatever components were passed in as content. ComponentHarness has several APIs that can be used to create HarnessLoader instances for cases like this.

Method Description
harnessLoaderFor(selector: string): Promise<HarnessLoader> Gets a Promise for a HarnessLoader rooted at the first element matching the given selector, if no element is found the Promise rejects.
harnessLoaderForOptional(selector: string): Promise<HarnessLoader | null> Gets a Promise for a HarnessLoader rooted at the first element matching the given selector, if no element is found the Promise resolves to null.
harnessLoaderForAll(selector: string): Promise<HarnessLoader[]> Gets a Promise for a list of HarnessLoader, one rooted at each element matching the given selector.

The MyPopup component discussed earlier is a good example of a component with arbitrary content that users may want to load harnesses for. MyPopupHarness could add support for this by extending ContentContainerComponentHarness.

class MyPopupHarness extends ContentContainerComponentHarness<string> {
  static hostSelector = 'my-popup';
}

There are times when a component harness might need to access elements outside of its corresponding component's host element. Components that use CDK overlay serve as examples of this. The CDK overlay creates an element that is attached directly to the body, outside of the component's host element. In this case, ComponentHarness provides a method that can be used to get a LocatorFactory for the root element of the document. The LocatorFactory supports most of the same APIs as the ComponentHarness base class, and can then be used to query relative to the document's root element.

Method Description
documentRootLocatorFactory(): LocatorFactory Creates a LocatorFactory rooted at the document's root element.

Consider if the MyPopup component above used the CDK overlay for the popup content, rather than an element in its own template. In this case, MyPopupHarness would have to access the content element via documentRootLocatorFactory():

class MyPopupHarness extends ComponentHarness {
  static hostSelector = 'my-popup';

  /** Gets a `HarnessLoader` whose root element is the popup's content element. */
  async getHarnessLoaderForContent(): Promise<HarnessLoader> {
    const rootLocator = this.documentRootLocatorFactory();
    return rootLocator.harnessLoaderFor('my-popup-content');
  }
}

The methods on TestElement automatically trigger Angular's change detection and wait for tasks inside the NgZone, so in most cases no special effort is required for harness authors to wait on asynchronous tasks. However, there are some edge cases where this may not be sufficient.

Under some circumstances, Angular animations may require a second cycle of change detection and subsequent NgZone stabilization before animation events are fully flushed. In cases where this is needed, the ComponentHarness offers a forceStabilize() method that can be called to do the second round.

Additionally, some components may intentionally schedule tasks outside of NgZone, this is typically accomplished by using NgZone.runOutsideAngular. In this case, the corresponding harness may need to explicitly wait for tasks outside NgZone, as this does not happen automatically. ComponentHarness offers a method called waitForTasksOutsideAngular for this purpose.

Method Description
forceStabilize(): Promise<void> Explicitly runs a round of change detection in Angular and waits for NgZone to stabilize.
waitForTasksOutsideAngular(): Promise<void> Waits for tasks scheduled outside of NgZone to complete.

Harness environment authors are developers who want to add support for using component harnesses in additional testing environments. Out-of-the-box, Angular CDK's component harnesses can be used in Selenium WebDriver E2E tests and Karma unit tests. Developers can support additional environments by creating custom implementations of TestElement and HarnessEnvironment.

The first step in adding support for a new testing environment is to create a TestElement implementation. The TestElement interface serves as an environment-agnostic representation of a DOM element; it lets harnesses interact with DOM elements regardless of the underlying environment. Because some environments don't support interacting with DOM elements synchronously (e.g. WebDriver), all of the TestElement methods are asynchronous, returning a Promise with the result of the operation.

Method Description
blur(): Promise<void> Blurs the element.
clear(): Promise<void> Clears the text from an element (only applies for <input> and <textarea>).
click(relativeX?: number, relativeY?: number): Promise<void> Clicks an element at a point relative to it's top-left corner.
focus(): Promise<void> Focuses the element.
getCssValue(property: string): Promise<string> Gets the computed CSS value of the given property for the element.
hover(): Promise<void> Hovers the mouse over the element.
sendKeys(...keys: (string | TestKey)[]): Promise<void> Sends a sequence of key events to the element.
sendKeys(modifiers: ModifierKeys, ...keys: (string | TestKey)[]): Promise<void> Sends a sequence of key events to the element, while holding a set of modifier keys.
text(): Promise<string> Gets the text content of the element.
getAttribute(name: string): Promise<string | null> Gets the value of the given HTML attribute for the element.
hasClass(name: string): Promise<boolean> Checks whether the element has the given class.
getDimensions(): Promise<ElementDimensions> Gets the dimensions of the element.
getProperty(name: string): Promise<any> Gets the value of the given property for the element.
matchesSelector(selector: string): Promise<boolean> Checks whether the given selector matches the element.
setInputValue(value: string): Promise<void>; Sets the value of a property of an input.
selectOptions(...optionIndexes: number[]): Promise<void>; Selects the options at the specified indexes inside of a native select element.
dispatchEvent(name: string, data?: Record<string, EventData>): Promise<void>; Dispatches an event with a particular name.

The TestElement interface consists largely of methods that resemble methods available on HTMLElement; similar methods exist in most test environments, which makes implementing the methods fairly straightforward. However, one important difference to note when implementing the sendKeys method, is that the key codes in the TestKey enum likely differ from the key codes used in the test environment. Environment authors should maintain a mapping from TestKey codes to the codes used in the particular testing environment.

The UnitTestElement and SeleniumWebDriverElement implementations in Angular CDK serve as good examples of implementations of this interface.

Test authors use HarnessEnvironment to create component harness instances for use in tests.

HarnessEnvironment is an abstract class that must be extended to create a concrete subclass for the new environment. When supporting a new test environment, you must create a HarnessEnvironment subclass that adds concrete implementations for all abstract members.

You will notice that HarnessEnvironment has a generic type parameter: HarnessEnvironment<E>. This parameter, E, represents the raw element type of the environment. For example, this parameter is Element for unit test environments.

The following are the abstract methods that must be implemented:

Method Description
abstract getDocumentRoot(): E Gets the root element for the environment (e.g. document.body).
abstract createTestElement(element: E): TestElement Creates a TestElement for the given raw element.
abstract createEnvironment(element: E): HarnessEnvironment Creates a HarnessEnvironment rooted at the given raw element.
abstract getAllRawElements(selector: string): Promise<E[]> Gets all of the raw elements under the root element of the environment matching the given selector.
abstract forceStabilize(): Promise<void> Gets a Promise that resolves when the NgZone is stable. Additionally, if applicable, tells NgZone to stabilize (e.g. calling flush() in a fakeAsync test).
abstract waitForTasksOutsideAngular(): Promise<void> Gets a Promise that resolves when the parent zone of NgZone is stable.

In addition to implementing the missing methods, this class should provide a way for test authors to get ComponentHarness instances. The recommended approach is to have a protected constructor and provide a static method called loader that returns a HarnessLoader instance. This allows test authors to write code like: SomeHarnessEnvironment.loader().getHarness(...). Depending on the needs of the particular environment, the class may provide several different static methods or require arguments to be passed. (e.g. the loader method on TestbedHarnessEnvironment takes a ComponentFixture, and the class provides additional static methods called documentRootLoader and harnessForFixture).

The TestbedHarnessEnvironment and SeleniumWebDriverHarnessEnvironment implementations in Angular CDK serve as good examples of implementations of this interface.

In order to support the manualChangeDetection and parallel APIs, your environment should install a handler for the auto change detection status.

When your environment wants to start handling the auto change detection status it can call handleAutoChangeDetectionStatus(handler). The handler function will receive a AutoChangeDetectionStatus which has two properties:

  • isDisabled: boolean - Indicates whether auto change detection is currently disabled. When true, your environment's forceStabilize method should act as a no-op. This allows users to trigger change detection manually instead.
  • onDetectChangesNow?: () => void - If this optional callback is specified, your environment should trigger change detection immediately and call the callback when change detection finishes.

If your environment wants to stop handling auto change detection status it can call stopHandlingAutoChangeDetectionStatus().

Azure & Blue theme selected.