diff --git a/docs/API.md b/docs/API.md index d1640073..fe925cb4 100644 --- a/docs/API.md +++ b/docs/API.md @@ -515,6 +515,8 @@ await expect(logo).toHaveSize({ width: 32, height: 32 }) Checks amount of fetched elements using [`$$`](https://webdriver.io/docs/api/element/$$) command. +**Note:** This matcher will update the passed array with the latest elements if the assertion passes. However, if you've reassigned the variable, you'll need to fetch the elements again. + ##### Usage ```js diff --git a/src/matchers/elements/toBeElementsArrayOfSize.ts b/src/matchers/elements/toBeElementsArrayOfSize.ts index d551dd0c..77ad0056 100644 --- a/src/matchers/elements/toBeElementsArrayOfSize.ts +++ b/src/matchers/elements/toBeElementsArrayOfSize.ts @@ -28,6 +28,7 @@ export async function toBeElementsArrayOfSize( } let elements = await received as WebdriverIO.ElementArray + const originalLength = elements.length; const pass = await waitUntil(async () => { /** * check numbers first before refetching elements @@ -40,8 +41,13 @@ export async function toBeElementsArrayOfSize( return false }, isNot, {...numberOptions, ...options}) + if (Array.isArray(received) && pass) { + received.length = 0; + received.push(...elements); + } + const error = numberError(numberOptions) - const message = enhanceError(elements, error, elements.length, this, verb, expectation, '', numberOptions) + const message = enhanceError(elements, error, originalLength, this, verb, expectation, '', numberOptions) const result: ExpectWebdriverIO.AssertionResult = { pass, diff --git a/test/matchers/elements/toBeElementsArrayOfSize.test.ts b/test/matchers/elements/toBeElementsArrayOfSize.test.ts index 2e2d7ce4..8ffe9864 100644 --- a/test/matchers/elements/toBeElementsArrayOfSize.test.ts +++ b/test/matchers/elements/toBeElementsArrayOfSize.test.ts @@ -4,15 +4,44 @@ import { $$ } from '@wdio/globals' import { getExpectMessage, getReceived, getExpected} from '../../__fixtures__/utils.js'; import { toBeElementsArrayOfSize } from '../../../src/matchers/elements/toBeElementsArrayOfSize.js'; -vi.mock('@wdio/globals') +const createMockElementArray = (length: number): WebdriverIO.ElementArray => { + const array = Array.from({ length }, () => ({})); + const mockArray = { + selector: 'parent', + get length() { return array.length; }, + set length(newLength: number) { array.length = newLength; }, + parent: { + $: vi.fn(), + $$: vi.fn().mockReturnValue(array), + }, + foundWith: '$$', + props: [], + [Symbol.iterator]: array[Symbol.iterator].bind(array), + filter: vi.fn().mockReturnThis(), + map: vi.fn().mockReturnThis(), + find: vi.fn().mockReturnThis(), + forEach: vi.fn(), + some: vi.fn(), + every: vi.fn(), + slice: vi.fn().mockReturnThis(), + toArray: vi.fn().mockReturnThis(), + }; + return Object.assign(array, mockArray) as unknown as WebdriverIO.ElementArray; +}; + +vi.mock('@wdio/globals', () => ({ + $$: vi.fn().mockImplementation(() => createMockElementArray(2)) +})) describe('toBeElementsArrayOfSize', () => { let els: WebdriverIO.ElementArray + beforeEach(() => { + els = $$('parent') as unknown as WebdriverIO.ElementArray; + }) + describe('success', () => { test('array of size 2', async () => { - // Create an element array of length 2 - els = await $$('parent'); const beforeAssertion = vi.fn() const afterAssertion = vi.fn() const result = await toBeElementsArrayOfSize.call({}, els, 2, { beforeAssertion, afterAssertion }) @@ -30,10 +59,7 @@ describe('toBeElementsArrayOfSize', () => { }) }) test('array of size 5', async () => { - // Create an element array of length 2 - els = await $$('parent'); - // @ts-ignore - els.parent._length = 5; + els = createMockElementArray(5); const result = await toBeElementsArrayOfSize.call({}, els, 5, {}); expect(result.pass).toBe(true) }) @@ -43,8 +69,6 @@ describe('toBeElementsArrayOfSize', () => { let result: any; beforeEach(async () => { - // Create an element array of length 2 - els = await $$('parent'); result = await toBeElementsArrayOfSize.call({}, els, 5, {}); }) @@ -67,80 +91,67 @@ describe('toBeElementsArrayOfSize', () => { describe('error catching', () => { test('throws error with incorrect size param', async () => { - // Create an element array of length 2 - els = await $$('parent'); - let error; - try { - // @ts-expect-error param check - await toBeElementsArrayOfSize.call({}, els, '5') - } catch (e) { - error = e; - } - expect(error).toEqual(new Error('Invalid params passed to toBeElementsArrayOfSize.')); + await expect(toBeElementsArrayOfSize.call({}, els, '5' as any)).rejects.toThrow('Invalid params passed to toBeElementsArrayOfSize.') }) test('works if size contains options', async () => { - // Create an element array of length 2 - els = await $$('parent'); const result = await toBeElementsArrayOfSize.call({}, els, {lte: 5}); expect(result.pass).toBe(true); }) }) describe('number options', () => { - describe('lte', () => { - test('should pass if lte', async () => { - // Create an element array of length 2 - els = await $$('parent'); - const result = await toBeElementsArrayOfSize.call({}, els, {lte: 10}); - expect(result.pass).toBe(true); - }) - - test('should fail if not lte', async () => { - // Create an element array of length 2 - els = await $$('parent'); - const result = await toBeElementsArrayOfSize.call({}, els, {lte: 1}); - expect(result.pass).toBe(false); - }) - }) - - describe('gte', () => { - test('should pass if gte', async () => { - // Create an element array of length 2 - els = await $$('parent'); - const result = await toBeElementsArrayOfSize.call({}, els, {gte: 1}); - expect(result.pass).toBe(true); - }) - - test('should fail if not gte', async () => { - // Create an element array of length 2 - els = await $$('parent'); - const result = await toBeElementsArrayOfSize.call({}, els, {gte: 10}); - expect(result.pass).toBe(false); - }) - }) - - describe('gte && lte', () => { - test('should pass if gte and lte', async () => { - // Create an element array of length 2 - els = await $$('parent'); - const result = await toBeElementsArrayOfSize.call({}, els, {gte: 1, lte: 10}); - expect(result.pass).toBe(true); - }) - - test('should fail if not gte but is lte', async () => { - // Create an element array of length 2 - els = await $$('parent'); - const result = await toBeElementsArrayOfSize.call({}, els, {gte: 10, lte: 10}); - expect(result.pass).toBe(false); - }) - - test('should fail if not lte but is gte', async () => { - // Create an element array of length 2 - els = await $$('parent'); - const result = await toBeElementsArrayOfSize.call({}, els, {gte: 1, lte: 1}); - expect(result.pass).toBe(false); - }) + test.each([ + ['lte', 10, true], + ['lte', 1, false], + ['gte', 1, true], + ['gte', 10, false], + ['gte and lte', { gte: 1, lte: 10 }, true], + ['not gte but is lte', { gte: 10, lte: 10 }, false], + ['not lte but is gte', { gte: 1, lte: 1 }, false], + ])('should handle %s correctly', async (_, option, expected) => { + const result = await toBeElementsArrayOfSize.call({}, els, typeof option === 'object' ? option : { [_ as string]: option }); + expect(result.pass).toBe(expected); }) }) + + describe('array update', () => { + test('updates the received array when assertion passes', async () => { + const receivedArray = createMockElementArray(2); + (receivedArray.parent as any)._length = 5; + (receivedArray.parent as any).$$ = vi.fn().mockReturnValue(createMockElementArray(5)); + + const result = await toBeElementsArrayOfSize.call({}, receivedArray, 5); + + expect(result.pass).toBe(true); + expect(receivedArray.length).toBe(5); + }); + + test('does not update the received array when assertion fails', async () => { + const receivedArray = createMockElementArray(2); + + const result = await toBeElementsArrayOfSize.call({}, receivedArray, 10); + + expect(result.pass).toBe(false); + expect(receivedArray.length).toBe(2); + }); + + test('does not modify non-array received values', async () => { + const nonArrayEls = { + selector: 'parent', + length: 2, + parent: { + $: vi.fn(), + $$: vi.fn().mockReturnValue(createMockElementArray(5)), + }, + foundWith: '$$', + props: [], + } as unknown as WebdriverIO.ElementArray; + + const result = await toBeElementsArrayOfSize.call({}, nonArrayEls, 5); + + expect(result.pass).toBe(true); + expect(nonArrayEls.length).toBe(2); + }); + }); }) diff --git a/types/expect-webdriverio.d.ts b/types/expect-webdriverio.d.ts index 599f9aa1..4aca967d 100644 --- a/types/expect-webdriverio.d.ts +++ b/types/expect-webdriverio.d.ts @@ -381,7 +381,7 @@ declare namespace ExpectWebdriverIO { toBeElementsArrayOfSize( size: number | ExpectWebdriverIO.NumberOptions, options?: ExpectWebdriverIO.NumberOptions - ): R + ): R & Promise; // ==== network mock ==== /**