Skip to content

Commit

Permalink
fix(browser): make userEvent more stable when running in parallel (#5974
Browse files Browse the repository at this point in the history
)
  • Loading branch information
sheremet-va authored Jun 25, 2024
1 parent 49e973c commit 14a217d
Show file tree
Hide file tree
Showing 14 changed files with 50 additions and 31 deletions.
5 changes: 3 additions & 2 deletions docs/guide/browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -953,15 +953,16 @@ Custom functions will override built-in ones if they have the same name.
Vitest exposes several `playwright` specific properties on the command context.

- `page` references the full page that contains the test iframe. This is the orchestrator HTML and you most likely shouldn't touch it to not break things.
- `frame` is the tester [iframe instance](https://playwright.dev/docs/api/class-frame). It has a simillar API to the page, but it doesn't support certain methods.
- `frame` is an async method that will resolve tester [`Frame`](https://playwright.dev/docs/api/class-frame). It has a simillar API to the `page`, but it doesn't support certain methods. If you need to query an element, you should prefer using `context.iframe` instead because it is more stable and faster.
- `iframe` is a [`FrameLocator`](https://playwright.dev/docs/api/class-framelocator) that should be used to query other elements on the page.
- `context` refers to the unique [BrowserContext](https://playwright.dev/docs/api/class-browsercontext).

```ts
import { defineCommand } from '@vitest/browser'

export const myCommand = defineCommand(async (ctx, arg1, arg2) => {
if (ctx.provider.name === 'playwright') {
const element = await ctx.frame.findByRole('alert')
const element = await ctx.iframe.findByRole('alert')
const screenshot = await element.screenshot()
// do something with the screenshot
return difference
Expand Down
4 changes: 3 additions & 1 deletion packages/browser/providers/playwright.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
BrowserContext,
BrowserContextOptions,
Frame,
FrameLocator,
LaunchOptions,
Page,
CDPSession
Expand All @@ -20,7 +21,8 @@ declare module 'vitest/node' {

export interface BrowserCommandContext {
page: Page
frame: Frame
frame(): Promise<Frame>
iframe: FrameLocator
context: BrowserContext
}
}
Expand Down
4 changes: 0 additions & 4 deletions packages/browser/src/node/cdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ export class BrowserServerCDPHandler {
return this.session.send(method, params)
}

detach() {
return this.session.detach()
}

on(event: string, id: string, once = false) {
if (!this.listenerIds[event]) {
this.listenerIds[event] = []
Expand Down
4 changes: 2 additions & 2 deletions packages/browser/src/node/commands/clear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export const clear: UserEventCommand<UserEvent['clear']> = async (
xpath,
) => {
if (context.provider instanceof PlaywrightBrowserProvider) {
const { frame } = context
const element = frame.locator(`xpath=${xpath}`)
const { iframe } = context
const element = iframe.locator(`xpath=${xpath}`)
await element.clear({
timeout: 1000,
})
Expand Down
4 changes: 2 additions & 2 deletions packages/browser/src/node/commands/click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const click: UserEventCommand<UserEvent['click']> = async (
) => {
const provider = context.provider
if (provider instanceof PlaywrightBrowserProvider) {
const tester = context.frame
const tester = context.iframe
await tester.locator(`xpath=${xpath}`).click({
timeout: 1000,
...options,
Expand All @@ -33,7 +33,7 @@ export const dblClick: UserEventCommand<UserEvent['dblClick']> = async (
) => {
const provider = context.provider
if (provider instanceof PlaywrightBrowserProvider) {
const tester = context.frame
const tester = context.iframe
await tester.locator(`xpath=${xpath}`).dblclick(options)
}
else if (provider instanceof WebdriverBrowserProvider) {
Expand Down
3 changes: 2 additions & 1 deletion packages/browser/src/node/commands/dragAndDrop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export const dragAndDrop: UserEventCommand<UserEvent['dragAndDrop']> = async (
options,
) => {
if (context.provider instanceof PlaywrightBrowserProvider) {
await context.frame.dragAndDrop(
const frame = await context.frame()
await frame.dragAndDrop(
`xpath=${source}`,
`xpath=${target}`,
{
Expand Down
4 changes: 2 additions & 2 deletions packages/browser/src/node/commands/fill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export const fill: UserEventCommand<UserEvent['fill']> = async (
options = {},
) => {
if (context.provider instanceof PlaywrightBrowserProvider) {
const { frame } = context
const element = frame.locator(`xpath=${xpath}`)
const { iframe } = context
const element = iframe.locator(`xpath=${xpath}`)
await element.fill(text, { timeout: 1000, ...options })
}
else if (context.provider instanceof WebdriverBrowserProvider) {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/node/commands/hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const hover: UserEventCommand<UserEvent['hover']> = async (
options = {},
) => {
if (context.provider instanceof PlaywrightBrowserProvider) {
await context.frame.locator(`xpath=${xpath}`).hover({
await context.iframe.locator(`xpath=${xpath}`).hover({
timeout: 1000,
...options,
})
Expand Down
6 changes: 4 additions & 2 deletions packages/browser/src/node/commands/keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export const keyboard: UserEventCommand<UserEvent['keyboard']> = async (
}

if (context.provider instanceof PlaywrightBrowserProvider) {
await context.frame.evaluate(focusIframe)
const frame = await context.frame()
await frame.evaluate(focusIframe)
}
else if (context.provider instanceof WebdriverBrowserProvider) {
await context.browser.execute(focusIframe)
Expand All @@ -39,7 +40,8 @@ export const keyboard: UserEventCommand<UserEvent['keyboard']> = async (
}
}
if (context.provider instanceof PlaywrightBrowserProvider) {
await context.frame.evaluate(selectAll)
const frame = await context.frame()
await frame.evaluate(selectAll)
}
else if (context.provider instanceof WebdriverBrowserProvider) {
await context.browser.execute(selectAll)
Expand Down
8 changes: 5 additions & 3 deletions packages/browser/src/node/commands/screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ export const screenshot: BrowserCommand<[string, ScreenshotOptions]> = async (
if (context.provider instanceof PlaywrightBrowserProvider) {
if (options.element) {
const { element: elementXpath, ...config } = options
const iframe = context.frame
const element = iframe.locator(`xpath=${elementXpath}`)
const element = context.iframe.locator(`xpath=${elementXpath}`)
await element.screenshot({ ...config, path: savePath })
}
else {
await context.frame.locator('body').screenshot({ ...options, path: savePath })
await context.iframe.locator('body').screenshot({
...options,
path: savePath,
})
}
return path
}
Expand Down
6 changes: 3 additions & 3 deletions packages/browser/src/node/commands/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ export const selectOptions: UserEventCommand<UserEvent['selectOptions']> = async
) => {
if (context.provider instanceof PlaywrightBrowserProvider) {
const value = userValues as any as (string | { element: string })[]
const { frame } = context
const selectElement = frame.locator(`xpath=${xpath}`)
const { iframe } = context
const selectElement = iframe.locator(`xpath=${xpath}`)

const values = await Promise.all(value.map(async (v) => {
if (typeof v === 'string') {
return v
}
const elementHandler = await frame.locator(`xpath=${v.element}`).elementHandle()
const elementHandler = await iframe.locator(`xpath=${v.element}`).elementHandle()
if (!elementHandler) {
throw new Error(`Element not found: ${v.element}`)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/browser/src/node/commands/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export const type: UserEventCommand<UserEvent['type']> = async (
const { skipClick = false, skipAutoClose = false } = options

if (context.provider instanceof PlaywrightBrowserProvider) {
const { frame } = context
const element = frame.locator(`xpath=${xpath}`)
const { iframe } = context
const element = iframe.locator(`xpath=${xpath}`)

if (!skipClick) {
await element.focus()
Expand Down
26 changes: 21 additions & 5 deletions packages/browser/src/node/providers/playwright.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type {
Browser,

BrowserContext,
BrowserContextOptions,
Frame,
LaunchOptions,
Page,
} from 'playwright'
Expand Down Expand Up @@ -105,8 +107,25 @@ export class PlaywrightBrowserProvider implements BrowserProvider {
return {
page,
context: this.contexts.get(contextId)!,
get frame() {
return page.frame('vitest-iframe')!
frame() {
return new Promise<Frame>((resolve, reject) => {
const frame = page.frame('vitest-iframe')
if (frame) {
return resolve(frame)
}

const timeout = setTimeout(() => {
const err = new Error(`Cannot find "vitest-iframe" on the page. This is a bug in Vitest, please report it.`)
reject(err)
}, 1000)
page.on('frameattached', (frame) => {
clearTimeout(timeout)
resolve(frame)
})
})
},
get iframe() {
return page.frameLocator('[data-vitest="true"]')!
},
}
}
Expand Down Expand Up @@ -147,9 +166,6 @@ export class PlaywrightBrowserProvider implements BrowserProvider {
once(event: string, listener: (...args: any[]) => void) {
cdp.once(event as 'Accessibility.loadComplete', listener)
},
detach() {
return cdp.detach()
},
}
}

Expand Down
1 change: 0 additions & 1 deletion packages/vitest/src/types/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export interface CDPSession {
on: (event: string, listener: (...args: unknown[]) => void) => void
once: (event: string, listener: (...args: unknown[]) => void) => void
off: (event: string, listener: (...args: unknown[]) => void) => void
detach: () => Promise<void>
}

export interface BrowserProvider {
Expand Down

0 comments on commit 14a217d

Please sign in to comment.