diff --git a/docs/api/browser/context.md b/docs/api/browser/context.md
index c4eee0d25..49f75e4f6 100644
--- a/docs/api/browser/context.md
+++ b/docs/api/browser/context.md
@@ -270,6 +270,17 @@ export const utils: {
* Creates "Cannot find element" error. Useful for custom locators.
*/
getElementError(selector: string, container?: Element): Error
+ /**
+ * Utilities for generating and working with ARIA trees and templates.
+ * @experimental
+ */
+ aria: {
+ generateAriaTree(rootElement: Element): AriaNode
+ renderAriaTree(root: AriaNode): string
+ renderAriaTemplate(template: AriaTemplateNode): string
+ parseAriaTemplate(text: string): AriaTemplateNode
+ matchAriaTree(root: AriaNode, template: AriaTemplateNode): { pass: boolean; resolved: string }
+ }
}
```
@@ -340,3 +351,22 @@ utils.configurePrettyDOM({
::: tip
This feature is inspired by Testing Library's [`defaultIgnore`](https://testing-library.com/docs/dom-testing-library/api-configuration/#defaultignore) configuration.
:::
+
+### aria 5.0.0 {#aria}
+
+The `aria` namespace exposes low-level utilities used by Vitest's ARIA snapshot matchers.
+
+```ts
+import { utils } from 'vitest/browser'
+
+document.body.innerHTML = `
+
Hello, World!
+
+
+`
+const tree = utils.aria.generateAriaTree(document.body)
+const yaml = utils.aria.renderAriaNode(tree)
+console.log(yaml)
+// - heading "Hello, World!" [level=1]
+// - button "Visible""
+```
diff --git a/docs/guide/browser/aria-snapshots.md b/docs/guide/browser/aria-snapshots.md
index d4dee8257..d7d8ae76f 100644
--- a/docs/guide/browser/aria-snapshots.md
+++ b/docs/guide/browser/aria-snapshots.md
@@ -30,6 +30,8 @@ await expect.element(page.getByRole('navigation')).toMatchAriaInlineSnapshot(`
This catches accessibility regressions: missing labels, broken roles, incorrect heading levels, and more — things that DOM snapshots would miss. Even if the underlying HTML structure changes, the assertion would not fail as long as content matches semantically.
+For advanced cases, you can also generate and inspect the ARIA tree through `utils.aria` from `vitest/browser`. See the [Context API](/api/browser/context#aria) for details.
+
## Snapshot Workflow
ARIA snapshots use the same Vitest snapshot workflow as other snapshot assertions. File snapshots, inline snapshots, `--update` / `-u`, watch mode updates, and CI snapshot behavior all work the same way.
diff --git a/packages/browser/context.d.ts b/packages/browser/context.d.ts
index d9a2f8e76..f99c32e27 100644
--- a/packages/browser/context.d.ts
+++ b/packages/browser/context.d.ts
@@ -2,6 +2,7 @@ import { SerializedConfig } from 'vitest'
import { StringifyOptions, CDPSession, BrowserCommands } from 'vitest/internal/browser'
import { ARIARole } from './aria-role.js'
import {} from './matchers.js'
+import { __ivyaAriaTypes } from '@vitest/browser/internal/vendor-types'
export type BufferEncoding =
| 'ascii'
@@ -934,6 +935,23 @@ export const utils: {
* Creates "Cannot find element" error. Useful for custom locators.
*/
getElementError(selector: string, container?: Element): Error
+
+ /**
+ * Utilities for generating and working with ARIA trees and templates.
+ * @experimental
+ */
+ aria: {
+ /** Captures the ARIA tree for a DOM subtree. */
+ generateAriaTree: typeof __ivyaAriaTypes.generateAriaTree
+ /** Renders a captured ARIA tree to the textual snapshot format. */
+ renderAriaTree: typeof __ivyaAriaTypes.renderAriaTree
+ /** Renders an ARIA template back to text. */
+ renderAriaTemplate: typeof __ivyaAriaTypes.renderAriaTemplate
+ /** Parses textual ARIA snapshot syntax into a template tree. */
+ parseAriaTemplate: typeof __ivyaAriaTypes.parseAriaTemplate
+ /** Matches a captured ARIA tree against a parsed template. */
+ matchAriaTree: typeof __ivyaAriaTypes.matchAriaTree
+ }
}
export const locators: BrowserLocators
diff --git a/packages/browser/package.json b/packages/browser/package.json
index 7dd27fbaf..75475f997 100644
--- a/packages/browser/package.json
+++ b/packages/browser/package.json
@@ -44,6 +44,10 @@
"./utils": {
"default": "./dummy.js"
},
+ "./internal/vendor-types": {
+ "types": "./dist/vendor-types.d.ts",
+ "default": "./dummy.js"
+ },
"./package.json": "./package.json"
},
"main": "./dist/index.js",
diff --git a/packages/browser/rollup.config.js b/packages/browser/rollup.config.js
index b32c1783e..b7ed904c7 100644
--- a/packages/browser/rollup.config.js
+++ b/packages/browser/rollup.config.js
@@ -157,4 +157,36 @@ export default () =>
external,
plugins: dtsUtilsClient.dts(),
},
+ {
+ input: {
+ 'vendor-types': './src/vendor-types.ts',
+ },
+ output: {
+ dir: 'dist',
+ entryFileNames: '[name].ts',
+ format: 'esm',
+ },
+ external,
+ plugins: [
+ ...dtsUtils.isolatedDecl(),
+ ...plugins,
+ ],
+ },
+ {
+ input: {
+ 'vendor-types': './dist/.types/vendor-types.d.ts',
+ },
+ output: {
+ dir: 'dist',
+ entryFileNames: '[name].d.ts',
+ format: 'esm',
+ },
+ external,
+ plugins: [
+ resolve({
+ preferBuiltins: true,
+ }),
+ dtsUtils.dts(),
+ ],
+ },
])
diff --git a/packages/browser/src/client/tester/aria.ts b/packages/browser/src/client/tester/aria.ts
index 98d5dd143..67335c05a 100644
--- a/packages/browser/src/client/tester/aria.ts
+++ b/packages/browser/src/client/tester/aria.ts
@@ -4,14 +4,19 @@ import type {
AriaNode,
AriaTemplateNode,
} from 'ivya/aria'
-import {
+import * as aria from 'ivya/aria'
+import { Snapshots } from 'vitest'
+import { getBrowserState } from '../utils'
+
+getBrowserState().aria = aria
+
+const {
generateAriaTree,
matchAriaTree,
parseAriaTemplate,
renderAriaTemplate,
renderAriaTree,
-} from 'ivya/aria'
-import { Snapshots } from 'vitest'
+} = aria
const ariaSnapshotAdapter: DomainSnapshotAdapter = {
name: 'aria',
diff --git a/packages/browser/src/client/tester/context.ts b/packages/browser/src/client/tester/context.ts
index 5d230a880..8bfedcd8c 100644
--- a/packages/browser/src/client/tester/context.ts
+++ b/packages/browser/src/client/tester/context.ts
@@ -556,4 +556,7 @@ export const utils = {
debug,
getElementLocatorSelectors,
configurePrettyDOM,
+ get aria() {
+ return getBrowserState().aria
+ },
}
diff --git a/packages/browser/src/client/utils.ts b/packages/browser/src/client/utils.ts
index 2091f614f..4693ad98d 100644
--- a/packages/browser/src/client/utils.ts
+++ b/packages/browser/src/client/utils.ts
@@ -95,6 +95,7 @@ export interface BrowserRunnerState {
send: (method: string, params?: Record) => Promise
emit: (event: string, payload: unknown) => void
}
+ aria: typeof import('ivya/aria')
}
/* @__NO_SIDE_EFFECTS__ */
diff --git a/packages/browser/src/vendor-types.ts b/packages/browser/src/vendor-types.ts
new file mode 100644
index 000000000..3a161be89
--- /dev/null
+++ b/packages/browser/src/vendor-types.ts
@@ -0,0 +1 @@
+export type * as __ivyaAriaTypes from 'ivya/aria'
diff --git a/test/browser/test/utils.test.ts b/test/browser/test/utils.test.ts
index b4099511d..d1b8e7299 100644
--- a/test/browser/test/utils.test.ts
+++ b/test/browser/test/utils.test.ts
@@ -206,3 +206,17 @@ test('filterNode with wildcard selector filters nested content', async () => {
"
`)
})
+
+test('aria tree utils', () => {
+ document.body.innerHTML = `
+ Hello, World!
+
+
+ `
+ const { generateAriaTree, renderAriaTree } = utils.aria
+ expect(`\n${renderAriaTree(generateAriaTree(document.body))}`).toMatchInlineSnapshot(`
+ "
+ - heading "Hello, World!" [level=1]
+ - button "Visible""
+ `)
+})
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 41c24c2a7..4b932dc6d 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -23,6 +23,7 @@
"@vitest/browser-playwright": ["./packages/browser-playwright/src/index.ts"],
"@vitest/browser": ["./packages/browser/src/node/index.ts"],
"@vitest/browser/client": ["./packages/browser/src/client/client.ts"],
+ "@vitest/browser/internal/vendor-types": ["./packages/browser/src/vendor-types.ts"],
"~/*": ["./packages/ui/client/*"],
"vitest": ["./packages/vitest/src/public/index.ts"],
"vitest/internal/browser": ["./packages/vitest/src/public/browser.ts"],