Skip to content

Commit cf071bc

Browse files
committed
Add missing element case and index out of bound from $()[x]
1 parent 5b73964 commit cf071bc

3 files changed

Lines changed: 87 additions & 0 deletions

File tree

test/__mocks__/@wdio/globals.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,30 @@ export function chainableElementArrayFactory(selector: string, length: number) {
179179
// Ensure `'getElements' in chainableElements` is false while allowing to use `await chainableElement.getElements()`
180180
const runtimeChainablePromiseArray = new Proxy(chainablePromiseArray, {
181181
get(target, prop) {
182+
if (typeof prop === 'string' && /^\d+$/.test(prop)) {
183+
const index = parseInt(prop, 10)
184+
if (index >= length) {
185+
const error = new Error(`Index out of bounds! $$(${selector}) returned only ${length} elements.`)
186+
return new Proxy(Promise.resolve(), {
187+
get(target, prop) {
188+
if (prop === 'then') {
189+
return (resolve: any, reject: any) => reject(error)
190+
}
191+
// Allow resolving methods like 'catch', 'finally' normally from the promise if needed,
192+
// but usually we want any interaction to fail?
193+
// Actually, standard promise methods might be accessed.
194+
// But the user requirements says: `$$('foo')[3].getText()` should return a promise (that rejects).
195+
196+
// If accessing a property that exists on Promise (like catch, finally, Symbol.toStringTag), maybe we should be careful.
197+
// However, the test expects `el` (the proxy) to be a Promise instance.
198+
// And `el.getText()` to return a promise.
199+
200+
// If I return a function that returns a rejected promise for everything else:
201+
return () => Promise.reject(error)
202+
}
203+
})
204+
}
205+
}
182206
if (elementArray && prop in elementArray) {
183207
return elementArray[prop as keyof WebdriverIO.ElementArray]
184208
}

test/globals_mock.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, it, expect, vi } from 'vitest'
22
import { $, $$ } from '@wdio/globals'
3+
import { notFoundElementFactory } from './__mocks__/@wdio/globals.js'
34

45
vi.mock('@wdio/globals')
56

@@ -90,5 +91,52 @@ describe('globals mock', () => {
9091

9192
expect(await els.getElements()).toEqual(els)
9293
})
94+
95+
it('should return a promise-like object when accessing index out of bounds', () => {
96+
const el = $$('foo')[3]
97+
// It shouldn't throw synchronously
98+
expect(el).toBeDefined()
99+
expect(el).toBeInstanceOf(Promise)
100+
expect(typeof (el as any).then).toBe('function')
101+
102+
// Methods should return a Promise
103+
const p1 = el.getElement()
104+
expect(p1).toBeInstanceOf(Promise)
105+
// catch unhandled rejection to avoid warnings
106+
p1.catch(() => {})
107+
108+
const p2 = el.getText()
109+
expect(p2).toBeInstanceOf(Promise)
110+
// catch unhandled rejection to avoid warnings
111+
p2.catch(() => {})
112+
})
113+
114+
it('should throw "Index out of bounds" when awaiting index out of bounds', async () => {
115+
await expect(async () => await $$('foo')[3]).rejects.toThrow('Index out of bounds! $$(foo) returned only 2 elements.')
116+
await expect(async () => await $$('foo')[3].getElement()).rejects.toThrow('Index out of bounds! $$(foo) returned only 2 elements.')
117+
await expect(async () => await $$('foo')[3].getText()).rejects.toThrow('Index out of bounds! $$(foo) returned only 2 elements.')
118+
})
119+
})
120+
121+
describe('notFoundElementFactory', () => {
122+
it('should return false for isExisting', async () => {
123+
const el = notFoundElementFactory('not-found')
124+
expect(await el.isExisting()).toBe(false)
125+
})
126+
127+
it('should resolve to itself when calling getElement', async () => {
128+
const el = notFoundElementFactory('not-found')
129+
expect(await el.getElement()).toBe(el)
130+
})
131+
132+
it('should throw error on method calls', async () => {
133+
const el = notFoundElementFactory('not-found')
134+
expect(() => el.click()).toThrow("Can't call click on element with selector not-found because element wasn't found")
135+
})
136+
137+
it('should throw error when awaiting a method call (sync throw)', async () => {
138+
const el = notFoundElementFactory('not-found')
139+
expect(() => el.getText()).toThrow("Can't call getText on element with selector not-found because element wasn't found")
140+
})
93141
})
94142
})

test/matchers/element/toHaveText.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { $, $$ } from '@wdio/globals'
22
import { beforeEach, describe, expect, test, vi } from 'vitest'
33
import { toHaveText } from '../../../src/matchers/element/toHaveText.js'
44
import type { ChainablePromiseArray, ChainablePromiseElement } from 'webdriverio'
5+
import { notFoundElementFactory } from '../../__mocks__/@wdio/globals.js'
56

67
vi.mock('@wdio/globals')
78

@@ -639,6 +640,20 @@ Expected: "webdriverio"
639640
Received: undefined`)
640641
})
641642

643+
// TODO view later to handle this case more gracefully
644+
test('given element is not found then it throws error when an element does not exists', async () => {
645+
const element: WebdriverIO.Element = notFoundElementFactory('sel')
646+
647+
await expect(thisContext.toHaveText(element, 'webdriverio')).rejects.toThrow("Can't call getText on element with selector sel because element wasn't found")
648+
})
649+
650+
// TODO view later to handle this case more gracefully
651+
test('given element from out of bound ChainableArray, then it throws error when an element does not exists', async () => {
652+
const element: ChainablePromiseElement = $$('elements')[3]
653+
654+
await expect(thisContext.toHaveText(element, 'webdriverio')).rejects.toThrow('Index out of bounds! $$(elements) returned only 2 elements.')
655+
})
656+
642657
test.for([
643658
{ actual: undefined, selectorName: 'undefined' },
644659
{ actual: null, selectorName: 'null' },

0 commit comments

Comments
 (0)