angular/components · error · Error

Cannot retrieve popup content because the combobox is…

Error message

Cannot retrieve popup content because the combobox is closed or not associated with a popup controls ID.

What it means

The combobox harness's getPopupWidget method reads the host element's 'aria-controls' attribute to locate the popup widget element by ID. When the combobox is closed (or was never wired to a popup), no aria-controls is present, so the harness cannot resolve the popup element and throws this error instead of returning null.

Solutions

  1. Open the combobox before querying the popup (e.g. await harness.open() or dispatch interaction on the trigger) so aria-controls is set.
  2. Verify the combobox component sets aria-controls on its host pointing at the popup element's ID.
  3. Await open state: check await harness.isOpen() before calling getPopupWidget.
  4. If using a custom combobox, ensure ngComboboxWidget assigns the popup an ID and links it via aria-controls.

Example fix

// before
const popup = await harness.getPopupWidget({selector: '.my-options'});
// after
await harness.open();
const popup = await harness.getPopupWidget({selector: '.my-options'});
Defensive patterns

Strategy: validation

Validate before calling

const host = await harness.host();
if (!(await host.getAttribute('aria-controls'))) {
  await harness.open(); // or fail fast
}

Type guard

async function popupAvailable(harness: {host(): Promise<any>}): Promise<boolean> {
  return !!(await (await harness.host()).getAttribute('aria-controls'));
}

Try / catch

try {
  const popup = await harness.getPopupWidget();
} catch (e) {
  if ((e as Error).message.includes('Cannot retrieve popup content')) {
    await harness.open();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling harness.getPopupWidget(...) (or getPopupLoader/getRootHarnessLoader) while the combobox popup is closed, or on a custom combobox whose host element never sets aria-controls to the popup's ID.

Common situations: Tests that query popup options without first opening the combobox (e.g. missing an open()/click on the trigger); custom combobox implementations that omit the aria-controls wiring the Angular ARIA combobox pattern requires; popup unmounted after close so the attribute is removed.

Related errors


AI-assisted analysis of angular/components@0411926e7d (2026-08-31). Data as JSON: /api/errors/ffb3226b717b62ad. Report an issue: GitHub.

Appendix: source

Thrown at src/aria/combobox/testing/combobox-harness.ts:57

        async (harness, disabled) => (await harness.isDisabled()) === disabled,
      );
  }

  /**
   * Gets the component harness for the active widget contained inside the popup.
   * Use this when you need to access the harness of the widget itself (e.g., `ListboxHarness`),
   * rather than querying items within it.
   * @param type The harness type to locate. Must implement standard static `.with()` method.
   */
  async getPopupWidget<T extends ComponentHarness>(
    type: ComponentHarnessConstructor<T> & {
      with: (options?: {selector?: string}) => HarnessPredicate<T>;
    },
  ): Promise<T> {
    const host = await this.host();
    const controlsId = await host.getAttribute('aria-controls');
    if (!controlsId) {
      throw new Error(
        'Cannot retrieve popup content because the combobox is closed or not associated with a popup controls ID.',
      );
    }
    return this.documentRootLocatorFactory().locatorFor(type.with({selector: `#${controlsId}`}))();
  }

  /**
   * Gets a harness loader scoped to the content inside the popup container.
   * Note that lookups performed by this loader will only find descendants of the popup container.
   */
  async getPopupLoader(): Promise<HarnessLoader> {
    return this.getRootHarnessLoader();
  }

  /** Overrides root loader to automatically resolve queries nested inside the associated popup. */
  protected override async getRootHarnessLoader(): Promise<HarnessLoader> {
    const host = await this.host();
    const controlsId = await host.getAttribute('aria-controls');

View on GitHub (pinned to 0411926e7d)