@@ -4,25 +4,20 @@ import type {
4
4
BrowserPage ,
5
5
Locator ,
6
6
UserEvent ,
7
- UserEventClickOptions ,
8
- UserEventDragAndDropOptions ,
9
- UserEventHoverOptions ,
10
- UserEventTabOptions ,
11
- UserEventTypeOptions ,
12
7
} from '../../../context'
13
8
import type { BrowserRunnerState } from '../utils'
14
- import { convertElementToCssSelector , ensureAwaited , getBrowserState , getWorkerState } from '../utils'
9
+ import { ensureAwaited , getBrowserState , getWorkerState } from '../utils'
10
+ import { convertElementToCssSelector , processTimeoutOptions } from './utils'
15
11
16
12
// this file should not import anything directly, only types and utils
17
13
18
- const state = ( ) => getWorkerState ( )
19
14
// @ts -expect-error not typed global
20
15
const provider = __vitest_browser_runner__ . provider
21
16
const sessionId = getBrowserState ( ) . sessionId
22
17
const channel = new BroadcastChannel ( `vitest:${ sessionId } ` )
23
18
24
- function triggerCommand < T > ( command : string , ... args : any [ ] ) {
25
- return getBrowserState ( ) . commands . triggerCommand < T > ( command , args )
19
+ function triggerCommand < T > ( command : string , args : any [ ] , error ?: Error ) {
20
+ return getBrowserState ( ) . commands . triggerCommand < T > ( command , args , error )
26
21
}
27
22
28
23
export function createUserEvent ( __tl_user_event_base__ ?: TestingLibraryUserEvent , options ?: TestingLibraryOptions ) : UserEvent {
@@ -51,68 +46,71 @@ export function createUserEvent(__tl_user_event_base__?: TestingLibraryUserEvent
51
46
if ( ! keyboard . unreleased . length ) {
52
47
return
53
48
}
54
- return ensureAwaited ( async ( ) => {
55
- await triggerCommand ( '__vitest_cleanup' , keyboard )
49
+ return ensureAwaited ( async ( error ) => {
50
+ await triggerCommand ( '__vitest_cleanup' , [ keyboard ] , error )
56
51
keyboard . unreleased = [ ]
57
52
} )
58
53
} ,
59
- click ( element : Element | Locator , options : UserEventClickOptions = { } ) {
60
- return convertToLocator ( element ) . click ( processClickOptions ( options ) )
54
+ click ( element , options ) {
55
+ return convertToLocator ( element ) . click ( options )
61
56
} ,
62
- dblClick ( element : Element | Locator , options : UserEventClickOptions = { } ) {
63
- return convertToLocator ( element ) . dblClick ( processClickOptions ( options ) )
57
+ dblClick ( element , options ) {
58
+ return convertToLocator ( element ) . dblClick ( options )
64
59
} ,
65
- tripleClick ( element : Element | Locator , options : UserEventClickOptions = { } ) {
66
- return convertToLocator ( element ) . tripleClick ( processClickOptions ( options ) )
60
+ tripleClick ( element , options ) {
61
+ return convertToLocator ( element ) . tripleClick ( options )
67
62
} ,
68
- selectOptions ( element , value ) {
69
- return convertToLocator ( element ) . selectOptions ( value )
63
+ selectOptions ( element , value , options ) {
64
+ return convertToLocator ( element ) . selectOptions ( value , options )
70
65
} ,
71
- clear ( element : Element | Locator ) {
72
- return convertToLocator ( element ) . clear ( )
66
+ clear ( element , options ) {
67
+ return convertToLocator ( element ) . clear ( options )
73
68
} ,
74
- hover ( element : Element | Locator , options : UserEventHoverOptions = { } ) {
75
- return convertToLocator ( element ) . hover ( processHoverOptions ( options ) )
69
+ hover ( element , options ) {
70
+ return convertToLocator ( element ) . hover ( options )
76
71
} ,
77
- unhover ( element : Element | Locator , options : UserEventHoverOptions = { } ) {
72
+ unhover ( element , options ) {
78
73
return convertToLocator ( element ) . unhover ( options )
79
74
} ,
80
- upload ( element : Element | Locator , files : string | string [ ] | File | File [ ] ) {
81
- return convertToLocator ( element ) . upload ( files )
75
+ upload ( element , files : string | string [ ] | File | File [ ] , options ) {
76
+ return convertToLocator ( element ) . upload ( files , options )
82
77
} ,
83
78
84
79
// non userEvent events, but still useful
85
- fill ( element : Element | Locator , text : string , options ) {
80
+ fill ( element , text , options ) {
86
81
return convertToLocator ( element ) . fill ( text , options )
87
82
} ,
88
- dragAndDrop ( source : Element | Locator , target : Element | Locator , options = { } ) {
83
+ dragAndDrop ( source , target , options ) {
89
84
const sourceLocator = convertToLocator ( source )
90
85
const targetLocator = convertToLocator ( target )
91
- return sourceLocator . dropTo ( targetLocator , processDragAndDropOptions ( options ) )
86
+ return sourceLocator . dropTo ( targetLocator , options )
92
87
} ,
93
88
94
89
// testing-library user-event
95
- async type ( element : Element | Locator , text : string , options : UserEventTypeOptions = { } ) {
96
- return ensureAwaited ( async ( ) => {
90
+ async type ( element , text , options ) {
91
+ return ensureAwaited ( async ( error ) => {
97
92
const selector = convertToSelector ( element )
98
93
const { unreleased } = await triggerCommand < { unreleased : string [ ] } > (
99
94
'__vitest_type' ,
100
- selector ,
101
- text ,
102
- { ...options , unreleased : keyboard . unreleased } ,
95
+ [
96
+ selector ,
97
+ text ,
98
+ { ...options , unreleased : keyboard . unreleased } ,
99
+ ] ,
100
+ error ,
103
101
)
104
102
keyboard . unreleased = unreleased
105
103
} )
106
104
} ,
107
- tab ( options : UserEventTabOptions = { } ) {
108
- return ensureAwaited ( ( ) => triggerCommand ( '__vitest_tab' , options ) )
105
+ tab ( options = { } ) {
106
+ return ensureAwaited ( error => triggerCommand ( '__vitest_tab' , [ options ] , error ) )
109
107
} ,
110
- async keyboard ( text : string ) {
111
- return ensureAwaited ( async ( ) => {
108
+ async keyboard ( text ) {
109
+ return ensureAwaited ( async ( error ) => {
112
110
const { unreleased } = await triggerCommand < { unreleased : string [ ] } > (
113
111
'__vitest_keyboard' ,
114
- text ,
115
- keyboard ,
112
+ [ text , keyboard ] ,
113
+ error ,
116
114
)
117
115
keyboard . unreleased = unreleased
118
116
} )
@@ -175,7 +173,7 @@ function createPreviewUserEvent(userEventBase: TestingLibraryUserEvent, options:
175
173
async unhover ( element : Element | Locator ) {
176
174
await userEvent . unhover ( toElement ( element ) )
177
175
} ,
178
- async upload ( element : Element | Locator , files : string | string [ ] | File | File [ ] ) {
176
+ async upload ( element , files : string | string [ ] | File | File [ ] ) {
179
177
const uploadPromise = ( Array . isArray ( files ) ? files : [ files ] ) . map ( async ( file ) => {
180
178
if ( typeof file !== 'string' ) {
181
179
return file
@@ -185,7 +183,7 @@ function createPreviewUserEvent(userEventBase: TestingLibraryUserEvent, options:
185
183
content : string
186
184
basename : string
187
185
mime : string
188
- } > ( '__vitest_fileInfo' , file , 'base64' )
186
+ } > ( '__vitest_fileInfo' , [ file , 'base64' ] )
189
187
190
188
const fileInstance = fetch ( `data:${ mime } ;base64,${ base64 } ` )
191
189
. then ( r => r . blob ( ) )
@@ -196,18 +194,18 @@ function createPreviewUserEvent(userEventBase: TestingLibraryUserEvent, options:
196
194
return userEvent . upload ( toElement ( element ) as HTMLElement , uploadFiles )
197
195
} ,
198
196
199
- async fill ( element : Element | Locator , text : string ) {
197
+ async fill ( element , text ) {
200
198
await userEvent . clear ( toElement ( element ) )
201
199
return userEvent . type ( toElement ( element ) , text )
202
200
} ,
203
201
async dragAndDrop ( ) {
204
202
throw new Error ( `The "preview" provider doesn't support 'userEvent.dragAndDrop'` )
205
203
} ,
206
204
207
- async type ( element : Element | Locator , text : string , options : UserEventTypeOptions = { } ) {
205
+ async type ( element , text , options ) {
208
206
await userEvent . type ( toElement ( element ) , text , options )
209
207
} ,
210
- async tab ( options : UserEventTabOptions = { } ) {
208
+ async tab ( options ) {
211
209
await userEvent . tab ( options )
212
210
} ,
213
211
async keyboard ( text : string ) {
@@ -282,36 +280,36 @@ export const page: BrowserPage = {
282
280
const name
283
281
= options . path || `${ taskName . replace ( / [ ^ a - z 0 - 9 ] / gi, '-' ) } -${ number } .png`
284
282
285
- return ensureAwaited ( ( ) => triggerCommand ( '__vitest_screenshot' , name , {
283
+ return ensureAwaited ( error => triggerCommand ( '__vitest_screenshot' , [ name , processTimeoutOptions ( {
286
284
...options ,
287
285
element : options . element
288
286
? convertToSelector ( options . element )
289
287
: undefined ,
290
- } ) )
288
+ } ) ] , error ) )
291
289
} ,
292
290
getByRole ( ) {
293
- throw new Error ( ' Method "getByRole" is not implemented in the current provider.' )
291
+ throw new Error ( ` Method "getByRole" is not implemented in the " ${ provider } " provider.` )
294
292
} ,
295
293
getByLabelText ( ) {
296
- throw new Error ( ' Method "getByLabelText" is not implemented in the current provider.' )
294
+ throw new Error ( ` Method "getByLabelText" is not implemented in the " ${ provider } " provider.` )
297
295
} ,
298
296
getByTestId ( ) {
299
- throw new Error ( ' Method "getByTestId" is not implemented in the current provider.' )
297
+ throw new Error ( ` Method "getByTestId" is not implemented in the " ${ provider } " provider.` )
300
298
} ,
301
299
getByAltText ( ) {
302
- throw new Error ( ' Method "getByAltText" is not implemented in the current provider.' )
300
+ throw new Error ( ` Method "getByAltText" is not implemented in the " ${ provider } " provider.` )
303
301
} ,
304
302
getByPlaceholder ( ) {
305
- throw new Error ( ' Method "getByPlaceholder" is not implemented in the current provider.' )
303
+ throw new Error ( ` Method "getByPlaceholder" is not implemented in the " ${ provider } " provider.` )
306
304
} ,
307
305
getByText ( ) {
308
- throw new Error ( ' Method "getByText" is not implemented in the current provider.' )
306
+ throw new Error ( ` Method "getByText" is not implemented in the " ${ provider } " provider.` )
309
307
} ,
310
308
getByTitle ( ) {
311
- throw new Error ( ' Method "getByTitle" is not implemented in the current provider.' )
309
+ throw new Error ( ` Method "getByTitle" is not implemented in the " ${ provider } " provider.` )
312
310
} ,
313
311
elementLocator ( ) {
314
- throw new Error ( ' Method "elementLocator" is not implemented in the current provider.' )
312
+ throw new Error ( ` Method "elementLocator" is not implemented in the " ${ provider } " provider.` )
315
313
} ,
316
314
extend ( methods ) {
317
315
for ( const key in methods ) {
@@ -344,130 +342,3 @@ function convertToSelector(elementOrLocator: Element | Locator): string {
344
342
function getTaskFullName ( task : RunnerTask ) : string {
345
343
return task . suite ? `${ getTaskFullName ( task . suite ) } ${ task . name } ` : task . name
346
344
}
347
-
348
- function processClickOptions ( options_ ?: UserEventClickOptions ) {
349
- // only ui scales the iframe, so we need to adjust the position
350
- if ( ! options_ || ! state ( ) . config . browser . ui ) {
351
- return options_
352
- }
353
- if ( provider === 'playwright' ) {
354
- const options = options_ as NonNullable <
355
- Parameters < import ( 'playwright' ) . Page [ 'click' ] > [ 1 ]
356
- >
357
- if ( options . position ) {
358
- options . position = processPlaywrightPosition ( options . position )
359
- }
360
- }
361
- if ( provider === 'webdriverio' ) {
362
- const options = options_ as import ( 'webdriverio' ) . ClickOptions
363
- if ( options . x != null || options . y != null ) {
364
- const cache = { }
365
- if ( options . x != null ) {
366
- options . x = scaleCoordinate ( options . x , cache )
367
- }
368
- if ( options . y != null ) {
369
- options . y = scaleCoordinate ( options . y , cache )
370
- }
371
- }
372
- }
373
- return options_
374
- }
375
-
376
- function processHoverOptions ( options_ ?: UserEventHoverOptions ) {
377
- // only ui scales the iframe, so we need to adjust the position
378
- if ( ! options_ || ! state ( ) . config . browser . ui ) {
379
- return options_
380
- }
381
-
382
- if ( provider === 'playwright' ) {
383
- const options = options_ as NonNullable <
384
- Parameters < import ( 'playwright' ) . Page [ 'hover' ] > [ 1 ]
385
- >
386
- if ( options . position ) {
387
- options . position = processPlaywrightPosition ( options . position )
388
- }
389
- }
390
- if ( provider === 'webdriverio' ) {
391
- const options = options_ as import ( 'webdriverio' ) . MoveToOptions
392
- const cache = { }
393
- if ( options . xOffset != null ) {
394
- options . xOffset = scaleCoordinate ( options . xOffset , cache )
395
- }
396
- if ( options . yOffset != null ) {
397
- options . yOffset = scaleCoordinate ( options . yOffset , cache )
398
- }
399
- }
400
- return options_
401
- }
402
-
403
- function processDragAndDropOptions ( options_ ?: UserEventDragAndDropOptions ) {
404
- // only ui scales the iframe, so we need to adjust the position
405
- if ( ! options_ || ! state ( ) . config . browser . ui ) {
406
- return options_
407
- }
408
- if ( provider === 'playwright' ) {
409
- const options = options_ as NonNullable <
410
- Parameters < import ( 'playwright' ) . Page [ 'dragAndDrop' ] > [ 2 ]
411
- >
412
- if ( options . sourcePosition ) {
413
- options . sourcePosition = processPlaywrightPosition ( options . sourcePosition )
414
- }
415
- if ( options . targetPosition ) {
416
- options . targetPosition = processPlaywrightPosition ( options . targetPosition )
417
- }
418
- }
419
- if ( provider === 'webdriverio' ) {
420
- const cache = { }
421
- const options = options_ as import ( 'webdriverio' ) . DragAndDropOptions & {
422
- targetX ?: number
423
- targetY ?: number
424
- sourceX ?: number
425
- sourceY ?: number
426
- }
427
- if ( options . sourceX != null ) {
428
- options . sourceX = scaleCoordinate ( options . sourceX , cache )
429
- }
430
- if ( options . sourceY != null ) {
431
- options . sourceY = scaleCoordinate ( options . sourceY , cache )
432
- }
433
- if ( options . targetX != null ) {
434
- options . targetX = scaleCoordinate ( options . targetX , cache )
435
- }
436
- if ( options . targetY != null ) {
437
- options . targetY = scaleCoordinate ( options . targetY , cache )
438
- }
439
- }
440
- return options_
441
- }
442
-
443
- function scaleCoordinate ( coordinate : number , cache : any ) {
444
- return Math . round ( coordinate * getCachedScale ( cache ) )
445
- }
446
-
447
- function getCachedScale ( cache : { scale : number | undefined } ) {
448
- return cache . scale ??= getIframeScale ( )
449
- }
450
-
451
- function processPlaywrightPosition ( position : { x : number ; y : number } ) {
452
- const scale = getIframeScale ( )
453
- if ( position . x != null ) {
454
- position . x *= scale
455
- }
456
- if ( position . y != null ) {
457
- position . y *= scale
458
- }
459
- return position
460
- }
461
-
462
- function getIframeScale ( ) {
463
- const testerUi = window . parent . document . querySelector ( '#tester-ui' ) as HTMLElement | null
464
- if ( ! testerUi ) {
465
- throw new Error ( `Cannot find Tester element. This is a bug in Vitest. Please, open a new issue with reproduction.` )
466
- }
467
- const scaleAttribute = testerUi . getAttribute ( 'data-scale' )
468
- const scale = Number ( scaleAttribute )
469
- if ( Number . isNaN ( scale ) ) {
470
- throw new TypeError ( `Cannot parse scale value from Tester element (${ scaleAttribute } ). This is a bug in Vitest. Please, open a new issue with reproduction.` )
471
- }
472
- return scale
473
- }
0 commit comments