# Ariakit Test

**Important:** This package is experimental and does not follow semantic versioning, meaning breaking changes may occur in patch and minor versions.

Utilities for simulating user interactions in Ariakit's unit and end-to-end tests.

## Contents

- [Installation](#installation)
- [Usage](#usage)
- [API reference](#api-reference)
- [React API reference](#react-api-reference)
- [Playwright API reference](#playwright-api-reference)
- [Core Team](#core-team)
- [Contributing](#contributing)

## Installation

```sh
npm i @ariakit/test
```

## Usage

Import helpers from the package root to simulate user interactions:

```ts
import { click, press, type } from "@ariakit/test";
```

The `@ariakit/test/react` entry point renders React components for testing, and the `@ariakit/test/playwright` entry point provides query helpers for Playwright tests.

### Test environment

The helpers expect a DOM that implements `PointerEvent`, which means happy-dom, jsdom v27 or later, or a real browser.

They still run on jsdom 26, which `jest-environment-jsdom` 30 depends on. Every event the helpers fire there carries the same mouse, modifier, and pointer members it carries anywhere else, so a listener reads the same `clientX`, `button`, `pointerId`, and modifier state it would in a browser. `click`, `auxclick`, and `contextmenu` are built from `MouseEvent` there rather than `PointerEvent`, which also keeps the members a browser computes.

The `pointer*` events are the ones that environment cannot build from an interface of their own, so they come from `Event` and are not `instanceof MouseEvent`. The dispatch layer still derives `pageX`, `pageY`, and `which`, but leaves the layout-dependent `offsetX` and `offsetY` undefined. The `PointerEvent` global is absent altogether, so both `new PointerEvent()` and `event instanceof PointerEvent` throw a `ReferenceError`.

<!-- ariakit-docs:start -->

## API reference

- [`blur`](#blur)
- [`click`](#click)
- [`dispatch`](#dispatch)
- [`focus`](#focus)
- [`hover`](#hover)
- [`mouseDown`](#mousedown)
- [`mouseUp`](#mouseup)
- [`press`](#press)
- [`query`](#query)
- [`q`](#q)
- [`rightClick`](#rightclick)
- [`select`](#select)
- [`sleep`](#sleep)
- [`tap`](#tap)
- [`type`](#type)
- [`waitFor`](#waitfor)

### `blur`

```ts
function blur(element?: DirtiableElement | null): Promise<void>;
```

Removes focus from an element, simulating a real user moving focus away from it. When no element is passed, the currently focused element (`document.activeElement`) is used. If typing changed the element's value since it gained focus, a `change` event is dispatched before it's blurred.

Example:

```ts
await type("hello", q.textbox());
// Dispatches the pending `change` event, then blurs the textbox.
await blur();
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `click`

```ts
function click(
  element: Element | null,
  options?: PointerEventInit,
  tap = false,
): Promise<void>;
```

Clicks on an element, simulating the sequence of events a real mouse click produces — hovering the target, then `pointerdown`, `mousedown`, `focus`, `pointerup`, `mouseup`, and `click`.

Hidden and disabled elements are handled the same way a browser would, and clicks on labels, `option` elements, and form controls behave like native interactions. Pass `options` to set event properties such as modifier keys (e.g. `{ shiftKey: true }`).

Pass `button` to click with another mouse button. Activation behavior runs on `click`, so a non-primary button fires `auxclick` instead and doesn't activate labels or `option` elements, and the secondary button also fires `contextmenu` while it's held down. Each step derives `buttons` from that button, so an explicit `buttons` is ignored here; `mouseDown` and `mouseUp` accept one to describe a chorded gesture.

Example:

```ts
await click(q.button("Submit"));
// With a modifier key held down:
await click(q.option("Item"), { shiftKey: true });
// With the middle mouse button, firing `auxclick`:
await click(q.link("Ariakit"), { button: 1 });
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `dispatch`

```ts
type Target = Document | Window | Node | Element | null;

type EventFunction = (element: Target, options?: object) => Promise<boolean>;

type DispatchEventType = Exclude<EventType, "doubleClick"> | "auxClick";

type EventsObject = {
  [K in DispatchEventType]: EventFunction;
};

const dispatch: typeof baseDispatch & EventsObject;
```

Creates and fires a DOM event on an element, then waits for the resulting microtasks to flush. Call `dispatch.<eventName>(element, options)` to build and fire a specific event (e.g. `dispatch.keyDown`, `dispatch.click`, `dispatch.input`), or call `dispatch(element, event)` directly with an `Event` instance.

Unlike higher-level helpers such as `click` and `type`, this fires a single event without simulating the surrounding interaction sequence. Pointer and mouse events fired on an element with `pointer-events: none` are re-dispatched on the nearest ancestor that has pointer events enabled, matching how browsers route those events.

A pointer event built by name reports the contact size and transducer angle browsers report for a device with neither, so `width` and `height` are `1` and `altitudeAngle` is a right angle. Supplying only the tilt or spherical angle pair derives the other pair. The members describing a gesture, such as `pressure` and `isPrimary`, keep their defaults here; the higher-level helpers fill those in. An event you construct yourself keeps whatever its constructor gave it.

Mouse and pointer events built by name derive `pageX` and `pageY` from the client coordinates and target window scroll, and derive `which` from `button`. The layout-dependent `offsetX` and `offsetY` keep the environment's values.

`click`, `auxclick`, and `contextmenu` are built as `PointerEvent`, the way browsers dispatch them, so they accept and report pointer properties such as `pointerType`. An environment with no `PointerEvent` builds them as `MouseEvent` instead, and they report the same properties there.

Returns: A promise that resolves to `false` when the event's default action was prevented with `event.preventDefault()`, and `true` otherwise.

Example:

```ts
await dispatch.keyDown(q.textbox(), { key: "Enter" });
await dispatch.click(q.button());
await dispatch.auxClick(q.link("Ariakit"), { button: 1 });
// Fire a custom event instance directly:
await dispatch(q.textbox(), new Event("selectstart", { bubbles: true }));
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `focus`

```ts
function focus(element: Element | null): Promise<void>;
```

Moves focus to an element, simulating a real user focusing it. Elements that aren't focusable are ignored, and focusing the already focused element is a no-op. If typing changed another element's value since it gained focus, its pending `change` event is dispatched before focus moves.

Example:

```ts
await focus(q.textbox());
expect(q.textbox()).toHaveFocus();
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `hover`

```ts
function hover(
  element: Element | null,
  options?: PointerEventInit,
): Promise<void>;
```

Moves the pointer over an element, simulating a real user hovering it. Fires the relevant `pointer`/`mouse` enter, over, and move events, and dispatches the matching leave events on the previously hovered element.

Hidden elements and elements with `pointer-events: none` are handled the way a browser would. Pass `options` to set event properties such as modifier keys. The pointer events report `pressure: 0`, or `0.5` when you pass `buttons` to describe a move with a button held down, as during a drag.

Example:

```ts
await hover(q.button("More options"));
expect(q.menu()).toBeVisible();
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `mouseDown`

```ts
function mouseDown(
  element: Element | null,
  options?: PointerEventInit,
): Promise<void>;
```

Presses a pointer button down on an element, firing `pointerdown` and `mousedown` and moving focus the way a browser would. Disabled elements still receive the pointer event but not `mousedown`, and focus falls back to the closest focusable ancestor when the target itself isn't focusable.

This is one step of a full `click`; use it directly to test press-and-hold behavior. Pass `options` to set event properties such as modifier keys, or `button` to press another mouse button. The events report the pressed button in `buttons`, like a browser does, unless you pass `buttons` yourself to describe a chorded gesture. When that value shows another button was already held down, the press fires `pointermove` instead of `pointerdown`, the way Pointer Events routes a chorded press, and the compatibility `mousedown` still fires. The pointer event reports `pressure: 0.5`, the value Pointer Events defines while a device with no pressure sensor holds a button down.

Example:

```ts
await mouseDown(q.button("Resize"));
// ...assert the press state, then release:
await mouseUp(q.button("Resize"));
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `mouseUp`

```ts
function mouseUp(
  element: Element | null,
  options?: PointerEventInit,
): Promise<void>;
```

Releases a pointer button on an element, firing `pointerup` and `mouseup`. Disabled elements still receive the pointer event but not `mouseup`.

This is the counterpart to `mouseDown` and one step of a full `click`. Pass `options` to set event properties such as modifier keys, or `button` to release another mouse button. The events report no button still held down in `buttons`, like a browser does, unless you pass `buttons` yourself to describe the buttons a chorded gesture keeps held. When that value shows another button stays held down, the release fires `pointermove` instead of `pointerup`, the way Pointer Events routes a chorded release, and the compatibility `mouseup` still fires. The pointer event reports `pressure: 0`, or `0.5` while a chorded gesture keeps a button held.

Example:

```ts
await mouseDown(q.button("Resize"));
await mouseUp(q.button("Resize"));
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `press`

```ts
function press(
  key: string,
  element?: Element | null,
  options: KeyboardEventInit = {},
): Promise<void>;
```

Presses a key on an element, simulating a real user keyboard interaction. Fires `keydown` and `keyup` and applies the browser's default behavior for that key — moving focus with `Tab`, activating buttons and submitting forms with `Enter`, clicking buttons, checkboxes, and radios with `Space`, moving the caret with the arrow and `Home`/`End` keys, and typing printable characters into text fields.

When no element is passed, the currently focused element is used. Shortcuts such as `press.Enter()` and `press.Tab()` are provided for common keys, and `press.ShiftTab()` moves focus backwards.

Use `press.down` and `press.up` to fire only the keydown or keyup half of a press. Each defaults to the currently focused element, so a key released after focus moved away — for example, an element that disables itself on keydown — lands where a real browser would deliver it.

Example:

```ts
await press.Tab();
await press.Enter();
// `press.Enter(element)` is shorthand for `press("Enter", element)`:
await press.Enter(q.button("Submit"));
// Split a press so the keyup lands on whatever is focused at release time:
await press.down.Space();
await press.up.Space();
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `query`

```ts
type Query = ReturnType<typeof createRoleQuery>;

type TextQuery = ReturnType<typeof createTextQuery>;

type RoleQueries = Record<AriaRole, Query>;

interface QueryObject extends RoleQueries {
  text: TextQuery;
  labeled: TextQuery;
  within: (element?: HTMLElement | null) => QueryObject;
}

const query: QueryObject;
```

Queries the DOM by ARIA role, accessible name, text, or label, built on top of Testing Library. Call a role method such as `query.button(name)` or `query.dialog()` to get the matching element, passing a string or `RegExp` to match its accessible name. Queries throw when no matching element is found. Use `query.text()` and `query.labeled()` to query by text content or associated label, and `query.within(element)` to scope queries to a subtree.

Every query also exposes `.lazy` (return a reusable function that runs the query when called), `.all` (return all matches, including an empty array), `.wait` (resolve once the element appears), and `.maybe` (return `null` when it's missing) variants. Role queries additionally expose `.hidden` to include otherwise-hidden elements.

Example:

```ts
const dialog = query.dialog.maybe.lazy("Settings");
expect(dialog()).not.toBeInTheDocument();
await click(query.button("Open settings"));
expect(dialog()).toBeVisible();
// Wait for an element to appear, or scope a query to a subtree:
await query.alert.wait();
query.within(dialog()).button("Close");
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `q`

```ts
type Query = ReturnType<typeof createRoleQuery>;

type TextQuery = ReturnType<typeof createTextQuery>;

type RoleQueries = Record<AriaRole, Query>;

interface QueryObject extends RoleQueries {
  text: TextQuery;
  labeled: TextQuery;
  within: (element?: HTMLElement | null) => QueryObject;
}

const q: QueryObject;
```

Short alias for `query`. Queries the DOM by ARIA role, accessible name, text, or label.

Example:

```ts
const dialog = q.dialog.maybe.lazy("Settings");
expect(dialog()).not.toBeInTheDocument();
await click(q.button("Open settings"));
expect(dialog()).toBeVisible();
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `rightClick`

```ts
function rightClick(
  element: Element | null,
  options?: PointerEventInit,
): Promise<void>;
```

Right-clicks on an element, simulating the sequence of events a real secondary mouse click produces — hovering the target, then right-button `pointerdown`, `mousedown`, `focus`, `contextmenu`, `pointerup`, `mouseup`, and `auxclick`.

Hidden elements are handled the same way a browser would, and no synthetic `click` event is fired. Pass `options` to set event properties such as modifier keys.

Example:

```ts
await rightClick(q.text("Open menu"));
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `select`

```ts
function select(
  text: string,
  element: Element | null = document.body,
  options?: PointerEventInit,
): Promise<void>;
```

Selects a range of text within an element, simulating a real user dragging across it. Hovers and presses on the element, finds the given `text` in its descendant text nodes, sets the document selection to cover it, then releases.

When no element is passed, `document.body` is used. Pass `options` to set event properties such as modifier keys. Each step derives `buttons` from the button it presses, so an explicit `buttons` is ignored.

Example:

```ts
await select("hello world");
expect(document.getSelection()?.toString()).toBe("hello world");
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `sleep`

```ts
function sleep(ms = defaultMs): Promise<void>;
```

Waits for the DOM to settle between simulated interactions by yielding across two animation frames and a short timeout.

The other helpers in this package call it internally, but you can await it directly to let pending updates, transitions, or effects flush before asserting. The default delay is small and environment-dependent; pass `ms` to override it. Outside a real browser it also drains the host scheduler so concurrent React work that the delay raced past settles before the call resolves.

Example:

```ts
await click(q.button("Open"));
await sleep();
expect(q.dialog()).toBeVisible();
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `tap`

```ts
function tap(
  element: Element | null,
  options?: PointerEventInit,
): Promise<void>;
```

Clicks on an element without the brief delay that `click` waits between pressing and releasing, reproducing the timing of a quick tap. It fires the same pointer, mouse, and click events as `click`. Pass `options` to set event properties such as modifier keys.

Example:

```ts
await tap(q.button("Submit"));
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `type`

```ts
function type(
  text: string,
  element?: (DirtiableElement & HTMLElement) | null,
  options: InputEventInit | KeyboardEventInit = {},
): Promise<void>;
```

Types text into an element, simulating a real user pressing each key. Focuses the element, then for each character fires `keydown`, updates the value and caret position of text fields through an `input` event (preceded by `keypress` when inserting a printable character), and fires `keyup`.

Special characters map to their keys: `"\b"` is Backspace, `"\x7f"` is Delete, `"\n"` is Enter, and `"\t"` is Tab. When no element is passed, the currently focused element is used. Pass `options` to set event properties such as modifier keys or composition state.

Example:

```ts
await type("Hello", q.textbox());
// Delete the last character with a Backspace:
await type("\b");
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

### `waitFor`

```ts
function waitFor<T>(
  callback: () => T,
  options?: DOMTestingLibrary.waitForOptions,
): Promise<T>;
```

Re-runs a callback until it stops throwing or the timeout is reached, re-exporting Testing Library's `waitFor` with this package's async batching applied. Use it to wait for an assertion to pass after an asynchronous update. Pass `options` to configure the `timeout`, `interval`, and other behavior.

Example:

```ts
await click(q.button("Close"));
await waitFor(() => expect(q.dialog.maybe()).not.toBeInTheDocument());
```

<div align="right">
  <a href="#api-reference">&uarr; back to top</a>
</div>

<!-- ariakit-docs:end -->

<!-- ariakit-docs:start react -->

## React API reference

- [`RenderOptions`](#renderoptions)
- [`render`](#render)

### `RenderOptions`

```ts
interface RenderOptions extends Omit<
  ReactTestingLibrary.RenderOptions,
  "queries"
> {
  strictMode?: boolean;
}
```

Options for the `render` function. Accepts every option from Testing Library's `render` (except `queries`), plus `strictMode` to wrap the rendered UI in React's `StrictMode`.

Example:

```tsx
const options: RenderOptions = { strictMode: true };
await render(<App />, options);
```

<div align="right">
  <a href="#react-api-reference">&uarr; back to top</a>
</div>

### `render`

```ts
function render(
  ui: ReactNode,
  options?: RenderOptions,
): Promise<{
  unmount: () => void;
  rerender: (newUi: ReactNode) => Promise<void>;
}>;
```

Renders a React element into the document for testing, waiting for effects and the next frame to flush before resolving.

Built on Testing Library's `render`, it returns `unmount` to remove the tree and an async `rerender` to update it with new UI. Pass `strictMode: true` to wrap the element in React's `StrictMode`, or any other Testing Library render option.

Example:

```tsx
const { rerender, unmount } = await render(<Button>Submit</Button>);
await click(q.button("Submit"));
await rerender(<Button>Sent</Button>);
unmount();
```

<div align="right">
  <a href="#react-api-reference">&uarr; back to top</a>
</div>

<!-- ariakit-docs:end react -->

<!-- ariakit-docs:start playwright -->

## Playwright API reference

### `query`

```ts
type RoleQuery = (
  name?: string | RegExp,
  options?: Parameters<Page["getByRole"]>[1],
) => Locator;

type TextQuery = (
  name: Parameters<Page["getByText"]>[0],
  options?: Parameters<Page["getByText"]>[1],
) => Locator;

type RoleQueries = Record<AriaRole, RoleQuery>;

type Queries = RoleQueries & { text: TextQuery };

function query(locator: Page | Locator | FrameLocator): Queries;
```

Creates role- and text-based query helpers for a Playwright `Page`, `Locator`, or `FrameLocator`. Call a role method such as `query(page).button(name)` to get a `Locator` from `getByRole`, or `query(page).text(name)` to match by text content.

Names are matched exactly by default. This mirrors the role-based `query` from the package root for end-to-end Playwright tests.

Example:

```ts
const { button, dialog } = query(page);
await button("Open").click();
await expect(dialog()).toBeVisible();
```

<div align="right">
  <a href="#playwright-api-reference">&uarr; back to top</a>
</div>

<!-- ariakit-docs:end playwright -->

## Core Team

- [Diego Haz](https://bsky.app/profile/haz.dev)
- [Ben Rodri](https://bsky.app/profile/ben.ariakit.org)
- [Dani Guardiola](https://bsky.app/profile/dio.la)

## Contributing

Follow the instructions on the [contributing guide](https://github.com/ariakit/ariakit/blob/main/contributing.md).
