@@ -20,6 +20,8 @@ const getElementMethods = () => ({
2020 getHTML : vi . spyOn ( { getHTML : async ( ) => { return '<Html/>' } } , 'getHTML' ) ,
2121 getComputedLabel : vi . spyOn ( { getComputedLabel : async ( ) => 'Computed Label' } , 'getComputedLabel' ) ,
2222 getComputedRole : vi . spyOn ( { getComputedRole : async ( ) => 'Computed Role' } , 'getComputedRole' ) ,
23+ // Null is not part of the type, to fix in wdio one day
24+ getAttribute : vi . spyOn ( { getAttribute : async ( _attr : string ) => null as unknown as string } , 'getAttribute' ) ,
2325 getSize : vi . spyOn ( { getSize : async ( prop ?: 'width' | 'height' ) => {
2426 if ( prop === 'width' ) { return 100 }
2527 if ( prop === 'height' ) { return 50 }
@@ -28,38 +30,67 @@ const getElementMethods = () => ({
2830 getAttribute : vi . spyOn ( { getAttribute : async ( _attr : string ) => 'some attribute' } , 'getAttribute' ) ,
2931} satisfies Partial < WebdriverIO . Element > )
3032
31- function $ ( _selector : string ) {
32- const element = {
33+ const elementFactory = ( _selector : string , index ?: number ) : WebdriverIO . Element => {
34+ const partialElement = {
3335 selector : _selector ,
3436 ...getElementMethods ( ) ,
37+ index,
3538 $,
3639 $$
37- } satisfies Partial < WebdriverIO . Element > as unknown as WebdriverIO . Element
38- element . getElement = async ( ) => Promise . resolve ( element )
39- return element as unknown as ChainablePromiseElement
40+ } satisfies Partial < WebdriverIO . Element >
41+
42+ const element = partialElement as unknown as WebdriverIO . Element
43+ element . getElement = vi . fn ( ) . mockResolvedValue ( element )
44+ return element
45+ }
46+
47+ function $ ( _selector : string ) {
48+ const element = elementFactory ( _selector )
49+
50+ // Wdio framework does return a Promise-wrapped element, so we need to mimic this behavior
51+ const chainablePromiseElement = Promise . resolve ( element ) as unknown as ChainablePromiseElement
52+
53+ // Ensure `'getElement' in chainableElement` is false while allowing to use `await chainableElement.getElement()`
54+ const runtimeChainableElement = new Proxy ( chainablePromiseElement , {
55+ get ( target , prop ) {
56+ if ( prop in element ) {
57+ return element [ prop as keyof WebdriverIO . Element ]
58+ }
59+ const value = Reflect . get ( target , prop )
60+ return typeof value === 'function' ? value . bind ( target ) : value
61+ }
62+ } )
63+ return runtimeChainableElement
4064}
4165
4266function $$ ( selector : string ) {
43- const length = ( this ) ?. _length || 2
44- const elements = Array ( length ) . fill ( null ) . map ( ( _ , index ) => {
45- const element = {
46- selector,
47- index,
48- ...getElementMethods ( ) ,
49- $,
50- $$
51- } satisfies Partial < WebdriverIO . Element > as unknown as WebdriverIO . Element
52- element . getElement = async ( ) => Promise . resolve ( element )
53- return element
54- } ) satisfies WebdriverIO . Element [ ] as unknown as WebdriverIO . ElementArray
67+ const length = ( this as any ) ?. _length || 2
68+ const elements : WebdriverIO . Element [ ] = Array ( length ) . fill ( null ) . map ( ( _ , index ) => elementFactory ( selector , index ) )
69+
70+ const elementArray = elements as unknown as WebdriverIO . ElementArray
71+
72+ elementArray . foundWith = '$$'
73+ elementArray . props = [ ]
74+ elementArray . props . length = length
75+ elementArray . selector = selector
76+ elementArray . getElements = async ( ) => elementArray
77+ elementArray . length = length
78+
79+ // Wdio framework does return a Promise-wrapped element, so we need to mimic this behavior
80+ const chainablePromiseArray = Promise . resolve ( elementArray ) as unknown as ChainablePromiseArray
81+
82+ // Ensure `'getElements' in chainableElements` is false while allowing to use `await chainableElement.getElements()`
83+ const runtimeChainablePromiseArray = new Proxy ( chainablePromiseArray , {
84+ get ( target , prop ) {
85+ if ( elementArray && prop in elementArray ) {
86+ return elementArray [ prop as keyof WebdriverIO . ElementArray ]
87+ }
88+ const value = Reflect . get ( target , prop )
89+ return typeof value === 'function' ? value . bind ( target ) : value
90+ }
91+ } )
5592
56- elements . foundWith = '$$'
57- elements . props = [ ]
58- elements . props . length = length
59- elements . selector = selector
60- elements . getElements = async ( ) => elements
61- elements . length = length
62- return elements as unknown as ChainablePromiseArray
93+ return runtimeChainablePromiseArray
6394}
6495
6596export const browser = {
0 commit comments