diff --git a/docs/guide/browser.md b/docs/guide/browser.md index 7ce49d808e160..a917a3f4b4aa7 100644 --- a/docs/guide/browser.md +++ b/docs/guide/browser.md @@ -259,6 +259,22 @@ export const userEvent: { * @see {@link https://testing-library.com/docs/user-event/convenience/#tab} testing-library API */ tab: (options?: UserEventTabOptions) => Promise + /** + * Hovers over an element. Uses provider's API under the hood. + * @see {@link https://playwright.dev/docs/api/class-locator#locator-hover} Playwright API + * @see {@link https://webdriver.io/docs/api/element/moveTo/} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/convenience/#hover} testing-library API + */ + hover: (element: Element, options?: UserEventHoverOptions) => Promise + /** + * Moves cursor position to the body element. Uses provider's API under the hood. + * By default, the cursor position is in the center (in webdriverio) or in some visible place (in playwright) + * of the body element, so if the current element is already there, this will have no effect. + * @see {@link https://playwright.dev/docs/api/class-locator#locator-hover} Playwright API + * @see {@link https://webdriver.io/docs/api/element/moveTo/} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/convenience/#hover} testing-library API + */ + unhover: (element: Element, options?: UserEventHoverOptions) => Promise /** * Fills an input element with text. This will remove any existing text in the input before typing the new text. * Uses provider's API under the hood. @@ -271,7 +287,7 @@ export const userEvent: { * @see {@link https://webdriver.io/docs/api/element/setValue} WebdriverIO API * @see {@link https://testing-library.com/docs/user-event/utility/#type} testing-library API */ - fill: (element: Element, text: string) => Promise + fill: (element: Element, text: string, options?: UserEventFillOptions) => Promise } /** @@ -415,7 +431,7 @@ export const myCommand = defineCommand(async (ctx, arg1, arg2) => { ``` ::: tip -If you are using TypeScript, don't forget to add `@vitest/browser/providers/playwright` to your `tsconfig` "compilerOptions.types" field to get autocompletion: +If you are using TypeScript, don't forget to add `@vitest/browser/providers/playwright` to your `tsconfig` "compilerOptions.types" field to get autocompletion in the config and on `userEvent` and `page` options: ```json { diff --git a/packages/browser/context.d.ts b/packages/browser/context.d.ts index 5c4a20420cffb..31a806d15a8b0 100644 --- a/packages/browser/context.d.ts +++ b/packages/browser/context.d.ts @@ -81,6 +81,22 @@ export interface UserEvent { * @see {@link https://testing-library.com/docs/user-event/convenience/#tab} testing-library API */ tab: (options?: UserEventTabOptions) => Promise + /** + * Hovers over an element. Uses provider's API under the hood. + * @see {@link https://playwright.dev/docs/api/class-locator#locator-hover} Playwright API + * @see {@link https://webdriver.io/docs/api/element/moveTo/} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/convenience/#hover} testing-library API + */ + hover: (element: Element, options?: UserEventHoverOptions) => Promise + /** + * Moves cursor position to the body element. Uses provider's API under the hood. + * By default, the cursor position is in the center (in webdriverio) or in some visible place (in playwright) + * of the body element, so if the current element is already there, this will have no effect. + * @see {@link https://playwright.dev/docs/api/class-locator#locator-hover} Playwright API + * @see {@link https://webdriver.io/docs/api/element/moveTo/} WebdriverIO API + * @see {@link https://testing-library.com/docs/user-event/convenience/#hover} testing-library API + */ + unhover: (element: Element, options?: UserEventHoverOptions) => Promise /** * Fills an input element with text. This will remove any existing text in the input before typing the new text. * Uses provider's API under the hood. @@ -93,22 +109,20 @@ export interface UserEvent { * @see {@link https://webdriver.io/docs/api/element/setValue} WebdriverIO API * @see {@link https://testing-library.com/docs/user-event/utility/#type} testing-library API */ - fill: (element: Element, text: string) => Promise + fill: (element: Element, text: string, options?: UserEventFillOptions) => Promise } -export interface UserEventClickOptions { - [key: string]: any -} +export interface UserEventFillOptions {} +export interface UserEventHoverOptions {} +export interface UserEventClickOptions {} export interface UserEventTabOptions { shift?: boolean - [key: string]: any } export interface UserEventTypeOptions { skipClick?: boolean skipAutoClose?: boolean - [key: string]: any } type Platform = diff --git a/packages/browser/providers/playwright.d.ts b/packages/browser/providers/playwright.d.ts index 0c34728cf9b6f..ecdd1e0abb06d 100644 --- a/packages/browser/providers/playwright.d.ts +++ b/packages/browser/providers/playwright.d.ts @@ -18,3 +18,15 @@ declare module 'vitest/node' { context: BrowserContext } } + +type PWHoverOptions = Parameters[1] +type PWClickOptions = Parameters[1] +type PWFillOptions = Parameters[2] +type PWScreenshotOptions = Parameters[0] + +declare module '@vitest/browser/context' { + export interface UserEventHoverOptions extends PWHoverOptions {} + export interface UserEventClickOptions extends PWClickOptions {} + export interface UserEventFillOptions extends PWFillOptions {} + export interface ScreenshotOptions extends PWScreenshotOptions {} +} diff --git a/packages/browser/src/client/context.ts b/packages/browser/src/client/context.ts index 2f05fb1b9cd53..52f441f262e3b 100644 --- a/packages/browser/src/client/context.ts +++ b/packages/browser/src/client/context.ts @@ -61,9 +61,9 @@ export const userEvent: UserEvent = { const xpath = convertElementToXPath(element) return triggerCommand('__vitest_clear', xpath) }, - fill(element: Element, text: string) { + fill(element: Element, text: string, options) { const xpath = convertElementToXPath(element) - return triggerCommand('__vitest_fill', xpath, text) + return triggerCommand('__vitest_fill', xpath, text, options) }, tab(options: UserEventTabOptions = {}) { return triggerCommand('__vitest_tab', options) @@ -71,6 +71,14 @@ export const userEvent: UserEvent = { keyboard(text: string) { return triggerCommand('__vitest_keyboard', text) }, + hover(element: Element) { + const xpath = convertElementToXPath(element) + return triggerCommand('__vitest_hover', xpath) + }, + unhover(element: Element) { + const xpath = convertElementToXPath(element.ownerDocument.body) + return triggerCommand('__vitest_hover', xpath) + }, } const screenshotIds: Record> = {} diff --git a/packages/browser/src/node/commands/fill.ts b/packages/browser/src/node/commands/fill.ts index 25ad56bbeead8..935720e337518 100644 --- a/packages/browser/src/node/commands/fill.ts +++ b/packages/browser/src/node/commands/fill.ts @@ -7,11 +7,12 @@ export const fill: UserEventCommand = async ( context, xpath, text, + options = {}, ) => { if (context.provider instanceof PlaywrightBrowserProvider) { const { frame } = context const element = frame.locator(`xpath=${xpath}`) - await element.fill(text) + await element.fill(text, options) } else if (context.provider instanceof WebdriverBrowserProvider) { const browser = context.browser diff --git a/packages/browser/src/node/commands/hover.ts b/packages/browser/src/node/commands/hover.ts new file mode 100644 index 0000000000000..44bc99812b4aa --- /dev/null +++ b/packages/browser/src/node/commands/hover.ts @@ -0,0 +1,23 @@ +import type { UserEvent } from '../../../context' +import { PlaywrightBrowserProvider } from '../providers/playwright' +import { WebdriverBrowserProvider } from '../providers/webdriver' +import type { UserEventCommand } from './utils' + +export const hover: UserEventCommand = async ( + context, + xpath, + options = {}, +) => { + if (context.provider instanceof PlaywrightBrowserProvider) { + await context.frame.locator(`xpath=${xpath}`).hover(options) + } + else if (context.provider instanceof WebdriverBrowserProvider) { + const browser = context.browser + const markedXpath = `//${xpath}` + const element = await browser.$(markedXpath) + await element.moveTo(options) + } + else { + throw new TypeError(`Provider "${context.provider.name}" does not support hover`) + } +}