Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(browser): fail playwright timeouts earlier than a test timeout #7565

Merged
merged 14 commits into from
Mar 7, 2025
Merged
Next Next commit
fix(browser): fail playwright timeouts earlier than a test timeout
  • Loading branch information
sheremet-va committed Feb 26, 2025
commit 358deef0f6c0b7ac774310221992ef4abb4d4bad
3 changes: 2 additions & 1 deletion docs/guide/browser/interactivity-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ References:
## userEvent.clear

```ts
function clear(element: Element | Locator): Promise<void>
function clear(element: Element | Locator, options?: UserEventClearOptions): Promise<void>
```

This method clears the input element content.
Expand Down Expand Up @@ -451,6 +451,7 @@ References:
function upload(
element: Element | Locator,
files: string[] | string | File[] | File,
options?: UserEventUploadOptions,
): Promise<void>
```

Expand Down
4 changes: 2 additions & 2 deletions docs/guide/browser/locators.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Locators | Browser Mode
outline: [2, 3]
---

# Locators <Version>2.1.0</Version>
# Locators

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.

Expand Down Expand Up @@ -505,7 +505,7 @@ await page.getByRole('img', { name: 'Rose' }).tripleClick()
### clear

```ts
function clear(): Promise<void>
function clear(options?: UserEventClearOptions): Promise<void>
```

Clears the input element content.
Expand Down
10 changes: 6 additions & 4 deletions packages/browser/context.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export interface UserEvent {
* @see {@link https://webdriver.io/docs/api/element/clearValue} WebdriverIO API
* @see {@link https://testing-library.com/docs/user-event/utility/#clear} testing-library API
*/
clear: (element: Element | Locator) => Promise<void>
clear: (element: Element | Locator, options?: UserEventClearOptions) => Promise<void>
/**
* Sends a `Tab` key event. Uses provider's API under the hood.
* @see {@link https://playwright.dev/docs/api/class-keyboard} Playwright API
Expand Down Expand Up @@ -171,7 +171,7 @@ export interface UserEvent {
* @see {@link https://playwright.dev/docs/api/class-locator#locator-set-input-files} Playwright API
* @see {@link https://testing-library.com/docs/user-event/utility#upload} testing-library API
*/
upload: (element: Element | Locator, files: File | File[] | string | string[]) => Promise<void>
upload: (element: Element | Locator, files: File | File[] | string | string[], options?: UserEventUploadOptions) => Promise<void>
/**
* Copies the selected content.
* @see {@link https://playwright.dev/docs/api/class-keyboard} Playwright API
Expand Down Expand Up @@ -218,9 +218,11 @@ export interface UserEventFillOptions {}
export interface UserEventHoverOptions {}
export interface UserEventSelectOptions {}
export interface UserEventClickOptions {}
export interface UserEventClearOptions {}
export interface UserEventDoubleClickOptions {}
export interface UserEventTripleClickOptions {}
export interface UserEventDragAndDropOptions {}
export interface UserEventUploadOptions {}

export interface LocatorOptions {
/**
Expand Down Expand Up @@ -358,7 +360,7 @@ export interface Locator extends LocatorSelectors {
* Clears the input element content
* @see {@link https://vitest.dev/guide/browser/interactivity-api#userevent-clear}
*/
clear(): Promise<void>
clear(options?: UserEventClearOptions): Promise<void>
/**
* Moves the cursor position to the selected element
* @see {@link https://vitest.dev/guide/browser/interactivity-api#userevent-hover}
Expand Down Expand Up @@ -391,7 +393,7 @@ export interface Locator extends LocatorSelectors {
* Change a file input element to have the specified files. Uses provider's API under the hood.
* @see {@link https://vitest.dev/guide/browser/interactivity-api#userevent-upload}
*/
upload(files: File | File[] | string | string[]): Promise<void>
upload(files: File | File[] | string | string[], options?: UserEventUploadOptions): Promise<void>

/**
* Make a screenshot of an element matching the locator.
Expand Down
3 changes: 2 additions & 1 deletion packages/browser/matchers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ declare module 'vitest' {
interface ExpectStatic {
/**
* `expect.element(locator)` is a shorthand for `expect.poll(() => locator.element())`.
* You can set default timeout via `expect.poll.timeout` config.
* You can set default timeout via `expect.poll.timeout` option in the config.
* @see {@link https://vitest.dev/api/expect#poll}
*/
element: <T extends Element | Locator>(element: T, options?: ExpectPollOptions) => PromisifyDomAssertion<Awaited<Element | null>>
}
Expand Down
2 changes: 2 additions & 0 deletions packages/browser/providers/playwright.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type PWFillOptions = NonNullable<Parameters<Page['fill']>[2]>
type PWScreenshotOptions = NonNullable<Parameters<Page['screenshot']>[0]>
type PWSelectOptions = NonNullable<Parameters<Page['selectOption']>[2]>
type PWDragAndDropOptions = NonNullable<Parameters<Page['dragAndDrop']>[2]>
type PWSetInputFiles = NonNullable<Parameters<Page['setInputFiles']>[1]>

declare module '@vitest/browser/context' {
export interface UserEventHoverOptions extends PWHoverOptions {}
Expand All @@ -50,6 +51,7 @@ declare module '@vitest/browser/context' {
export interface UserEventFillOptions extends PWFillOptions {}
export interface UserEventSelectOptions extends PWSelectOptions {}
export interface UserEventDragAndDropOptions extends PWDragAndDropOptions {}
export interface UserEventUploadOptions extends PWSetInputFiles {}

export interface ScreenshotOptions extends PWScreenshotOptions {}

Expand Down
65 changes: 39 additions & 26 deletions packages/browser/src/client/tester/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import type {
UserEventTypeOptions,
} from '../../../context'
import type { BrowserRunnerState } from '../utils'
import { convertElementToCssSelector, ensureAwaited, getBrowserState, getWorkerState } from '../utils'
import { ensureAwaited, getBrowserState, getWorkerState } from '../utils'
import { convertElementToCssSelector, processTimeoutOptions } from './utils'

// this file should not import anything directly, only types and utils

Expand Down Expand Up @@ -57,38 +58,50 @@ export function createUserEvent(__tl_user_event_base__?: TestingLibraryUserEvent
})
},
click(element: Element | Locator, options: UserEventClickOptions = {}) {
return convertToLocator(element).click(processClickOptions(options))
return convertToLocator(element).click(processTimeoutOptions(
processClickOptions(options),
))
},
dblClick(element: Element | Locator, options: UserEventClickOptions = {}) {
return convertToLocator(element).dblClick(processClickOptions(options))
return convertToLocator(element).dblClick(processTimeoutOptions(
processClickOptions(options),
))
},
tripleClick(element: Element | Locator, options: UserEventClickOptions = {}) {
return convertToLocator(element).tripleClick(processClickOptions(options))
return convertToLocator(element).tripleClick(processTimeoutOptions(
processClickOptions(options),
))
},
selectOptions(element, value) {
return convertToLocator(element).selectOptions(value)
selectOptions(element, value, options) {
return convertToLocator(element).selectOptions(value, processTimeoutOptions(
options,
))
},
clear(element: Element | Locator) {
return convertToLocator(element).clear()
clear(element, options) {
return convertToLocator(element).clear(options)
},
hover(element: Element | Locator, options: UserEventHoverOptions = {}) {
return convertToLocator(element).hover(processHoverOptions(options))
hover(element, options: UserEventHoverOptions = {}) {
return convertToLocator(element).hover(processTimeoutOptions(
processHoverOptions(options),
))
},
unhover(element: Element | Locator, options: UserEventHoverOptions = {}) {
return convertToLocator(element).unhover(options)
unhover(element, options: UserEventHoverOptions = {}) {
return convertToLocator(element).unhover(processTimeoutOptions(options))
},
upload(element: Element | Locator, files: string | string[] | File | File[]) {
return convertToLocator(element).upload(files)
upload(element, files: string | string[] | File | File[], options) {
return convertToLocator(element).upload(files, processTimeoutOptions(options))
},

// non userEvent events, but still useful
fill(element: Element | Locator, text: string, options) {
return convertToLocator(element).fill(text, options)
fill(element, text, options) {
return convertToLocator(element).fill(text, processTimeoutOptions(options))
},
dragAndDrop(source: Element | Locator, target: Element | Locator, options = {}) {
dragAndDrop(source, target, options = {}) {
const sourceLocator = convertToLocator(source)
const targetLocator = convertToLocator(target)
return sourceLocator.dropTo(targetLocator, processDragAndDropOptions(options))
return sourceLocator.dropTo(targetLocator, processTimeoutOptions(
processDragAndDropOptions(options),
))
},

// testing-library user-event
Expand Down Expand Up @@ -290,28 +303,28 @@ export const page: BrowserPage = {
}))
},
getByRole() {
throw new Error('Method "getByRole" is not implemented in the current provider.')
throw new Error(`Method "getByRole" is not implemented in the "${provider}" provider.`)
},
getByLabelText() {
throw new Error('Method "getByLabelText" is not implemented in the current provider.')
throw new Error(`Method "getByLabelText" is not implemented in the "${provider}" provider.`)
},
getByTestId() {
throw new Error('Method "getByTestId" is not implemented in the current provider.')
throw new Error(`Method "getByTestId" is not implemented in the "${provider}" provider.`)
},
getByAltText() {
throw new Error('Method "getByAltText" is not implemented in the current provider.')
throw new Error(`Method "getByAltText" is not implemented in the "${provider}" provider.`)
},
getByPlaceholder() {
throw new Error('Method "getByPlaceholder" is not implemented in the current provider.')
throw new Error(`Method "getByPlaceholder" is not implemented in the "${provider}" provider.`)
},
getByText() {
throw new Error('Method "getByText" is not implemented in the current provider.')
throw new Error(`Method "getByText" is not implemented in the "${provider}" provider.`)
},
getByTitle() {
throw new Error('Method "getByTitle" is not implemented in the current provider.')
throw new Error(`Method "getByTitle" is not implemented in the "${provider}" provider.`)
},
elementLocator() {
throw new Error('Method "elementLocator" is not implemented in the current provider.')
throw new Error(`Method "elementLocator" is not implemented in the "${provider}" provider.`)
},
extend(methods) {
for (const key in methods) {
Expand Down
3 changes: 2 additions & 1 deletion packages/browser/src/client/tester/expect-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Locator } from '@vitest/browser/context'
import type { ExpectPollOptions } from 'vitest'
import * as matchers from '@testing-library/jest-dom/matchers'
import { chai, expect } from 'vitest'
import { processTimeoutOptions } from './utils'

export async function setupExpectDom(): Promise<void> {
expect.extend(matchers)
Expand Down Expand Up @@ -38,6 +39,6 @@ export async function setupExpectDom(): Promise<void> {
}

return result
}, options)
}, processTimeoutOptions(options))
}
}
10 changes: 6 additions & 4 deletions packages/browser/src/client/tester/locators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import type {
LocatorByRoleOptions,
LocatorOptions,
LocatorScreenshotOptions,
UserEventClearOptions,
UserEventClickOptions,
UserEventDragAndDropOptions,
UserEventFillOptions,
UserEventHoverOptions,
UserEventUploadOptions,
} from '@vitest/browser/context'
import { page, server } from '@vitest/browser/context'
import {
Expand Down Expand Up @@ -57,8 +59,8 @@ export abstract class Locator {
return this.triggerCommand<void>('__vitest_tripleClick', this.selector, options)
}

public clear(): Promise<void> {
return this.triggerCommand<void>('__vitest_clear', this.selector)
public clear(options?: UserEventClearOptions): Promise<void> {
return this.triggerCommand<void>('__vitest_clear', this.selector, options)
}

public hover(options: UserEventHoverOptions): Promise<void> {
Expand All @@ -73,7 +75,7 @@ export abstract class Locator {
return this.triggerCommand<void>('__vitest_fill', this.selector, text, options)
}

public async upload(files: string | string[] | File | File[]): Promise<void> {
public async upload(files: string | string[] | File | File[], options: UserEventUploadOptions): Promise<void> {
const filesPromise = (Array.isArray(files) ? files : [files]).map(async (file) => {
if (typeof file === 'string') {
return file
Expand All @@ -91,7 +93,7 @@ export abstract class Locator {
base64: bas64String,
}
})
return this.triggerCommand<void>('__vitest_upload', this.selector, await Promise.all(filesPromise))
return this.triggerCommand<void>('__vitest_upload', this.selector, await Promise.all(filesPromise), options)
}

public dropTo(target: Locator, options: UserEventDragAndDropOptions = {}): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/client/tester/locators/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
getByTextSelector,
getByTitleSelector,
} from 'ivya'
import { convertElementToCssSelector } from '../../utils'
import { getElementError } from '../public-utils'
import { convertElementToCssSelector } from '../utils'
import { Locator, selectorEngine } from './index'

page.extend({
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/client/tester/locators/webdriverio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
getByTextSelector,
getByTitleSelector,
} from 'ivya'
import { convertElementToCssSelector } from '../../utils'
import { getElementError } from '../public-utils'
import { convertElementToCssSelector } from '../utils'
import { Locator, selectorEngine } from './index'

page.extend({
Expand Down
3 changes: 2 additions & 1 deletion packages/browser/src/client/tester/tester.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { channel, client, onCancel } from '@vitest/browser/client'
import { page, server, userEvent } from '@vitest/browser/context'
import { collectTests, setupCommonEnv, SpyModule, startCoverageInsideWorker, startTests, stopCoverageInsideWorker } from 'vitest/browser'
import { CommandsManager, executor, getBrowserState, getConfig, getWorkerState } from '../utils'
import { executor, getBrowserState, getConfig, getWorkerState } from '../utils'
import { setupDialogsSpy } from './dialog'
import { setupExpectDom } from './expect-element'
import { setupConsoleLogSpy } from './logger'
import { VitestBrowserClientMocker } from './mocker'
import { createModuleMockerInterceptor } from './msw'
import { createSafeRpc } from './rpc'
import { browserHashMap, initiateRunner } from './runner'
import { CommandsManager } from './utils'

const cleanupSymbol = Symbol.for('vitest:component-cleanup')

Expand Down
Loading
Loading