@ariakit/test

中文文档
0.8.2发布于 2026/10/14MITTypeScriptES Module构建来源已验证近 30 天下载 11.2万

Ariakit test utils

原始使用文档
文档目录28 节
  1. Contents
  2. Installation
  3. Usage
  4. Test environment
  5. API reference
  6. blur
  7. click
  8. dispatch
  9. focus
  10. hover
  11. mouseDown
  12. mouseUp
  13. press
  14. query
  15. q
  16. rightClick
  17. select
  18. sleep
  19. tap
  20. type
  21. waitFor
  22. React API reference
  23. RenderOptions
  24. render
  25. Playwright API reference
  26. query
  27. Core Team
  28. Contributing

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

npm i @ariakit/test

Usage

Import helpers from the package root to simulate user interactions:

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.

API reference

blur

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:

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

click

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:

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 });

dispatch

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:

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 }));

focus

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:

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

hover

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:

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

mouseDown

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:

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

mouseUp

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:

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

press

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:

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();

query

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:

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");

q

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:

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

rightClick

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:

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

select

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:

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

sleep

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:

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

tap

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:

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

type

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:

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

waitFor

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:

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

React API reference

RenderOptions

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:

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

render

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:

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

Playwright API reference

query

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:

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

Core Team

Contributing

Follow the instructions on the contributing guide.

版本与发布记录

@ariakit/test 共发布 67 个版本,首次发布于 2022/10/08;过去 12 个月发布了 16 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.8.2latest2026/10/1424 天前
0.8.12026/09/151 个月前
0.8.02026/08/242 个月前
0.7.42026/08/102 个月前
0.7.32026/08/023 个月前
0.7.22026/07/243 个月前

关于 @ariakit/test 的常见问题

@ariakit/test 是什么?

Ariakit test utils

如何安装 @ariakit/test?

在项目目录运行 npm install @ariakit/test,或使用 pnpm add / yarn add / bun add。

@ariakit/test 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@ariakit/test 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@ariakit/test 可以商用吗?

@ariakit/test 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@ariakit/test 的下载量有多少?

根据 npm 官方下载统计,@ariakit/test 在 2026/10/07 – 2026/11/05 这 30 天内被下载 112,336 次,日均约 3,745 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@ariakit/test 还在维护吗?

最新版本 0.8.2 发布于 2026/10/14(24 天前);至今共发布 67 个版本,其中过去 12 个月发布了 16 个;首次发布于 2022/10/08。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。