···11-import type { SerializedConfig } from 'vitest'
11+import { SerializedConfig } from 'vitest'
22import { ARIARole } from './aria-role.js'
33+import {} from './matchers.js'
3445export type BufferEncoding =
56 | 'ascii'
+623-712
packages/browser/jest-dom.d.ts
···2233import { ARIARole } from './aria-role.ts'
4455-declare namespace matchers {
66- interface TestingLibraryMatchers<E, R> {
77- /**
88- * @deprecated
99- * since v1.9.0
1010- * @description
1111- * Assert whether a value is a DOM element, or not. Contrary to what its name implies, this matcher only checks
1212- * that you passed to it a valid DOM element.
1313- *
1414- * It does not have a clear definition of what "the DOM" is. Therefore, it does not check whether that element
1515- * is contained anywhere.
1616- * @see
1717- * [testing-library/jest-dom#toBeInTheDom](https://github.com/testing-library/jest-dom#toBeInTheDom)
1818- */
1919- toBeInTheDOM(container?: HTMLElement | SVGElement): R
2020- /**
2121- * @description
2222- * Assert whether an element is present in the document or not.
2323- * @example
2424- * <svg data-testid="svg-element"></svg>
2525- *
2626- * expect(queryByTestId('svg-element')).toBeInTheDocument()
2727- * expect(queryByTestId('does-not-exist')).not.toBeInTheDocument()
2828- * @see
2929- * [testing-library/jest-dom#tobeinthedocument](https://github.com/testing-library/jest-dom#tobeinthedocument)
3030- */
3131- toBeInTheDocument(): R
3232- /**
3333- * @description
3434- * This allows you to check if an element is currently visible to the user.
3535- *
3636- * An element is visible if **all** the following conditions are met:
3737- * * it does not have its css property display set to none
3838- * * it does not have its css property visibility set to either hidden or collapse
3939- * * it does not have its css property opacity set to 0
4040- * * its parent element is also visible (and so on up to the top of the DOM tree)
4141- * * it does not have the hidden attribute
4242- * * if `<details />` it has the open attribute
4343- * @example
4444- * <div
4545- * data-testid="zero-opacity"
4646- * style="opacity: 0"
4747- * >
4848- * Zero Opacity
4949- * </div>
5050- *
5151- * <div data-testid="visible">Visible Example</div>
5252- *
5353- * expect(getByTestId('zero-opacity')).not.toBeVisible()
5454- * expect(getByTestId('visible')).toBeVisible()
5555- * @see
5656- * [testing-library/jest-dom#tobevisible](https://github.com/testing-library/jest-dom#tobevisible)
5757- */
5858- toBeVisible(): R
5959- /**
6060- * @deprecated
6161- * since v5.9.0
6262- * @description
6363- * Assert whether an element has content or not.
6464- * @example
6565- * <span data-testid="not-empty">
6666- * <span data-testid="empty"></span>
6767- * </span>
6868- *
6969- * expect(getByTestId('empty')).toBeEmpty()
7070- * expect(getByTestId('not-empty')).not.toBeEmpty()
7171- * @see
7272- * [testing-library/jest-dom#tobeempty](https://github.com/testing-library/jest-dom#tobeempty)
7373- */
7474- toBeEmpty(): R
7575- /**
7676- * @description
7777- * Assert whether an element has content or not.
7878- * @example
7979- * <span data-testid="not-empty">
8080- * <span data-testid="empty"></span>
8181- * </span>
8282- *
8383- * expect(getByTestId('empty')).toBeEmptyDOMElement()
8484- * expect(getByTestId('not-empty')).not.toBeEmptyDOMElement()
8585- * @see
8686- * [testing-library/jest-dom#tobeemptydomelement](https://github.com/testing-library/jest-dom#tobeemptydomelement)
8787- */
8888- toBeEmptyDOMElement(): R
8989- /**
9090- * @description
9191- * Allows you to check whether an element is disabled from the user's perspective.
9292- *
9393- * Matches if the element is a form control and the `disabled` attribute is specified on this element or the
9494- * element is a descendant of a form element with a `disabled` attribute.
9595- * @example
9696- * <button
9797- * data-testid="button"
9898- * type="submit"
9999- * disabled
100100- * >
101101- * submit
102102- * </button>
103103- *
104104- * expect(getByTestId('button')).toBeDisabled()
105105- * @see
106106- * [testing-library/jest-dom#tobedisabled](https://github.com/testing-library/jest-dom#tobedisabled)
107107- */
108108- toBeDisabled(): R
109109- /**
110110- * @description
111111- * Allows you to check whether an element is not disabled from the user's perspective.
112112- *
113113- * Works like `not.toBeDisabled()`.
114114- *
115115- * Use this matcher to avoid double negation in your tests.
116116- * @example
117117- * <button
118118- * data-testid="button"
119119- * type="submit"
120120- * >
121121- * submit
122122- * </button>
123123- *
124124- * expect(getByTestId('button')).toBeEnabled()
125125- * @see
126126- * [testing-library/jest-dom#tobeenabled](https://github.com/testing-library/jest-dom#tobeenabled)
127127- */
128128- toBeEnabled(): R
129129- /**
130130- * @description
131131- * Check if a form element, or the entire `form`, is currently invalid.
132132- *
133133- * An `input`, `select`, `textarea`, or `form` element is invalid if it has an `aria-invalid` attribute with no
134134- * value or a value of "true", or if the result of `checkValidity()` is false.
135135- * @example
136136- * <input data-testid="no-aria-invalid" />
137137- *
138138- * <form data-testid="invalid-form">
139139- * <input required />
140140- * </form>
141141- *
142142- * expect(getByTestId('no-aria-invalid')).not.toBeInvalid()
143143- * expect(getByTestId('invalid-form')).toBeInvalid()
144144- * @see
145145- * [testing-library/jest-dom#tobeinvalid](https://github.com/testing-library/jest-dom#tobeinvalid)
146146- */
147147- toBeInvalid(): R
148148- /**
149149- * @description
150150- * This allows you to check if a form element is currently required.
151151- *
152152- * An element is required if it is having a `required` or `aria-required="true"` attribute.
153153- * @example
154154- * <input data-testid="required-input" required />
155155- * <div
156156- * data-testid="supported-role"
157157- * role="tree"
158158- * required />
159159- *
160160- * expect(getByTestId('required-input')).toBeRequired()
161161- * expect(getByTestId('supported-role')).not.toBeRequired()
162162- * @see
163163- * [testing-library/jest-dom#toberequired](https://github.com/testing-library/jest-dom#toberequired)
164164- */
165165- toBeRequired(): R
166166- /**
167167- * @description
168168- * Allows you to check if a form element is currently required.
169169- *
170170- * An `input`, `select`, `textarea`, or `form` element is invalid if it has an `aria-invalid` attribute with no
171171- * value or a value of "false", or if the result of `checkValidity()` is true.
172172- * @example
173173- * <input data-testid="aria-invalid" aria-invalid />
174174- *
175175- * <form data-testid="valid-form">
176176- * <input />
177177- * </form>
178178- *
179179- * expect(getByTestId('no-aria-invalid')).not.toBeValid()
180180- * expect(getByTestId('invalid-form')).toBeInvalid()
181181- * @see
182182- * [testing-library/jest-dom#tobevalid](https://github.com/testing-library/jest-dom#tobevalid)
183183- */
184184- toBeValid(): R
185185- /**
186186- * @description
187187- * Allows you to assert whether an element contains another element as a descendant or not.
188188- * @example
189189- * <span data-testid="ancestor">
190190- * <span data-testid="descendant"></span>
191191- * </span>
192192- *
193193- * const ancestor = getByTestId('ancestor')
194194- * const descendant = getByTestId('descendant')
195195- * const nonExistentElement = getByTestId('does-not-exist')
196196- * expect(ancestor).toContainElement(descendant)
197197- * expect(descendant).not.toContainElement(ancestor)
198198- * expect(ancestor).not.toContainElement(nonExistentElement)
199199- * @see
200200- * [testing-library/jest-dom#tocontainelement](https://github.com/testing-library/jest-dom#tocontainelement)
201201- */
202202- toContainElement(element: HTMLElement | SVGElement | null): R
203203- /**
204204- * @description
205205- * Assert whether a string representing a HTML element is contained in another element.
206206- * @example
207207- * <span data-testid="parent"><span data-testid="child"></span></span>
208208- *
209209- * expect(getByTestId('parent')).toContainHTML('<span data-testid="child"></span>')
210210- * @see
211211- * [testing-library/jest-dom#tocontainhtml](https://github.com/testing-library/jest-dom#tocontainhtml)
212212- */
213213- toContainHTML(htmlText: string): R
214214- /**
215215- * @description
216216- * Allows you to check if a given element has an attribute or not.
217217- *
218218- * You can also optionally check that the attribute has a specific expected value or partial match using
219219- * [expect.stringContaining](https://jestjs.io/docs/en/expect.html#expectnotstringcontainingstring) or
220220- * [expect.stringMatching](https://jestjs.io/docs/en/expect.html#expectstringmatchingstring-regexp).
221221- * @example
222222- * <button
223223- * data-testid="ok-button"
224224- * type="submit"
225225- * disabled
226226- * >
227227- * ok
228228- * </button>
229229- *
230230- * expect(button).toHaveAttribute('disabled')
231231- * expect(button).toHaveAttribute('type', 'submit')
232232- * expect(button).not.toHaveAttribute('type', 'button')
233233- * @see
234234- * [testing-library/jest-dom#tohaveattribute](https://github.com/testing-library/jest-dom#tohaveattribute)
235235- */
236236- toHaveAttribute(attr: string, value?: unknown): R
237237- /**
238238- * @description
239239- * Check whether the given element has certain classes within its `class` attribute.
240240- *
241241- * You must provide at least one class, unless you are asserting that an element does not have any classes.
242242- * @example
243243- * <button
244244- * data-testid="delete-button"
245245- * class="btn xs btn-danger"
246246- * >
247247- * delete item
248248- * </button>
249249- *
250250- * <div data-testid="no-classes">no classes</div>
251251- *
252252- * const deleteButton = getByTestId('delete-button')
253253- * const noClasses = getByTestId('no-classes')
254254- * expect(deleteButton).toHaveClass('btn')
255255- * expect(deleteButton).toHaveClass('btn-danger xs')
256256- * expect(deleteButton).toHaveClass(/danger/, 'xs')
257257- * expect(deleteButton).toHaveClass('btn xs btn-danger', {exact: true})
258258- * expect(deleteButton).not.toHaveClass('btn xs btn-danger', {exact: true})
259259- * expect(noClasses).not.toHaveClass()
260260- * @see
261261- * [testing-library/jest-dom#tohaveclass](https://github.com/testing-library/jest-dom#tohaveclass)
262262- */
263263- toHaveClass(...classNames: (string | RegExp)[] | [string, options?: {exact: boolean}]): R
264264- /**
265265- * @description
266266- * This allows you to check whether the given form element has the specified displayed value (the one the
267267- * end user will see). It accepts <input>, <select> and <textarea> elements with the exception of <input type="checkbox">
268268- * and <input type="radio">, which can be meaningfully matched only using toBeChecked or toHaveFormValues.
269269- * @example
270270- * <label for="input-example">First name</label>
271271- * <input type="text" id="input-example" value="Luca" />
272272- *
273273- * <label for="textarea-example">Description</label>
274274- * <textarea id="textarea-example">An example description here.</textarea>
275275- *
276276- * <label for="single-select-example">Fruit</label>
277277- * <select id="single-select-example">
278278- * <option value="">Select a fruit...</option>
279279- * <option value="banana">Banana</option>
280280- * <option value="ananas">Ananas</option>
281281- * <option value="avocado">Avocado</option>
282282- * </select>
283283- *
284284- * <label for="multiple-select-example">Fruits</label>
285285- * <select id="multiple-select-example" multiple>
286286- * <option value="">Select a fruit...</option>
287287- * <option value="banana" selected>Banana</option>
288288- * <option value="ananas">Ananas</option>
289289- * <option value="avocado" selected>Avocado</option>
290290- * </select>
291291- *
292292- * const input = screen.getByLabelText('First name')
293293- * const textarea = screen.getByLabelText('Description')
294294- * const selectSingle = screen.getByLabelText('Fruit')
295295- * const selectMultiple = screen.getByLabelText('Fruits')
296296- *
297297- * expect(input).toHaveDisplayValue('Luca')
298298- * expect(textarea).toHaveDisplayValue('An example description here.')
299299- * expect(selectSingle).toHaveDisplayValue('Select a fruit...')
300300- * expect(selectMultiple).toHaveDisplayValue(['Banana', 'Avocado'])
301301- *
302302- * @see
303303- * [testing-library/jest-dom#tohavedisplayvalue](https://github.com/testing-library/jest-dom#tohavedisplayvalue)
304304- */
305305- toHaveDisplayValue(value: string | RegExp | Array<string | RegExp>): R
306306- /**
307307- * @description
308308- * Assert whether an element has focus or not.
309309- * @example
310310- * <div>
311311- * <input type="text" data-testid="element-to-focus" />
312312- * </div>
313313- *
314314- * const input = getByTestId('element-to-focus')
315315- * input.focus()
316316- * expect(input).toHaveFocus()
317317- * input.blur()
318318- * expect(input).not.toHaveFocus()
319319- * @see
320320- * [testing-library/jest-dom#tohavefocus](https://github.com/testing-library/jest-dom#tohavefocus)
321321- */
322322- toHaveFocus(): R
323323- /**
324324- * @description
325325- * Check if a form or fieldset contains form controls for each given name, and having the specified value.
326326- *
327327- * Can only be invoked on a form or fieldset element.
328328- * @example
329329- * <form data-testid="login-form">
330330- * <input type="text" name="username" value="jane.doe" />
331331- * <input type="password" name="password" value="123" />
332332- * <input type="checkbox" name="rememberMe" checked />
333333- * <button type="submit">Sign in</button>
334334- * </form>
335335- *
336336- * expect(getByTestId('login-form')).toHaveFormValues({
337337- * username: 'jane.doe',
338338- * rememberMe: true,
339339- * })
340340- * @see
341341- * [testing-library/jest-dom#tohaveformvalues](https://github.com/testing-library/jest-dom#tohaveformvalues)
342342- */
343343- toHaveFormValues(expectedValues: Record<string, unknown>): R
344344- /**
345345- * @description
346346- * Check if an element has specific css properties with specific values applied.
347347- *
348348- * Only matches if the element has *all* the expected properties applied, not just some of them.
349349- * @example
350350- * <button
351351- * data-testid="submit-button"
352352- * style="background-color: green; display: none"
353353- * >
354354- * submit
355355- * </button>
356356- *
357357- * const button = getByTestId('submit-button')
358358- * expect(button).toHaveStyle('background-color: green')
359359- * expect(button).toHaveStyle({
360360- * 'background-color': 'green',
361361- * display: 'none'
362362- * })
363363- * @see
364364- * [testing-library/jest-dom#tohavestyle](https://github.com/testing-library/jest-dom#tohavestyle)
365365- */
366366- toHaveStyle(css: string | Record<string, unknown>): R
367367- /**
368368- * @description
369369- * Check whether the given element has a text content or not.
370370- *
371371- * When a string argument is passed through, it will perform a partial case-sensitive match to the element
372372- * content.
373373- *
374374- * To perform a case-insensitive match, you can use a RegExp with the `/i` modifier.
375375- *
376376- * If you want to match the whole content, you can use a RegExp to do it.
377377- * @example
378378- * <span data-testid="text-content">Text Content</span>
379379- *
380380- * const element = getByTestId('text-content')
381381- * expect(element).toHaveTextContent('Content')
382382- * // to match the whole content
383383- * expect(element).toHaveTextContent(/^Text Content$/)
384384- * // to use case-insensitive match
385385- * expect(element).toHaveTextContent(/content$/i)
386386- * expect(element).not.toHaveTextContent('content')
387387- * @see
388388- * [testing-library/jest-dom#tohavetextcontent](https://github.com/testing-library/jest-dom#tohavetextcontent)
389389- */
390390- toHaveTextContent(
391391- text: string | RegExp,
392392- options?: {normalizeWhitespace: boolean},
393393- ): R
394394- /**
395395- * @description
396396- * Check whether the given form element has the specified value.
397397- *
398398- * Accepts `<input>`, `<select>`, and `<textarea>` elements with the exception of `<input type="checkbox">` and
399399- * `<input type="radiobox">`, which can be matched only using
400400- * [toBeChecked](https://github.com/testing-library/jest-dom#tobechecked) or
401401- * [toHaveFormValues](https://github.com/testing-library/jest-dom#tohaveformvalues).
402402- * @example
403403- * <input
404404- * type="number"
405405- * value="5"
406406- * data-testid="input-number" />
407407- *
408408- * const numberInput = getByTestId('input-number')
409409- * expect(numberInput).toHaveValue(5)
410410- * @see
411411- * [testing-library/jest-dom#tohavevalue](https://github.com/testing-library/jest-dom#tohavevalue)
412412- */
413413- toHaveValue(value?: string | string[] | number | null): R
414414- /**
415415- * @description
416416- * Assert whether the given element is checked.
417417- *
418418- * It accepts an `input` of type `checkbox` or `radio` and elements with a `role` of `radio` with a valid
419419- * `aria-checked` attribute of "true" or "false".
420420- * @example
421421- * <input
422422- * type="checkbox"
423423- * checked
424424- * data-testid="input-checkbox" />
425425- * <input
426426- * type="radio"
427427- * value="foo"
428428- * data-testid="input-radio" />
429429- *
430430- * const inputCheckbox = getByTestId('input-checkbox')
431431- * const inputRadio = getByTestId('input-radio')
432432- * expect(inputCheckbox).toBeChecked()
433433- * expect(inputRadio).not.toBeChecked()
434434- * @see
435435- * [testing-library/jest-dom#tobechecked](https://github.com/testing-library/jest-dom#tobechecked)
436436- */
437437- toBeChecked(): R
438438- /**
439439- * @deprecated
440440- * since v5.14.1
441441- * @description
442442- * Check the accessible description for an element.
443443- * This allows you to check whether the given element has a description or not.
444444- *
445445- * An element gets its description via the
446446- * [`aria-describedby` attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-describedby_attribute).
447447- * Set this to the `id` of one or more other elements. These elements may be nested
448448- * inside, be outside, or a sibling of the passed in element.
449449- *
450450- * Whitespace is normalized. Using multiple ids will
451451- * [join the referenced elements’ text content separated by a space](https://www.w3.org/TR/accname-1.1/#mapping_additional_nd_description).
452452- *
453453- * When a `string` argument is passed through, it will perform a whole
454454- * case-sensitive match to the description text.
455455- *
456456- * To perform a case-insensitive match, you can use a `RegExp` with the `/i`
457457- * modifier.
458458- *
459459- * To perform a partial match, you can pass a `RegExp` or use
460460- * `expect.stringContaining("partial string")`.
461461- *
462462- * @example
463463- * <button aria-label="Close" aria-describedby="description-close">
464464- * X
465465- * </button>
466466- * <div id="description-close">
467467- * Closing will discard any changes
468468- * </div>
469469- *
470470- * <button>Delete</button>
471471- *
472472- * const closeButton = getByRole('button', {name: 'Close'})
473473- *
474474- * expect(closeButton).toHaveDescription('Closing will discard any changes')
475475- * expect(closeButton).toHaveDescription(/will discard/) // to partially match
476476- * expect(closeButton).toHaveDescription(expect.stringContaining('will discard')) // to partially match
477477- * expect(closeButton).toHaveDescription(/^closing/i) // to use case-insensitive match
478478- * expect(closeButton).not.toHaveDescription('Other description')
479479- *
480480- * const deleteButton = getByRole('button', {name: 'Delete'})
481481- * expect(deleteButton).not.toHaveDescription()
482482- * expect(deleteButton).toHaveDescription('') // Missing or empty description always becomes a blank string
483483- * @see
484484- * [testing-library/jest-dom#tohavedescription](https://github.com/testing-library/jest-dom#tohavedescription)
485485- */
486486- toHaveDescription(text?: string | RegExp | E): R
487487- /**
488488- * @description
489489- * This allows to assert that an element has the expected [accessible description](https://w3c.github.io/accname/).
490490- *
491491- * You can pass the exact string of the expected accessible description, or you can make a
492492- * partial match passing a regular expression, or by using either
493493- * [expect.stringContaining](https://jestjs.io/docs/en/expect.html#expectnotstringcontainingstring)
494494- * or [expect.stringMatching](https://jestjs.io/docs/en/expect.html#expectstringmatchingstring-regexp).
495495- * @example
496496- * <a data-testid="link" href="/" aria-label="Home page" title="A link to start over">Start</a>
497497- * <a data-testid="extra-link" href="/about" aria-label="About page">About</a>
498498- * <img src="avatar.jpg" data-testid="avatar" alt="User profile pic" />
499499- * <img src="logo.jpg" data-testid="logo" alt="Company logo" aria-describedby="t1" />
500500- * <span id="t1" role="presentation">The logo of Our Company</span>
501501- *
502502- * expect(getByTestId('link')).toHaveAccessibleDescription()
503503- * expect(getByTestId('link')).toHaveAccessibleDescription('A link to start over')
504504- * expect(getByTestId('link')).not.toHaveAccessibleDescription('Home page')
505505- * expect(getByTestId('extra-link')).not.toHaveAccessibleDescription()
506506- * expect(getByTestId('avatar')).not.toHaveAccessibleDescription()
507507- * expect(getByTestId('logo')).not.toHaveAccessibleDescription('Company logo')
508508- * expect(getByTestId('logo')).toHaveAccessibleDescription('The logo of Our Company')
509509- * @see
510510- * [testing-library/jest-dom#tohaveaccessibledescription](https://github.com/testing-library/jest-dom#tohaveaccessibledescription)
511511- */
512512- toHaveAccessibleDescription(text?: string | RegExp | E): R
55+export interface TestingLibraryMatchers<E, R> {
66+ /**
77+ * @description
88+ * Assert whether an element is present in the document or not.
99+ * @example
1010+ * <svg data-testid="svg-element"></svg>
1111+ *
1212+ * await expect.element(page.getByTestId('svg-element')).toBeInTheDocument()
1313+ * await expect.element(page.getByTestId('does-not-exist')).not.toBeInTheDocument()
1414+ * @see https://vitest.dev/guide/browser/assertion-api#tobeinthedocument
1515+ */
1616+ toBeInTheDocument(): R
1717+ /**
1818+ * @description
1919+ * This allows you to check if an element is currently visible to the user.
2020+ *
2121+ * An element is visible if **all** the following conditions are met:
2222+ * * it does not have its css property display set to none
2323+ * * it does not have its css property visibility set to either hidden or collapse
2424+ * * it does not have its css property opacity set to 0
2525+ * * its parent element is also visible (and so on up to the top of the DOM tree)
2626+ * * it does not have the hidden attribute
2727+ * * if `<details />` it has the open attribute
2828+ * @example
2929+ * <div
3030+ * data-testid="zero-opacity"
3131+ * style="opacity: 0"
3232+ * >
3333+ * Zero Opacity
3434+ * </div>
3535+ *
3636+ * <div data-testid="visible">Visible Example</div>
3737+ *
3838+ * await expect.element(page.getByTestId('zero-opacity')).not.toBeVisible()
3939+ * await expect.element(page.getByTestId('visible')).toBeVisible()
4040+ * @see https://vitest.dev/guide/browser/assertion-api#tobevisible
4141+ */
4242+ toBeVisible(): R
4343+ /**
4444+ * @description
4545+ * Assert whether an element has content or not.
4646+ * @example
4747+ * <span data-testid="not-empty">
4848+ * <span data-testid="empty"></span>
4949+ * </span>
5050+ *
5151+ * await expect.element(page.getByTestId('empty')).toBeEmptyDOMElement()
5252+ * await expect.element(page.getByTestId('not-empty')).not.toBeEmptyDOMElement()
5353+ * @see https://vitest.dev/guide/browser/assertion-api#tobeemptydomelement
5454+ */
5555+ toBeEmptyDOMElement(): R
5656+ /**
5757+ * @description
5858+ * Allows you to check whether an element is disabled from the user's perspective.
5959+ *
6060+ * Matches if the element is a form control and the `disabled` attribute is specified on this element or the
6161+ * element is a descendant of a form element with a `disabled` attribute.
6262+ * @example
6363+ * <button
6464+ * data-testid="button"
6565+ * type="submit"
6666+ * disabled
6767+ * >
6868+ * submit
6969+ * </button>
7070+ *
7171+ * await expect.element(page.getByTestId('button')).toBeDisabled()
7272+ * @see https://vitest.dev/guide/browser/assertion-api#tobedisabled
7373+ */
7474+ toBeDisabled(): R
7575+ /**
7676+ * @description
7777+ * Allows you to check whether an element is not disabled from the user's perspective.
7878+ *
7979+ * Works like `not.toBeDisabled()`.
8080+ *
8181+ * Use this matcher to avoid double negation in your tests.
8282+ * @example
8383+ * <button
8484+ * data-testid="button"
8585+ * type="submit"
8686+ * >
8787+ * submit
8888+ * </button>
8989+ *
9090+ * await expect.element(page.getByTestId('button')).toBeEnabled()
9191+ * @see https://vitest.dev/guide/browser/assertion-api#tobeenabled
9292+ */
9393+ toBeEnabled(): R
9494+ /**
9595+ * @description
9696+ * Check if a form element, or the entire `form`, is currently invalid.
9797+ *
9898+ * An `input`, `select`, `textarea`, or `form` element is invalid if it has an `aria-invalid` attribute with no
9999+ * value or a value of "true", or if the result of `checkValidity()` is false.
100100+ * @example
101101+ * <input data-testid="no-aria-invalid" />
102102+ *
103103+ * <form data-testid="invalid-form">
104104+ * <input required />
105105+ * </form>
106106+ *
107107+ * await expect(page.getByTestId('no-aria-invalid')).not.toBeInvalid()
108108+ * await expect(page.getByTestId('invalid-form')).toBeInvalid()
109109+ * @see https://vitest.dev/guide/browser/assertion-api#tobeinvalid
110110+ */
111111+ toBeInvalid(): R
112112+ /**
113113+ * @description
114114+ * This allows you to check if a form element is currently required.
115115+ *
116116+ * An element is required if it is having a `required` or `aria-required="true"` attribute.
117117+ * @example
118118+ * <input data-testid="required-input" required />
119119+ * <div
120120+ * data-testid="supported-role"
121121+ * role="tree"
122122+ * required />
123123+ *
124124+ * await expect.element(page.getByTestId('required-input')).toBeRequired()
125125+ * await expect.element(page.getByTestId('supported-role')).not.toBeRequired()
126126+ * @see https://vitest.dev/guide/browser/assertion-api#toberequired
127127+ */
128128+ toBeRequired(): R
129129+ /**
130130+ * @description
131131+ * Allows you to check if a form element is currently required.
132132+ *
133133+ * An `input`, `select`, `textarea`, or `form` element is invalid if it has an `aria-invalid` attribute with no
134134+ * value or a value of "false", or if the result of `checkValidity()` is true.
135135+ * @example
136136+ * <input data-testid="aria-invalid" aria-invalid />
137137+ *
138138+ * <form data-testid="valid-form">
139139+ * <input />
140140+ * </form>
141141+ *
142142+ * await expect.element(page.getByTestId('no-aria-invalid')).not.toBeValid()
143143+ * await expect.element(page.getByTestId('invalid-form')).toBeInvalid()
144144+ * @see https://vitest.dev/guide/browser/assertion-api#tobevalid
145145+ */
146146+ toBeValid(): R
147147+ /**
148148+ * @description
149149+ * Allows you to assert whether an element contains another element as a descendant or not.
150150+ * @example
151151+ * <span data-testid="ancestor">
152152+ * <span data-testid="descendant"></span>
153153+ * </span>
154154+ *
155155+ * const ancestor = page.getByTestId('ancestor')
156156+ * const descendant = page.getByTestId('descendant')
157157+ * const nonExistentElement = page.getByTestId('does-not-exist')
158158+ * await expect.element(ancestor).toContainElement(descendant)
159159+ * await expect.element(descendant).not.toContainElement(ancestor)
160160+ * await expect.element(ancestor).not.toContainElement(nonExistentElement)
161161+ * @see https://vitest.dev/guide/browser/assertion-api#tocontainelement
162162+ */
163163+ toContainElement(element: HTMLElement | SVGElement | null): R
164164+ /**
165165+ * @description
166166+ * Assert whether a string representing a HTML element is contained in another element.
167167+ * @example
168168+ * <span data-testid="parent"><span data-testid="child"></span></span>
169169+ *
170170+ * const parent = page.getByTestId('parent')
171171+ * await expect.element(parent).toContainHTML('<span data-testid="child"></span>')
172172+ * @see https://vitest.dev/guide/browser/assertion-api#tocontainhtml
173173+ */
174174+ toContainHTML(htmlText: string): R
175175+ /**
176176+ * @description
177177+ * Allows you to check if a given element has an attribute or not.
178178+ *
179179+ * You can also optionally check that the attribute has a specific expected value or partial match using
180180+ * [expect.stringContaining](https://jestjs.io/docs/en/expect.html#expectnotstringcontainingstring) or
181181+ * [expect.stringMatching](https://jestjs.io/docs/en/expect.html#expectstringmatchingstring-regexp).
182182+ * @example
183183+ * <button
184184+ * data-testid="ok-button"
185185+ * type="submit"
186186+ * disabled
187187+ * >
188188+ * ok
189189+ * </button>
190190+ *
191191+ * await expect.element(button).toHaveAttribute('disabled')
192192+ * await expect.element(button).toHaveAttribute('type', 'submit')
193193+ * await expect.element(button).not.toHaveAttribute('type', 'button')
194194+ * @see https://vitest.dev/guide/browser/assertion-api#tohaveattribute
195195+ */
196196+ toHaveAttribute(attr: string, value?: unknown): R
197197+ /**
198198+ * @description
199199+ * Check whether the given element has certain classes within its `class` attribute.
200200+ *
201201+ * You must provide at least one class, unless you are asserting that an element does not have any classes.
202202+ * @example
203203+ * <button
204204+ * data-testid="delete-button"
205205+ * class="btn xs btn-danger"
206206+ * >
207207+ * delete item
208208+ * </button>
209209+ *
210210+ * <div data-testid="no-classes">no classes</div>
211211+ *
212212+ * const deleteButton = page.getByTestId('delete-button')
213213+ * const noClasses = page.getByTestId('no-classes')
214214+ * await expect.element(deleteButton).toHaveClass('btn')
215215+ * await expect.element(deleteButton).toHaveClass('btn-danger xs')
216216+ * await expect.element(deleteButton).toHaveClass(/danger/, 'xs')
217217+ * await expect.element(deleteButton).toHaveClass('btn xs btn-danger', {exact: true})
218218+ * await expect.element(deleteButton).not.toHaveClass('btn xs btn-danger', {exact: true})
219219+ * await expect.element(noClasses).not.toHaveClass()
220220+ * @see https://vitest.dev/guide/browser/assertion-api#tohaveclass
221221+ */
222222+ toHaveClass(...classNames:
223223+ | (string | RegExp)[]
224224+ | [string, options?: {exact: boolean}]
225225+ | [string, string, options?: {exact: boolean}]
226226+ | [string, string, string, options?: {exact: boolean}]
227227+ | [string, string, string, string, options?: {exact: boolean}]
228228+ | [string, string, string, string, string, options?: {exact: boolean}]
229229+ | [string, string, string, string, string, string, options?: {exact: boolean}]
230230+ | [string, string, string, string, string, string, string, options?: {exact: boolean}]
231231+ | [string, string, string, string, string, string, string, string, options?: {exact: boolean}]
232232+ | [string, string, string, string, string, string, string, string, string, options?: {exact: boolean}]
233233+ ): R
234234+ /**
235235+ * @description
236236+ * This allows you to check whether the given form element has the specified displayed value (the one the
237237+ * end user will see). It accepts <input>, <select> and <textarea> elements with the exception of <input type="checkbox">
238238+ * and <input type="radio">, which can be meaningfully matched only using toBeChecked or toHaveFormValues.
239239+ * @example
240240+ * <label for="input-example">First name</label>
241241+ * <input type="text" id="input-example" value="Luca" />
242242+ *
243243+ * <label for="textarea-example">Description</label>
244244+ * <textarea id="textarea-example">An example description here.</textarea>
245245+ *
246246+ * <label for="single-select-example">Fruit</label>
247247+ * <select id="single-select-example">
248248+ * <option value="">Select a fruit...</option>
249249+ * <option value="banana">Banana</option>
250250+ * <option value="ananas">Ananas</option>
251251+ * <option value="avocado">Avocado</option>
252252+ * </select>
253253+ *
254254+ * <label for="multiple-select-example">Fruits</label>
255255+ * <select id="multiple-select-example" multiple>
256256+ * <option value="">Select a fruit...</option>
257257+ * <option value="banana" selected>Banana</option>
258258+ * <option value="ananas">Ananas</option>
259259+ * <option value="avocado" selected>Avocado</option>
260260+ * </select>
261261+ *
262262+ * const input = page.getByLabelText('First name')
263263+ * const textarea = page.getByLabelText('Description')
264264+ * const selectSingle = page.getByLabelText('Fruit')
265265+ * const selectMultiple = page.getByLabelText('Fruits')
266266+ *
267267+ * await expect.element(input).toHaveDisplayValue('Luca')
268268+ * await expect.element(textarea).toHaveDisplayValue('An example description here.')
269269+ * await expect.element(selectSingle).toHaveDisplayValue('Select a fruit...')
270270+ * await expect.element(selectMultiple).toHaveDisplayValue(['Banana', 'Avocado'])
271271+ *
272272+ * @see https://vitest.dev/guide/browser/assertion-api#tohavedisplayvalue
273273+ */
274274+ toHaveDisplayValue(value: string | number | RegExp | Array<string | RegExp | number>): R
275275+ /**
276276+ * @description
277277+ * Assert whether an element has focus or not.
278278+ * @example
279279+ * <div>
280280+ * <input type="text" data-testid="element-to-focus" />
281281+ * </div>
282282+ *
283283+ * const input = page.getByTestId('element-to-focus')
284284+ * input.element().focus()
285285+ * await expect.element(input).toHaveFocus()
286286+ * input.element().blur()
287287+ * await expect.element(input).not.toHaveFocus()
288288+ * @see https://vitest.dev/guide/browser/assertion-api#tohavefocus
289289+ */
290290+ toHaveFocus(): R
291291+ /**
292292+ * @description
293293+ * Check if a form or fieldset contains form controls for each given name, and having the specified value.
294294+ *
295295+ * Can only be invoked on a form or fieldset element.
296296+ * @example
297297+ * <form data-testid="login-form">
298298+ * <input type="text" name="username" value="jane.doe" />
299299+ * <input type="password" name="password" value="123" />
300300+ * <input type="checkbox" name="rememberMe" checked />
301301+ * <button type="submit">Sign in</button>
302302+ * </form>
303303+ *
304304+ * await expect.element(page.getByTestId('login-form')).toHaveFormValues({
305305+ * username: 'jane.doe',
306306+ * rememberMe: true,
307307+ * })
308308+ * @see https://vitest.dev/guide/browser/assertion-api#tohaveformvalues
309309+ */
310310+ toHaveFormValues(expectedValues: Record<string, unknown>): R
311311+ /**
312312+ * @description
313313+ * Check if an element has specific css properties with specific values applied.
314314+ *
315315+ * Only matches if the element has *all* the expected properties applied, not just some of them.
316316+ * @example
317317+ * <button
318318+ * data-testid="submit-button"
319319+ * style="background-color: green; display: none"
320320+ * >
321321+ * submit
322322+ * </button>
323323+ *
324324+ * const button = page.getByTestId('submit-button')
325325+ * await expect.element(button).toHaveStyle('background-color: green')
326326+ * await expect.element(button).toHaveStyle({
327327+ * 'background-color': 'green',
328328+ * display: 'none'
329329+ * })
330330+ * @see https://vitest.dev/guide/browser/assertion-api#tohavestyle
331331+ */
332332+ toHaveStyle(css: string | Partial<CSSStyleDeclaration>): R
333333+ /**
334334+ * @description
335335+ * Check whether the given element has a text content or not.
336336+ *
337337+ * When a string argument is passed through, it will perform a partial case-sensitive match to the element
338338+ * content.
339339+ *
340340+ * To perform a case-insensitive match, you can use a RegExp with the `/i` modifier.
341341+ *
342342+ * If you want to match the whole content, you can use a RegExp to do it.
343343+ * @example
344344+ * <span data-testid="text-content">Text Content</span>
345345+ *
346346+ * const element = page.getByTestId('text-content')
347347+ * await expect.element(element).toHaveTextContent('Content')
348348+ * // to match the whole content
349349+ * await expect.element(element).toHaveTextContent(/^Text Content$/)
350350+ * // to use case-insensitive match
351351+ * await expect.element(element).toHaveTextContent(/content$/i)
352352+ * await expect.element(element).not.toHaveTextContent('content')
353353+ * @see https://vitest.dev/guide/browser/assertion-api#tohavetextcontent
354354+ */
355355+ toHaveTextContent(
356356+ text: string | number | RegExp,
357357+ options?: {normalizeWhitespace: boolean},
358358+ ): R
359359+ /**
360360+ * @description
361361+ * Check whether the given form element has the specified value.
362362+ *
363363+ * Accepts `<input>`, `<select>`, and `<textarea>` elements with the exception of `<input type="checkbox">` and
364364+ * `<input type="radiobox">`, which can be matched only using
365365+ * [toBeChecked](https://vitest.dev/guide/browser/assertion-api#tobechecked) or
366366+ * [toHaveFormValues](https://vitest.dev/guide/browser/assertion-api#tohaveformvalues).
367367+ * @example
368368+ * <input
369369+ * type="number"
370370+ * value="5"
371371+ * data-testid="input-number" />
372372+ *
373373+ * const numberInput = page.getByTestId('input-number')
374374+ * await expect.element(numberInput).toHaveValue(5)
375375+ * @see https://vitest.dev/guide/browser/assertion-api#tohavevalue
376376+ */
377377+ toHaveValue(value?: string | string[] | number | null): R
378378+ /**
379379+ * @description
380380+ * Assert whether the given element is checked.
381381+ *
382382+ * It accepts an `input` of type `checkbox` or `radio` and elements with a `role` of `radio` with a valid
383383+ * `aria-checked` attribute of "true" or "false".
384384+ * @example
385385+ * <input
386386+ * type="checkbox"
387387+ * checked
388388+ * data-testid="input-checkbox" />
389389+ * <input
390390+ * type="radio"
391391+ * value="foo"
392392+ * data-testid="input-radio" />
393393+ *
394394+ * const inputCheckbox = page.getByTestId('input-checkbox')
395395+ * const inputRadio = page.getByTestId('input-radio')
396396+ * await expect.element(inputCheckbox).toBeChecked()
397397+ * await expect.element(inputRadio).not.toBeChecked()
398398+ * @see https://vitest.dev/guide/browser/assertion-api#tobechecked
399399+ */
400400+ toBeChecked(): R
401401+ /**
402402+ * @description
403403+ * This allows to assert that an element has the expected [accessible description](https://w3c.github.io/accname/).
404404+ *
405405+ * You can pass the exact string of the expected accessible description, or you can make a
406406+ * partial match passing a regular expression, or by using either
407407+ * [expect.stringContaining](https://jestjs.io/docs/en/expect.html#expectnotstringcontainingstring)
408408+ * or [expect.stringMatching](https://jestjs.io/docs/en/expect.html#expectstringmatchingstring-regexp).
409409+ * @example
410410+ * <a data-testid="link" href="/" aria-label="Home page" title="A link to start over">Start</a>
411411+ * <a data-testid="extra-link" href="/about" aria-label="About page">About</a>
412412+ * <img src="avatar.jpg" data-testid="avatar" alt="User profile pic" />
413413+ * <img src="logo.jpg" data-testid="logo" alt="Company logo" aria-describedby="t1" />
414414+ * <span id="t1" role="presentation">The logo of Our Company</span>
415415+ *
416416+ * await expect.element(page.getByTestId('link')).toHaveAccessibleDescription()
417417+ * await expect.element(page.getByTestId('link')).toHaveAccessibleDescription('A link to start over')
418418+ * await expect.element(page.getByTestId('link')).not.toHaveAccessibleDescription('Home page')
419419+ * await expect.element(page.getByTestId('extra-link')).not.toHaveAccessibleDescription()
420420+ * await expect.element(page.getByTestId('avatar')).not.toHaveAccessibleDescription()
421421+ * await expect.element(page.getByTestId('logo')).not.toHaveAccessibleDescription('Company logo')
422422+ * await expect.element(page.getByTestId('logo')).toHaveAccessibleDescription('The logo of Our Company')
423423+ * @see https://vitest.dev/guide/browser/assertion-api#tohaveaccessibledescription
424424+ */
425425+ toHaveAccessibleDescription(text?: string | RegExp | E): R
513426514514- /**
515515- * @description
516516- * This allows you to assert that an element has the expected
517517- * [accessible error message](https://w3c.github.io/aria/#aria-errormessage).
518518- *
519519- * You can pass the exact string of the expected accessible error message.
520520- * Alternatively, you can perform a partial match by passing a regular expression
521521- * or by using either
522522- * [expect.stringContaining](https://jestjs.io/docs/en/expect.html#expectnotstringcontainingstring)
523523- * or [expect.stringMatching](https://jestjs.io/docs/en/expect.html#expectstringmatchingstring-regexp).
524524- *
525525- * @example
526526- * <input aria-label="Has Error" aria-invalid="true" aria-errormessage="error-message" />
527527- * <div id="error-message" role="alert">This field is invalid</div>
528528- *
529529- * <input aria-label="No Error Attributes" />
530530- * <input aria-label="Not Invalid" aria-invalid="false" aria-errormessage="error-message" />
531531- *
532532- * // Inputs with Valid Error Messages
533533- * expect(getByRole('textbox', {name: 'Has Error'})).toHaveAccessibleErrorMessage()
534534- * expect(getByRole('textbox', {name: 'Has Error'})).toHaveAccessibleErrorMessage('This field is invalid')
535535- * expect(getByRole('textbox', {name: 'Has Error'})).toHaveAccessibleErrorMessage(/invalid/i)
536536- * expect(
537537- * getByRole('textbox', {name: 'Has Error'}),
538538- * ).not.toHaveAccessibleErrorMessage('This field is absolutely correct!')
539539- *
540540- * // Inputs without Valid Error Messages
541541- * expect(
542542- * getByRole('textbox', {name: 'No Error Attributes'}),
543543- * ).not.toHaveAccessibleErrorMessage()
544544- *
545545- * expect(
546546- * getByRole('textbox', {name: 'Not Invalid'}),
547547- * ).not.toHaveAccessibleErrorMessage()
548548- *
549549- * @see
550550- * [testing-library/jest-dom#tohaveaccessibleerrormessage](https://github.com/testing-library/jest-dom#tohaveaccessibleerrormessage)
551551- */
552552- toHaveAccessibleErrorMessage(text?: string | RegExp | E): R
427427+ /**
428428+ * @description
429429+ * This allows you to assert that an element has the expected
430430+ * [accessible error message](https://w3c.github.io/aria/#aria-errormessage).
431431+ *
432432+ * You can pass the exact string of the expected accessible error message.
433433+ * Alternatively, you can perform a partial match by passing a regular expression
434434+ * or by using either
435435+ * [expect.stringContaining](https://jestjs.io/docs/en/expect.html#expectnotstringcontainingstring)
436436+ * or [expect.stringMatching](https://jestjs.io/docs/en/expect.html#expectstringmatchingstring-regexp).
437437+ *
438438+ * @example
439439+ * <input aria-label="Has Error" aria-invalid="true" aria-errormessage="error-message" />
440440+ * <div id="error-message" role="alert">This field is invalid</div>
441441+ *
442442+ * <input aria-label="No Error Attributes" />
443443+ * <input aria-label="Not Invalid" aria-invalid="false" aria-errormessage="error-message" />
444444+ *
445445+ * // Inputs with Valid Error Messages
446446+ * await expect.element(page.getByRole('textbox', {name: 'Has Error'})).toHaveAccessibleErrorMessage()
447447+ * await expect.element(page.getByRole('textbox', {name: 'Has Error'})).toHaveAccessibleErrorMessage('This field is invalid')
448448+ * await expect.element(page.getByRole('textbox', {name: 'Has Error'})).toHaveAccessibleErrorMessage(/invalid/i)
449449+ * await expect.element(
450450+ * page.getByRole('textbox', {name: 'Has Error'}),
451451+ * ).not.toHaveAccessibleErrorMessage('This field is absolutely correct!')
452452+ *
453453+ * // Inputs without Valid Error Messages
454454+ * await expect.element(
455455+ * page.getByRole('textbox', {name: 'No Error Attributes'}),
456456+ * ).not.toHaveAccessibleErrorMessage()
457457+ *
458458+ * await expect.element(
459459+ * page.getByRole('textbox', {name: 'Not Invalid'}),
460460+ * ).not.toHaveAccessibleErrorMessage()
461461+ *
462462+ * @see https://vitest.dev/guide/browser/assertion-api#tohaveaccessibleerrormessage
463463+ */
464464+ toHaveAccessibleErrorMessage(text?: string | RegExp | E): R
553465554554- /**
555555- * @description
556556- * This allows to assert that an element has the expected [accessible name](https://w3c.github.io/accname/).
557557- * It is useful, for instance, to assert that form elements and buttons are properly labelled.
558558- *
559559- * You can pass the exact string of the expected accessible name, or you can make a
560560- * partial match passing a regular expression, or by using either
561561- * [expect.stringContaining](https://jestjs.io/docs/en/expect.html#expectnotstringcontainingstring)
562562- * or [expect.stringMatching](https://jestjs.io/docs/en/expect.html#expectstringmatchingstring-regexp).
563563- * @example
564564- * <img data-testid="img-alt" src="" alt="Test alt" />
565565- * <img data-testid="img-empty-alt" src="" alt="" />
566566- * <svg data-testid="svg-title"><title>Test title</title></svg>
567567- * <button data-testid="button-img-alt"><img src="" alt="Test" /></button>
568568- * <p><img data-testid="img-paragraph" src="" alt="" /> Test content</p>
569569- * <button data-testid="svg-button"><svg><title>Test</title></svg></p>
570570- * <div><svg data-testid="svg-without-title"></svg></div>
571571- * <input data-testid="input-title" title="test" />
572572- *
573573- * expect(getByTestId('img-alt')).toHaveAccessibleName('Test alt')
574574- * expect(getByTestId('img-empty-alt')).not.toHaveAccessibleName()
575575- * expect(getByTestId('svg-title')).toHaveAccessibleName('Test title')
576576- * expect(getByTestId('button-img-alt')).toHaveAccessibleName()
577577- * expect(getByTestId('img-paragraph')).not.toHaveAccessibleName()
578578- * expect(getByTestId('svg-button')).toHaveAccessibleName()
579579- * expect(getByTestId('svg-without-title')).not.toHaveAccessibleName()
580580- * expect(getByTestId('input-title')).toHaveAccessibleName()
581581- * @see
582582- * [testing-library/jest-dom#tohaveaccessiblename](https://github.com/testing-library/jest-dom#tohaveaccessiblename)
583583- */
584584- toHaveAccessibleName(text?: string | RegExp | E): R
585585- /**
586586- * @description
587587- * This allows you to assert that an element has the expected
588588- * [role](https://www.w3.org/TR/html-aria/#docconformance).
589589- *
590590- * This is useful in cases where you already have access to an element via
591591- * some query other than the role itself, and want to make additional
592592- * assertions regarding its accessibility.
593593- *
594594- * The role can match either an explicit role (via the `role` attribute), or
595595- * an implicit one via the [implicit ARIA
596596- * semantics](https://www.w3.org/TR/html-aria/).
597597- *
598598- * Note: roles are matched literally by string equality, without inheriting
599599- * from the ARIA role hierarchy. As a result, querying a superclass role
600600- * like 'checkbox' will not include elements with a subclass role like
601601- * 'switch'.
602602- *
603603- * @example
604604- * <button data-testid="button">Continue</button>
605605- * <div role="button" data-testid="button-explicit">Continue</button>
606606- * <button role="switch button" data-testid="button-explicit-multiple">Continue</button>
607607- * <a href="/about" data-testid="link">About</a>
608608- * <a data-testid="link-invalid">Invalid link<a/>
609609- *
610610- * expect(getByTestId('button')).toHaveRole('button')
611611- * expect(getByTestId('button-explicit')).toHaveRole('button')
612612- * expect(getByTestId('button-explicit-multiple')).toHaveRole('button')
613613- * expect(getByTestId('button-explicit-multiple')).toHaveRole('switch')
614614- * expect(getByTestId('link')).toHaveRole('link')
615615- * expect(getByTestId('link-invalid')).not.toHaveRole('link')
616616- * expect(getByTestId('link-invalid')).toHaveRole('generic')
617617- *
618618- * @see
619619- * [testing-library/jest-dom#tohaverole](https://github.com/testing-library/jest-dom#tohaverole)
620620- */
621621- toHaveRole(
622622- // Get autocomplete for ARIARole union types, while still supporting another string
623623- // Ref: https://github.com/microsoft/TypeScript/issues/29729#issuecomment-567871939
624624- role: ARIARole | (string & {}),
625625- ): R
626626- /**
627627- * @description
628628- * This allows you to check whether the given element is partially checked.
629629- * It accepts an input of type checkbox and elements with a role of checkbox
630630- * with a aria-checked="mixed", or input of type checkbox with indeterminate
631631- * set to true
632632- *
633633- * @example
634634- * <input type="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />
635635- * <input type="checkbox" checked data-testid="input-checkbox-checked" />
636636- * <input type="checkbox" data-testid="input-checkbox-unchecked" />
637637- * <div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
638638- * <div
639639- * role="checkbox"
640640- * aria-checked="false"
641641- * data-testid="aria-checkbox-unchecked"
642642- * />
643643- * <input type="checkbox" data-testid="input-checkbox-indeterminate" />
644644- *
645645- * const ariaCheckboxMixed = getByTestId('aria-checkbox-mixed')
646646- * const inputCheckboxChecked = getByTestId('input-checkbox-checked')
647647- * const inputCheckboxUnchecked = getByTestId('input-checkbox-unchecked')
648648- * const ariaCheckboxChecked = getByTestId('aria-checkbox-checked')
649649- * const ariaCheckboxUnchecked = getByTestId('aria-checkbox-unchecked')
650650- * const inputCheckboxIndeterminate = getByTestId('input-checkbox-indeterminate')
651651- *
652652- * expect(ariaCheckboxMixed).toBePartiallyChecked()
653653- * expect(inputCheckboxChecked).not.toBePartiallyChecked()
654654- * expect(inputCheckboxUnchecked).not.toBePartiallyChecked()
655655- * expect(ariaCheckboxChecked).not.toBePartiallyChecked()
656656- * expect(ariaCheckboxUnchecked).not.toBePartiallyChecked()
657657- *
658658- * inputCheckboxIndeterminate.indeterminate = true
659659- * expect(inputCheckboxIndeterminate).toBePartiallyChecked()
660660- * @see
661661- * [testing-library/jest-dom#tobepartiallychecked](https://github.com/testing-library/jest-dom#tobepartiallychecked)
662662- */
663663- toBePartiallyChecked(): R
664664- /**
665665- * @deprecated
666666- * since v5.17.0
667667- *
668668- * @description
669669- * Check whether the given element has an [ARIA error message](https://www.w3.org/TR/wai-aria/#aria-errormessage) or not.
670670- *
671671- * Use the `aria-errormessage` attribute to reference another element that contains
672672- * custom error message text. Multiple ids is **NOT** allowed. Authors MUST use
673673- * `aria-invalid` in conjunction with `aria-errormessage`. Learn more from the
674674- * [`aria-errormessage` spec](https://www.w3.org/TR/wai-aria/#aria-errormessage).
675675- *
676676- * Whitespace is normalized.
677677- *
678678- * When a `string` argument is passed through, it will perform a whole
679679- * case-sensitive match to the error message text.
680680- *
681681- * To perform a case-insensitive match, you can use a `RegExp` with the `/i`
682682- * modifier.
683683- *
684684- * To perform a partial match, you can pass a `RegExp` or use
685685- * expect.stringContaining("partial string")`.
686686- *
687687- * @example
688688- * <label for="startTime"> Please enter a start time for the meeting: </label>
689689- * <input id="startTime" type="text" aria-errormessage="msgID" aria-invalid="true" value="11:30 PM" />
690690- * <span id="msgID" aria-live="assertive" style="visibility:visible">
691691- * Invalid time: the time must be between 9:00 AM and 5:00 PM"
692692- * </span>
693693- *
694694- *
695695- * const timeInput = getByLabel('startTime')
696696- *
697697- * expect(timeInput).toHaveErrorMessage(
698698- * 'Invalid time: the time must be between 9:00 AM and 5:00 PM',
699699- * )
700700- * expect(timeInput).toHaveErrorMessage(/invalid time/i) // to partially match
701701- * expect(timeInput).toHaveErrorMessage(expect.stringContaining('Invalid time')) // to partially match
702702- * expect(timeInput).not.toHaveErrorMessage('Pikachu!')
703703- * @see
704704- * [testing-library/jest-dom#tohaveerrormessage](https://github.com/testing-library/jest-dom#tohaveerrormessage)
705705- */
706706- toHaveErrorMessage(text?: string | RegExp | E): R
707707- }
466466+ /**
467467+ * @description
468468+ * This allows to assert that an element has the expected [accessible name](https://w3c.github.io/accname/).
469469+ * It is useful, for instance, to assert that form elements and buttons are properly labelled.
470470+ *
471471+ * You can pass the exact string of the expected accessible name, or you can make a
472472+ * partial match passing a regular expression, or by using either
473473+ * [expect.stringContaining](https://jestjs.io/docs/en/expect.html#expectnotstringcontainingstring)
474474+ * or [expect.stringMatching](https://jestjs.io/docs/en/expect.html#expectstringmatchingstring-regexp).
475475+ * @example
476476+ * <img data-testid="img-alt" src="" alt="Test alt" />
477477+ * <img data-testid="img-empty-alt" src="" alt="" />
478478+ * <svg data-testid="svg-title"><title>Test title</title></svg>
479479+ * <button data-testid="button-img-alt"><img src="" alt="Test" /></button>
480480+ * <p><img data-testid="img-paragraph" src="" alt="" /> Test content</p>
481481+ * <button data-testid="svg-button"><svg><title>Test</title></svg></p>
482482+ * <div><svg data-testid="svg-without-title"></svg></div>
483483+ * <input data-testid="input-title" title="test" />
484484+ *
485485+ * await expect.element(page.getByTestId('img-alt')).toHaveAccessibleName('Test alt')
486486+ * await expect.element(page.getByTestId('img-empty-alt')).not.toHaveAccessibleName()
487487+ * await expect.element(page.getByTestId('svg-title')).toHaveAccessibleName('Test title')
488488+ * await expect.element(page.getByTestId('button-img-alt')).toHaveAccessibleName()
489489+ * await expect.element(page.getByTestId('img-paragraph')).not.toHaveAccessibleName()
490490+ * await expect.element(page.getByTestId('svg-button')).toHaveAccessibleName()
491491+ * await expect.element(page.getByTestId('svg-without-title')).not.toHaveAccessibleName()
492492+ * await expect.element(page.getByTestId('input-title')).toHaveAccessibleName()
493493+ * @see https://vitest.dev/guide/browser/assertion-api#tohaveaccessiblename
494494+ */
495495+ toHaveAccessibleName(text?: string | RegExp | E): R
496496+ /**
497497+ * @description
498498+ * This allows you to assert that an element has the expected
499499+ * [role](https://www.w3.org/TR/html-aria/#docconformance).
500500+ *
501501+ * This is useful in cases where you already have access to an element via
502502+ * some query other than the role itself, and want to make additional
503503+ * assertions regarding its accessibility.
504504+ *
505505+ * The role can match either an explicit role (via the `role` attribute), or
506506+ * an implicit one via the [implicit ARIA
507507+ * semantics](https://www.w3.org/TR/html-aria/).
508508+ *
509509+ * Note: roles are matched literally by string equality, without inheriting
510510+ * from the ARIA role hierarchy. As a result, querying a superclass role
511511+ * like 'checkbox' will not include elements with a subclass role like
512512+ * 'switch'.
513513+ *
514514+ * @example
515515+ * <button data-testid="button">Continue</button>
516516+ * <div role="button" data-testid="button-explicit">Continue</button>
517517+ * <button role="switch button" data-testid="button-explicit-multiple">Continue</button>
518518+ * <a href="/about" data-testid="link">About</a>
519519+ * <a data-testid="link-invalid">Invalid link<a/>
520520+ *
521521+ * await expect.element(page.getByTestId('button')).toHaveRole('button')
522522+ * await expect.element(page.getByTestId('button-explicit')).toHaveRole('button')
523523+ * await expect.element(page.getByTestId('button-explicit-multiple')).toHaveRole('button')
524524+ * await expect.element(page.getByTestId('button-explicit-multiple')).toHaveRole('switch')
525525+ * await expect.element(page.getByTestId('link')).toHaveRole('link')
526526+ * await expect.element(page.getByTestId('link-invalid')).not.toHaveRole('link')
527527+ * await expect.element(page.getByTestId('link-invalid')).toHaveRole('generic')
528528+ *
529529+ * @see https://vitest.dev/guide/browser/assertion-api#tohaverole
530530+ */
531531+ toHaveRole(
532532+ // Get autocomplete for ARIARole union types, while still supporting another string
533533+ // Ref: https://github.com/microsoft/TypeScript/issues/29729#issuecomment-567871939
534534+ role: ARIARole | (string & {}),
535535+ ): R
536536+ /**
537537+ * @description
538538+ * This allows you to check whether the given element is partially checked.
539539+ * It accepts an input of type checkbox and elements with a role of checkbox
540540+ * with a aria-checked="mixed", or input of type checkbox with indeterminate
541541+ * set to true
542542+ *
543543+ * @example
544544+ * <input type="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />
545545+ * <input type="checkbox" checked data-testid="input-checkbox-checked" />
546546+ * <input type="checkbox" data-testid="input-checkbox-unchecked" />
547547+ * <div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
548548+ * <div
549549+ * role="checkbox"
550550+ * aria-checked="false"
551551+ * data-testid="aria-checkbox-unchecked"
552552+ * />
553553+ * <input type="checkbox" data-testid="input-checkbox-indeterminate" />
554554+ *
555555+ * const ariaCheckboxMixed = getByTestId('aria-checkbox-mixed')
556556+ * const inputCheckboxChecked = getByTestId('input-checkbox-checked')
557557+ * const inputCheckboxUnchecked = getByTestId('input-checkbox-unchecked')
558558+ * const ariaCheckboxChecked = getByTestId('aria-checkbox-checked')
559559+ * const ariaCheckboxUnchecked = getByTestId('aria-checkbox-unchecked')
560560+ * const inputCheckboxIndeterminate = getByTestId('input-checkbox-indeterminate')
561561+ *
562562+ * await expect.element(ariaCheckboxMixed).toBePartiallyChecked()
563563+ * await expect.element(inputCheckboxChecked).not.toBePartiallyChecked()
564564+ * await expect.element(inputCheckboxUnchecked).not.toBePartiallyChecked()
565565+ * await expect.element(ariaCheckboxChecked).not.toBePartiallyChecked()
566566+ * await expect.element(ariaCheckboxUnchecked).not.toBePartiallyChecked()
567567+ *
568568+ * inputCheckboxIndeterminate.indeterminate = true
569569+ * await expect.element(inputCheckboxIndeterminate).toBePartiallyChecked()
570570+ * @see https://vitest.dev/guide/browser/assertion-api#tobepartiallychecked
571571+ */
572572+ toBePartiallyChecked(): R
573573+ /**
574574+ * @description
575575+ * This allows to assert that an element has a
576576+ * [text selection](https://developer.mozilla.org/en-US/docs/Web/API/Selection).
577577+ *
578578+ * This is useful to check if text or part of the text is selected within an
579579+ * element. The element can be either an input of type text, a textarea, or any
580580+ * other element that contains text, such as a paragraph, span, div etc.
581581+ *
582582+ * NOTE: the expected selection is a string, it does not allow to check for
583583+ * selection range indeces.
584584+ *
585585+ * @example
586586+ * <div>
587587+ * <input type="text" value="text selected text" data-testid="text" />
588588+ * <textarea data-testid="textarea">text selected text</textarea>
589589+ * <p data-testid="prev">prev</p>
590590+ * <p data-testid="parent">text <span data-testid="child">selected</span> text</p>
591591+ * <p data-testid="next">next</p>
592592+ * </div>
593593+ *
594594+ * page.getByTestId('text').element().setSelectionRange(5, 13)
595595+ * await expect.element(page.getByTestId('text')).toHaveSelection('selected')
596596+ *
597597+ * page.getByTestId('textarea').element().setSelectionRange(0, 5)
598598+ * await expect.element('textarea').toHaveSelection('text ')
599599+ *
600600+ * const selection = document.getSelection()
601601+ * const range = document.createRange()
602602+ * selection.removeAllRanges()
603603+ * selection.empty()
604604+ * selection.addRange(range)
605605+ *
606606+ * // selection of child applies to the parent as well
607607+ * range.selectNodeContents(page.getByTestId('child').element())
608608+ * await expect.element(page.getByTestId('child')).toHaveSelection('selected')
609609+ * await expect.element(page.getByTestId('parent')).toHaveSelection('selected')
610610+ *
611611+ * // selection that applies from prev all, parent text before child, and part child.
612612+ * range.setStart(page.getByTestId('prev').element(), 0)
613613+ * range.setEnd(page.getByTestId('child').element().childNodes[0], 3)
614614+ * await expect.element(page.queryByTestId('prev')).toHaveSelection('prev')
615615+ * await expect.element(page.queryByTestId('child')).toHaveSelection('sel')
616616+ * await expect.element(page.queryByTestId('parent')).toHaveSelection('text sel')
617617+ * await expect.element(page.queryByTestId('next')).not.toHaveSelection()
618618+ *
619619+ * // selection that applies from part child, parent text after child and part next.
620620+ * range.setStart(page.getByTestId('child').element().childNodes[0], 3)
621621+ * range.setEnd(page.getByTestId('next').element().childNodes[0], 2)
622622+ * await expect.element(page.queryByTestId('child')).toHaveSelection('ected')
623623+ * await expect.element(page.queryByTestId('parent')).toHaveSelection('ected text')
624624+ * await expect.element(page.queryByTestId('prev')).not.toHaveSelection()
625625+ * await expect.element(page.queryByTestId('next')).toHaveSelection('ne')
626626+ *
627627+ * @see https://vitest.dev/guide/browser/assertion-api#tohaveselection
628628+ */
629629+ toHaveSelection(selection?: string): R
708630}
709709-710710-// Needs to extend Record<string, any> to be accepted by expect.extend()
711711-// as it requires a string index signature.
712712-declare const matchers: matchers.TestingLibraryMatchers<any, void> &
713713- Record<string, any>
714714-715715-declare namespace matchers$1 {
716716- export { matchers as default };
717717-}
718718-719719-export { matchers$1 as default };
+4-4
packages/browser/matchers.d.ts
···11import type { Locator } from '@vitest/browser/context'
22-import type jsdomMatchers from './jest-dom.js'
22+import type { TestingLibraryMatchers } from './jest-dom.js'
33import type { Assertion, ExpectPollOptions } from 'vitest'
4455declare module 'vitest' {
66- interface JestAssertion<T = any> extends jsdomMatchers.default.TestingLibraryMatchers<void, T> {}
77- interface AsymmetricMatchersContaining extends jsdomMatchers.default.TestingLibraryMatchers<void, void> {}
66+ interface JestAssertion<T = any> extends TestingLibraryMatchers<void, T> {}
77+ interface AsymmetricMatchersContaining extends TestingLibraryMatchers<void, void> {}
8899 type Promisify<O> = {
1010 [K in keyof O]: O[K] extends (...args: infer A) => infer R
···2222 * You can set default timeout via `expect.poll.timeout` option in the config.
2323 * @see {@link https://vitest.dev/api/expect#poll}
2424 */
2525- element: <T extends Element | Locator>(element: T, options?: ExpectPollOptions) => PromisifyDomAssertion<Awaited<Element | null>>
2525+ element: <T extends Element | Locator | null>(element: T, options?: ExpectPollOptions) => PromisifyDomAssertion<Awaited<Element | null>>
2626 }
2727}
2828
···4455# Assertion API
6677-Vitest bundles the [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) library to provide a wide range of DOM assertions out of the box. For detailed documentation, you can read the `jest-dom` readme:
77+Vitest provides a wide range of DOM assertions out of the box forked from [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) library with the added support for locators and built-in retry-ability.
8899-- [`toBeDisabled`](https://github.com/testing-library/jest-dom#toBeDisabled)
1010-- [`toBeEnabled`](https://github.com/testing-library/jest-dom#toBeEnabled)
1111-- [`toBeEmptyDOMElement`](https://github.com/testing-library/jest-dom#toBeEmptyDOMElement)
1212-- [`toBeInTheDocument`](https://github.com/testing-library/jest-dom#toBeInTheDocument)
1313-- [`toBeInvalid`](https://github.com/testing-library/jest-dom#toBeInvalid)
1414-- [`toBeRequired`](https://github.com/testing-library/jest-dom#toBeRequired)
1515-- [`toBeValid`](https://github.com/testing-library/jest-dom#toBeValid)
1616-- [`toBeVisible`](https://github.com/testing-library/jest-dom#toBeVisible)
1717-- [`toContainElement`](https://github.com/testing-library/jest-dom#toContainElement)
1818-- [`toContainHTML`](https://github.com/testing-library/jest-dom#toContainHTML)
1919-- [`toHaveAccessibleDescription`](https://github.com/testing-library/jest-dom#toHaveAccessibleDescription)
2020-- [`toHaveAccessibleErrorMessage`](https://github.com/testing-library/jest-dom#toHaveAccessibleErrorMessage)
2121-- [`toHaveAccessibleName`](https://github.com/testing-library/jest-dom#toHaveAccessibleName)
2222-- [`toHaveAttribute`](https://github.com/testing-library/jest-dom#toHaveAttribute)
2323-- [`toHaveClass`](https://github.com/testing-library/jest-dom#toHaveClass)
2424-- [`toHaveFocus`](https://github.com/testing-library/jest-dom#toHaveFocus)
2525-- [`toHaveFormValues`](https://github.com/testing-library/jest-dom#toHaveFormValues)
2626-- [`toHaveStyle`](https://github.com/testing-library/jest-dom#toHaveStyle)
2727-- [`toHaveTextContent`](https://github.com/testing-library/jest-dom#toHaveTextContent)
2828-- [`toHaveValue`](https://github.com/testing-library/jest-dom#toHaveValue)
2929-- [`toHaveDisplayValue`](https://github.com/testing-library/jest-dom#toHaveDisplayValue)
3030-- [`toBeChecked`](https://github.com/testing-library/jest-dom#toBeChecked)
3131-- [`toBePartiallyChecked`](https://github.com/testing-library/jest-dom#toBePartiallyChecked)
3232-- [`toHaveRole`](https://github.com/testing-library/jest-dom#toHaveRole)
3333-- [`toHaveErrorMessage`](https://github.com/testing-library/jest-dom#toHaveErrorMessage)
99+::: tip TypeScript Support
1010+If you are using [TypeScript](/guide/browser/#typescript) or want to have correct type hints in `expect`, make sure you have `@vitest/browser/context` referenced somewhere. If you never imported from there, you can add a `reference` comment in any file that's covered by your `tsconfig.json`:
34113535-If you are using [TypeScript](/guide/browser/#typescript) or want to have correct type hints in `expect`, make sure you have either `@vitest/browser/providers/playwright` or `@vitest/browser/providers/webdriverio` referenced in your [setup file](/config/#setupfile) or a [config file](/config/) depending on the provider you use. If you use the default `preview` provider, you can specify `@vitest/browser/matchers` instead.
3636-3737-::: code-group
3838-```ts [preview]
3939-/// <reference types="@vitest/browser/matchers" />
4040-```
4141-```ts [playwright]
4242-/// <reference types="@vitest/browser/providers/playwright" />
4343-```
4444-```ts [webdriverio]
4545-/// <reference types="@vitest/browser/providers/webdriverio" />
1212+```ts
1313+/// <reference types="@vitest/browser/context" />
4614```
4715:::
4816···5523test('error banner is rendered', async () => {
5624 triggerError()
57255858- // @testing-library provides queries with built-in retry-ability
5959- // It will try to find the banner until it's rendered
2626+ // This creates a locator that will try to find the element
2727+ // when any of its methods are called.
2828+ // This call by itself doesn't check the existence of the element.
6029 const banner = page.getByRole('alert', {
6130 name: /error/i,
6231 })
63326433 // Vitest provides `expect.element` with built-in retry-ability
6565- // It will check `element.textContent` until it's equal to "Error!"
3434+ // It will repeatedly check that the element exists in the DOM and that
3535+ // the content of `element.textContent` is equal to "Error!"
3636+ // until all the conditions are met
6637 await expect.element(banner).toHaveTextContent('Error!')
6738})
3939+```
4040+4141+We recommend to always use `expect.element` when working with `page.getBy*` locators to reduce test flakiness. Note that `expect.element` accepts a second option:
4242+4343+```ts
4444+interface ExpectPollOptions {
4545+ // The interval to retry the assertion for in milliseconds
4646+ // Defaults to "expect.poll.interval" config option
4747+ interval?: number
4848+ // Time to retry the assertion for in milliseconds
4949+ // Defaults to "expect.poll.timeout" config option
5050+ timeout?: number
5151+ // The message printed when the assertion fails
5252+ message?: string
5353+}
6854```
69557056::: tip
7157`expect.element` is a shorthand for `expect.poll(() => element)` and works in exactly the same way.
72587373-`toHaveTextContent` and all other [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) assertions are still available on a regular `expect` without a built-in retry-ability mechanism:
5959+`toHaveTextContent` and all other assertions are still available on a regular `expect` without a built-in retry-ability mechanism:
74607561```ts
7662// will fail immediately if .textContent is not `'Error!'`
7763expect(banner).toHaveTextContent('Error!')
7864```
7965:::
6666+6767+## toBeDisabled
6868+6969+```ts
7070+function toBeDisabled(): Promise<void>
7171+```
7272+7373+Allows you to check whether an element is disabled from the user's perspective.
7474+7575+Matches if the element is a form control and the `disabled` attribute is specified on this element or the
7676+element is a descendant of a form element with a `disabled` attribute.
7777+7878+Note that only native control elements such as HTML `button`, `input`, `select`, `textarea`, `option`, `optgroup`
7979+can be disabled by setting "disabled" attribute. "disabled" attribute on other elements is ignored, unless it's a custom element.
8080+8181+```html
8282+<button
8383+ data-testid="button"
8484+ type="submit"
8585+ disabled
8686+>
8787+ submit
8888+</button>
8989+```
9090+9191+```ts
9292+await expect.element(getByTestId('button')).toBeDisabled() // ✅
9393+await expect.element(getByTestId('button')).not.toBeDisabled() // ❌
9494+```
9595+9696+## toBeEnabled
9797+9898+```ts
9999+function toBeEnabled(): Promise<void>
100100+```
101101+102102+Allows you to check whether an element is not disabled from the user's perspective.
103103+104104+Works like [`not.toBeDisabled()`](#tobedisabled). Use this matcher to avoid double negation in your tests.
105105+106106+```html
107107+<button
108108+ data-testid="button"
109109+ type="submit"
110110+ disabled
111111+>
112112+ submit
113113+</button>
114114+```
115115+116116+```ts
117117+await expect.element(getByTestId('button')).toBeEnabled() // ✅
118118+await expect.element(getByTestId('button')).not.toBeEnabled() // ❌
119119+```
120120+121121+## toBeEmptyDOMElement
122122+123123+```ts
124124+function toBeEmptyDOMElement(): Promise<void>
125125+```
126126+127127+This allows you to assert whether an element has no visible content for the user. It ignores comments but will fail if the element contains white-space.
128128+129129+```html
130130+<span data-testid="not-empty"><span data-testid="empty"></span></span>
131131+<span data-testid="with-whitespace"> </span>
132132+<span data-testid="with-comment"><!-- comment --></span>
133133+```
134134+135135+```ts
136136+await expect.element(getByTestId('empty')).toBeEmptyDOMElement()
137137+await expect.element(getByTestId('not-empty')).not.toBeEmptyDOMElement()
138138+await expect.element(
139139+ getByTestId('with-whitespace')
140140+).not.toBeEmptyDOMElement()
141141+```
142142+143143+## toBeInTheDocument
144144+145145+```ts
146146+function toBeInTheDocument(): Promise<void>
147147+```
148148+149149+Assert whether an element is present in the document or not.
150150+151151+```html
152152+<svg data-testid="svg-element"></svg>
153153+```
154154+155155+```ts
156156+await expect.element(getByTestId('svg-element')).toBeInTheDocument()
157157+await expect.element(getByTestId('does-not-exist')).not.toBeInTheDocument()
158158+```
159159+160160+::: warning
161161+This matcher does not find detached elements. The element must be added to the document to be found by `toBeInTheDocument`. If you desire to search in a detached element, please use: [`toContainElement`](#tocontainelement).
162162+:::
163163+164164+## toBeInvalid
165165+166166+```ts
167167+function toBeInvalid(): Promise<void>
168168+```
169169+170170+This allows you to check if an element, is currently invalid.
171171+172172+An element is invalid if it has an [`aria-invalid` attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-invalid) with no value or a value of `"true"`, or if the result of [`checkValidity()`](https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation) is `false`.
173173+174174+```html
175175+<input data-testid="no-aria-invalid" />
176176+<input data-testid="aria-invalid" aria-invalid />
177177+<input data-testid="aria-invalid-value" aria-invalid="true" />
178178+<input data-testid="aria-invalid-false" aria-invalid="false" />
179179+180180+<form data-testid="valid-form">
181181+ <input />
182182+</form>
183183+184184+<form data-testid="invalid-form">
185185+ <input required />
186186+</form>
187187+```
188188+189189+```ts
190190+await expect.element(getByTestId('no-aria-invalid')).not.toBeInvalid()
191191+await expect.element(getByTestId('aria-invalid')).toBeInvalid()
192192+await expect.element(getByTestId('aria-invalid-value')).toBeInvalid()
193193+await expect.element(getByTestId('aria-invalid-false')).not.toBeInvalid()
194194+195195+await expect.element(getByTestId('valid-form')).not.toBeInvalid()
196196+await expect.element(getByTestId('invalid-form')).toBeInvalid()
197197+```
198198+199199+## toBeRequired
200200+201201+```ts
202202+function toBeRequired(): Promise<void>
203203+```
204204+205205+This allows you to check if a form element is currently required.
206206+207207+An element is required if it is having a `required` or `aria-required="true"` attribute.
208208+209209+```html
210210+<input data-testid="required-input" required />
211211+<input data-testid="aria-required-input" aria-required="true" />
212212+<input data-testid="conflicted-input" required aria-required="false" />
213213+<input data-testid="aria-not-required-input" aria-required="false" />
214214+<input data-testid="optional-input" />
215215+<input data-testid="unsupported-type" type="image" required />
216216+<select data-testid="select" required></select>
217217+<textarea data-testid="textarea" required></textarea>
218218+<div data-testid="supported-role" role="tree" required></div>
219219+<div data-testid="supported-role-aria" role="tree" aria-required="true"></div>
220220+```
221221+222222+```ts
223223+await expect.element(getByTestId('required-input')).toBeRequired()
224224+await expect.element(getByTestId('aria-required-input')).toBeRequired()
225225+await expect.element(getByTestId('conflicted-input')).toBeRequired()
226226+await expect.element(getByTestId('aria-not-required-input')).not.toBeRequired()
227227+await expect.element(getByTestId('optional-input')).not.toBeRequired()
228228+await expect.element(getByTestId('unsupported-type')).not.toBeRequired()
229229+await expect.element(getByTestId('select')).toBeRequired()
230230+await expect.element(getByTestId('textarea')).toBeRequired()
231231+await expect.element(getByTestId('supported-role')).not.toBeRequired()
232232+await expect.element(getByTestId('supported-role-aria')).toBeRequired()
233233+```
234234+235235+## toBeValid
236236+237237+```ts
238238+function toBeValid(): Promise<void>
239239+```
240240+241241+This allows you to check if the value of an element, is currently valid.
242242+243243+An element is valid if it has no [`aria-invalid` attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-invalid) or an attribute value of "false". The result of [`checkValidity()`](https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation) must also be `true` if it's a form element.
244244+245245+```html
246246+<input data-testid="no-aria-invalid" />
247247+<input data-testid="aria-invalid" aria-invalid />
248248+<input data-testid="aria-invalid-value" aria-invalid="true" />
249249+<input data-testid="aria-invalid-false" aria-invalid="false" />
250250+251251+<form data-testid="valid-form">
252252+ <input />
253253+</form>
254254+255255+<form data-testid="invalid-form">
256256+ <input required />
257257+</form>
258258+```
259259+260260+```ts
261261+await expect.element(getByTestId('no-aria-invalid')).toBeValid()
262262+await expect.element(getByTestId('aria-invalid')).not.toBeValid()
263263+await expect.element(getByTestId('aria-invalid-value')).not.toBeValid()
264264+await expect.element(getByTestId('aria-invalid-false')).toBeValid()
265265+266266+await expect.element(getByTestId('valid-form')).toBeValid()
267267+await expect.element(getByTestId('invalid-form')).not.toBeValid()
268268+```
269269+270270+## toBeVisible
271271+272272+```ts
273273+function toBeVisible(): Promise<void>
274274+```
275275+276276+This allows you to check if an element is currently visible to the user.
277277+278278+Element is considered visible when it has non-empty bounding box and does not have `visibility:hidden` computed style.
279279+280280+Note that according to this definition:
281281+282282+- Elements of zero size **are not** considered visible.
283283+- Elements with `display:none` **are not** considered visible.
284284+- Elements with `opacity:0` **are** considered visible.
285285+286286+To check that at least one element from the list is visible, use `locator.first()`.
287287+288288+```ts
289289+// A specific element is visible.
290290+await expect.element(page.getByText('Welcome')).toBeVisible()
291291+292292+// At least one item in the list is visible.
293293+await expect.element(page.getByTestId('todo-item').first()).toBeVisible()
294294+295295+// At least one of the two elements is visible, possibly both.
296296+await expect.element(
297297+ page.getByRole('button', { name: 'Sign in' })
298298+ .or(page.getByRole('button', { name: 'Sign up' }))
299299+ .first()
300300+).toBeVisible()
301301+```
302302+303303+## toContainElement
304304+305305+```ts
306306+function toContainElement(element: HTMLElement | SVGElement | null): Promise<void>
307307+```
308308+309309+This allows you to assert whether an element contains another element as a descendant or not.
310310+311311+```html
312312+<span data-testid="ancestor"><span data-testid="descendant"></span></span>
313313+```
314314+315315+```ts
316316+const ancestor = getByTestId('ancestor')
317317+const descendant = getByTestId('descendant')
318318+const nonExistantElement = getByTestId('does-not-exist')
319319+320320+await expect.element(ancestor).toContainElement(descendant)
321321+await expect.element(descendant).not.toContainElement(ancestor)
322322+await expect.element(ancestor).not.toContainElement(nonExistantElement)
323323+```
324324+325325+## toContainHTML
326326+327327+```ts
328328+function toContainHTML(htmlText: string): Promise<void>
329329+```
330330+331331+Assert whether a string representing a HTML element is contained in another element. The string should contain valid html, and not any incomplete html.
332332+333333+```html
334334+<span data-testid="parent"><span data-testid="child"></span></span>
335335+```
336336+337337+```ts
338338+// These are valid usages
339339+await expect.element(getByTestId('parent')).toContainHTML('<span data-testid="child"></span>')
340340+await expect.element(getByTestId('parent')).toContainHTML('<span data-testid="child" />')
341341+await expect.element(getByTestId('parent')).not.toContainHTML('<br />')
342342+343343+// These won't work
344344+await expect.element(getByTestId('parent')).toContainHTML('data-testid="child"')
345345+await expect.element(getByTestId('parent')).toContainHTML('data-testid')
346346+await expect.element(getByTestId('parent')).toContainHTML('</span>')
347347+```
348348+349349+::: warning
350350+Chances are you probably do not need to use this matcher. We encourage testing from the perspective of how the user perceives the app in a browser. That's why testing against a specific DOM structure is not advised.
351351+352352+It could be useful in situations where the code being tested renders html that was obtained from an external source, and you want to validate that that html code was used as intended.
353353+354354+It should not be used to check DOM structure that you control. Please, use [`toContainElement`](#tocontainelement) instead.
355355+:::
356356+357357+## toHaveAccessibleDescription
358358+359359+```ts
360360+function toHaveAccessibleDescription(description?: string | RegExp): Promise<void>
361361+```
362362+363363+This allows you to assert that an element has the expected
364364+[accessible description](https://w3c.github.io/accname/).
365365+366366+You can pass the exact string of the expected accessible description, or you can
367367+make a partial match passing a regular expression, or by using
368368+[`expect.stringContaining`](/api/expect#expect-stringcontaining) or [`expect.stringMatching`](/api/expect#expect-stringmatching).
369369+370370+```html
371371+<a
372372+ data-testid="link"
373373+ href="/"
374374+ aria-label="Home page"
375375+ title="A link to start over"
376376+ >Start</a
377377+>
378378+<a data-testid="extra-link" href="/about" aria-label="About page">About</a>
379379+<img src="avatar.jpg" data-testid="avatar" alt="User profile pic" />
380380+<img
381381+ src="logo.jpg"
382382+ data-testid="logo"
383383+ alt="Company logo"
384384+ aria-describedby="t1"
385385+/>
386386+<span id="t1" role="presentation">The logo of Our Company</span>
387387+<img
388388+ src="logo.jpg"
389389+ data-testid="logo2"
390390+ alt="Company logo"
391391+ aria-description="The logo of Our Company"
392392+/>
393393+```
394394+395395+```ts
396396+await expect.element(getByTestId('link')).toHaveAccessibleDescription()
397397+await expect.element(getByTestId('link')).toHaveAccessibleDescription('A link to start over')
398398+await expect.element(getByTestId('link')).not.toHaveAccessibleDescription('Home page')
399399+await expect.element(getByTestId('extra-link')).not.toHaveAccessibleDescription()
400400+await expect.element(getByTestId('avatar')).not.toHaveAccessibleDescription()
401401+await expect.element(getByTestId('logo')).not.toHaveAccessibleDescription('Company logo')
402402+await expect.element(getByTestId('logo')).toHaveAccessibleDescription(
403403+ 'The logo of Our Company',
404404+)
405405+await expect.element(getByTestId('logo2')).toHaveAccessibleDescription(
406406+ 'The logo of Our Company',
407407+)
408408+```
409409+410410+## toHaveAccessibleErrorMessage
411411+412412+```ts
413413+function toHaveAccessibleErrorMessage(message?: string | RegExp): Promise<void>
414414+```
415415+416416+This allows you to assert that an element has the expected
417417+[accessible error message](https://w3c.github.io/aria/#aria-errormessage).
418418+419419+You can pass the exact string of the expected accessible error message.
420420+Alternatively, you can perform a partial match by passing a regular expression
421421+or by using
422422+[`expect.stringContaining`](/api/expect#expect-stringcontaining) or [`expect.stringMatching`](/api/expect#expect-stringmatching).
423423+424424+```html
425425+<input
426426+ aria-label="Has Error"
427427+ aria-invalid="true"
428428+ aria-errormessage="error-message"
429429+/>
430430+<div id="error-message" role="alert">This field is invalid</div>
431431+432432+<input aria-label="No Error Attributes" />
433433+<input
434434+ aria-label="Not Invalid"
435435+ aria-invalid="false"
436436+ aria-errormessage="error-message"
437437+/>
438438+```
439439+440440+```ts
441441+// Inputs with Valid Error Messages
442442+await expect.element(getByRole('textbox', { name: 'Has Error' })).toHaveAccessibleErrorMessage()
443443+await expect.element(getByRole('textbox', { name: 'Has Error' })).toHaveAccessibleErrorMessage(
444444+ 'This field is invalid',
445445+)
446446+await expect.element(getByRole('textbox', { name: 'Has Error' })).toHaveAccessibleErrorMessage(
447447+ /invalid/i,
448448+)
449449+await expect.element(
450450+ getByRole('textbox', { name: 'Has Error' }),
451451+).not.toHaveAccessibleErrorMessage('This field is absolutely correct!')
452452+453453+// Inputs without Valid Error Messages
454454+await expect.element(
455455+ getByRole('textbox', { name: 'No Error Attributes' }),
456456+).not.toHaveAccessibleErrorMessage()
457457+458458+await expect.element(
459459+ getByRole('textbox', { name: 'Not Invalid' }),
460460+).not.toHaveAccessibleErrorMessage()
461461+```
462462+463463+## toHaveAccessibleName
464464+465465+```ts
466466+function toHaveAccessibleName(name?: string | RegExp): Promise<void>
467467+```
468468+469469+This allows you to assert that an element has the expected
470470+[accessible name](https://w3c.github.io/accname/). It is useful, for instance,
471471+to assert that form elements and buttons are properly labelled.
472472+473473+You can pass the exact string of the expected accessible name, or you can make a
474474+partial match passing a regular expression, or by using
475475+[`expect.stringContaining`](/api/expect#expect-stringcontaining) or [`expect.stringMatching`](/api/expect#expect-stringmatching).
476476+477477+```html
478478+<img data-testid="img-alt" src="" alt="Test alt" />
479479+<img data-testid="img-empty-alt" src="" alt="" />
480480+<svg data-testid="svg-title"><title>Test title</title></svg>
481481+<button data-testid="button-img-alt"><img src="" alt="Test" /></button>
482482+<p><img data-testid="img-paragraph" src="" alt="" /> Test content</p>
483483+<button data-testid="svg-button"><svg><title>Test</title></svg></p>
484484+<div><svg data-testid="svg-without-title"></svg></div>
485485+<input data-testid="input-title" title="test" />
486486+```
487487+488488+```javascript
489489+await expect.element(getByTestId('img-alt')).toHaveAccessibleName('Test alt')
490490+await expect.element(getByTestId('img-empty-alt')).not.toHaveAccessibleName()
491491+await expect.element(getByTestId('svg-title')).toHaveAccessibleName('Test title')
492492+await expect.element(getByTestId('button-img-alt')).toHaveAccessibleName()
493493+await expect.element(getByTestId('img-paragraph')).not.toHaveAccessibleName()
494494+await expect.element(getByTestId('svg-button')).toHaveAccessibleName()
495495+await expect.element(getByTestId('svg-without-title')).not.toHaveAccessibleName()
496496+await expect.element(getByTestId('input-title')).toHaveAccessibleName()
497497+```
498498+499499+## toHaveAttribute
500500+501501+```ts
502502+function toHaveAttribute(attribute: string, value?: unknown): Promise<void>
503503+```
504504+505505+This allows you to check whether the given element has an attribute or not. You
506506+can also optionally check that the attribute has a specific expected value or
507507+partial match using [`expect.stringContaining`](/api/expect#expect-stringcontaining) or [`expect.stringMatching`](/api/expect#expect-stringmatching).
508508+509509+```html
510510+<button data-testid="ok-button" type="submit" disabled>ok</button>
511511+```
512512+513513+```ts
514514+const button = getByTestId('ok-button')
515515+516516+await expect.element(button).toHaveAttribute('disabled')
517517+await expect.element(button).toHaveAttribute('type', 'submit')
518518+await expect.element(button).not.toHaveAttribute('type', 'button')
519519+520520+await expect.element(button).toHaveAttribute(
521521+ 'type',
522522+ expect.stringContaining('sub')
523523+)
524524+await expect.element(button).toHaveAttribute(
525525+ 'type',
526526+ expect.not.stringContaining('but')
527527+)
528528+```
529529+530530+## toHaveClass
531531+532532+```ts
533533+function toHaveClass(...classNames: string[], options?: { exact: boolean }): Promise<void>
534534+function toHaveClass(...classNames: (string | RegExp)[]): Promise<void>
535535+```
536536+537537+This allows you to check whether the given element has certain classes within
538538+its `class` attribute. You must provide at least one class, unless you are
539539+asserting that an element does not have any classes.
540540+541541+The list of class names may include strings and regular expressions. Regular
542542+expressions are matched against each individual class in the target element, and
543543+it is NOT matched against its full `class` attribute value as whole.
544544+545545+::: warning
546546+Note that you cannot use `exact: true` option when only regular expressions are provided.
547547+:::
548548+549549+```html
550550+<button data-testid="delete-button" class="btn extra btn-danger">
551551+ Delete item
552552+</button>
553553+<button data-testid="no-classes">No Classes</button>
554554+```
555555+556556+```ts
557557+const deleteButton = getByTestId('delete-button')
558558+const noClasses = getByTestId('no-classes')
559559+560560+await expect.element(deleteButton).toHaveClass('extra')
561561+await expect.element(deleteButton).toHaveClass('btn-danger btn')
562562+await expect.element(deleteButton).toHaveClass(/danger/, 'btn')
563563+await expect.element(deleteButton).toHaveClass('btn-danger', 'btn')
564564+await expect.element(deleteButton).not.toHaveClass('btn-link')
565565+await expect.element(deleteButton).not.toHaveClass(/link/)
566566+567567+// ⚠️ regexp matches against individual classes, not the whole classList
568568+await expect.element(deleteButton).not.toHaveClass(/btn extra/)
569569+570570+// the element has EXACTLY a set of classes (in any order)
571571+await expect.element(deleteButton).toHaveClass('btn-danger extra btn', {
572572+ exact: true
573573+})
574574+// if it has more than expected it is going to fail
575575+await expect.element(deleteButton).not.toHaveClass('btn-danger extra', {
576576+ exact: true
577577+})
578578+579579+await expect.element(noClasses).not.toHaveClass()
580580+```
581581+582582+## toHaveFocus
583583+584584+```ts
585585+function toHaveFocus(): Promise<void>
586586+```
587587+588588+This allows you to assert whether an element has focus or not.
589589+590590+```html
591591+<div><input type="text" data-testid="element-to-focus" /></div>
592592+```
593593+594594+```ts
595595+const input = page.getByTestId('element-to-focus')
596596+input.element().focus()
597597+await expect.element(input).toHaveFocus()
598598+input.element().blur()
599599+await expect.element(input).not.toHaveFocus()
600600+```
601601+602602+## toHaveFormValues
603603+604604+```ts
605605+function toHaveFormValues(expectedValues: Record<string, unknown>): Promise<void>
606606+```
607607+608608+This allows you to check if a form or fieldset contains form controls for each given name, and having the specified value.
609609+610610+::: tip
611611+It is important to stress that this matcher can only be invoked on a [form](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement) or a [fieldset](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFieldSetElement) element.
612612+613613+This allows it to take advantage of the [`.elements`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements) property in `form` and `fieldset` to reliably fetch all form controls within them.
614614+615615+This also avoids the possibility that users provide a container that contains more than one `form`, thereby intermixing form controls that are not related, and could even conflict with one another.
616616+:::
617617+618618+This matcher abstracts away the particularities with which a form control value
619619+is obtained depending on the type of form control. For instance, `<input>`
620620+elements have a `value` attribute, but `<select>` elements do not. Here's a list
621621+of all cases covered:
622622+623623+- `<input type="number">` elements return the value as a **number**, instead of
624624+ a string.
625625+- `<input type="checkbox">` elements:
626626+ - if there's a single one with the given `name` attribute, it is treated as a
627627+ **boolean**, returning `true` if the checkbox is checked, `false` if
628628+ unchecked.
629629+ - if there's more than one checkbox with the same `name` attribute, they are
630630+ all treated collectively as a single form control, which returns the value
631631+ as an **array** containing all the values of the selected checkboxes in the
632632+ collection.
633633+- `<input type="radio">` elements are all grouped by the `name` attribute, and
634634+ such a group treated as a single form control. This form control returns the
635635+ value as a **string** corresponding to the `value` attribute of the selected
636636+ radio button within the group.
637637+- `<input type="text">` elements return the value as a **string**. This also
638638+ applies to `<input>` elements having any other possible `type` attribute
639639+ that's not explicitly covered in different rules above (e.g. `search`,
640640+ `email`, `date`, `password`, `hidden`, etc.)
641641+- `<select>` elements without the `multiple` attribute return the value as a
642642+ **string** corresponding to the `value` attribute of the selected `option`, or
643643+ `undefined` if there's no selected option.
644644+- `<select multiple>` elements return the value as an **array** containing all
645645+ the values of the [selected options](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/selectedOptions).
646646+- `<textarea>` elements return their value as a **string**. The value
647647+ corresponds to their node content.
648648+649649+The above rules make it easy, for instance, to switch from using a single select
650650+control to using a group of radio buttons. Or to switch from a multi select
651651+control, to using a group of checkboxes. The resulting set of form values used
652652+by this matcher to compare against would be the same.
653653+654654+```html
655655+<form data-testid="login-form">
656656+ <input type="text" name="username" value="jane.doe" />
657657+ <input type="password" name="password" value="12345678" />
658658+ <input type="checkbox" name="rememberMe" checked />
659659+ <button type="submit">Sign in</button>
660660+</form>
661661+```
662662+663663+```ts
664664+await expect.element(getByTestId('login-form')).toHaveFormValues({
665665+ username: 'jane.doe',
666666+ rememberMe: true,
667667+})
668668+```
669669+670670+## toHaveStyle
671671+672672+```ts
673673+function toHaveStyle(css: string | Partial<CSSStyleDeclaration>): Promise<void>
674674+```
675675+676676+This allows you to check if a certain element has some specific css properties
677677+with specific values applied. It matches only if the element has _all_ the
678678+expected properties applied, not just some of them.
679679+680680+```html
681681+<button
682682+ data-testid="delete-button"
683683+ style="display: none; background-color: red"
684684+>
685685+ Delete item
686686+</button>
687687+```
688688+689689+```ts
690690+const button = getByTestId('delete-button')
691691+692692+await expect.element(button).toHaveStyle('display: none')
693693+await expect.element(button).toHaveStyle({ display: 'none' })
694694+await expect.element(button).toHaveStyle(`
695695+ background-color: red;
696696+ display: none;
697697+`)
698698+await expect.element(button).toHaveStyle({
699699+ backgroundColor: 'red',
700700+ display: 'none',
701701+})
702702+await expect.element(button).not.toHaveStyle(`
703703+ background-color: blue;
704704+ display: none;
705705+`)
706706+await expect.element(button).not.toHaveStyle({
707707+ backgroundColor: 'blue',
708708+ display: 'none',
709709+})
710710+```
711711+712712+This also works with rules that are applied to the element via a class name for
713713+which some rules are defined in a stylesheet currently active in the document.
714714+The usual rules of css precedence apply.
715715+716716+## toHaveTextContent
717717+718718+```ts
719719+function toHaveTextContent(
720720+ text: string | RegExp,
721721+ options?: { normalizeWhitespace: boolean }
722722+): Promise<void>
723723+```
724724+725725+This allows you to check whether the given node has a text content or not. This
726726+supports elements, but also text nodes and fragments.
727727+728728+When a `string` argument is passed through, it will perform a partial
729729+case-sensitive match to the node content.
730730+731731+To perform a case-insensitive match, you can use a `RegExp` with the `/i`
732732+modifier.
733733+734734+If you want to match the whole content, you can use a `RegExp` to do it.
735735+736736+```html
737737+<span data-testid="text-content">Text Content</span>
738738+```
739739+740740+```ts
741741+const element = getByTestId('text-content')
742742+743743+await expect.element(element).toHaveTextContent('Content')
744744+// to match the whole content
745745+await expect.element(element).toHaveTextContent(/^Text Content$/)
746746+// to use case-insensitive match
747747+await expect.element(element).toHaveTextContent(/content$/i)
748748+await expect.element(element).not.toHaveTextContent('content')
749749+```
750750+751751+## toHaveValue
752752+753753+```ts
754754+function toHaveValue(value: string | string[] | number | null): Promise<void>
755755+```
756756+757757+This allows you to check whether the given form element has the specified value.
758758+It accepts `<input>`, `<select>` and `<textarea>` elements with the exception of
759759+`<input type="checkbox">` and `<input type="radio">`, which can be meaningfully
760760+matched only using [`toBeChecked`](#tobechecked) or
761761+[`toHaveFormValues`](#tohaveformvalues).
762762+763763+It also accepts elements with roles `meter`, `progressbar`, `slider` or
764764+`spinbutton` and checks their `aria-valuenow` attribute (as a number).
765765+766766+For all other form elements, the value is matched using the same algorithm as in
767767+[`toHaveFormValues`](#tohaveformvalues) does.
768768+769769+```html
770770+<input type="text" value="text" data-testid="input-text" />
771771+<input type="number" value="5" data-testid="input-number" />
772772+<input type="text" data-testid="input-empty" />
773773+<select multiple data-testid="select-number">
774774+ <option value="first">First Value</option>
775775+ <option value="second" selected>Second Value</option>
776776+ <option value="third" selected>Third Value</option>
777777+</select>
778778+```
779779+780780+```ts
781781+const textInput = getByTestId('input-text')
782782+const numberInput = getByTestId('input-number')
783783+const emptyInput = getByTestId('input-empty')
784784+const selectInput = getByTestId('select-number')
785785+786786+await expect.element(textInput).toHaveValue('text')
787787+await expect.element(numberInput).toHaveValue(5)
788788+await expect.element(emptyInput).not.toHaveValue()
789789+await expect.element(selectInput).toHaveValue(['second', 'third'])
790790+```
791791+792792+## toHaveDisplayValue
793793+794794+```typescript
795795+function toHaveDisplayValue(
796796+ value: string | RegExp | (string | RegExp)[]
797797+): Promise<void>
798798+```
799799+800800+This allows you to check whether the given form element has the specified
801801+displayed value (the one the end user will see). It accepts `<input>`,
802802+`<select>` and `<textarea>` elements with the exception of
803803+`<input type="checkbox">` and `<input type="radio">`, which can be meaningfully
804804+matched only using [`toBeChecked`](#tobechecked) or
805805+[`toHaveFormValues`](#tohaveformvalues).
806806+807807+```html
808808+<label for="input-example">First name</label>
809809+<input type="text" id="input-example" value="Luca" />
810810+811811+<label for="textarea-example">Description</label>
812812+<textarea id="textarea-example">An example description here.</textarea>
813813+814814+<label for="single-select-example">Fruit</label>
815815+<select id="single-select-example">
816816+ <option value="">Select a fruit...</option>
817817+ <option value="banana">Banana</option>
818818+ <option value="ananas">Ananas</option>
819819+ <option value="avocado">Avocado</option>
820820+</select>
821821+822822+<label for="multiple-select-example">Fruits</label>
823823+<select id="multiple-select-example" multiple>
824824+ <option value="">Select a fruit...</option>
825825+ <option value="banana" selected>Banana</option>
826826+ <option value="ananas">Ananas</option>
827827+ <option value="avocado" selected>Avocado</option>
828828+</select>
829829+```
830830+831831+```ts
832832+const input = page.getByLabelText('First name')
833833+const textarea = page.getByLabelText('Description')
834834+const selectSingle = page.getByLabelText('Fruit')
835835+const selectMultiple = page.getByLabelText('Fruits')
836836+837837+await expect.element(input).toHaveDisplayValue('Luca')
838838+await expect.element(input).toHaveDisplayValue(/Luc/)
839839+await expect.element(textarea).toHaveDisplayValue('An example description here.')
840840+await expect.element(textarea).toHaveDisplayValue(/example/)
841841+await expect.element(selectSingle).toHaveDisplayValue('Select a fruit...')
842842+await expect.element(selectSingle).toHaveDisplayValue(/Select/)
843843+await expect.element(selectMultiple).toHaveDisplayValue([/Avocado/, 'Banana'])
844844+```
845845+846846+## toBeChecked
847847+848848+```ts
849849+function toBeChecked(): Promise<void>
850850+```
851851+852852+This allows you to check whether the given element is checked. It accepts an
853853+`input` of type `checkbox` or `radio` and elements with a `role` of `checkbox`,
854854+`radio` or `switch` with a valid `aria-checked` attribute of `"true"` or
855855+`"false"`.
856856+857857+```html
858858+<input type="checkbox" checked data-testid="input-checkbox-checked" />
859859+<input type="checkbox" data-testid="input-checkbox-unchecked" />
860860+<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
861861+<div
862862+ role="checkbox"
863863+ aria-checked="false"
864864+ data-testid="aria-checkbox-unchecked"
865865+/>
866866+867867+<input type="radio" checked value="foo" data-testid="input-radio-checked" />
868868+<input type="radio" value="foo" data-testid="input-radio-unchecked" />
869869+<div role="radio" aria-checked="true" data-testid="aria-radio-checked" />
870870+<div role="radio" aria-checked="false" data-testid="aria-radio-unchecked" />
871871+<div role="switch" aria-checked="true" data-testid="aria-switch-checked" />
872872+<div role="switch" aria-checked="false" data-testid="aria-switch-unchecked" />
873873+```
874874+875875+```ts
876876+const inputCheckboxChecked = getByTestId('input-checkbox-checked')
877877+const inputCheckboxUnchecked = getByTestId('input-checkbox-unchecked')
878878+const ariaCheckboxChecked = getByTestId('aria-checkbox-checked')
879879+const ariaCheckboxUnchecked = getByTestId('aria-checkbox-unchecked')
880880+await expect.element(inputCheckboxChecked).toBeChecked()
881881+await expect.element(inputCheckboxUnchecked).not.toBeChecked()
882882+await expect.element(ariaCheckboxChecked).toBeChecked()
883883+await expect.element(ariaCheckboxUnchecked).not.toBeChecked()
884884+885885+const inputRadioChecked = getByTestId('input-radio-checked')
886886+const inputRadioUnchecked = getByTestId('input-radio-unchecked')
887887+const ariaRadioChecked = getByTestId('aria-radio-checked')
888888+const ariaRadioUnchecked = getByTestId('aria-radio-unchecked')
889889+await expect.element(inputRadioChecked).toBeChecked()
890890+await expect.element(inputRadioUnchecked).not.toBeChecked()
891891+await expect.element(ariaRadioChecked).toBeChecked()
892892+await expect.element(ariaRadioUnchecked).not.toBeChecked()
893893+894894+const ariaSwitchChecked = getByTestId('aria-switch-checked')
895895+const ariaSwitchUnchecked = getByTestId('aria-switch-unchecked')
896896+await expect.element(ariaSwitchChecked).toBeChecked()
897897+await expect.element(ariaSwitchUnchecked).not.toBeChecked()
898898+```
899899+900900+## toBePartiallyChecked
901901+902902+```typescript
903903+function toBePartiallyChecked(): Promise<void>
904904+```
905905+906906+This allows you to check whether the given element is partially checked. It
907907+accepts an `input` of type `checkbox` and elements with a `role` of `checkbox`
908908+with a `aria-checked="mixed"`, or `input` of type `checkbox` with
909909+`indeterminate` set to `true`
910910+911911+```html
912912+<input type="checkbox" aria-checked="mixed" data-testid="aria-checkbox-mixed" />
913913+<input type="checkbox" checked data-testid="input-checkbox-checked" />
914914+<input type="checkbox" data-testid="input-checkbox-unchecked" />
915915+<div role="checkbox" aria-checked="true" data-testid="aria-checkbox-checked" />
916916+<div
917917+ role="checkbox"
918918+ aria-checked="false"
919919+ data-testid="aria-checkbox-unchecked"
920920+/>
921921+<input type="checkbox" data-testid="input-checkbox-indeterminate" />
922922+```
923923+924924+```ts
925925+const ariaCheckboxMixed = getByTestId('aria-checkbox-mixed')
926926+const inputCheckboxChecked = getByTestId('input-checkbox-checked')
927927+const inputCheckboxUnchecked = getByTestId('input-checkbox-unchecked')
928928+const ariaCheckboxChecked = getByTestId('aria-checkbox-checked')
929929+const ariaCheckboxUnchecked = getByTestId('aria-checkbox-unchecked')
930930+const inputCheckboxIndeterminate = getByTestId('input-checkbox-indeterminate')
931931+932932+await expect.element(ariaCheckboxMixed).toBePartiallyChecked()
933933+await expect.element(inputCheckboxChecked).not.toBePartiallyChecked()
934934+await expect.element(inputCheckboxUnchecked).not.toBePartiallyChecked()
935935+await expect.element(ariaCheckboxChecked).not.toBePartiallyChecked()
936936+await expect.element(ariaCheckboxUnchecked).not.toBePartiallyChecked()
937937+938938+inputCheckboxIndeterminate.element().indeterminate = true
939939+await expect.element(inputCheckboxIndeterminate).toBePartiallyChecked()
940940+```
941941+942942+## toHaveRole
943943+944944+```ts
945945+function toHaveRole(role: ARIARole): Promise<void>
946946+```
947947+948948+This allows you to assert that an element has the expected [role](https://www.w3.org/TR/html-aria/#docconformance).
949949+950950+This is useful in cases where you already have access to an element via some query other than the role itself, and want to make additional assertions regarding its accessibility.
951951+952952+The role can match either an explicit role (via the `role` attribute), or an implicit one via the [implicit ARIA semantics](https://www.w3.org/TR/html-aria/#docconformance).
953953+954954+```html
955955+<button data-testid="button">Continue</button>
956956+<div role="button" data-testid="button-explicit">Continue</button>
957957+<button role="switch button" data-testid="button-explicit-multiple">Continue</button>
958958+<a href="/about" data-testid="link">About</a>
959959+<a data-testid="link-invalid">Invalid link<a/>
960960+```
961961+962962+```ts
963963+await expect.element(getByTestId('button')).toHaveRole('button')
964964+await expect.element(getByTestId('button-explicit')).toHaveRole('button')
965965+await expect.element(getByTestId('button-explicit-multiple')).toHaveRole('button')
966966+await expect.element(getByTestId('button-explicit-multiple')).toHaveRole('switch')
967967+await expect.element(getByTestId('link')).toHaveRole('link')
968968+await expect.element(getByTestId('link-invalid')).not.toHaveRole('link')
969969+await expect.element(getByTestId('link-invalid')).toHaveRole('generic')
970970+```
971971+972972+::: warning
973973+Roles are matched literally by string equality, without inheriting from the ARIA role hierarchy. As a result, querying a superclass role like `checkbox` will not include elements with a subclass role like `switch`.
974974+975975+Also note that unlike `testing-library`, Vitest ignores all custom roles except the first valid one, following Playwright's behaviour:
976976+977977+```jsx
978978+<div data-testid="switch" role="switch alert"></div>
979979+980980+await expect.element(getByTestId('switch')).toHaveRole('switch') // ✅
981981+await expect.element(getByTestId('switch')).toHaveRole('alert') // ❌
982982+```
983983+:::
984984+985985+## toHaveSelection
986986+987987+```ts
988988+function toHaveSelection(selection?: string): Promise<void>
989989+```
990990+991991+This allows to assert that an element has a
992992+[text selection](https://developer.mozilla.org/en-US/docs/Web/API/Selection).
993993+994994+This is useful to check if text or part of the text is selected within an
995995+element. The element can be either an input of type text, a textarea, or any
996996+other element that contains text, such as a paragraph, span, div etc.
997997+998998+::: warning
999999+The expected selection is a string, it does not allow to check for
10001000+selection range indeces.
10011001+:::
10021002+10031003+```html
10041004+<div>
10051005+ <input type="text" value="text selected text" data-testid="text" />
10061006+ <textarea data-testid="textarea">text selected text</textarea>
10071007+ <p data-testid="prev">prev</p>
10081008+ <p data-testid="parent">
10091009+ text <span data-testid="child">selected</span> text
10101010+ </p>
10111011+ <p data-testid="next">next</p>
10121012+</div>
10131013+```
10141014+10151015+```ts
10161016+getByTestId('text').element().setSelectionRange(5, 13)
10171017+await expect.element(getByTestId('text')).toHaveSelection('selected')
10181018+10191019+getByTestId('textarea').element().setSelectionRange(0, 5)
10201020+await expect.element('textarea').toHaveSelection('text ')
10211021+10221022+const selection = document.getSelection()
10231023+const range = document.createRange()
10241024+selection.removeAllRanges()
10251025+selection.empty()
10261026+selection.addRange(range)
10271027+10281028+// selection of child applies to the parent as well
10291029+range.selectNodeContents(getByTestId('child').element())
10301030+await expect.element(getByTestId('child')).toHaveSelection('selected')
10311031+await expect.element(getByTestId('parent')).toHaveSelection('selected')
10321032+10331033+// selection that applies from prev all, parent text before child, and part child.
10341034+range.setStart(getByTestId('prev').element(), 0)
10351035+range.setEnd(getByTestId('child').element().childNodes[0], 3)
10361036+await expect.element(queryByTestId('prev')).toHaveSelection('prev')
10371037+await expect.element(queryByTestId('child')).toHaveSelection('sel')
10381038+await expect.element(queryByTestId('parent')).toHaveSelection('text sel')
10391039+await expect.element(queryByTestId('next')).not.toHaveSelection()
10401040+10411041+// selection that applies from part child, parent text after child and part next.
10421042+range.setStart(getByTestId('child').element().childNodes[0], 3)
10431043+range.setEnd(getByTestId('next').element().childNodes[0], 2)
10441044+await expect.element(queryByTestId('child')).toHaveSelection('ected')
10451045+await expect.element(queryByTestId('parent')).toHaveSelection('ected text')
10461046+await expect.element(queryByTestId('prev')).not.toHaveSelection()
10471047+await expect.element(queryByTestId('next')).toHaveSelection('ne')
10481048+```
+1-1
docs/guide/browser/index.md
···401401402402If your framework is not represented, feel free to create your own package - it is a simple wrapper around the framework renderer and `page.elementLocator` API. We will add a link to it on this page. Make sure it has a name starting with `vitest-browser-`.
403403404404-Besides rendering components and locating elements, you will also need to make assertions. Vitest bundles the [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) library to provide a wide range of DOM assertions out of the box. Read more at the [Assertions API](/guide/browser/assertion-api).
404404+Besides rendering components and locating elements, you will also need to make assertions. Vitest forks the [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) library to provide a wide range of DOM assertions out of the box. Read more at the [Assertions API](/guide/browser/assertion-api).
405405406406```ts
407407import { expect } from 'vitest'
+6-2
docs/guide/browser/locators.md
···5566# Locators
7788-A locator is a representation of an element or a number of elements. Every locator is defined by a string called a selector. Vitest abstracts this selector by providing convenient methods that generate those selectors behind the scenes.
88+A locator is a representation of an element or a number of elements. Every locator is defined by a string called a selector. Vitest abstracts this selector by providing convenient methods that generate them behind the scenes.
991010-The locator API uses a fork of [Playwright's locators](https://playwright.dev/docs/api/class-locator) called [Ivya](https://npmjs.com/ivya). However, Vitest provides this API to every [provider](/guide/browser/config.html#browser-provider).
1010+The locator API uses a fork of [Playwright's locators](https://playwright.dev/docs/api/class-locator) called [Ivya](https://npmjs.com/ivya). However, Vitest provides this API to every [provider](/guide/browser/config.html#browser-provider), not just playwright.
1111+1212+::: tip
1313+This page covers API usage. To better understand locators and their usage, read [Playwright's "Locators" documentation](https://playwright.dev/docs/locators).
1414+:::
11151216## getByRole
1317
···1919export class ProjectBrowser implements IProjectBrowser {
2020 public testerHtml: Promise<string> | string
2121 public testerFilepath: string
2222- public locatorsUrl: string | undefined
23222423 public provider!: BrowserProvider
2524 public vitest: Vitest
+2
packages/browser/src/node/projectParent.ts
···3535 public injectorJs: Promise<string> | string
3636 public errorCatcherUrl: string
3737 public locatorsUrl: string | undefined
3838+ public matchersUrl: string
3839 public stateJs: Promise<string> | string
39404041 public commands: Record<string, BrowserCommand<any>> = {}
···131132 if (builtinProviders.includes(providerName)) {
132133 this.locatorsUrl = join('/@fs/', distRoot, 'locators', `${providerName}.js`)
133134 }
135135+ this.matchersUrl = join('/@fs/', distRoot, 'expect-element.js')
134136 this.stateJs = readFile(
135137 resolve(distRoot, 'state.js'),
136138 'utf-8',
···11import type { Locator } from '@vitest/browser/context'
22import type { ExpectPollOptions } from 'vitest'
33-import * as matchers from '@testing-library/jest-dom/matchers'
43import { chai, expect } from 'vitest'
44+import { matchers } from './expect'
55import { processTimeoutOptions } from './utils'
6677-export async function setupExpectDom(): Promise<void> {
88- expect.extend(matchers)
99- expect.element = <T extends Element | Locator>(elementOrLocator: T, options?: ExpectPollOptions) => {
1010- if (!(elementOrLocator instanceof Element) && !('element' in elementOrLocator)) {
1111- throw new Error(`Invalid element or locator: ${elementOrLocator}. Expected an instance of Element or Locator, received ${typeof elementOrLocator}`)
77+function element<T extends Element | null | Locator>(elementOrLocator: T, options?: ExpectPollOptions): unknown {
88+ if (elementOrLocator != null && !(elementOrLocator instanceof Element) && !('element' in elementOrLocator)) {
99+ throw new Error(`Invalid element or locator: ${elementOrLocator}. Expected an instance of Element or Locator, received ${typeof elementOrLocator}`)
1010+ }
1111+1212+ return expect.poll<Element | null>(function element(this: object) {
1313+ if (elementOrLocator instanceof Element || elementOrLocator == null) {
1414+ return elementOrLocator
1515+ }
1616+ chai.util.flag(this, '_poll.element', true)
1717+1818+ const isNot = chai.util.flag(this, 'negate') as boolean
1919+ const name = chai.util.flag(this, '_name') as string
2020+ // element selector uses prettyDOM under the hood, which is an expensive call
2121+ // that should not be called on each failed locator attempt to avoid memory leak:
2222+ // https://github.com/vitest-dev/vitest/issues/7139
2323+ const isLastPollAttempt = chai.util.flag(this, '_isLastPollAttempt')
2424+ // special case for `toBeInTheDocument` matcher
2525+ if (isNot && name === 'toBeInTheDocument') {
2626+ return elementOrLocator.query()
1227 }
13281414- return expect.poll<Element | null>(function element(this: object) {
1515- if (elementOrLocator instanceof Element || elementOrLocator == null) {
1616- return elementOrLocator
1717- }
1818- chai.util.flag(this, '_poll.element', true)
2929+ if (isLastPollAttempt) {
3030+ return elementOrLocator.element()
3131+ }
19322020- const isNot = chai.util.flag(this, 'negate') as boolean
2121- const name = chai.util.flag(this, '_name') as string
2222- // element selector uses prettyDOM under the hood, which is an expensive call
2323- // that should not be called on each failed locator attempt to avoid memory leak:
2424- // https://github.com/vitest-dev/vitest/issues/7139
2525- const isLastPollAttempt = chai.util.flag(this, '_isLastPollAttempt')
2626- // special case for `toBeInTheDocument` matcher
2727- if (isNot && name === 'toBeInTheDocument') {
2828- return elementOrLocator.query()
2929- }
3333+ const result = elementOrLocator.query()
30343131- if (isLastPollAttempt) {
3232- return elementOrLocator.element()
3333- }
3535+ if (!result) {
3636+ throw new Error(`Cannot find element with locator: ${JSON.stringify(elementOrLocator)}`)
3737+ }
34383535- const result = elementOrLocator.query()
3636-3737- if (!result) {
3838- throw new Error(`Cannot find element with locator: ${JSON.stringify(elementOrLocator)}`)
3939- }
4040-4141- return result
4242- }, processTimeoutOptions(options))
4343- }
3939+ return result
4040+ }, processTimeoutOptions(options))
4441}
4242+4343+expect.extend(matchers)
4444+// Vitest typecheck doesn't pick up this assignment for some reason
4545+Object.assign(expect, { element })
-1
packages/browser/src/client/tester/jest-dom.ts
···11-export type { default } from '@testing-library/jest-dom/matchers'
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { getElementFromUserInput } from './utils'
1919+2020+export default function toBeEmptyDOMElement(
2121+ this: MatcherState,
2222+ actual: Element | Locator,
2323+): ExpectationResult {
2424+ const htmlElement = getElementFromUserInput(actual, toBeEmptyDOMElement, this)
2525+2626+ return {
2727+ pass: isEmptyElement(htmlElement),
2828+ message: () => {
2929+ return [
3030+ this.utils.matcherHint(
3131+ `${this.isNot ? '.not' : ''}.toBeEmptyDOMElement`,
3232+ 'element',
3333+ '',
3434+ ),
3535+ '',
3636+ 'Received:',
3737+ ` ${this.utils.printReceived(htmlElement.innerHTML)}`,
3838+ ].join('\n')
3939+ },
4040+ }
4141+}
4242+4343+/**
4444+ * Identifies if an element doesn't contain child nodes (excluding comments)
4545+ */
4646+function isEmptyElement(element: HTMLElement | SVGElement): boolean {
4747+ const nonCommentChildNodes = [...element.childNodes].filter(node => node.nodeType !== Node.COMMENT_NODE)
4848+ return nonCommentChildNodes.length === 0
4949+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { getElementFromUserInput } from './utils'
1919+2020+export default function toBeInTheDocument(
2121+ this: MatcherState,
2222+ actual: Element | Locator | null,
2323+): ExpectationResult {
2424+ let htmlElement: null | HTMLElement | SVGElement = null
2525+2626+ if (actual !== null || !this.isNot) {
2727+ htmlElement = getElementFromUserInput(actual, toBeInTheDocument, this)
2828+ }
2929+3030+ const pass
3131+ = htmlElement === null
3232+ ? false
3333+ : htmlElement.ownerDocument === htmlElement.getRootNode({ composed: true })
3434+3535+ const errorFound = () => {
3636+ return `expected document not to contain element, found ${this.utils.stringify(
3737+ htmlElement?.cloneNode(true),
3838+ )} instead`
3939+ }
4040+ const errorNotFound = () => {
4141+ return `element could not be found in the document`
4242+ }
4343+4444+ return {
4545+ pass,
4646+ message: () => {
4747+ return [
4848+ this.utils.matcherHint(
4949+ `${this.isNot ? '.not' : ''}.toBeInTheDocument`,
5050+ 'element',
5151+ '',
5252+ ),
5353+ '',
5454+5555+ this.utils.RECEIVED_COLOR(this.isNot ? errorFound() : errorNotFound()),
5656+ ].join('\n')
5757+ },
5858+ }
5959+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { getAriaChecked as ivyaGetAriaChecked } from 'ivya/utils'
1919+import { getElementFromUserInput, isInputElement } from './utils'
2020+2121+export default function toBePartiallyChecked(
2222+ this: MatcherState,
2323+ actual: Element | Locator,
2424+): ExpectationResult {
2525+ const htmlElement = getElementFromUserInput(actual, toBePartiallyChecked, this)
2626+2727+ const isValidInput = () => {
2828+ return (
2929+ isInputElement(htmlElement) && htmlElement.type === 'checkbox'
3030+ )
3131+ }
3232+3333+ const isValidAriaElement = () => {
3434+ return htmlElement.getAttribute('role') === 'checkbox'
3535+ }
3636+3737+ if (!isValidInput() && !isValidAriaElement()) {
3838+ return {
3939+ pass: false,
4040+ message: () =>
4141+ 'only inputs with type="checkbox" or elements with role="checkbox" and a valid aria-checked attribute can be used with .toBePartiallyChecked(). Use .toHaveValue() instead',
4242+ }
4343+ }
4444+4545+ const isPartiallyChecked = isAriaMixed(htmlElement)
4646+4747+ return {
4848+ pass: isPartiallyChecked,
4949+ message: () => {
5050+ const is = isPartiallyChecked ? 'is' : 'is not'
5151+ return [
5252+ this.utils.matcherHint(
5353+ `${this.isNot ? '.not' : ''}.toBePartiallyChecked`,
5454+ 'element',
5555+ '',
5656+ ),
5757+ '',
5858+ `Received element ${is} partially checked:`,
5959+ ` ${this.utils.printReceived(htmlElement.cloneNode(false))}`,
6060+ ].join('\n')
6161+ },
6262+ }
6363+}
6464+6565+function isAriaMixed(element: HTMLElement | SVGElement): boolean {
6666+ const isMixed = ivyaGetAriaChecked(element) === 'mixed'
6767+ if (!isMixed) {
6868+ // playwright only looks at aria-checked if element is not a checkbox/radio
6969+ if (
7070+ isInputElement(element)
7171+ && ['checkbox', 'radio'].includes((element as HTMLInputElement).type)
7272+ ) {
7373+ const ariaValue = element.getAttribute('aria-checked')
7474+ if (ariaValue === 'mixed') {
7575+ return true
7676+ }
7777+ }
7878+ }
7979+ return isMixed
8080+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { server } from '@vitest/browser/context'
1919+import { beginAriaCaches, endAriaCaches, isElementVisible as ivyaIsVisible } from 'ivya/utils'
2020+import { getElementFromUserInput } from './utils'
2121+2222+export default function toBeVisible(
2323+ this: MatcherState,
2424+ actual: Element | Locator,
2525+): ExpectationResult {
2626+ const htmlElement = getElementFromUserInput(actual, toBeVisible, this)
2727+ const isInDocument
2828+ = htmlElement.ownerDocument === htmlElement.getRootNode({ composed: true })
2929+ beginAriaCaches()
3030+ const isVisible = isInDocument && isElementVisible(htmlElement)
3131+ endAriaCaches()
3232+ return {
3333+ pass: isVisible,
3434+ message: () => {
3535+ const is = isVisible ? 'is' : 'is not'
3636+ return [
3737+ this.utils.matcherHint(
3838+ `${this.isNot ? '.not' : ''}.toBeVisible`,
3939+ 'element',
4040+ '',
4141+ ),
4242+ '',
4343+ `Received element ${is} visible${
4444+ isInDocument ? '' : ' (element is not in the document)'
4545+ }:`,
4646+ ` ${this.utils.printReceived(htmlElement.cloneNode(false))}`,
4747+ ].join('\n')
4848+ },
4949+ }
5050+}
5151+5252+function isElementVisible(element: HTMLElement | SVGElement): boolean {
5353+ const isIvyaVisible = ivyaIsVisible(element)
5454+ // if it's visible or not, but we are not in webkit, respect the result
5555+ if (server.browser !== 'webkit') {
5656+ return isIvyaVisible
5757+ }
5858+ // if we are in webkit and it's not visible, fallback to jest-dom check
5959+ // because ivya doesn't use .checkVisibility here
6060+ const detailsElement = element.closest('details')
6161+ if (!detailsElement || element === detailsElement) {
6262+ return isIvyaVisible
6363+ }
6464+ return isElementVisibleInDetails(element as HTMLElement)
6565+}
6666+6767+function isElementVisibleInDetails(targetElement: HTMLElement) {
6868+ let currentElement: HTMLElement | null = targetElement
6969+7070+ while (currentElement) {
7171+ if (currentElement.tagName === 'DETAILS') {
7272+ const isSummary = currentElement.querySelector('summary') === targetElement
7373+ if (!(currentElement as HTMLDetailsElement).open && !isSummary) {
7474+ return false
7575+ }
7676+ }
7777+ currentElement = currentElement.parentElement
7878+ }
7979+8080+ return targetElement.offsetParent !== null
8181+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { getElementFromUserInput } from './utils'
1919+2020+export default function toContainElement(
2121+ this: MatcherState,
2222+ actual: Element | Locator,
2323+ expectedElement: Element | Locator | null,
2424+): ExpectationResult {
2525+ const containerElement = getElementFromUserInput(actual, toContainElement, this)
2626+ const childElement = expectedElement !== null
2727+ ? getElementFromUserInput(expectedElement, toContainElement, this)
2828+ : null
2929+3030+ return {
3131+ pass: containerElement.contains(childElement),
3232+ message: () => {
3333+ return [
3434+ this.utils.matcherHint(
3535+ `${this.isNot ? '.not' : ''}.toContainElement`,
3636+ 'element',
3737+ 'element',
3838+ ),
3939+ '',
4040+4141+ this.utils.RECEIVED_COLOR(`${this.utils.stringify(
4242+ containerElement.cloneNode(false),
4343+ )} ${
4444+ this.isNot ? 'contains:' : 'does not contain:'
4545+ } ${this.utils.stringify(childElement ? childElement.cloneNode(false) : null)}
4646+ `),
4747+ ].join('\n')
4848+ },
4949+ }
5050+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { getElementFromUserInput } from './utils'
1919+2020+function getNormalizedHtml(container: HTMLElement | SVGElement, htmlText: string) {
2121+ const div = container.ownerDocument.createElement('div')
2222+ div.innerHTML = htmlText
2323+ return div.innerHTML
2424+}
2525+2626+export default function toContainHTML(
2727+ this: MatcherState,
2828+ actual: Element | Locator,
2929+ htmlText: string,
3030+): ExpectationResult {
3131+ const htmlElement = getElementFromUserInput(actual, toContainHTML, this)
3232+3333+ if (typeof htmlText !== 'string') {
3434+ throw new TypeError(`.toContainHTML() expects a string value, got ${htmlText}`)
3535+ }
3636+3737+ return {
3838+ pass: htmlElement.outerHTML.includes(getNormalizedHtml(htmlElement, htmlText)),
3939+ message: () => {
4040+ return [
4141+ this.utils.matcherHint(
4242+ `${this.isNot ? '.not' : ''}.toContainHTML`,
4343+ 'element',
4444+ '',
4545+ ),
4646+ 'Expected:',
4747+ ` ${this.utils.EXPECTED_COLOR(htmlText)}`,
4848+ 'Received:',
4949+ ` ${this.utils.printReceived(htmlElement.cloneNode(true))}`,
5050+ ].join('\n')
5151+ },
5252+ }
5353+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { getElementAccessibleDescription } from 'ivya/utils'
1919+import { getElementFromUserInput, getMessage } from './utils'
2020+2121+export default function toHaveAccessibleDescription(
2222+ this: MatcherState,
2323+ actual: Element | Locator,
2424+ expectedAccessibleDescription?: string | RegExp,
2525+): ExpectationResult {
2626+ const htmlElement = getElementFromUserInput(actual, toHaveAccessibleDescription, this)
2727+ const actualAccessibleDescription = getElementAccessibleDescription(htmlElement, false)
2828+2929+ const missingExpectedValue = arguments.length === 1
3030+3131+ let pass = false
3232+ if (missingExpectedValue) {
3333+ // When called without an expected value we only want to validate that the element has an
3434+ // accessible description, whatever it may be.
3535+ pass = actualAccessibleDescription !== ''
3636+ }
3737+ else {
3838+ pass
3939+ = expectedAccessibleDescription instanceof RegExp
4040+ ? expectedAccessibleDescription.test(actualAccessibleDescription)
4141+ : this.equals(
4242+ actualAccessibleDescription,
4343+ expectedAccessibleDescription,
4444+ this.customTesters,
4545+ )
4646+ }
4747+4848+ return {
4949+ pass,
5050+5151+ message: () => {
5252+ const to = this.isNot ? 'not to' : 'to'
5353+ return getMessage(
5454+ this,
5555+ this.utils.matcherHint(
5656+ `${this.isNot ? '.not' : ''}.toHaveAccessibleDescription`,
5757+ 'element',
5858+ '',
5959+ ),
6060+ `Expected element ${to} have accessible description`,
6161+ expectedAccessibleDescription,
6262+ 'Received',
6363+ actualAccessibleDescription,
6464+ )
6565+ },
6666+ }
6767+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { getElementAccessibleErrorMessage } from 'ivya/utils'
1919+import { getElementFromUserInput, getMessage, redent } from './utils'
2020+2121+export default function toHaveAccessibleErrorMessage(
2222+ this: MatcherState,
2323+ actual: Element | Locator,
2424+ expectedAccessibleErrorMessage?: string | RegExp,
2525+): ExpectationResult {
2626+ const htmlElement = getElementFromUserInput(actual, toHaveAccessibleErrorMessage, this)
2727+ const actualAccessibleErrorMessage = getElementAccessibleErrorMessage(htmlElement) ?? ''
2828+2929+ const missingExpectedValue = arguments.length === 1
3030+3131+ let pass = false
3232+ if (missingExpectedValue) {
3333+ // When called without an expected value we only want to validate that the element has an
3434+ // accessible description, whatever it may be.
3535+ pass = actualAccessibleErrorMessage !== ''
3636+ }
3737+ else {
3838+ pass
3939+ = expectedAccessibleErrorMessage instanceof RegExp
4040+ ? expectedAccessibleErrorMessage.test(actualAccessibleErrorMessage)
4141+ : this.equals(
4242+ actualAccessibleErrorMessage,
4343+ expectedAccessibleErrorMessage,
4444+ this.customTesters,
4545+ )
4646+ }
4747+4848+ return {
4949+ pass,
5050+5151+ message: () => {
5252+ const to = this.isNot ? 'not to' : 'to'
5353+ if (expectedAccessibleErrorMessage == null) {
5454+ return [
5555+ this.utils.matcherHint(
5656+ `${this.isNot ? '.not' : ''}.toHaveAccessibleErrorMessage`,
5757+ 'element',
5858+ '',
5959+ ),
6060+ `Expected element ${to} have accessible error message, but got${!this.isNot ? ' nothing' : ''}`,
6161+ this.isNot ? this.utils.RECEIVED_COLOR(redent(actualAccessibleErrorMessage, 2)) : '',
6262+ ].filter(Boolean).join('\n\n')
6363+ }
6464+ return getMessage(
6565+ this,
6666+ this.utils.matcherHint(
6767+ `${this.isNot ? '.not' : ''}.toHaveAccessibleErrorMessage`,
6868+ 'element',
6969+ '',
7070+ ),
7171+ `Expected element ${to} have accessible error message`,
7272+ expectedAccessibleErrorMessage,
7373+ 'Received',
7474+ actualAccessibleErrorMessage,
7575+ )
7676+ },
7777+ }
7878+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { getElementAccessibleName } from 'ivya/utils'
1919+import { getElementFromUserInput, getMessage } from './utils'
2020+2121+export default function toHaveAccessibleName(
2222+ this: MatcherState,
2323+ actual: Element | Locator,
2424+ expectedAccessibleName?: string | RegExp,
2525+): ExpectationResult {
2626+ const htmlElement = getElementFromUserInput(actual, toHaveAccessibleName, this)
2727+ const actualAccessibleName = getElementAccessibleName(htmlElement, false)
2828+ const missingExpectedValue = arguments.length === 1
2929+3030+ let pass = false
3131+ if (missingExpectedValue) {
3232+ // When called without an expected value we only want to validate that the element has an
3333+ // accessible name, whatever it may be.
3434+ pass = actualAccessibleName !== ''
3535+ }
3636+ else {
3737+ pass
3838+ = expectedAccessibleName instanceof RegExp
3939+ ? expectedAccessibleName.test(actualAccessibleName)
4040+ : this.equals(actualAccessibleName, expectedAccessibleName, this.customTesters)
4141+ }
4242+4343+ return {
4444+ pass,
4545+4646+ message: () => {
4747+ const to = this.isNot ? 'not to' : 'to'
4848+ return getMessage(
4949+ this,
5050+ this.utils.matcherHint(
5151+ `${this.isNot ? '.not' : ''}.${toHaveAccessibleName.name}`,
5252+ 'element',
5353+ '',
5454+ ),
5555+ `Expected element ${to} have accessible name`,
5656+ expectedAccessibleName,
5757+ 'Received',
5858+ actualAccessibleName,
5959+ )
6060+ },
6161+ }
6262+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { getElementFromUserInput } from './utils'
1919+2020+export default function toHaveFocus(
2121+ this: MatcherState,
2222+ actual: Element | Locator,
2323+): ExpectationResult {
2424+ const htmlElement = getElementFromUserInput(actual, toHaveFocus, this)
2525+2626+ return {
2727+ pass: htmlElement.ownerDocument.activeElement === htmlElement,
2828+ message: () => {
2929+ return [
3030+ this.utils.matcherHint(
3131+ `${this.isNot ? '.not' : ''}.toHaveFocus`,
3232+ 'element',
3333+ '',
3434+ ),
3535+ '',
3636+ ...(this.isNot
3737+ ? [
3838+ 'Received element is focused:',
3939+ ` ${this.utils.printReceived(htmlElement)}`,
4040+ ]
4141+ : [
4242+ 'Expected element with focus:',
4343+ ` ${this.utils.printExpected(htmlElement)}`,
4444+ 'Received element with focus:',
4545+ ` ${this.utils.printReceived(
4646+ htmlElement.ownerDocument.activeElement,
4747+ )}`,
4848+ ]),
4949+ ].join('\n')
5050+ },
5151+ }
5252+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { cssEscape } from 'ivya/utils'
1919+import { arrayAsSetComparison, getElementFromUserInput, getSingleElementValue, getTag } from './utils'
2020+2121+export default function toHaveFormValues(
2222+ this: MatcherState,
2323+ actual: Element | Locator,
2424+ expectedValues: Record<string, unknown>,
2525+): ExpectationResult {
2626+ const formElement = getElementFromUserInput(actual, toHaveFormValues, this)
2727+2828+ if (!(formElement instanceof HTMLFieldSetElement) && !(formElement instanceof HTMLFormElement)) {
2929+ throw new TypeError(`toHaveFormValues must be called on a form or a fieldset, instead got ${getTag(formElement)}`)
3030+ }
3131+3232+ if (!expectedValues || typeof expectedValues !== 'object') {
3333+ throw new TypeError(
3434+ `toHaveFormValues must be called with an object of expected form values. Got ${expectedValues}`,
3535+ )
3636+ }
3737+3838+ const formValues = getAllFormValues(formElement)
3939+ return {
4040+ pass: Object.entries(expectedValues).every(([name, expectedValue]) =>
4141+ this.equals(formValues[name], expectedValue, [arrayAsSetComparison, ...this.customTesters]),
4242+ ),
4343+ message: () => {
4444+ const to = this.isNot ? 'not to' : 'to'
4545+ const matcher = `${this.isNot ? '.not' : ''}.toHaveFormValues`
4646+4747+ const commonKeyValues: Record<string, unknown> = {}
4848+ for (const key in formValues) {
4949+ if (!Object.hasOwn(expectedValues, key)) {
5050+ continue
5151+ }
5252+ commonKeyValues[key] = formValues[key]
5353+ }
5454+5555+ return [
5656+ this.utils.matcherHint(matcher, 'element', ''),
5757+ `Expected the element ${to} have form values`,
5858+ this.utils.diff(expectedValues, commonKeyValues),
5959+ ].join('\n\n')
6060+ },
6161+ }
6262+}
6363+6464+// Returns the combined value of several elements that have the same name
6565+// e.g. radio buttons or groups of checkboxes
6666+function getMultiElementValue(elements: HTMLInputElement[]) {
6767+ const types = [...new Set(elements.map(element => element.type))]
6868+ if (types.length !== 1) {
6969+ throw new Error(
7070+ 'Multiple form elements with the same name must be of the same type',
7171+ )
7272+ }
7373+ switch (types[0]) {
7474+ case 'radio': {
7575+ const selected = elements.find(radio => radio.checked)
7676+ return selected ? selected.value : undefined
7777+ }
7878+ case 'checkbox':
7979+ return elements
8080+ .filter(checkbox => checkbox.checked)
8181+ .map(checkbox => checkbox.value)
8282+ default:
8383+ // NOTE: Not even sure this is a valid use case, but just in case...
8484+ return elements.map(element => element.value)
8585+ }
8686+}
8787+8888+function getFormValue(container: HTMLFormElement | HTMLFieldSetElement, name: string) {
8989+ const elements = [...container.querySelectorAll(`[name="${cssEscape(name)}"]`)]
9090+ /* istanbul ignore if */
9191+ if (elements.length === 0) {
9292+ return undefined // shouldn't happen, but just in case
9393+ }
9494+ switch (elements.length) {
9595+ case 1:
9696+ return getSingleElementValue(elements[0])
9797+ default:
9898+ return getMultiElementValue(elements as HTMLInputElement[])
9999+ }
100100+}
101101+102102+// Strips the `[]` suffix off a form value name
103103+function getPureName(name: string) {
104104+ return /\[\]$/.test(name) ? name.slice(0, -2) : name
105105+}
106106+107107+function getAllFormValues(container: HTMLFormElement | HTMLFieldSetElement) {
108108+ const values: Record<string, unknown> = {}
109109+110110+ for (const element of container.elements) {
111111+ if (!('name' in element)) {
112112+ continue
113113+ }
114114+ const name = element.name as string
115115+ values[getPureName(name)] = getFormValue(container, name)
116116+ }
117117+118118+ return values
119119+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { beginAriaCaches, endAriaCaches, getAriaRole } from 'ivya/utils'
1919+import { getElementFromUserInput, getMessage } from './utils'
2020+2121+export default function toHaveRole(
2222+ this: MatcherState,
2323+ actual: Element | Locator,
2424+ expectedRole: string,
2525+): ExpectationResult {
2626+ const htmlElement = getElementFromUserInput(actual, toHaveRole, this)
2727+ beginAriaCaches()
2828+ const actualRole = getAriaRole(htmlElement)
2929+ endAriaCaches()
3030+ return {
3131+ pass: actualRole === expectedRole,
3232+ message: () => {
3333+ const to = this.isNot ? 'not to' : 'to'
3434+ return getMessage(
3535+ this,
3636+ this.utils.matcherHint(
3737+ `${this.isNot ? '.not' : ''}.toHaveRole`,
3838+ 'element',
3939+ '',
4040+ ),
4141+ `Expected element ${to} have role`,
4242+ expectedRole,
4343+ 'Received',
4444+ actualRole,
4545+ )
4646+ },
4747+ }
4848+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { getMessage, getNodeFromUserInput, matches, normalize } from './utils'
1919+2020+export default function toHaveTextContent(
2121+ this: MatcherState,
2222+ actual: Element | Locator,
2323+ matcher: string | RegExp,
2424+ options: { normalizeWhitespace?: boolean } = { normalizeWhitespace: true },
2525+): ExpectationResult {
2626+ const node = getNodeFromUserInput(actual, toHaveTextContent, this)
2727+2828+ const textContent = options.normalizeWhitespace
2929+ ? normalize(node.textContent || '')
3030+ : (node.textContent || '').replace(/\u00A0/g, ' ') // Replace with normal spaces
3131+3232+ const checkingWithEmptyString = textContent !== '' && matcher === ''
3333+3434+ return {
3535+ pass: !checkingWithEmptyString && matches(textContent, matcher),
3636+ message: () => {
3737+ const to = this.isNot ? 'not to' : 'to'
3838+ return getMessage(
3939+ this,
4040+ this.utils.matcherHint(
4141+ `${this.isNot ? '.not' : ''}.toHaveTextContent`,
4242+ 'element',
4343+ '',
4444+ ),
4545+ checkingWithEmptyString
4646+ ? `Checking with empty string will always match, use .toBeEmptyDOMElement() instead`
4747+ : `Expected element ${to} have text content`,
4848+ matcher,
4949+ 'Received',
5050+ textContent,
5151+ )
5252+ },
5353+ }
5454+}
···11+/**
22+ * The MIT License (MIT)
33+ * Copyright (c) 2017 Kent C. Dodds
44+ *
55+ * Permission is hereby granted, free of charge, to any person obtaining a copy
66+ * of this software and associated documentation files (the "Software"), to deal
77+ * in the Software without restriction, including without limitation the rights
88+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99+ * copies of the Software, and to permit persons to whom the Software is
1010+ * furnished to do so, subject to the following conditions:
1111+ *
1212+ * The above copyright notice and this permission notice shall be included in all
1313+ * copies or substantial portions of the Software.
1414+ */
1515+1616+import type { ExpectationResult, MatcherState } from '@vitest/expect'
1717+import type { Locator } from '../locators'
1818+import { arrayAsSetComparison, getElementFromUserInput, getMessage, getSingleElementValue, isInputElement } from './utils'
1919+2020+export default function toHaveValue(
2121+ this: MatcherState,
2222+ actual: Element | Locator,
2323+ expectedValue?: string,
2424+): ExpectationResult {
2525+ const htmlElement = getElementFromUserInput(actual, toHaveValue, this)
2626+2727+ if (
2828+ isInputElement(htmlElement)
2929+ && ['checkbox', 'radio'].includes(htmlElement.type)
3030+ ) {
3131+ throw new Error(
3232+ 'input with type=checkbox or type=radio cannot be used with .toHaveValue(). Use .toBeChecked() for type=checkbox or .toHaveFormValues() instead',
3333+ )
3434+ }
3535+3636+ const receivedValue = getSingleElementValue(htmlElement)
3737+ const expectsValue = expectedValue !== undefined
3838+3939+ let expectedTypedValue = expectedValue
4040+ let receivedTypedValue = receivedValue
4141+ // eslint-disable-next-line eqeqeq
4242+ if (expectedValue == receivedValue && expectedValue !== receivedValue) {
4343+ expectedTypedValue = `${expectedValue} (${typeof expectedValue})`
4444+ receivedTypedValue = `${receivedValue} (${typeof receivedValue})`
4545+ }
4646+4747+ return {
4848+ pass: expectsValue
4949+ ? this.equals(receivedValue, expectedValue, [arrayAsSetComparison, ...this.customTesters])
5050+ : Boolean(receivedValue),
5151+ message: () => {
5252+ const to = this.isNot ? 'not to' : 'to'
5353+ const matcher = this.utils.matcherHint(
5454+ `${this.isNot ? '.not' : ''}.toHaveValue`,
5555+ 'element',
5656+ expectedValue,
5757+ )
5858+ return getMessage(
5959+ this,
6060+ matcher,
6161+ `Expected the element ${to} have value`,
6262+ expectsValue ? expectedTypedValue : '(any)',
6363+ 'Received',
6464+ receivedTypedValue,
6565+ )
6666+ },
6767+ }
6868+}