From 2cf3a4652b482d4af862d46282b691dc1ec6a35e Mon Sep 17 00:00:00 2001 From: Erwin Heitzman <15839059+erwinheitzman@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:42:44 +0200 Subject: [PATCH 1/2] feature(expect-webdriverio): add number options support for toHaveHeight and toHaveWidth --- src/matchers/element/toHaveHeight.ts | 28 +++++++++++++++------- src/matchers/element/toHaveWidth.ts | 28 +++++++++++++++------- test/matchers/element/toHaveHeight.test.ts | 19 +++++++++++++++ test/matchers/element/toHaveWidth.test.ts | 19 +++++++++++++++ 4 files changed, 78 insertions(+), 16 deletions(-) diff --git a/src/matchers/element/toHaveHeight.ts b/src/matchers/element/toHaveHeight.ts index 60d1c02a6..0905d2183 100644 --- a/src/matchers/element/toHaveHeight.ts +++ b/src/matchers/element/toHaveHeight.ts @@ -1,24 +1,25 @@ import { DEFAULT_OPTIONS } from '../../constants.js' import type { WdioElementMaybePromise } from '../../types.js' import { + compareNumbers, enhanceError, executeCommand, + numberError, waitUntil, - wrapExpectedWithArray } from '../../utils.js' -async function condition(el: WebdriverIO.Element, height: number) { +async function condition(el: WebdriverIO.Element, height: number, options: ExpectWebdriverIO.NumberOptions) { const actualHeight = await el.getSize('height') return { - value: actualHeight, - result: actualHeight === height, + result: compareNumbers(actualHeight, options), + value: actualHeight } } export async function toHaveHeight( received: WdioElementMaybePromise, - expectedValue: number, + expectedValue: number | ExpectWebdriverIO.NumberOptions, options: ExpectWebdriverIO.CommandOptions = DEFAULT_OPTIONS ) { const isNot = this.isNot @@ -30,12 +31,22 @@ export async function toHaveHeight( options, }) + // type check + let numberOptions: ExpectWebdriverIO.NumberOptions + if (typeof expectedValue === 'number') { + numberOptions = { eq: expectedValue } as ExpectWebdriverIO.NumberOptions + } else if (!expectedValue || (typeof expectedValue.eq !== 'number' && typeof expectedValue.gte !== 'number' && typeof expectedValue.lte !== 'number')) { + throw new Error('Invalid params passed to toHaveHeight.') + } else { + numberOptions = expectedValue + } + let el = await received?.getElement() let actualHeight const pass = await waitUntil( async () => { - const result = await executeCommand.call(this, el, condition, options, [expectedValue, options]) + const result = await executeCommand.call(this, el, condition, numberOptions, [expectedValue, numberOptions]) el = result.el as WebdriverIO.Element actualHeight = result.values @@ -43,12 +54,13 @@ export async function toHaveHeight( return result.success }, isNot, - options + { ...numberOptions, ...options } ) + const error = numberError(numberOptions) const message = enhanceError( el, - wrapExpectedWithArray(el, actualHeight, expectedValue), + error, actualHeight, this, verb, diff --git a/src/matchers/element/toHaveWidth.ts b/src/matchers/element/toHaveWidth.ts index 011bb0682..6f706ffcb 100644 --- a/src/matchers/element/toHaveWidth.ts +++ b/src/matchers/element/toHaveWidth.ts @@ -1,24 +1,25 @@ import { DEFAULT_OPTIONS } from '../../constants.js' import type { WdioElementMaybePromise } from '../../types.js' import { + compareNumbers, enhanceError, executeCommand, + numberError, waitUntil, - wrapExpectedWithArray } from '../../utils.js' -async function condition(el: WebdriverIO.Element, width: number) { +async function condition(el: WebdriverIO.Element, width: number, options: ExpectWebdriverIO.NumberOptions) { const actualWidth = await el.getSize('width') return { - value: actualWidth, - result: actualWidth === width, + result: compareNumbers(actualWidth, options), + value: actualWidth } } export async function toHaveWidth( received: WdioElementMaybePromise, - expectedValue: number, + expectedValue: number | ExpectWebdriverIO.NumberOptions, options: ExpectWebdriverIO.CommandOptions = DEFAULT_OPTIONS ) { const isNot = this.isNot @@ -30,12 +31,22 @@ export async function toHaveWidth( options, }) + // type check + let numberOptions: ExpectWebdriverIO.NumberOptions + if (typeof expectedValue === 'number') { + numberOptions = { eq: expectedValue } as ExpectWebdriverIO.NumberOptions + } else if (!expectedValue || (typeof expectedValue.eq !== 'number' && typeof expectedValue.gte !== 'number' && typeof expectedValue.lte !== 'number')) { + throw new Error('Invalid params passed to toHaveHeight.') + } else { + numberOptions = expectedValue + } + let el = await received?.getElement() let actualWidth const pass = await waitUntil( async () => { - const result = await executeCommand.call(this, el, condition, options, [expectedValue, options]) + const result = await executeCommand.call(this, el, condition, numberOptions, [expectedValue, numberOptions]) el = result.el as WebdriverIO.Element actualWidth = result.values @@ -43,12 +54,13 @@ export async function toHaveWidth( return result.success }, isNot, - options + { ...numberOptions, ...options } ) + const error = numberError(numberOptions) const message = enhanceError( el, - wrapExpectedWithArray(el, actualWidth, expectedValue), + error, actualWidth, this, verb, diff --git a/test/matchers/element/toHaveHeight.test.ts b/test/matchers/element/toHaveHeight.test.ts index 8c504aecc..4102a92ad 100755 --- a/test/matchers/element/toHaveHeight.test.ts +++ b/test/matchers/element/toHaveHeight.test.ts @@ -109,6 +109,25 @@ describe('toHaveHeight', () => { expect(el._attempts).toBe(1) }) + test('gte and lte', async () => { + const el: any = await $('sel') + el._attempts = 0 + el._size = function (property?: 'width' | 'height') { + this._attempts++ + if (property === 'width') { + return 50 + } + if (property === 'height') { + return 32 + } + return { width: 50, height: 32 } + } + + const result = await toHaveHeight.call({}, el, { gte: 31, lte: 33 }, { wait: 0 }) + expect(result.pass).toBe(true) + expect(el._attempts).toBe(1) + }) + test('not - failure', async () => { const el: any = await $('sel') el._size = function (property?: 'width' | 'height') { diff --git a/test/matchers/element/toHaveWidth.test.ts b/test/matchers/element/toHaveWidth.test.ts index c6ea86724..27cfe26ce 100755 --- a/test/matchers/element/toHaveWidth.test.ts +++ b/test/matchers/element/toHaveWidth.test.ts @@ -109,6 +109,25 @@ describe('toHaveWidth', () => { expect(el._attempts).toBe(1) }) + test('gte and lte', async () => { + const el: any = await $('sel') + el._attempts = 0 + el._size = function (property?: 'width' | 'height') { + this._attempts++ + if (property === 'width') { + return 50 + } + if (property === 'height') { + return 32 + } + return { width: 50, height: 32 } + } + + const result = await toHaveWidth.call({}, el, { gte: 49, lte: 51 }, { wait: 0 }) + expect(result.pass).toBe(true) + expect(el._attempts).toBe(1) + }) + test('not - failure', async () => { const el: any = await $('sel') el._size = function (property?: 'width' | 'height') { From c859514407f0f17cc2c1cbec072fedfe5bf47676 Mon Sep 17 00:00:00 2001 From: Erwin Heitzman <15839059+erwinheitzman@users.noreply.github.com> Date: Thu, 5 Jun 2025 17:15:08 +0200 Subject: [PATCH 2/2] feature(expect-webdriverio): add number options support for toHaveHeight and toHaveWidth --- test/matchers/element/toHaveHeight.test.ts | 3 +++ test/matchers/element/toHaveWidth.test.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/test/matchers/element/toHaveHeight.test.ts b/test/matchers/element/toHaveHeight.test.ts index 4102a92ad..fae9b55a0 100755 --- a/test/matchers/element/toHaveHeight.test.ts +++ b/test/matchers/element/toHaveHeight.test.ts @@ -67,6 +67,7 @@ describe('toHaveHeight', () => { } const result = await toHaveHeight.call({}, el, 32, {}) + expect(result.message()).toEqual('Expect $(`sel`) to have height\n\nExpected: 32\nReceived: serializes to the same string') expect(result.pass).toBe(true) expect(el._attempts).toBe(1) }) @@ -86,6 +87,7 @@ describe('toHaveHeight', () => { } const result = await toHaveHeight.call({}, el, 10, { wait: 0 }) + expect(result.message()).toEqual('Expect $(`sel`) to have height\n\nExpected: 10\nReceived: 32') expect(result.pass).toBe(false) expect(el._attempts).toBe(1) }) @@ -124,6 +126,7 @@ describe('toHaveHeight', () => { } const result = await toHaveHeight.call({}, el, { gte: 31, lte: 33 }, { wait: 0 }) + expect(result.message()).toEqual('Expect $(`sel`) to have height\n\nExpected: ">= 31 && <= 33"\nReceived: 32') expect(result.pass).toBe(true) expect(el._attempts).toBe(1) }) diff --git a/test/matchers/element/toHaveWidth.test.ts b/test/matchers/element/toHaveWidth.test.ts index 27cfe26ce..14bae40e2 100755 --- a/test/matchers/element/toHaveWidth.test.ts +++ b/test/matchers/element/toHaveWidth.test.ts @@ -67,6 +67,7 @@ describe('toHaveWidth', () => { } const result = await toHaveWidth.call({}, el, 50, {}) + expect(result.message()).toEqual('Expect $(`sel`) to have width\n\nExpected: 50\nReceived: serializes to the same string') expect(result.pass).toBe(true) expect(el._attempts).toBe(1) }) @@ -86,6 +87,7 @@ describe('toHaveWidth', () => { } const result = await toHaveWidth.call({}, el, 10, { wait: 0 }) + expect(result.message()).toEqual('Expect $(`sel`) to have width\n\nExpected: 10\nReceived: 50') expect(result.pass).toBe(false) expect(el._attempts).toBe(1) }) @@ -124,6 +126,7 @@ describe('toHaveWidth', () => { } const result = await toHaveWidth.call({}, el, { gte: 49, lte: 51 }, { wait: 0 }) + expect(result.message()).toEqual('Expect $(`sel`) to have width\n\nExpected: ">= 49 && <= 51"\nReceived: 50') expect(result.pass).toBe(true) expect(el._attempts).toBe(1) })