@@ -5,44 +5,6 @@ import type { BrowserRPC } from '../client'
55
66// this file should not import anything directly, only types
77
8- function convertElementToXPath ( element : Element ) {
9- if ( ! element || ! ( element instanceof Element ) ) {
10- throw new Error (
11- `Expected DOM element to be an instance of Element, received ${ typeof element } ` ,
12- )
13- }
14-
15- return getPathTo ( element )
16- }
17-
18- function getPathTo ( element : Element ) : string {
19- if ( element . id !== '' ) {
20- return `id("${ element . id } ")`
21- }
22-
23- if ( ! element . parentNode || element === document . documentElement ) {
24- return element . tagName
25- }
26-
27- let ix = 0
28- const siblings = element . parentNode . childNodes
29- for ( let i = 0 ; i < siblings . length ; i ++ ) {
30- const sibling = siblings [ i ]
31- if ( sibling === element ) {
32- return `${ getPathTo ( element . parentNode as Element ) } /${ element . tagName } [${
33- ix + 1
34- } ]`
35- }
36- if (
37- sibling . nodeType === 1
38- && ( sibling as Element ) . tagName === element . tagName
39- ) {
40- ix ++
41- }
42- }
43- return 'invalid xpath'
44- }
45-
468// @ts -expect-error not typed global
479const state = ( ) : WorkerGlobalState => __vitest_worker__
4810// @ts -expect-error not typed global
@@ -60,37 +22,99 @@ function triggerCommand<T>(command: string, ...args: any[]) {
6022
6123const provider = runner ( ) . provider
6224
25+ function convertElementToCssSelector ( element : Element ) {
26+ if ( ! element || ! ( element instanceof Element ) ) {
27+ throw new Error (
28+ `Expected DOM element to be an instance of Element, received ${ typeof element } ` ,
29+ )
30+ }
31+
32+ return getUniqueCssSelector ( element )
33+ }
34+
35+ function getUniqueCssSelector ( el : Element ) {
36+ const path = [ ]
37+ let parent : null | ParentNode
38+ let hasShadowRoot = false
39+ // eslint-disable-next-line no-cond-assign
40+ while ( parent = getParent ( el ) ) {
41+ if ( ( parent as Element ) . shadowRoot ) {
42+ hasShadowRoot = true
43+ }
44+
45+ const tag = el . tagName
46+ if ( el . id ) {
47+ path . push ( `#${ el . id } ` )
48+ }
49+ else if ( ! el . nextElementSibling && ! el . previousElementSibling ) {
50+ path . push ( tag )
51+ }
52+ else {
53+ let index = 0
54+ let sameTagSiblings = 0
55+ let elementIndex = 0
56+
57+ for ( const sibling of parent . children ) {
58+ index ++
59+ if ( sibling . tagName === tag ) {
60+ sameTagSiblings ++
61+ }
62+ if ( sibling === el ) {
63+ elementIndex = index
64+ }
65+ }
66+
67+ if ( sameTagSiblings > 1 ) {
68+ path . push ( `${ tag } :nth-child(${ elementIndex } )` )
69+ }
70+ else {
71+ path . push ( tag )
72+ }
73+ }
74+ el = parent as Element
75+ } ;
76+ return `${ provider === 'webdriverio' && hasShadowRoot ? '>>>' : '' } ${ path . reverse ( ) . join ( ' > ' ) } ` . toLowerCase ( )
77+ }
78+
79+ function getParent ( el : Element ) {
80+ const parent = el . parentNode
81+ if ( parent instanceof ShadowRoot ) {
82+ return parent . host
83+ }
84+ return parent
85+ }
86+
6387export const userEvent : UserEvent = {
6488 // TODO: actually setup userEvent with config options
6589 setup ( ) {
6690 return userEvent
6791 } ,
6892 click ( element : Element , options : UserEventClickOptions = { } ) {
69- const xpath = convertElementToXPath ( element )
70- return triggerCommand ( '__vitest_click' , xpath , options )
93+ const css = convertElementToCssSelector ( element )
94+ return triggerCommand ( '__vitest_click' , css , options )
7195 } ,
7296 dblClick ( element : Element , options : UserEventClickOptions = { } ) {
73- const xpath = convertElementToXPath ( element )
74- return triggerCommand ( '__vitest_dblClick' , xpath , options )
97+ const css = convertElementToCssSelector ( element )
98+ return triggerCommand ( '__vitest_dblClick' , css , options )
7599 } ,
76100 tripleClick ( element : Element , options : UserEventClickOptions = { } ) {
77- const xpath = convertElementToXPath ( element )
78- return triggerCommand ( '__vitest_tripleClick' , xpath , options )
101+ const css = convertElementToCssSelector ( element )
102+ return triggerCommand ( '__vitest_tripleClick' , css , options )
79103 } ,
80104 selectOptions ( element , value ) {
81105 const values = provider === 'webdriverio'
82106 ? getWebdriverioSelectOptions ( element , value )
83107 : getSimpleSelectOptions ( element , value )
84- const xpath = convertElementToXPath ( element )
85- return triggerCommand ( '__vitest_selectOptions' , xpath , values )
108+ const css = convertElementToCssSelector ( element )
109+ return triggerCommand ( '__vitest_selectOptions' , css , values )
86110 } ,
87111 type ( element : Element , text : string , options : UserEventTypeOptions = { } ) {
88- const xpath = convertElementToXPath ( element )
89- return triggerCommand ( '__vitest_type' , xpath , text , options )
112+ const css = convertElementToCssSelector ( element )
113+ return triggerCommand ( '__vitest_type' , css , text , options )
90114 } ,
91115 clear ( element : Element ) {
92- const xpath = convertElementToXPath ( element )
93- return triggerCommand ( '__vitest_clear' , xpath )
116+ const css = convertElementToCssSelector ( element )
117+ return triggerCommand ( '__vitest_clear' , css )
94118 } ,
95119 tab ( options : UserEventTabOptions = { } ) {
96120 return triggerCommand ( '__vitest_tab' , options )
@@ -99,23 +123,23 @@ export const userEvent: UserEvent = {
99123 return triggerCommand ( '__vitest_keyboard' , text )
100124 } ,
101125 hover ( element : Element ) {
102- const xpath = convertElementToXPath ( element )
103- return triggerCommand ( '__vitest_hover' , xpath )
126+ const css = convertElementToCssSelector ( element )
127+ return triggerCommand ( '__vitest_hover' , css )
104128 } ,
105129 unhover ( element : Element ) {
106- const xpath = convertElementToXPath ( element . ownerDocument . body )
107- return triggerCommand ( '__vitest_hover' , xpath )
130+ const css = convertElementToCssSelector ( element . ownerDocument . body )
131+ return triggerCommand ( '__vitest_hover' , css )
108132 } ,
109133
110134 // non userEvent events, but still useful
111135 fill ( element : Element , text : string , options ) {
112- const xpath = convertElementToXPath ( element )
113- return triggerCommand ( '__vitest_fill' , xpath , text , options )
136+ const css = convertElementToCssSelector ( element )
137+ return triggerCommand ( '__vitest_fill' , css , text , options )
114138 } ,
115139 dragAndDrop ( source : Element , target : Element , options = { } ) {
116- const sourceXpath = convertElementToXPath ( source )
117- const targetXpath = convertElementToXPath ( target )
118- return triggerCommand ( '__vitest_dragAndDrop' , sourceXpath , targetXpath , options )
140+ const sourceCss = convertElementToCssSelector ( source )
141+ const targetCss = convertElementToCssSelector ( target )
142+ return triggerCommand ( '__vitest_dragAndDrop' , sourceCss , targetCss , options )
119143 } ,
120144}
121145
@@ -137,7 +161,7 @@ function getWebdriverioSelectOptions(element: Element, value: string | string[]
137161 if ( typeof optionValue !== 'string' ) {
138162 const index = options . indexOf ( optionValue as HTMLOptionElement )
139163 if ( index === - 1 ) {
140- throw new Error ( `The element ${ convertElementToXPath ( optionValue ) } was not found in the "select" options.` )
164+ throw new Error ( `The element ${ convertElementToCssSelector ( optionValue ) } was not found in the "select" options.` )
141165 }
142166
143167 return [ { index } ]
@@ -162,7 +186,7 @@ function getWebdriverioSelectOptions(element: Element, value: string | string[]
162186function getSimpleSelectOptions ( element : Element , value : string | string [ ] | HTMLElement [ ] | HTMLElement ) {
163187 return ( Array . isArray ( value ) ? value : [ value ] ) . map ( ( v ) => {
164188 if ( typeof v !== 'string' ) {
165- return { element : convertElementToXPath ( v ) }
189+ return { element : convertElementToCssSelector ( v ) }
166190 }
167191 return v
168192 } )
@@ -220,7 +244,7 @@ export const page: BrowserPage = {
220244 return triggerCommand ( '__vitest_screenshot' , name , {
221245 ...options ,
222246 element : options . element
223- ? convertElementToXPath ( options . element )
247+ ? convertElementToCssSelector ( options . element )
224248 : undefined ,
225249 } )
226250 } ,
0 commit comments