From 9ccdc54142a5f90f2c1e9ca19dde14315b8f1793 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:52:21 +0000 Subject: [PATCH 1/9] Initial plan From 5169daa661fb2b57d92ee7599a8a04e2ba35aaaf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:01:26 +0000 Subject: [PATCH 2/9] Add filterNode option to prettyDOM for filtering output in browser mode - Created DOMElementFilter plugin to support node filtering during pretty printing - Added filterNode option to StringifyOptions interface - Added createNodeFilter helper function to easily create CSS selector-based filters - Updated configurePrettyDOM to accept filterNode option - Added tests for the filtering functionality - Exported createDOMElementFilter from pretty-format package Co-authored-by: sheremet-va <16173870+sheremet-va@users.noreply.github.com> --- packages/browser/context.d.ts | 19 +++ packages/browser/src/client/tester/context.ts | 42 +++++ packages/pretty-format/src/index.ts | 32 ++-- .../src/plugins/DOMElementFilter.ts | 153 ++++++++++++++++++ packages/utils/src/display.ts | 21 ++- test/browser/test/utils.test.ts | 73 +++++++++ 6 files changed, 321 insertions(+), 19 deletions(-) create mode 100644 packages/pretty-format/src/plugins/DOMElementFilter.ts diff --git a/packages/browser/context.d.ts b/packages/browser/context.d.ts index ef1c524e8462..18844e24f004 100644 --- a/packages/browser/context.d.ts +++ b/packages/browser/context.d.ts @@ -869,6 +869,25 @@ export const utils: { * @experimental */ configurePrettyDOM(options: StringifyOptions): void + /** + * Creates a filter function for prettyDOM that filters out nodes based on CSS selectors. + * This is similar to Testing Library's defaultIgnore configuration. + * + * @example + * ```ts + * import { utils } from 'vitest/browser' + * + * // Filter out script, style, and elements with data-test-hide attribute + * utils.configurePrettyDOM({ + * filterNode: utils.createNodeFilter('script, style, [data-test-hide]') + * }) + * ``` + * + * @param selector - CSS selector to filter out (e.g., 'script, style, [data-test-hide]') + * @returns A filter function that can be used with configurePrettyDOM + * @experimental + */ + createNodeFilter(selector: string): (node: any) => boolean /** * Creates "Cannot find element" error. Useful for custom locators. */ diff --git a/packages/browser/src/client/tester/context.ts b/packages/browser/src/client/tester/context.ts index 8229969ea444..b291d17d676b 100644 --- a/packages/browser/src/client/tester/context.ts +++ b/packages/browser/src/client/tester/context.ts @@ -499,10 +499,52 @@ function configurePrettyDOM(options: StringifyOptions) { defaultOptions = options } +/** + * Creates a filter function for prettyDOM that filters out nodes based on CSS selectors. + * This is similar to Testing Library's defaultIgnore configuration. + * + * @example + * ```ts + * import { utils } from 'vitest/browser' + * + * // Filter out script, style, and elements with data-test-hide attribute + * utils.configurePrettyDOM({ + * filterNode: utils.createNodeFilter('script, style, [data-test-hide]') + * }) + * ``` + * + * @param selector - CSS selector to filter out (e.g., 'script, style, [data-test-hide]') + * @returns A filter function that can be used with configurePrettyDOM + */ +function createNodeFilter(selector: string): (node: any) => boolean { + const ELEMENT_NODE = 1 + const COMMENT_NODE = 8 + + return (node: any) => { + // Filter out comments + if (node.nodeType === COMMENT_NODE) { + return false + } + + // Filter out elements matching the selector + if (node.nodeType === ELEMENT_NODE && node.matches) { + try { + return !node.matches(selector) + } + catch { + return true + } + } + + return true + } +} + export const utils = { getElementError, prettyDOM, debug, getElementLocatorSelectors, configurePrettyDOM, + createNodeFilter, } diff --git a/packages/pretty-format/src/index.ts b/packages/pretty-format/src/index.ts index e96cba860f28..c1936504d11f 100644 --- a/packages/pretty-format/src/index.ts +++ b/packages/pretty-format/src/index.ts @@ -30,21 +30,7 @@ import Immutable from './plugins/Immutable' import ReactElement from './plugins/ReactElement' import ReactTestComponent from './plugins/ReactTestComponent' -export type { - Colors, - CompareKeys, - Config, - NewPlugin, - OldPlugin, - Options, - OptionsReceived, - Plugin, - Plugins, - PrettyFormatOptions, - Printer, - Refs, - Theme, -} from './types' +export { createDOMElementFilter } from './plugins/DOMElementFilter' const toString = Object.prototype.toString const toISOString = Date.prototype.toISOString @@ -559,6 +545,22 @@ export function format(val: unknown, options?: OptionsReceived): string { return printComplexValue(val, getConfig(options), '', 0, []) } +export type { + Colors, + CompareKeys, + Config, + NewPlugin, + OldPlugin, + Options, + OptionsReceived, + Plugin, + Plugins, + PrettyFormatOptions, + Printer, + Refs, + Theme, +} from './types' + export const plugins: { AsymmetricMatcher: NewPlugin DOMCollection: NewPlugin diff --git a/packages/pretty-format/src/plugins/DOMElementFilter.ts b/packages/pretty-format/src/plugins/DOMElementFilter.ts new file mode 100644 index 000000000000..37560e159c2c --- /dev/null +++ b/packages/pretty-format/src/plugins/DOMElementFilter.ts @@ -0,0 +1,153 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type { Config, NewPlugin, Printer, Refs } from '../types' +import { + printChildren, + printComment, + printElement, + printElementAsLeaf, + printProps, + printShadowRoot, + printText, +} from './lib/markup' + +const ELEMENT_NODE = 1 +const TEXT_NODE = 3 +const COMMENT_NODE = 8 +const FRAGMENT_NODE = 11 + +const ELEMENT_REGEXP = /^(?:(?:HTML|SVG)\w*)?Element$/ + +function testHasAttribute(val: any) { + try { + return typeof val.hasAttribute === 'function' && val.hasAttribute('is') + } + catch { + return false + } +} + +function testNode(val: any) { + const constructorName = val.constructor.name + const { nodeType, tagName } = val + const isCustomElement + = (typeof tagName === 'string' && tagName.includes('-')) + || testHasAttribute(val) + + return ( + (nodeType === ELEMENT_NODE + && (ELEMENT_REGEXP.test(constructorName) || isCustomElement)) + || (nodeType === TEXT_NODE && constructorName === 'Text') + || (nodeType === COMMENT_NODE && constructorName === 'Comment') + || (nodeType === FRAGMENT_NODE && constructorName === 'DocumentFragment') + ) +} + +export const test: NewPlugin['test'] = (val: any) => + val?.constructor?.name && testNode(val) + +type HandledType = Element | Text | Comment | DocumentFragment + +function nodeIsText(node: HandledType): node is Text { + return node.nodeType === TEXT_NODE +} + +function nodeIsComment(node: HandledType): node is Comment { + return node.nodeType === COMMENT_NODE +} + +function nodeIsFragment(node: HandledType): node is DocumentFragment { + return node.nodeType === FRAGMENT_NODE +} + +export interface FilterConfig extends Config { + filterNode?: (node: any) => boolean +} + +function filterChildren(children: any[], filterNode?: (node: any) => boolean): any[] { + if (!filterNode) { + return children + } + return children.filter(filterNode) +} + +export function createDOMElementFilter(filterNode?: (node: any) => boolean): NewPlugin { + return { + test, + serialize: ( + node: HandledType, + config: Config, + indentation: string, + depth: number, + refs: Refs, + printer: Printer, + ) => { + if (nodeIsText(node)) { + return printText(node.data, config) + } + + if (nodeIsComment(node)) { + return printComment(node.data, config) + } + + const type = nodeIsFragment(node) + ? 'DocumentFragment' + : node.tagName.toLowerCase() + + if (++depth > config.maxDepth) { + return printElementAsLeaf(type, config) + } + + const children = Array.prototype.slice.call(node.childNodes || node.children) + const filteredChildren = filterChildren(children, filterNode) + + const shadowChildren = (nodeIsFragment(node) || !node.shadowRoot) + ? [] + : Array.prototype.slice.call(node.shadowRoot.children) + const filteredShadowChildren = filterChildren(shadowChildren, filterNode) + + return printElement( + type, + printProps( + nodeIsFragment(node) + ? [] + : Array.from(node.attributes, attr => attr.name).sort(), + nodeIsFragment(node) + ? {} + : [...node.attributes].reduce>( + (props, attribute) => { + props[attribute.name] = attribute.value + return props + }, + {}, + ), + config, + indentation + config.indent, + depth, + refs, + printer, + ), + (filteredShadowChildren.length > 0 + ? printShadowRoot(filteredShadowChildren, config, indentation + config.indent, depth, refs, printer) + : '') + + printChildren( + filteredChildren, + config, + indentation + config.indent, + depth, + refs, + printer, + ), + config, + indentation, + ) + }, + } +} + +export default createDOMElementFilter diff --git a/packages/utils/src/display.ts b/packages/utils/src/display.ts index 0a47fb88f727..3b9a2e38b8f5 100644 --- a/packages/utils/src/display.ts +++ b/packages/utils/src/display.ts @@ -1,5 +1,6 @@ import type { PrettyFormatOptions } from '@vitest/pretty-format' import { + createDOMElementFilter, format as prettyFormat, plugins as prettyFormatPlugins, } from '@vitest/pretty-format' @@ -42,22 +43,34 @@ const PLUGINS = [ export interface StringifyOptions extends PrettyFormatOptions { maxLength?: number + filterNode?: (node: any) => boolean } export function stringify( object: unknown, maxDepth = 10, - { maxLength, ...options }: StringifyOptions = {}, + { maxLength, filterNode, ...options }: StringifyOptions = {}, ): string { const MAX_LENGTH = maxLength ?? 10000 let result + const plugins = filterNode + ? [ + ReactTestComponent, + ReactElement, + createDOMElementFilter(filterNode), + DOMCollection, + Immutable, + AsymmetricMatcher, + ] + : PLUGINS + try { result = prettyFormat(object, { maxDepth, escapeString: false, // min: true, - plugins: PLUGINS, + plugins, ...options, }) } @@ -67,14 +80,14 @@ export function stringify( maxDepth, escapeString: false, // min: true, - plugins: PLUGINS, + plugins, ...options, }) } // Prevents infinite loop https://github.com/vitest-dev/vitest/issues/7249 return result.length >= MAX_LENGTH && maxDepth > 1 - ? stringify(object, Math.floor(Math.min(maxDepth, Number.MAX_SAFE_INTEGER) / 2), { maxLength, ...options }) + ? stringify(object, Math.floor(Math.min(maxDepth, Number.MAX_SAFE_INTEGER) / 2), { maxLength, filterNode, ...options }) : result } diff --git a/test/browser/test/utils.test.ts b/test/browser/test/utils.test.ts index 5d97ae4952cd..85e1c3be1507 100644 --- a/test/browser/test/utils.test.ts +++ b/test/browser/test/utils.test.ts @@ -109,3 +109,76 @@ test('changing the defaults works', async () => { " `) }) + +test('filterNode option filters out matching elements', async () => { + utils.configurePrettyDOM({}) + + const div = document.createElement('div') + div.innerHTML = ` +
+ + + hidden content + visible content +
+ ` + document.body.append(div) + + const filterNode = utils.createNodeFilter('script, style, [data-test-hide]') + const result = await commands.stripVTControlCharacters(utils.prettyDOM(div, undefined, { filterNode })) + + expect(result).not.toContain('console.log') + expect(result).not.toContain('color: red') + expect(result).not.toContain('hidden content') + expect(result).toContain('visible content') +}) + +test('filterNode with configurePrettyDOM affects default behavior', async () => { + const filterNode = utils.createNodeFilter('script, style, [data-test-hide]') + utils.configurePrettyDOM({ filterNode }) + + const div = document.createElement('div') + div.innerHTML = ` +
+ + + hidden content + visible content +
+ ` + document.body.append(div) + + const result = await commands.stripVTControlCharacters(utils.prettyDOM(div)) + + expect(result).not.toContain('console.log') + expect(result).not.toContain('color: red') + expect(result).not.toContain('hidden content') + expect(result).toContain('visible content') + + // Reset + utils.configurePrettyDOM({}) +}) + +test('filterNode with wildcard selector filters nested content', async () => { + utils.configurePrettyDOM({}) + + const div = document.createElement('div') + div.innerHTML = ` +
+
+ nested hidden +
deeply nested hidden
+
+ visible +
+ ` + document.body.append(div) + + const filterNode = utils.createNodeFilter('[data-test-hide-content] *') + const result = await commands.stripVTControlCharacters(utils.prettyDOM(div, undefined, { filterNode })) + + expect(result).not.toContain('nested hidden') + expect(result).not.toContain('deeply nested hidden') + expect(result).toContain('visible') + expect(result).toContain('data-test-hide-content') +}) From 37380ac1c393794add80ffd59878f20cd66cdf9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:13:52 +0000 Subject: [PATCH 3/9] Address PR feedback: add inline snapshot test, mark as experimental, add documentation - Added toMatchInlineSnapshot to filterNode test for better visibility - Marked createNodeFilter as @experimental in JSDoc - Added comprehensive documentation section in docs/api/browser/context.md explaining: - configurePrettyDOM options - createNodeFilter usage with examples - Common filtering patterns Co-authored-by: sheremet-va <16173870+sheremet-va@users.noreply.github.com> --- docs/api/browser/context.md | 77 +++++++++++++++++++ packages/browser/src/client/tester/context.ts | 1 + test/browser/test/utils.test.ts | 1 + 3 files changed, 79 insertions(+) diff --git a/docs/api/browser/context.md b/docs/api/browser/context.md index b16d098ed869..b291d67bc816 100644 --- a/docs/api/browser/context.md +++ b/docs/api/browser/context.md @@ -225,9 +225,86 @@ export const utils: { * @experimental */ configurePrettyDOM(options: StringifyOptions): void + /** + * Creates a filter function for prettyDOM that filters out nodes based on CSS selectors. + * This is similar to Testing Library's defaultIgnore configuration. + * @experimental + */ + createNodeFilter(selector: string): (node: any) => boolean /** * Creates "Cannot find element" error. Useful for custom locators. */ getElementError(selector: string, container?: Element): Error } ``` + +### configurePrettyDOM + +4.1.0 + +The `configurePrettyDOM` function allows you to configure default options for the `prettyDOM` and `debug` functions. This is useful for customizing how HTML is formatted in test failure messages. + +```ts +import { utils } from 'vitest/browser' + +utils.configurePrettyDOM({ + maxDepth: 3, + filterNode: utils.createNodeFilter('script, style') +}) +``` + +#### Options + +- **`maxDepth`** - Maximum depth to print nested elements (default: `Infinity`) +- **`maxLength`** - Maximum length of the output string (default: `7000`) +- **`filterNode`** - A function to filter out nodes from the output. Return `false` to exclude a node. +- **`highlight`** - Enable syntax highlighting (default: `true`) +- And other options from [`pretty-format`](https://www.npmjs.com/package/@vitest/pretty-format) + +### createNodeFilter + +4.1.0 + +The `createNodeFilter` function creates a filter function that can be used with `configurePrettyDOM` or passed directly to `prettyDOM`/`debug`. It filters out DOM nodes based on CSS selectors. + +This is particularly useful for hiding irrelevant markup (like scripts, styles, or hidden elements) from test failure messages, making it easier to identify the actual cause of failures. + +```ts +import { utils } from 'vitest/browser' + +// Filter out common noise elements +const filterNode = utils.createNodeFilter('script, style, [data-test-hide]') + +// Use with configurePrettyDOM (affects all debug/prettyDOM calls) +utils.configurePrettyDOM({ filterNode }) + +// Or use directly with prettyDOM +const html = utils.prettyDOM(element, undefined, { filterNode }) +``` + +#### Common Patterns + +**Filter out scripts and styles:** +```ts +utils.createNodeFilter('script, style') +``` + +**Hide specific elements with data attributes:** +```ts +utils.createNodeFilter('[data-test-hide]') +``` + +**Hide nested content within an element:** +```ts +// Hides all children of elements with data-test-hide-content +utils.createNodeFilter('[data-test-hide-content] *') +``` + +**Combine multiple selectors:** +```ts +utils.createNodeFilter('script, style, [data-test-hide], svg') +``` + +::: tip +This feature is inspired by Testing Library's [`defaultIgnore`](https://testing-library.com/docs/dom-testing-library/api-configuration/#defaultignore) configuration. +::: diff --git a/packages/browser/src/client/tester/context.ts b/packages/browser/src/client/tester/context.ts index b291d17d676b..a162b7408f16 100644 --- a/packages/browser/src/client/tester/context.ts +++ b/packages/browser/src/client/tester/context.ts @@ -515,6 +515,7 @@ function configurePrettyDOM(options: StringifyOptions) { * * @param selector - CSS selector to filter out (e.g., 'script, style, [data-test-hide]') * @returns A filter function that can be used with configurePrettyDOM + * @experimental */ function createNodeFilter(selector: string): (node: any) => boolean { const ELEMENT_NODE = 1 diff --git a/test/browser/test/utils.test.ts b/test/browser/test/utils.test.ts index 85e1c3be1507..18b095b0156e 100644 --- a/test/browser/test/utils.test.ts +++ b/test/browser/test/utils.test.ts @@ -131,6 +131,7 @@ test('filterNode option filters out matching elements', async () => { expect(result).not.toContain('color: red') expect(result).not.toContain('hidden content') expect(result).toContain('visible content') + expect(result).toMatchInlineSnapshot() }) test('filterNode with configurePrettyDOM affects default behavior', async () => { From 7c66451ff46178f09ec5bcb78a14c1832f79224b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:29:35 +0000 Subject: [PATCH 4/9] Simplify API: filterNode now accepts string directly, remove createNodeFilter - Changed filterNode to accept string | function in StringifyOptions - Added internal createNodeFilterFromSelector function in display.ts - Removed createNodeFilter from utils export - Updated all tests to use string selectors directly - Moved configurePrettyDOM reset to beforeEach hook - Added toMatchInlineSnapshot to all filter tests - Updated documentation to reflect simplified API - Updated type definitions to remove createNodeFilter Co-authored-by: sheremet-va <16173870+sheremet-va@users.noreply.github.com> --- docs/api/browser/context.md | 47 +++++++++---------- packages/browser/context.d.ts | 19 -------- packages/browser/src/client/tester/context.ts | 43 ----------------- packages/utils/src/display.ts | 35 ++++++++++++-- test/browser/test/utils.test.ts | 24 ++++------ 5 files changed, 63 insertions(+), 105 deletions(-) diff --git a/docs/api/browser/context.md b/docs/api/browser/context.md index b291d67bc816..bb85cec013b9 100644 --- a/docs/api/browser/context.md +++ b/docs/api/browser/context.md @@ -225,12 +225,6 @@ export const utils: { * @experimental */ configurePrettyDOM(options: StringifyOptions): void - /** - * Creates a filter function for prettyDOM that filters out nodes based on CSS selectors. - * This is similar to Testing Library's defaultIgnore configuration. - * @experimental - */ - createNodeFilter(selector: string): (node: any) => boolean /** * Creates "Cannot find element" error. Useful for custom locators. */ @@ -249,7 +243,7 @@ import { utils } from 'vitest/browser' utils.configurePrettyDOM({ maxDepth: 3, - filterNode: utils.createNodeFilter('script, style') + filterNode: 'script, style, [data-test-hide]' }) ``` @@ -257,52 +251,53 @@ utils.configurePrettyDOM({ - **`maxDepth`** - Maximum depth to print nested elements (default: `Infinity`) - **`maxLength`** - Maximum length of the output string (default: `7000`) -- **`filterNode`** - A function to filter out nodes from the output. Return `false` to exclude a node. +- **`filterNode`** - A CSS selector string or function to filter out nodes from the output. When a string is provided, elements matching the selector will be excluded. When a function is provided, it should return `false` to exclude a node. - **`highlight`** - Enable syntax highlighting (default: `true`) - And other options from [`pretty-format`](https://www.npmjs.com/package/@vitest/pretty-format) -### createNodeFilter +#### Filtering with CSS Selectors 4.1.0 -The `createNodeFilter` function creates a filter function that can be used with `configurePrettyDOM` or passed directly to `prettyDOM`/`debug`. It filters out DOM nodes based on CSS selectors. - -This is particularly useful for hiding irrelevant markup (like scripts, styles, or hidden elements) from test failure messages, making it easier to identify the actual cause of failures. +The `filterNode` option allows you to hide irrelevant markup (like scripts, styles, or hidden elements) from test failure messages, making it easier to identify the actual cause of failures. ```ts import { utils } from 'vitest/browser' // Filter out common noise elements -const filterNode = utils.createNodeFilter('script, style, [data-test-hide]') - -// Use with configurePrettyDOM (affects all debug/prettyDOM calls) -utils.configurePrettyDOM({ filterNode }) +utils.configurePrettyDOM({ + filterNode: 'script, style, [data-test-hide]' +}) // Or use directly with prettyDOM -const html = utils.prettyDOM(element, undefined, { filterNode }) +const html = utils.prettyDOM(element, undefined, { + filterNode: 'script, style' +}) ``` -#### Common Patterns +**Common Patterns:** -**Filter out scripts and styles:** +Filter out scripts and styles: ```ts -utils.createNodeFilter('script, style') +utils.configurePrettyDOM({ filterNode: 'script, style' }) ``` -**Hide specific elements with data attributes:** +Hide specific elements with data attributes: ```ts -utils.createNodeFilter('[data-test-hide]') +utils.configurePrettyDOM({ filterNode: '[data-test-hide]' }) ``` -**Hide nested content within an element:** +Hide nested content within an element: ```ts // Hides all children of elements with data-test-hide-content -utils.createNodeFilter('[data-test-hide-content] *') +utils.configurePrettyDOM({ filterNode: '[data-test-hide-content] *' }) ``` -**Combine multiple selectors:** +Combine multiple selectors: ```ts -utils.createNodeFilter('script, style, [data-test-hide], svg') +utils.configurePrettyDOM({ + filterNode: 'script, style, [data-test-hide], svg' +}) ``` ::: tip diff --git a/packages/browser/context.d.ts b/packages/browser/context.d.ts index 18844e24f004..ef1c524e8462 100644 --- a/packages/browser/context.d.ts +++ b/packages/browser/context.d.ts @@ -869,25 +869,6 @@ export const utils: { * @experimental */ configurePrettyDOM(options: StringifyOptions): void - /** - * Creates a filter function for prettyDOM that filters out nodes based on CSS selectors. - * This is similar to Testing Library's defaultIgnore configuration. - * - * @example - * ```ts - * import { utils } from 'vitest/browser' - * - * // Filter out script, style, and elements with data-test-hide attribute - * utils.configurePrettyDOM({ - * filterNode: utils.createNodeFilter('script, style, [data-test-hide]') - * }) - * ``` - * - * @param selector - CSS selector to filter out (e.g., 'script, style, [data-test-hide]') - * @returns A filter function that can be used with configurePrettyDOM - * @experimental - */ - createNodeFilter(selector: string): (node: any) => boolean /** * Creates "Cannot find element" error. Useful for custom locators. */ diff --git a/packages/browser/src/client/tester/context.ts b/packages/browser/src/client/tester/context.ts index a162b7408f16..8229969ea444 100644 --- a/packages/browser/src/client/tester/context.ts +++ b/packages/browser/src/client/tester/context.ts @@ -499,53 +499,10 @@ function configurePrettyDOM(options: StringifyOptions) { defaultOptions = options } -/** - * Creates a filter function for prettyDOM that filters out nodes based on CSS selectors. - * This is similar to Testing Library's defaultIgnore configuration. - * - * @example - * ```ts - * import { utils } from 'vitest/browser' - * - * // Filter out script, style, and elements with data-test-hide attribute - * utils.configurePrettyDOM({ - * filterNode: utils.createNodeFilter('script, style, [data-test-hide]') - * }) - * ``` - * - * @param selector - CSS selector to filter out (e.g., 'script, style, [data-test-hide]') - * @returns A filter function that can be used with configurePrettyDOM - * @experimental - */ -function createNodeFilter(selector: string): (node: any) => boolean { - const ELEMENT_NODE = 1 - const COMMENT_NODE = 8 - - return (node: any) => { - // Filter out comments - if (node.nodeType === COMMENT_NODE) { - return false - } - - // Filter out elements matching the selector - if (node.nodeType === ELEMENT_NODE && node.matches) { - try { - return !node.matches(selector) - } - catch { - return true - } - } - - return true - } -} - export const utils = { getElementError, prettyDOM, debug, getElementLocatorSelectors, configurePrettyDOM, - createNodeFilter, } diff --git a/packages/utils/src/display.ts b/packages/utils/src/display.ts index 3b9a2e38b8f5..7c1aebc9da02 100644 --- a/packages/utils/src/display.ts +++ b/packages/utils/src/display.ts @@ -43,7 +43,7 @@ const PLUGINS = [ export interface StringifyOptions extends PrettyFormatOptions { maxLength?: number - filterNode?: (node: any) => boolean + filterNode?: string | ((node: any) => boolean) } export function stringify( @@ -54,11 +54,16 @@ export function stringify( const MAX_LENGTH = maxLength ?? 10000 let result - const plugins = filterNode + // Convert string selector to filter function + const filterFn = typeof filterNode === 'string' + ? createNodeFilterFromSelector(filterNode) + : filterNode + + const plugins = filterFn ? [ ReactTestComponent, ReactElement, - createDOMElementFilter(filterNode), + createDOMElementFilter(filterFn), DOMCollection, Immutable, AsymmetricMatcher, @@ -91,6 +96,30 @@ export function stringify( : result } +function createNodeFilterFromSelector(selector: string): (node: any) => boolean { + const ELEMENT_NODE = 1 + const COMMENT_NODE = 8 + + return (node: any) => { + // Filter out comments + if (node.nodeType === COMMENT_NODE) { + return false + } + + // Filter out elements matching the selector + if (node.nodeType === ELEMENT_NODE && node.matches) { + try { + return !node.matches(selector) + } + catch { + return true + } + } + + return true + } +} + export const formatRegExp: RegExp = /%[sdjifoOc%]/g interface FormatOptions { diff --git a/test/browser/test/utils.test.ts b/test/browser/test/utils.test.ts index 18b095b0156e..db7d8baa432c 100644 --- a/test/browser/test/utils.test.ts +++ b/test/browser/test/utils.test.ts @@ -1,4 +1,4 @@ -import { afterEach, expect, it, test } from 'vitest' +import { afterEach, beforeEach, expect, it, test } from 'vitest' import { commands, utils } from 'vitest/browser' import { inspect } from 'vitest/internal/browser' @@ -7,6 +7,10 @@ afterEach(() => { document.body.innerHTML = '' }) +beforeEach(() => { + utils.configurePrettyDOM({}) +}) + it('utils package correctly uses loupe', async () => { expect(inspect({ test: 1 })).toBe('{ test: 1 }') }) @@ -111,8 +115,6 @@ test('changing the defaults works', async () => { }) test('filterNode option filters out matching elements', async () => { - utils.configurePrettyDOM({}) - const div = document.createElement('div') div.innerHTML = `
@@ -124,8 +126,7 @@ test('filterNode option filters out matching elements', async () => { ` document.body.append(div) - const filterNode = utils.createNodeFilter('script, style, [data-test-hide]') - const result = await commands.stripVTControlCharacters(utils.prettyDOM(div, undefined, { filterNode })) + const result = await commands.stripVTControlCharacters(utils.prettyDOM(div, undefined, { filterNode: 'script, style, [data-test-hide]' })) expect(result).not.toContain('console.log') expect(result).not.toContain('color: red') @@ -135,8 +136,7 @@ test('filterNode option filters out matching elements', async () => { }) test('filterNode with configurePrettyDOM affects default behavior', async () => { - const filterNode = utils.createNodeFilter('script, style, [data-test-hide]') - utils.configurePrettyDOM({ filterNode }) + utils.configurePrettyDOM({ filterNode: 'script, style, [data-test-hide]' }) const div = document.createElement('div') div.innerHTML = ` @@ -155,14 +155,10 @@ test('filterNode with configurePrettyDOM affects default behavior', async () => expect(result).not.toContain('color: red') expect(result).not.toContain('hidden content') expect(result).toContain('visible content') - - // Reset - utils.configurePrettyDOM({}) + expect(result).toMatchInlineSnapshot() }) test('filterNode with wildcard selector filters nested content', async () => { - utils.configurePrettyDOM({}) - const div = document.createElement('div') div.innerHTML = `
@@ -175,11 +171,11 @@ test('filterNode with wildcard selector filters nested content', async () => { ` document.body.append(div) - const filterNode = utils.createNodeFilter('[data-test-hide-content] *') - const result = await commands.stripVTControlCharacters(utils.prettyDOM(div, undefined, { filterNode })) + const result = await commands.stripVTControlCharacters(utils.prettyDOM(div, undefined, { filterNode: '[data-test-hide-content] *' })) expect(result).not.toContain('nested hidden') expect(result).not.toContain('deeply nested hidden') expect(result).toContain('visible') expect(result).toContain('data-test-hide-content') + expect(result).toMatchInlineSnapshot() }) From a6927f775c00b1130d01a2c281eccbce9445b6be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:30:38 +0000 Subject: [PATCH 5/9] Add inline snapshots to tests with expected filtered output Co-authored-by: sheremet-va <16173870+sheremet-va@users.noreply.github.com> --- test/browser/test/utils.test.ts | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/test/browser/test/utils.test.ts b/test/browser/test/utils.test.ts index db7d8baa432c..f0ca2a356091 100644 --- a/test/browser/test/utils.test.ts +++ b/test/browser/test/utils.test.ts @@ -132,7 +132,15 @@ test('filterNode option filters out matching elements', async () => { expect(result).not.toContain('color: red') expect(result).not.toContain('hidden content') expect(result).toContain('visible content') - expect(result).toMatchInlineSnapshot() + expect(result).toMatchInlineSnapshot(` + "
+
+ + visible content + +
+
" + `) }) test('filterNode with configurePrettyDOM affects default behavior', async () => { @@ -155,7 +163,15 @@ test('filterNode with configurePrettyDOM affects default behavior', async () => expect(result).not.toContain('color: red') expect(result).not.toContain('hidden content') expect(result).toContain('visible content') - expect(result).toMatchInlineSnapshot() + expect(result).toMatchInlineSnapshot(` + "
+
+ + visible content + +
+
" + `) }) test('filterNode with wildcard selector filters nested content', async () => { @@ -177,5 +193,16 @@ test('filterNode with wildcard selector filters nested content', async () => { expect(result).not.toContain('deeply nested hidden') expect(result).toContain('visible') expect(result).toContain('data-test-hide-content') - expect(result).toMatchInlineSnapshot() + expect(result).toMatchInlineSnapshot(` + "
+
+
+ + visible + +
+
" + `) }) From 15d16970829ca233fce7b726b1d67b65021253de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:36:05 +0000 Subject: [PATCH 6/9] Move version tags to heading line and add custom anchors in docs Co-authored-by: sheremet-va <16173870+sheremet-va@users.noreply.github.com> --- docs/api/browser/context.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/api/browser/context.md b/docs/api/browser/context.md index bb85cec013b9..f2fc68c34991 100644 --- a/docs/api/browser/context.md +++ b/docs/api/browser/context.md @@ -232,9 +232,7 @@ export const utils: { } ``` -### configurePrettyDOM - -4.1.0 +### configurePrettyDOM 4.1.0 {#configureprettydom} The `configurePrettyDOM` function allows you to configure default options for the `prettyDOM` and `debug` functions. This is useful for customizing how HTML is formatted in test failure messages. @@ -255,9 +253,7 @@ utils.configurePrettyDOM({ - **`highlight`** - Enable syntax highlighting (default: `true`) - And other options from [`pretty-format`](https://www.npmjs.com/package/@vitest/pretty-format) -#### Filtering with CSS Selectors - -4.1.0 +#### Filtering with CSS Selectors 4.1.0 {#filtering-with-css-selectors} The `filterNode` option allows you to hide irrelevant markup (like scripts, styles, or hidden elements) from test failure messages, making it easier to identify the actual cause of failures. From 8e179eebaadfcb8a039626b066c07f80d93666ff Mon Sep 17 00:00:00 2001 From: Vladimir Date: Fri, 16 Jan 2026 17:45:30 +0100 Subject: [PATCH 7/9] Update docs/api/browser/context.md --- docs/api/browser/context.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/browser/context.md b/docs/api/browser/context.md index f2fc68c34991..c21120246236 100644 --- a/docs/api/browser/context.md +++ b/docs/api/browser/context.md @@ -232,7 +232,7 @@ export const utils: { } ``` -### configurePrettyDOM 4.1.0 {#configureprettydom} +### configurePrettyDOM 4.0.0 {#configureprettydom} The `configurePrettyDOM` function allows you to configure default options for the `prettyDOM` and `debug` functions. This is useful for customizing how HTML is formatted in test failure messages. From 4baa4378716bec768dc8ae19d4818db64cee994f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 17:07:08 +0000 Subject: [PATCH 8/9] Filter out whitespace-only text nodes to prevent empty lines in output Co-authored-by: sheremet-va <16173870+sheremet-va@users.noreply.github.com> --- .../src/plugins/DOMElementFilter.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/pretty-format/src/plugins/DOMElementFilter.ts b/packages/pretty-format/src/plugins/DOMElementFilter.ts index 37560e159c2c..a66f206daefe 100644 --- a/packages/pretty-format/src/plugins/DOMElementFilter.ts +++ b/packages/pretty-format/src/plugins/DOMElementFilter.ts @@ -70,10 +70,24 @@ export interface FilterConfig extends Config { } function filterChildren(children: any[], filterNode?: (node: any) => boolean): any[] { - if (!filterNode) { - return children + // Filter out text nodes that only contain whitespace to prevent empty lines + // This is done regardless of whether a filterNode is provided + let filtered = children.filter((node) => { + // Filter out text nodes that are only whitespace + if (node.nodeType === TEXT_NODE) { + const text = node.data || '' + // Keep text nodes that have non-whitespace content + return text.trim().length > 0 + } + return true + }) + + // Apply additional user-provided filter if specified + if (filterNode) { + filtered = filtered.filter(filterNode) } - return children.filter(filterNode) + + return filtered } export function createDOMElementFilter(filterNode?: (node: any) => boolean): NewPlugin { From 89b3b502f14f0b5dd01a3149b0e96f207aca8c9c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:03:45 +0000 Subject: [PATCH 9/9] Remove trailing spaces and run linter Co-authored-by: sheremet-va <16173870+sheremet-va@users.noreply.github.com> --- packages/pretty-format/src/plugins/DOMElementFilter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pretty-format/src/plugins/DOMElementFilter.ts b/packages/pretty-format/src/plugins/DOMElementFilter.ts index a66f206daefe..7123aee2838e 100644 --- a/packages/pretty-format/src/plugins/DOMElementFilter.ts +++ b/packages/pretty-format/src/plugins/DOMElementFilter.ts @@ -81,12 +81,12 @@ function filterChildren(children: any[], filterNode?: (node: any) => boolean): a } return true }) - + // Apply additional user-provided filter if specified if (filterNode) { filtered = filtered.filter(filterNode) } - + return filtered }