From 242e8494109abd187a24039929fae74d4f9a9b9a Mon Sep 17 00:00:00 2001 From: dprevost-perso Date: Fri, 30 Jan 2026 08:34:03 -0500 Subject: [PATCH 1/8] Revert "fix: isNot was not always properly considered (#1983)" This reverts commit 084d4a62d999349f61e9b23889f1c0ec1513ea27. --- src/matchers/browser/toHaveClipboardText.ts | 3 +- src/matchers/browser/toHaveTitle.ts | 3 +- src/matchers/browser/toHaveUrl.ts | 3 +- src/matchers/element/toHaveAttribute.ts | 14 +- src/matchers/element/toHaveChildren.ts | 3 +- src/matchers/element/toHaveClass.ts | 3 +- src/matchers/element/toHaveComputedLabel.ts | 2 + src/matchers/element/toHaveComputedRole.ts | 2 + src/matchers/element/toHaveElementProperty.ts | 9 +- src/matchers/element/toHaveHTML.ts | 3 +- src/matchers/element/toHaveHeight.ts | 2 + src/matchers/element/toHaveSize.ts | 2 + src/matchers/element/toHaveStyle.ts | 3 +- src/matchers/element/toHaveText.ts | 3 +- src/matchers/element/toHaveWidth.ts | 2 + .../elements/toBeElementsArrayOfSize.ts | 3 +- src/matchers/mock/toBeRequestedTimes.ts | 3 +- src/matchers/mock/toBeRequestedWith.ts | 4 +- src/utils.ts | 56 ++- test/__fixtures__/utils.ts | 5 - test/__mocks__/@wdio/globals.ts | 3 +- test/matchers.test.ts | 386 +----------------- test/matchers/beMatchers.test.ts | 44 +- test/matchers/browserMatchers.test.ts | 43 +- test/matchers/element/toBeDisabled.test.ts | 47 +-- test/matchers/element/toBeDisplayed.test.ts | 44 +- test/matchers/element/toHaveChildren.test.ts | 16 +- .../element/toHaveComputedLabel.test.ts | 22 +- .../element/toHaveComputedRole.test.ts | 22 +- .../element/toHaveElementProperty.test.ts | 122 +----- test/matchers/element/toHaveHTML.test.ts | 42 +- test/matchers/element/toHaveHeight.test.ts | 24 +- test/matchers/element/toHaveId.test.ts | 6 +- test/matchers/element/toHaveSize.test.ts | 21 +- test/matchers/element/toHaveStyle.test.ts | 42 +- test/matchers/element/toHaveText.test.ts | 46 +-- test/matchers/element/toHaveWidth.test.ts | 24 +- .../elements/toBeElementsArrayOfSize.test.ts | 34 +- test/matchers/mock/toBeRequested.test.ts | 30 +- test/matchers/mock/toBeRequestedTimes.test.ts | 40 +- test/matchers/mock/toBeRequestedWith.test.ts | 14 +- test/softAssertions.test.ts | 39 +- test/utils.test.ts | 96 +---- types/expect-webdriverio.d.ts | 13 +- 44 files changed, 313 insertions(+), 1035 deletions(-) diff --git a/src/matchers/browser/toHaveClipboardText.ts b/src/matchers/browser/toHaveClipboardText.ts index bcf863e7d..00b023408 100644 --- a/src/matchers/browser/toHaveClipboardText.ts +++ b/src/matchers/browser/toHaveClipboardText.ts @@ -10,6 +10,7 @@ export async function toHaveClipboardText( expectedValue: string | RegExp | WdioAsymmetricMatcher, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'clipboard text', verb = 'have' } = this await options.beforeAssertion?.({ @@ -27,7 +28,7 @@ export async function toHaveClipboardText( .catch((err) => log.warn(`Couldn't set clipboard permissions: ${err}`)) actual = await browser.execute(() => window.navigator.clipboard.readText()) return compareText(actual, expectedValue, options).result - }, options) + }, isNot, options) const message = enhanceError('browser', expectedValue, actual, this, verb, expectation, '', options) const result: ExpectWebdriverIO.AssertionResult = { diff --git a/src/matchers/browser/toHaveTitle.ts b/src/matchers/browser/toHaveTitle.ts index 236c55d4d..4c18dd7f8 100644 --- a/src/matchers/browser/toHaveTitle.ts +++ b/src/matchers/browser/toHaveTitle.ts @@ -6,6 +6,7 @@ export async function toHaveTitle( expectedValue: string | RegExp | WdioAsymmetricMatcher, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'title', verb = 'have' } = this await options.beforeAssertion?.({ @@ -19,7 +20,7 @@ export async function toHaveTitle( actual = await browser.getTitle() return compareText(actual, expectedValue, options).result - }, options) + }, isNot, options) const message = enhanceError('window', expectedValue, actual, this, verb, expectation, '', options) const result: ExpectWebdriverIO.AssertionResult = { diff --git a/src/matchers/browser/toHaveUrl.ts b/src/matchers/browser/toHaveUrl.ts index 3dcc50195..06719ac5d 100644 --- a/src/matchers/browser/toHaveUrl.ts +++ b/src/matchers/browser/toHaveUrl.ts @@ -6,6 +6,7 @@ export async function toHaveUrl( expectedValue: string | RegExp | WdioAsymmetricMatcher, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'url', verb = 'have' } = this await options.beforeAssertion?.({ @@ -19,7 +20,7 @@ export async function toHaveUrl( actual = await browser.getUrl() return compareText(actual, expectedValue, options).result - }, options) + }, isNot, options) const message = enhanceError('window', expectedValue, actual, this, verb, expectation, '', options) const result: ExpectWebdriverIO.AssertionResult = { diff --git a/src/matchers/element/toHaveAttribute.ts b/src/matchers/element/toHaveAttribute.ts index c6ca5ac9d..9d42293e7 100644 --- a/src/matchers/element/toHaveAttribute.ts +++ b/src/matchers/element/toHaveAttribute.ts @@ -27,6 +27,7 @@ async function conditionAttrAndValue(el: WebdriverIO.Element, attribute: string, } export async function toHaveAttributeAndValue(received: WdioElementMaybePromise, attribute: string, value: string | RegExp | WdioAsymmetricMatcher, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS) { + const isNot = this.isNot const { expectation = 'attribute', verb = 'have' } = this let el = await received?.getElement() @@ -37,7 +38,7 @@ export async function toHaveAttributeAndValue(received: WdioElementMaybePromise, attr = result.values return result.success - }, options) + }, isNot, options) const expected = wrapExpectedWithArray(el, attr, value) const message = enhanceError(el, expected, attr, this, verb, expectation, attribute, options) @@ -48,8 +49,9 @@ export async function toHaveAttributeAndValue(received: WdioElementMaybePromise, } as ExpectWebdriverIO.AssertionResult } -async function toHaveAttributeFn(received: WdioElementMaybePromise, attribute: string, options: ExpectWebdriverIO.StringOptions) { - const { expectation = 'attribute', verb = 'have', isNot } = this +async function toHaveAttributeFn(received: WdioElementMaybePromise, attribute: string) { + const isNot = this.isNot + const { expectation = 'attribute', verb = 'have' } = this let el = await received?.getElement() @@ -58,9 +60,9 @@ async function toHaveAttributeFn(received: WdioElementMaybePromise, attribute: s el = result.el as WebdriverIO.Element return result.success - }, options) + }, isNot, {}) - const message = enhanceError(el, !isNot, pass, this, verb, expectation, attribute, options) + const message = enhanceError(el, !isNot, pass, this, verb, expectation, attribute, {}) return { pass, @@ -84,7 +86,7 @@ export async function toHaveAttribute( // Name and value is passed in e.g. el.toHaveAttribute('attr', 'value', (opts)) ? await toHaveAttributeAndValue.call(this, received, attribute, value, options) // Only name is passed in e.g. el.toHaveAttribute('attr') - : await toHaveAttributeFn.call(this, received, attribute, options) + : await toHaveAttributeFn.call(this, received, attribute) await options.afterAssertion?.({ matcherName: 'toHaveAttribute', diff --git a/src/matchers/element/toHaveChildren.ts b/src/matchers/element/toHaveChildren.ts index 712070e55..f9b035965 100644 --- a/src/matchers/element/toHaveChildren.ts +++ b/src/matchers/element/toHaveChildren.ts @@ -35,6 +35,7 @@ export async function toHaveChildren( expectedValue?: number | ExpectWebdriverIO.NumberOptions, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'children', verb = 'have' } = this await options.beforeAssertion?.({ @@ -55,7 +56,7 @@ export async function toHaveChildren( children = result.values return result.success - }, { ...numberOptions, ...options }) + }, isNot, { ...numberOptions, ...options }) const error = numberError(numberOptions) const expectedArray = wrapExpectedWithArray(el, children, error) diff --git a/src/matchers/element/toHaveClass.ts b/src/matchers/element/toHaveClass.ts index 0c7d53b9d..864d4ad04 100644 --- a/src/matchers/element/toHaveClass.ts +++ b/src/matchers/element/toHaveClass.ts @@ -42,6 +42,7 @@ export async function toHaveElementClass( expectedValue: string | RegExp | Array | WdioAsymmetricMatcher, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'class', verb = 'have' } = this await options.beforeAssertion?.({ @@ -61,7 +62,7 @@ export async function toHaveElementClass( attr = result.values return result.success - }, options) + }, isNot, options) const message = enhanceError(el, wrapExpectedWithArray(el, attr, expectedValue), attr, this, verb, expectation, '', options) const result: ExpectWebdriverIO.AssertionResult = { diff --git a/src/matchers/element/toHaveComputedLabel.ts b/src/matchers/element/toHaveComputedLabel.ts index f5729919c..50e2a9324 100644 --- a/src/matchers/element/toHaveComputedLabel.ts +++ b/src/matchers/element/toHaveComputedLabel.ts @@ -26,6 +26,7 @@ export async function toHaveComputedLabel( expectedValue: string | RegExp | WdioAsymmetricMatcher | Array, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'computed label', verb = 'have' } = this await options.beforeAssertion?.({ @@ -45,6 +46,7 @@ export async function toHaveComputedLabel( return result.success }, + isNot, options ) diff --git a/src/matchers/element/toHaveComputedRole.ts b/src/matchers/element/toHaveComputedRole.ts index 72209acb4..916506b97 100644 --- a/src/matchers/element/toHaveComputedRole.ts +++ b/src/matchers/element/toHaveComputedRole.ts @@ -26,6 +26,7 @@ export async function toHaveComputedRole( expectedValue: string | RegExp | WdioAsymmetricMatcher | Array, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'computed role', verb = 'have' } = this await options.beforeAssertion?.({ @@ -45,6 +46,7 @@ export async function toHaveComputedRole( return result.success }, + isNot, options ) diff --git a/src/matchers/element/toHaveElementProperty.ts b/src/matchers/element/toHaveElementProperty.ts index 68456901a..cdf4146b0 100644 --- a/src/matchers/element/toHaveElementProperty.ts +++ b/src/matchers/element/toHaveElementProperty.ts @@ -17,13 +17,10 @@ async function condition( const { asString = false } = options let prop = await el.getProperty(property) - - // As specified in the w3c spec, cases where property does not exist if (prop === null || prop === undefined) { return { result: false, value: prop } } - // As specified in the w3c spec, cases where property simply exists, missing undefined here? if (value === null) { return { result: true, value: prop } } @@ -39,10 +36,11 @@ async function condition( export async function toHaveElementProperty( received: WdioElementMaybePromise, property: string, - value?: string | RegExp | WdioAsymmetricMatcher | null, + value?: string | RegExp | WdioAsymmetricMatcher, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { - const { expectation = 'property', verb = 'have', isNot } = this + const isNot = this.isNot + const { expectation = 'property', verb = 'have' } = this await options.beforeAssertion?.({ matcherName: 'toHaveElementProperty', @@ -60,6 +58,7 @@ export async function toHaveElementProperty( return result.success }, + isNot, options ) diff --git a/src/matchers/element/toHaveHTML.ts b/src/matchers/element/toHaveHTML.ts index d8ceafb52..1f7b976e2 100644 --- a/src/matchers/element/toHaveHTML.ts +++ b/src/matchers/element/toHaveHTML.ts @@ -22,6 +22,7 @@ export async function toHaveHTML( expectedValue: string | RegExp | WdioAsymmetricMatcher | Array, options: ExpectWebdriverIO.HTMLOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'HTML', verb = 'have' } = this await options.beforeAssertion?.({ @@ -43,7 +44,7 @@ export async function toHaveHTML( actualHTML = result.values return result.success - }, options) + }, isNot, options) const message = enhanceError(el, wrapExpectedWithArray(el, actualHTML, expectedValue), actualHTML, this, verb, expectation, '', options) diff --git a/src/matchers/element/toHaveHeight.ts b/src/matchers/element/toHaveHeight.ts index 5666b3338..0905d2183 100644 --- a/src/matchers/element/toHaveHeight.ts +++ b/src/matchers/element/toHaveHeight.ts @@ -22,6 +22,7 @@ export async function toHaveHeight( expectedValue: number | ExpectWebdriverIO.NumberOptions, options: ExpectWebdriverIO.CommandOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'height', verb = 'have' } = this await options.beforeAssertion?.({ @@ -52,6 +53,7 @@ export async function toHaveHeight( return result.success }, + isNot, { ...numberOptions, ...options } ) diff --git a/src/matchers/element/toHaveSize.ts b/src/matchers/element/toHaveSize.ts index 0976e7cc7..a2d2cc80a 100644 --- a/src/matchers/element/toHaveSize.ts +++ b/src/matchers/element/toHaveSize.ts @@ -19,6 +19,7 @@ export async function toHaveSize( expectedValue: { height: number; width: number }, options: ExpectWebdriverIO.CommandOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'size', verb = 'have' } = this await options.beforeAssertion?.({ @@ -39,6 +40,7 @@ export async function toHaveSize( return result.success }, + isNot, options ) diff --git a/src/matchers/element/toHaveStyle.ts b/src/matchers/element/toHaveStyle.ts index d13c032dc..5d61baf41 100644 --- a/src/matchers/element/toHaveStyle.ts +++ b/src/matchers/element/toHaveStyle.ts @@ -17,6 +17,7 @@ export async function toHaveStyle( expectedValue: { [key: string]: string; }, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'style', verb = 'have' } = this await options.beforeAssertion?.({ @@ -34,7 +35,7 @@ export async function toHaveStyle( actualStyle = result.values return result.success - }, options) + }, isNot, options) const message = enhanceError(el, wrapExpectedWithArray(el, actualStyle, expectedValue), actualStyle, this, verb, expectation, '', options) diff --git a/src/matchers/element/toHaveText.ts b/src/matchers/element/toHaveText.ts index edcab174e..82d4450c7 100644 --- a/src/matchers/element/toHaveText.ts +++ b/src/matchers/element/toHaveText.ts @@ -42,6 +42,7 @@ export async function toHaveText( expectedValue: string | RegExp | WdioAsymmetricMatcher | Array, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'text', verb = 'have' } = this await options.beforeAssertion?.({ @@ -63,7 +64,7 @@ export async function toHaveText( actualText = result.values return result.success - }, options) + }, isNot, options) const message = enhanceError(el, wrapExpectedWithArray(el, actualText, expectedValue), actualText, this, verb, expectation, '', options) const result: ExpectWebdriverIO.AssertionResult = { diff --git a/src/matchers/element/toHaveWidth.ts b/src/matchers/element/toHaveWidth.ts index 42eb8c0ad..6f706ffcb 100644 --- a/src/matchers/element/toHaveWidth.ts +++ b/src/matchers/element/toHaveWidth.ts @@ -22,6 +22,7 @@ export async function toHaveWidth( expectedValue: number | ExpectWebdriverIO.NumberOptions, options: ExpectWebdriverIO.CommandOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'width', verb = 'have' } = this await options.beforeAssertion?.({ @@ -52,6 +53,7 @@ export async function toHaveWidth( return result.success }, + isNot, { ...numberOptions, ...options } ) diff --git a/src/matchers/elements/toBeElementsArrayOfSize.ts b/src/matchers/elements/toBeElementsArrayOfSize.ts index 9e3621a49..39cd07ccd 100644 --- a/src/matchers/elements/toBeElementsArrayOfSize.ts +++ b/src/matchers/elements/toBeElementsArrayOfSize.ts @@ -8,6 +8,7 @@ export async function toBeElementsArrayOfSize( expectedValue: number | ExpectWebdriverIO.NumberOptions, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot const { expectation = 'elements array of size', verb = 'be' } = this await options.beforeAssertion?.({ @@ -37,7 +38,7 @@ export async function toBeElementsArrayOfSize( } elements = await refetchElements(elements, numberOptions.wait, true) return false - }, { ...numberOptions, ...options }) + }, isNot, { ...numberOptions, ...options }) if (Array.isArray(received) && pass) { for (let index = originalLength; index < elements.length; index++) { diff --git a/src/matchers/mock/toBeRequestedTimes.ts b/src/matchers/mock/toBeRequestedTimes.ts index e6953dd22..0a50b3af7 100644 --- a/src/matchers/mock/toBeRequestedTimes.ts +++ b/src/matchers/mock/toBeRequestedTimes.ts @@ -7,6 +7,7 @@ export async function toBeRequestedTimes( expectedValue: number | ExpectWebdriverIO.NumberOptions = {}, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { + const isNot = this.isNot || false const { expectation = `called${typeof expectedValue === 'number' ? ' ' + expectedValue : '' } time${expectedValue !== 1 ? 's' : ''}`, verb = 'be' } = this await options.beforeAssertion?.({ @@ -24,7 +25,7 @@ export async function toBeRequestedTimes( const pass = await waitUntil(async () => { actual = received.calls.length return compareNumbers(actual, numberOptions) - }, { ...numberOptions, ...options }) + }, isNot, { ...numberOptions, ...options }) const error = numberError(numberOptions) const message = enhanceError('mock', error, actual, this, verb, expectation, '', numberOptions) diff --git a/src/matchers/mock/toBeRequestedWith.ts b/src/matchers/mock/toBeRequestedWith.ts index c735c4057..338fb83df 100644 --- a/src/matchers/mock/toBeRequestedWith.ts +++ b/src/matchers/mock/toBeRequestedWith.ts @@ -24,7 +24,8 @@ export async function toBeRequestedWith( expectedValue: ExpectWebdriverIO.RequestedWith = {}, options: ExpectWebdriverIO.CommandOptions = DEFAULT_OPTIONS ) { - const { expectation = 'called with', verb = 'be', isNot } = this + const isNot = this.isNot || false + const { expectation = 'called with', verb = 'be' } = this await options.beforeAssertion?.({ matcherName: 'toBeRequestedWith', @@ -53,6 +54,7 @@ export async function toBeRequestedWith( return false }, + isNot, { ...options, wait: isNot ? 0 : options.wait } ) diff --git a/src/utils.ts b/src/utils.ts index 8720b86cc..3987241ab 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -39,32 +39,49 @@ function isStringContainingMatcher(expected: unknown): expected is WdioAsymmetri */ const waitUntil = async ( condition: () => Promise, + isNot = false, { wait = DEFAULT_OPTIONS.wait, interval = DEFAULT_OPTIONS.interval } = {} ): Promise => { - const start = Date.now() - let error: unknown - let result = false - - do { - try { - result = await condition() - error = undefined - if (result) { - break + // single attempt + if (wait === 0) { + return await condition() + } + + let error: Error | undefined + + // wait for condition to be truthy + try { + const start = Date.now() + while (true) { + if (Date.now() - start > wait) { + throw new Error('timeout') + } + + try { + const result = isNot !== (await condition()) + error = undefined + if (result) { + break + } + await sleep(interval) + } catch (err) { + error = err + await sleep(interval) } - } catch (err) { - error = err } - // No need to sleep again if time is already over - if (Date.now() - start < wait) { - await sleep(interval) + if (error) { + throw error } - } while (Date.now() - start < wait) - if (error) { throw error } + return !isNot + } catch { + if (error) { + throw error + } - return result + return isNot + } } async function executeCommandBe( @@ -72,7 +89,7 @@ async function executeCommandBe( command: (el: WebdriverIO.Element) => Promise, options: ExpectWebdriverIO.CommandOptions ): ExpectWebdriverIO.AsyncAssertionResult { - const { expectation, verb = 'be' } = this + const { isNot, expectation, verb = 'be' } = this let el = await received?.getElement() const pass = await waitUntil( @@ -86,6 +103,7 @@ async function executeCommandBe( el = result.el as WebdriverIO.Element return result.success }, + isNot, options ) diff --git a/test/__fixtures__/utils.ts b/test/__fixtures__/utils.ts index 95bca72a4..1149d08d0 100644 --- a/test/__fixtures__/utils.ts +++ b/test/__fixtures__/utils.ts @@ -2,11 +2,6 @@ export function matcherNameToString(matcherName: string) { return matcherName.replace(/([A-Z])/g, ' $1').toLowerCase() } -export function matcherLastWordName(matcherName: string) { - return matcherName.replace(/^toHave/, '').replace(/^toBe/, '') - .replace(/([A-Z])/g, ' $1').trim().toLowerCase() -} - export function getExpectMessage(msg: string) { return msg.split('\n')[0] } diff --git a/test/__mocks__/@wdio/globals.ts b/test/__mocks__/@wdio/globals.ts index d9f4a3b4a..d14bc54d0 100644 --- a/test/__mocks__/@wdio/globals.ts +++ b/test/__mocks__/@wdio/globals.ts @@ -15,7 +15,7 @@ const getElementMethods = () => ({ isClickable: vi.spyOn({ isClickable: async () => true }, 'isClickable'), isFocused: vi.spyOn({ isFocused: async () => true }, 'isFocused'), isEnabled: vi.spyOn({ isEnabled: async () => true }, 'isEnabled'), - getProperty: vi.spyOn({ getProperty: async (_prop: string) => '1' }, 'getProperty'), + getProperty: vi.spyOn({ getProperty: async (_prop: string) => undefined }, 'getProperty'), getText: vi.spyOn({ getText: async () => ' Valid Text ' }, 'getText'), getHTML: vi.spyOn({ getHTML: async () => { return '' } }, 'getHTML'), getComputedLabel: vi.spyOn({ getComputedLabel: async () => 'Computed Label' }, 'getComputedLabel'), @@ -25,7 +25,6 @@ const getElementMethods = () => ({ if (prop === 'height') { return 50 } return { width: 100, height: 50 } satisfies Size } }, 'getSize') as unknown as WebdriverIO.Element['getSize'], - getAttribute: vi.spyOn({ getAttribute: async (_attr: string) => 'some attribute' }, 'getAttribute'), } satisfies Partial) function $(_selector: string) { diff --git a/test/matchers.test.ts b/test/matchers.test.ts index 7cb19b8aa..f96455a01 100644 --- a/test/matchers.test.ts +++ b/test/matchers.test.ts @@ -1,8 +1,5 @@ -import { test, expect, vi, describe } from 'vitest' +import { test, expect, vi } from 'vitest' import { matchers, expect as expectLib } from '../src/index.js' -import { $ } from '@wdio/globals' - -vi.mock('@wdio/globals') const ALL_MATCHERS = [ // browser @@ -74,384 +71,3 @@ test('Generic asymmetric matchers from Expect library should work', () => { expectLib(1).toEqual(expectLib.closeTo(1.0001, 0.0001)) expectLib(['apple', 'banana', 'cherry']).toEqual(expectLib.arrayOf(expectLib.any(String))) }) - -describe('Custom Wdio Matchers Integration Tests', async () => { - - describe('Matchers pass with success with default mocked values', async () => { - const el = await $('selector') - - test('toBe matchers', async () => { - await expectLib(el).toBeDisplayed() - await expectLib(el).toBeExisting() - await expectLib(el).toBeEnabled() - await expectLib(el).toBeClickable() - await expectLib(el).toBeFocused() - await expectLib(el).toBeSelected() - }) - - test('toHave matchers', async () => { - await expectLib(el).toHaveText('Valid Text') - await expectLib(el).toHaveHTML('') - await expectLib(el).toHaveComputedLabel('Computed Label') - await expectLib(el).toHaveComputedRole('Computed Role') - await expectLib(el).toHaveSize({ width: 100, height: 50 }) - await expectLib(el).toHaveHeight(50) - await expectLib(el).toHaveWidth(100) - await expectLib(el).toHaveAttribute('someAttribute', 'some attribute') - await expectLib(el).toHaveAttribute('someAttribute') - await expectLib(el).toHaveAttr('someAttribute', 'some attribute') - await expectLib(el).toHaveElementProperty('someProperty', '1') - }) - }) - - describe('Matchers fails when using `.not` with proper message', async () => { - const el = await $('selector') - - test('toBe matchers', async () => { - await expect(() => expectLib(el).not.toBeDisplayed({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to be displayed - -Expected [not]: "not displayed" -Received : "displayed"` - ) - - await expect(() => expectLib(el).not.toBeExisting({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to be existing - -Expected [not]: "not existing" -Received : "existing"` - ) - - await expect(() => expectLib(el).not.toBeEnabled({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to be enabled - -Expected [not]: "not enabled" -Received : "enabled"` - ) - - await expect(() => expectLib(el).not.toBeClickable({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to be clickable - -Expected [not]: "not clickable" -Received : "clickable"` - ) - - await expect(() => expectLib(el).not.toBeFocused({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to be focused - -Expected [not]: "not focused" -Received : "focused"` - ) - - await expect(() => expectLib(el).not.toBeSelected({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to be selected - -Expected [not]: "not selected" -Received : "selected"` - ) - }) - - test('toHave matchers', async () => { - await expect(() => expectLib(el).not.toHaveText(' Valid Text ', { trim: false, wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to have text - -Expected [not]: " Valid Text " -Received : " Valid Text "` - ) - - await expect(() => expectLib(el).not.toHaveHTML('', { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to have HTML - -Expected [not]: "" -Received : ""` - ) - - await expect(() => expectLib(el).not.toHaveComputedLabel('Computed Label', { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to have computed label - -Expected [not]: "Computed Label" -Received : "Computed Label"` - ) - - await expect(() => expectLib(el).not.toHaveComputedRole('Computed Role', { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to have computed role - -Expected [not]: "Computed Role" -Received : "Computed Role"` - ) - }) - - test('size matchers', async () => { - await expect(() => expectLib(el).not.toHaveSize({ width: 100, height: 50 }, { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to have size - -Expected [not]: {"height": 50, "width": 100} -Received : {"height": 50, "width": 100}` - ) - - await expect(() => expectLib(el).not.toHaveHeight(50, { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to have height - -Expected [not]: 50 -Received : 50` - ) - - await expect(() => expectLib(el).not.toHaveWidth(100, { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to have width - -Expected [not]: 100 -Received : 100` - ) - }) - - test('attribute and property matchers', async () => { - await expect(() => expectLib(el).not.toHaveAttribute('someAttribute', 'some attribute')).rejects.toThrow(`\ -Expect $(\`selector\`) not to have attribute someAttribute - -Expected [not]: "some attribute" -Received : "some attribute"` - ) - - await expect(() => expectLib(el).not.toHaveAttribute('someAttribute')).rejects.toThrow(`\ -Expect $(\`selector\`) not to have attribute someAttribute - -Expected [not]: false -Received : true` - ) - - await expect(() => expectLib(el).not.toHaveAttr('someAttribute', 'some attribute')).rejects.toThrow(`\ -Expect $(\`selector\`) not to have attribute someAttribute - -Expected [not]: "some attribute" -Received : "some attribute"` - ) - await expect(() => expectLib(el).not.toHaveElementProperty('someProperty', '1')).rejects.toThrow(`\ -Expect $(\`selector\`) not to have property someProperty - -Expected [not]: "1" -Received : "1"` - ) - }) - }) - - describe('Matchers fails with proper messages', async () => { - const el = await $('selector') - vi.mocked(el.isDisplayed).mockResolvedValue(false) - vi.mocked(el.isExisting).mockResolvedValue(false) - vi.mocked(el.isEnabled).mockResolvedValue(false) - vi.mocked(el.isClickable).mockResolvedValue(false) - vi.mocked(el.isFocused).mockResolvedValue(false) - vi.mocked(el.isSelected).mockResolvedValue(false) - - test('Ensure toBe matchers throws and show proper failing message', async () => { - await expect(() => expectLib(el).toBeDisplayed({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to be displayed - -Expected: "displayed" -Received: "not displayed"`) - - await expect(() => expectLib(el).toBeExisting({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to be existing - -Expected: "existing" -Received: "not existing"`) - - await expect(() => expectLib(el).toExist({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to exist - -Expected: "exist" -Received: "not exist"`) - - await expect(() => expectLib(el).toBeEnabled({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to be enabled - -Expected: "enabled" -Received: "not enabled"`) - - await expect(() => expectLib(el).toBeClickable({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to be clickable - -Expected: "clickable" -Received: "not clickable"`) - - await expect(() => expectLib(el).toBeFocused({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to be focused - -Expected: "focused" -Received: "not focused"`) - - await expect(() => expectLib(el).toBeSelected({ wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to be selected - -Expected: "selected" -Received: "not selected"`) - - }) - - test('Ensure toHave matchers throws and show proper failing message', async () => { - await expect(() => expectLib(el).toHaveText('Some other text', { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to have text - -Expected: "Some other text" -Received: " Valid Text "`) - - await expect(() => expectLib(el).toHaveHTML('', { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to have HTML - -Expected: "" -Received: ""`) - - await expect(() => expectLib(el).toHaveComputedLabel('Some Other Computed Label', { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to have computed label - -Expected: "Some Other Computed Label" -Received: "Computed Label"`) - - await expect(() => expectLib(el).toHaveComputedRole('Some Other Computed Role', { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to have computed role - -Expected: "Some Other Computed Role" -Received: "Computed Role"`) - - await expect(() => expectLib(el).toHaveElementProperty('someProperty', 'some other value', { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to have property someProperty - -Expected: "some other value" -Received: "1"`) - - }) - - test('Ensure toHaveAttribute matchers throw and show proper failing message', async () => { - await expect(() => expectLib(el).toHaveAttribute('someAttribute', 'some other attribute', { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to have attribute someAttribute - -Expected: "some other attribute" -Received: "some attribute"`) - - vi.mocked(el.getAttribute).mockResolvedValue(null as unknown as string) - await expect(() => expectLib(el).toHaveAttribute('notExistingAttribute')).rejects.toThrow(`\ -Expect $(\`selector\`) to have attribute notExistingAttribute - -Expected: true -Received: false`) - await expect(() => expectLib(el).toHaveAttr('someAttribute', 'some other attribute', { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to have attribute someAttribute - -Expected: "some other attribute" -Received: null`) - }) - - test('Ensure toHaveSize, toHaveHeight, toHaveWidth matchers throw and show proper failing message', async () => { - await expect(() => expectLib(el).toHaveSize({ width: 200, height: 100 }, { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to have size - -- Expected - 2 -+ Received + 2 - - Object { -- "height": 100, -- "width": 200, -+ "height": 50, -+ "width": 100, - }`) - await expect(() => expectLib(el).toHaveHeight(100, { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to have height - -Expected: 100 -Received: 50`) - - await expect(() => expectLib(el).toHaveWidth(200, { wait: 1 })).rejects.toThrow(`\ -Expect $(\`selector\`) to have width - -Expected: 200 -Received: 100`) - }) - - }) - - describe('Matchers pass when using `.not`', async () => { - const el = await $('selector') - vi.mocked(el.isDisplayed).mockResolvedValue(false) - vi.mocked(el.isExisting).mockResolvedValue(false) - vi.mocked(el.isEnabled).mockResolvedValue(false) - vi.mocked(el.isClickable).mockResolvedValue(false) - vi.mocked(el.isFocused).mockResolvedValue(false) - vi.mocked(el.isSelected).mockResolvedValue(false) - - test('toBe matchers', async () => { - await expectLib(el).not.toBeDisplayed({ wait: 1 }) - await expectLib(el).not.toBeExisting({ wait: 1 }) - await expectLib(el).not.toBeEnabled({ wait: 1 }) - await expectLib(el).not.toBeClickable({ wait: 1 }) - await expectLib(el).not.toBeFocused({ wait: 1 }) - await expectLib(el).not.toBeSelected({ wait: 1 }) - }) - - test('toHave matchers', async () => { - await expectLib(el).not.toHaveText('Some other text', { wait: 1 }) - await expectLib(el).not.toHaveHTML('', { wait: 1 }) - await expectLib(el).not.toHaveComputedLabel('Some Other Computed Label', { wait: 1 }) - await expectLib(el).not.toHaveComputedRole('Some Other Computed Role', { wait: 1 }) - await expectLib(el).not.toHaveElementProperty('someProperty', 'some other value', { wait: 1 }) - await expectLib(el).not.toHaveAttribute('someAttribute', 'some other attribute', { wait: 1 }) - await expectLib(el).not.toHaveAttr('someAttribute', 'some other attribute', { wait: 1 }) - await expectLib(el).not.toHaveSize({ width: 200, height: 100 }, { wait: 1 }) - await expectLib(el).not.toHaveHeight(100, { wait: 1 }) - await expectLib(el).not.toHaveWidth(200, { wait: 1 }) - }) - - }) - - describe('Matcher eventually passing', async () => { - - test('when element eventually is displayed, matcher and .not matcher should be consistent', async () => { - const el = await $('selector') - vi.mocked(el.isDisplayed) - .mockResolvedValueOnce(false) - .mockResolvedValueOnce(false) - .mockResolvedValueOnce(true) - - // Passes when element becomes displayed - await expectLib(el).toBeDisplayed({ wait: 300, interval: 100 }) - - vi.mocked(el.isDisplayed) - .mockResolvedValueOnce(false) - .mockResolvedValueOnce(false) - .mockResolvedValueOnce(true) - - // Should not pass with the same scenario to be consistent - await expect(() => expectLib(el).not.toBeDisplayed({ wait: 300, interval: 100 })).rejects.toThrow(`\ -Expect $(\`selector\`) not to be displayed - -Expected [not]: "not displayed" -Received : "displayed"`) - - expect(el.isDisplayed).toHaveBeenCalledTimes(6) - }) - - test('when element eventually is not displayed, matcher and .not matcher should be consistent', async () => { - const el = await $('selector') - vi.mocked(el.isDisplayed) - .mockResolvedValueOnce(false) - .mockResolvedValueOnce(false) - .mockResolvedValueOnce(false) - - // Does not pass since element never becomes displayed - await expect(expectLib(el).toBeDisplayed({ wait: 300, interval: 100 })).rejects.toThrow(`\ -Expect $(\`selector\`) to be displayed - -Expected: "displayed" -Received: "not displayed"`) - - vi.mocked(el.isDisplayed) - .mockResolvedValueOnce(false) - .mockResolvedValueOnce(false) - .mockResolvedValueOnce(false) - - // Should pass with the same scenario to be consistent - await expectLib(el).not.toBeDisplayed({ wait: 300, interval: 100 }) - - expect(el.isDisplayed).toHaveBeenCalledTimes(6) - }) - }) -}) diff --git a/test/matchers/beMatchers.test.ts b/test/matchers/beMatchers.test.ts index c2eba4864..4ebf22cd9 100644 --- a/test/matchers/beMatchers.test.ts +++ b/test/matchers/beMatchers.test.ts @@ -1,6 +1,6 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' -import { matcherLastWordName } from '../__fixtures__/utils.js' +import { getExpectMessage, getReceived, matcherNameToString } from '../__fixtures__/utils.js' import * as Matchers from '../../src/matchers.js' vi.mock('@wdio/globals') @@ -82,58 +82,56 @@ describe('be* matchers', () => { expect(el[elementFnName]).toHaveBeenCalledTimes(1) }) - test('not - failure - pass should be true', async () => { + test('not - failure', async () => { const el = await $('sel') const result = await matcherFn.call({ isNot: true }, el, { wait: 0 }) as ExpectWebdriverIO.AssertionResult + const received = getReceived(result.message()) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to be ${matcherLastWordName(matcherName)} - -Expected [not]: "not ${matcherLastWordName(matcherName)}" -Received : "${matcherLastWordName(matcherName)}"` - ) + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) - test('not - success - pass should be false', async () => { + test('not - success', async () => { const el = await $('sel') el[elementFnName] = vi.fn().mockResolvedValue(false) const result = await matcherFn.call({ isNot: true }, el, { wait: 0 }) as ExpectWebdriverIO.AssertionResult + const received = getReceived(result.message()) - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(received).toContain('not') + expect(result.pass).toBe(false) }) - test('not - failure (with wait) - pass should be true', async () => { + test('not - failure (with wait)', async () => { const el = await $('sel') const result = await matcherFn.call({ isNot: true }, el, { wait: 1 }) as ExpectWebdriverIO.AssertionResult + const received = getReceived(result.message()) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) - test('not - success (with wait) - pass should be false', async () => { + test('not - success (with wait)', async () => { const el = await $('sel') el[elementFnName] = vi.fn().mockResolvedValue(false) + const result = await matcherFn.call({ isNot: true }, el, { wait: 1 }) as ExpectWebdriverIO.AssertionResult - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + const received = getReceived(result.message()) + expect(received).toContain('not') + expect(result.pass).toBe(false) }) test('message', async () => { const el = await $('sel') el[elementFnName] = vi.fn().mockResolvedValue(false) - const result = await matcherFn.call({}, el, { wait: 1 }) as ExpectWebdriverIO.AssertionResult - expect(result.pass).toBe(false) - expect(result.message()).toBe(`\ -Expect $(\`sel\`) to be ${matcherLastWordName(matcherName)} - -Expected: "${matcherLastWordName(matcherName)}" -Received: "not ${matcherLastWordName(matcherName)}"` - ) + const result = await matcherFn.call({}, el) as ExpectWebdriverIO.AssertionResult + expect(getExpectMessage(result.message())) + .toContain(matcherNameToString(matcherName)) }) }) }) diff --git a/test/matchers/browserMatchers.test.ts b/test/matchers/browserMatchers.test.ts index f017b5674..72f9a0a4d 100644 --- a/test/matchers/browserMatchers.test.ts +++ b/test/matchers/browserMatchers.test.ts @@ -1,7 +1,7 @@ import { vi, test, describe, expect } from 'vitest' import { browser } from '@wdio/globals' -import { getExpectMessage, matcherNameToString, matcherLastWordName } from '../__fixtures__/utils.js' +import { getExpectMessage, getReceived, matcherNameToString, getExpected } from '../__fixtures__/utils.js' import * as Matchers from '../../src/matchers.js' vi.mock('@wdio/globals') @@ -28,7 +28,7 @@ describe('browser matchers', () => { expect(browser[browserFnName]).toHaveBeenCalledTimes(3) }) - test('wait but error', async () => { + test('wait but failure', async () => { browser[browserFnName] = vi.fn().mockRejectedValue(new Error('some error')) await expect(() => matcherFn.call({}, browser, validText, { trim: false })) @@ -61,47 +61,48 @@ describe('browser matchers', () => { expect(browser[browserFnName]).toHaveBeenCalledTimes(1) }) - test('not - failure - pass should be true', async () => { - browser[browserFnName] = vi.fn().mockResolvedValue(validText) + test('not - failure', async () => { const result = await matcherFn.call({ isNot: true }, browser, validText, { wait: 0, trim: false }) as ExpectWebdriverIO.AssertionResult - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect window not to have ${matcherLastWordName(matcherName)} + expect(getExpectMessage(result.message())).toContain('not') + expect(getExpected(result.message())).toContain('not') -Expected [not]: " Valid Text " -Received : " Valid Text "` - ) + expect(result.pass).toBe(true) }) - test('not - success - pass should be false', async () => { + test('not - success', async () => { browser[browserFnName] = vi.fn().mockResolvedValue(wrongText) const result = await matcherFn.call({ isNot: true }, browser, validText, { wait: 0 }) as ExpectWebdriverIO.AssertionResult - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(getExpectMessage(result.message())).toContain('not') + expect(getExpected(result.message())).toContain('Valid') + expect(getReceived(result.message())).toContain('Wrong') + + expect(result.pass).toBe(false) }) - test('not - failure (with wait) - pass should be true', async () => { + test('not - failure (with wait)', async () => { browser[browserFnName] = vi.fn().mockResolvedValue(validText) const result = await matcherFn.call({ isNot: true }, browser, validText, { wait: 1, trim: false }) as ExpectWebdriverIO.AssertionResult - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect window not to have ${matcherLastWordName(matcherName)} + expect(getExpectMessage(result.message())).toContain('not') + expect(getExpected(result.message())).toContain('not') -Expected [not]: " Valid Text " -Received : " Valid Text "` - ) + expect(result.pass).toBe(true) }) - test('not - success (with wait) - pass should be false', async () => { + test('not - success (with wait)', async () => { browser[browserFnName] = vi.fn().mockResolvedValue(wrongText) const result = await matcherFn.call({ isNot: true }, browser, validText, { wait: 1 }) as ExpectWebdriverIO.AssertionResult - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(getExpectMessage(result.message())).toContain('not') + expect(getExpected(result.message())).toContain('Valid') + expect(getReceived(result.message())).toContain('Wrong') + + expect(result.pass).toBe(false) }) test('message', async () => { diff --git a/test/matchers/element/toBeDisabled.test.ts b/test/matchers/element/toBeDisabled.test.ts index 1b0dac3e4..f53601413 100644 --- a/test/matchers/element/toBeDisabled.test.ts +++ b/test/matchers/element/toBeDisabled.test.ts @@ -1,6 +1,7 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' +import { getExpectMessage, getReceived } from '../../__fixtures__/utils.js' import { toBeDisabled } from '../../../src/matchers/element/toBeDisabled.js' vi.mock('@wdio/globals') @@ -68,65 +69,55 @@ describe('toBeDisabled', () => { expect(el.isEnabled).toHaveBeenCalledTimes(1) }) - test('not - failure - pass should be true', async () => { + test('not - failure', async () => { const el = await $('sel') el.isEnabled = vi.fn().mockResolvedValue(false) const result = await toBeDisabled.call({ isNot: true }, el, { wait: 0 }) + const received = getReceived(result.message()) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to be disabled - -Expected [not]: "not disabled" -Received : "disabled"` - ) + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) - test('not - success - pass should be false', async () => { + test('not - success', async () => { const el = await $('sel') el.isEnabled = vi.fn().mockResolvedValue(true) const result = await toBeDisabled.call({ isNot: true }, el, { wait: 0 }) + const received = getReceived(result.message()) - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(received).toContain('not') + expect(result.pass).toBe(false) }) - test('not - failure (with wait) - pass should be true', async () => { + test('not - failure (with wait)', async () => { const el = await $('sel') el.isEnabled = vi.fn().mockResolvedValue(false) const result = await toBeDisabled.call({ isNot: true }, el, { wait: 1 }) + const received = getReceived(result.message()) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to be disabled - -Expected [not]: "not disabled" -Received : "disabled"` - ) + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) - test('not - success (with wait) - pass should be false', async () => { + test('not - success (with wait)', async () => { const el = await $('sel') el.isEnabled = vi.fn().mockResolvedValue(true) const result = await toBeDisabled.call({ isNot: true }, el, { wait: 1 }) + const received = getReceived(result.message()) - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(received).toContain('not') + expect(result.pass).toBe(false) }) test('message', async () => { const el = await $('sel') - el.isEnabled = vi.fn().mockResolvedValue(true) + el.isEnabled = vi.fn().mockResolvedValue(false) const result = await toBeDisabled.call({}, el) - expect(result.pass).toBe(false) - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) to be disabled - -Expected: "disabled" -Received: "not disabled"` - ) + expect(getExpectMessage(result.message())).toContain('to be disabled') }) }) diff --git a/test/matchers/element/toBeDisplayed.test.ts b/test/matchers/element/toBeDisplayed.test.ts index 250ead0bd..84a660674 100644 --- a/test/matchers/element/toBeDisplayed.test.ts +++ b/test/matchers/element/toBeDisplayed.test.ts @@ -1,6 +1,7 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' +import { getExpectMessage, getReceived } from '../../__fixtures__/utils.js' import { toBeDisplayed } from '../../../src/matchers/element/toBeDisplayed.js' import { executeCommandBe } from '../../../src/utils.js' import { DEFAULT_OPTIONS } from '../../../src/constants.js' @@ -122,42 +123,45 @@ describe('toBeDisplayed', () => { expect(el.isDisplayed).toHaveBeenCalledTimes(1) }) - test('not - failure - pass must be true', async () => { + test('not - failure', async () => { const el = await $('sel') + const result = await toBeDisplayed.call({ isNot: true }, el, { wait: 0 }) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + const received = getReceived(result.message()) + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) - test('not - success - pass should be false', async () => { + test('not - success', async () => { const el = await $('sel') el.isDisplayed = vi.fn().mockResolvedValue(false) const result = await toBeDisplayed.call({ isNot: true }, el, { wait: 0 }) - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + const received = getReceived(result.message()) + expect(received).toContain('not') + expect(result.pass).toBe(false) }) - test('not - failure (with wait) - pass should be true', async () => { + test('not - failure (with wait)', async () => { const el = await $('sel') - const result = await toBeDisplayed.call({ isNot: true }, el, { wait: 1 }) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to be displayed + const result = await toBeDisplayed.call({ isNot: true }, el, { wait: 1 }) + const received = getReceived(result.message()) -Expected [not]: "not displayed" -Received : "displayed"` - ) + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) - test('not - success (with wait) - pass should be false', async () => { + test('not - success (with wait)', async () => { const el = await $('sel') el.isDisplayed = vi.fn().mockResolvedValue(false) const result = await toBeDisplayed.call({ isNot: true }, el, { wait: 1 }) + const received = getReceived(result.message()) expect(el.isDisplayed).toHaveBeenCalledWith( { @@ -171,7 +175,8 @@ Received : "displayed"` wait: 1, interval: DEFAULT_OPTIONS.interval })) - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(received).toContain('not') + expect(result.pass).toBe(false) }) test('message', async () => { @@ -179,14 +184,7 @@ Received : "displayed"` el.isDisplayed = vi.fn().mockResolvedValue(false) - const result = await toBeDisplayed.call({}, el, { wait: 0 }) - - expect(result.pass).toBe(false) - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) to be displayed - -Expected: "displayed" -Received: "not displayed"` - ) + const result = await toBeDisplayed.call({}, el) + expect(getExpectMessage(result.message())).toContain('to be displayed') }) }) diff --git a/test/matchers/element/toHaveChildren.test.ts b/test/matchers/element/toHaveChildren.test.ts index 4699cb5dc..f37bb3d98 100644 --- a/test/matchers/element/toHaveChildren.test.ts +++ b/test/matchers/element/toHaveChildren.test.ts @@ -66,25 +66,17 @@ describe('toHaveChildren', () => { expect(result.pass).toBe(false) }) - test('.not exact value - failure - pass should be true', async () => { + test('.not exact value - failure', async () => { const el = await $('sel') const result = await toHaveChildren.bind({ isNot: true })(el, { eq: 2, wait: 0 }) - - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to have children - -Expected [not]: 2 -Received : 2`) - + expect(result.pass).toBe(true) }) - test('.not exact value - success - pass should be false', async () => { + test('.not exact value - success', async () => { const el = await $('sel') const result = await toHaveChildren.bind({ isNot: true })(el, { eq: 3, wait: 1 }) - - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(result.pass).toBe(false) }) }) diff --git a/test/matchers/element/toHaveComputedLabel.test.ts b/test/matchers/element/toHaveComputedLabel.test.ts index 02f95a5b2..ae8c2a1df 100644 --- a/test/matchers/element/toHaveComputedLabel.test.ts +++ b/test/matchers/element/toHaveComputedLabel.test.ts @@ -67,35 +67,30 @@ describe('toHaveComputedLabel', () => { expect(el.getComputedLabel).toHaveBeenCalledTimes(1) }) - test('not - failure - pass should be true', async () => { + test('not - failure', async () => { const el = await $('sel') el.getComputedLabel = vi.fn().mockResolvedValue('WebdriverIO') const result = await toHaveComputedLabel.call({ isNot: true }, el, 'WebdriverIO', { wait: 0 }) + const received = getReceived(result.message()) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to have computed label - -Expected [not]: "WebdriverIO" -Received : "WebdriverIO"` - ) + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) - test('not - success - pass should be false', async () => { + test("should return false if computed labels don't match", async () => { const el = await $('sel') el.getComputedLabel = vi.fn().mockResolvedValue('WebdriverIO') - const result = await toHaveComputedLabel.call({ isNot: true }, el, 'not WebdriverIO', { wait: 0 }) - - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + const result = await toHaveComputedLabel.bind({ isNot: true })(el, 'foobar', { wait: 1 }) + expect(result.pass).toBe(false) }) test('should return true if computed labels match', async () => { const el = await $('sel') el.getComputedLabel = vi.fn().mockResolvedValue('WebdriverIO') - const result = await toHaveComputedLabel.bind({})(el, 'WebdriverIO', { wait: 1 }) + const result = await toHaveComputedLabel.bind({ isNot: true })(el, 'WebdriverIO', { wait: 1 }) expect(result.pass).toBe(true) }) @@ -106,7 +101,6 @@ Received : "WebdriverIO"` const result = await toHaveComputedLabel.bind({})(el, 'BrowserdriverIO', { replace: ['Web', 'Browser'], }) - expect(result.pass).toBe(true) }) diff --git a/test/matchers/element/toHaveComputedRole.test.ts b/test/matchers/element/toHaveComputedRole.test.ts index f01df18af..a2b143744 100644 --- a/test/matchers/element/toHaveComputedRole.test.ts +++ b/test/matchers/element/toHaveComputedRole.test.ts @@ -69,35 +69,31 @@ describe('toHaveComputedcomputed role', () => { expect(el.getComputedRole).toHaveBeenCalledTimes(1) }) - test('not - failure - pass should be true', async () => { + test('not - failure', async () => { const el = await $('sel') el.getComputedRole = vi.fn().mockResolvedValueOnce('WebdriverIO') const result = await toHaveComputedRole.call({ isNot: true }, el, 'WebdriverIO', { wait: 0 }) + const received = getReceived(result.message()) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to have computed role - -Expected [not]: "WebdriverIO" -Received : "WebdriverIO"` - ) + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) - test('not - success - pass should be false', async () => { + test("should return false if computed roles don't match", async () => { const el = await $('sel') el.getComputedRole = vi.fn().mockResolvedValueOnce('WebdriverIO') - const result = await toHaveComputedRole.call({ isNot: true }, el, 'not WebdriverIO', { wait: 0 }) - - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + const result = await toHaveComputedRole.bind({ isNot: true })(el, 'foobar', { wait: 1 }) + expect(result.pass).toBe(false) }) test('should return true if computed roles match', async () => { const el = await $('sel') el.getComputedRole = vi.fn().mockResolvedValueOnce('WebdriverIO') - const result = await toHaveComputedRole.bind({})(el, 'WebdriverIO', { wait: 1 }) + const result = await toHaveComputedRole.bind({ isNot: true })(el, 'WebdriverIO', { wait: 1 }) + expect(result.pass).toBe(true) }) diff --git a/test/matchers/element/toHaveElementProperty.test.ts b/test/matchers/element/toHaveElementProperty.test.ts index affb68bf3..977e75b1e 100644 --- a/test/matchers/element/toHaveElementProperty.test.ts +++ b/test/matchers/element/toHaveElementProperty.test.ts @@ -45,41 +45,18 @@ describe('toHaveElementProperty', () => { const el = await $('sel') el.getProperty = vi.fn().mockResolvedValue('iphone') - const result = await toHaveElementProperty.bind({})(el, 'property', 'foobar', { wait: 1 }) - - expect(result.pass).toBe(false) - }) - - test('should return success (false) if values dont match when isNot is true', async () => { - const el = await $('sel') - el.getProperty = vi.fn().mockResolvedValue('iphone') - const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', 'foobar', { wait: 1 }) - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(result.pass).toBe(false) }) test('should return true if values match', async () => { const el = await $('sel') - el.getProperty = vi.fn().mockResolvedValue('iphone') - const result = await toHaveElementProperty.bind({})(el, 'property', 'iphone', { wait: 1 }) - expect(result.pass).toBe(true) - }) - - test('should return failure (true) if values match when isNot is true', async () => { - const el = await $('sel') el.getProperty = vi.fn().mockResolvedValue('iphone') const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', 'iphone', { wait: 1 }) - - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to have property property - -Expected [not]: "iphone" -Received : "iphone"` - ) + expect(result.pass).toBe(true) }) test('with RegExp should return true if values match', async () => { @@ -91,112 +68,31 @@ Received : "iphone"` expect(result.pass).toBe(true) }) - test.for([ - { propertyActualValue: null }, - { propertyActualValue: undefined }] - )('return false for not defined actual if expected is defined since property does not exist', async ( { propertyActualValue }) => { - const el = await $('sel') - el.getProperty = vi.fn().mockResolvedValue(propertyActualValue) - - const result = await toHaveElementProperty.bind({})(el, 'property', 'iphone', { wait: 1 }) - expect(result.pass).toBe(false) - }) - - test.for([ - { propertyActualValue: null }, - { propertyActualValue: undefined }] - )('return success (false) for not defined actual and defined expected when isNot is true since property does not exist', async ({ propertyActualValue }) => { + test('should return false for null input', async () => { const el = await $('sel') - el.getProperty = vi.fn().mockResolvedValue(propertyActualValue) + el.getProperty = vi.fn().mockResolvedValue(undefined) const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', 'iphone', { wait: 1 }) - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` - }) - - test.for([ - { expectedValue: null }, - // { expectedValue: undefined } // fails a bug? - ] - )('should return true when property does exist by passing an not defined expected value', async ( { expectedValue }) => { - const el = await $('sel') - el.getProperty = vi.fn().mockResolvedValue('Test Value') - - const result = await toHaveElementProperty.bind({})(el, 'property', expectedValue) - - expect(result.pass).toBe(true) + expect(result.pass).toBe(false) }) - test.for([ - { expectedValue: null }, - //{ expectedValue: undefined } // fails a bug? - ] - )('should return failure (true) if property exists by passing not defined expected value when isNot is true', async ( { expectedValue }) => { + test('should return true if value is null', async () => { const el = await $('sel') el.getProperty = vi.fn().mockResolvedValue('Test Value') - const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', expectedValue) - - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - }) + const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', null as any) - // Bug? When requesting to have element property and it does exist should we return true here? - test.skip('return true if property is present', async () => { - const el = await $('sel') - el.getProperty = vi.fn().mockResolvedValue('Test Value') - - const result = await toHaveElementProperty.bind({})(el, 'property') expect(result.pass).toBe(true) }) - // Bug? When requesting to not have element property and it does exist should we have a failure (pass=true? - test.skip('return failure (true) if property is present when isNot is true', async () => { - const el = await $('sel') - el.getProperty = vi.fn().mockResolvedValue('Test Value') - - const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property') - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - }) - - test.for([ - { expectedValue: null }, - { expectedValue: undefined } - ] - )('return false if property is not present', async ({ expectedValue }) => { - const el = await $('sel') - el.getProperty = vi.fn().mockResolvedValue(expectedValue) - - const result = await toHaveElementProperty.bind({})(el, 'property') - expect(result.pass).toBe(false) - }) - test.for([ - { expectedValue: null }, - { expectedValue: undefined } - ] - )('return success (false) if value is not present when isNot is true', async ({ expectedValue }) => { - const el = await $('sel') - el.getProperty = vi.fn().mockResolvedValue(expectedValue) - - const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property') - - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` - }) - test('should return false if value is non-string', async () => { const el = await $('sel') el.getProperty = vi.fn().mockResolvedValue(5) - const result = await toHaveElementProperty.bind({})(el, 'property', 'Test Value') - expect(result.pass).toBe(false) - }) - - test('should return success (false) if value is non-string when isNot is true', async () => { - const el = await $('sel') - el.getProperty = vi.fn().mockResolvedValue(5) - const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', 'Test Value') - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(result.pass).toBe(false) }) describe('failure with RegExp when value does not match', () => { @@ -206,7 +102,7 @@ Received : "iphone"` const el = await $('sel') el.getProperty = vi.fn().mockResolvedValue('iphone') - result = await toHaveElementProperty.call({}, el, 'property', /WDIO/, { wait: 1 }) + result = await toHaveElementProperty.call({}, el, 'property', /WDIO/) }) test('failure', () => { diff --git a/test/matchers/element/toHaveHTML.test.ts b/test/matchers/element/toHaveHTML.test.ts index f8dbbd2a2..5e380d012 100755 --- a/test/matchers/element/toHaveHTML.test.ts +++ b/test/matchers/element/toHaveHTML.test.ts @@ -70,59 +70,29 @@ describe('toHaveHTML', () => { expect(element.getHTML).toHaveBeenCalledTimes(1) }) - test('not - failure - pass should be true', async () => { + test('not - failure', async () => { const element = await $('sel') element.getHTML = vi.fn().mockResolvedValue('
foo
') - const result = await toHaveHTML.call({ isNot: true }, element, '
foo
', { wait: 0 }) + const received = getReceived(result.message()) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to have HTML - -Expected [not]: "
foo
" -Received : "
foo
"` - ) - }) - - test('not - success - pass should be false', async () => { - const el = await $('sel') - el.getHTML = vi.fn().mockResolvedValue('
foo
') - - const result = await toHaveHTML.call({ isNot: true }, el, '
Notfoo
', { wait: 0 }) - - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) test("should return false if htmls don't match", async () => { const element = await $('sel') element.getHTML = vi.fn().mockResolvedValue('
foo
') - const result = await toHaveHTML.bind({})(element, 'foobar', { wait: 1 }) - expect(result.pass).toBe(false) - }) - - test("should suceeds (false) if htmls don't match when isNot is true", async () => { - const element = await $('sel') - element.getHTML = vi.fn().mockReturnValue('
foo
') - const result = await toHaveHTML.bind({ isNot: true })(element, 'foobar', { wait: 1 }) - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` - }) - - test('should fails (pass=true) if htmls match when isNot is true', async () => { - const element = await $('sel') - element.getHTML = vi.fn().mockReturnValue('
foo
') - - const result = await toHaveHTML.bind({ isNot: true })(element, '
foo
', { wait: 1 }) - expect(result.pass).toBe(true) // success, boolean is inverted later because of `.not` + expect(result.pass).toBe(false) }) test('should return true if htmls match', async () => { const element = await $('sel') element.getHTML = vi.fn().mockResolvedValue('
foo
') - const result = await toHaveHTML.bind({})(element, '
foo
', { wait: 1 }) + const result = await toHaveHTML.bind({ isNot: true })(element, '
foo
', { wait: 1 }) expect(result.pass).toBe(true) }) diff --git a/test/matchers/element/toHaveHeight.test.ts b/test/matchers/element/toHaveHeight.test.ts index 8f6134cf7..83f289cba 100755 --- a/test/matchers/element/toHaveHeight.test.ts +++ b/test/matchers/element/toHaveHeight.test.ts @@ -1,7 +1,7 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' -import { getExpectMessage } from '../../__fixtures__/utils.js' +import { getExpectMessage, getReceived } from '../../__fixtures__/utils.js' import { toHaveHeight } from '../../../src/matchers/element/toHaveHeight.js' vi.mock('@wdio/globals') @@ -84,27 +84,15 @@ describe('toHaveHeight', () => { expect(el.getSize).toHaveBeenCalledTimes(1) }) - test('not - failure - pass should be true', async () => { + test('not - failure', async () => { const el = await $('sel') el.getSize = vi.fn().mockResolvedValue(32) - const result = await toHaveHeight.call({ isNot: true }, el, 32, { wait: 0 }) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to have height - -Expected [not]: 32 -Received : 32` - ) - }) - - test('not - success - pass should be false', async () => { - const el = await $('sel') - el.getSize = vi.fn().mockResolvedValue(31) - - const result = await toHaveHeight.call({ isNot: true }, el, 32, { wait: 0 }) + const result = await toHaveHeight.call({}, el, 32, { wait: 0 }) + const received = getReceived(result.message()) - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) test("should return false if sizes don't match", async () => { diff --git a/test/matchers/element/toHaveId.test.ts b/test/matchers/element/toHaveId.test.ts index 3c67a939c..382fa3141 100644 --- a/test/matchers/element/toHaveId.test.ts +++ b/test/matchers/element/toHaveId.test.ts @@ -31,20 +31,20 @@ describe('toHaveId', () => { const afterAssertion = vi.fn() beforeEach(async () => { - result = await toHaveId.call({}, el, 'an attribute', { beforeAssertion, afterAssertion, wait: 0 }) + result = await toHaveId.call({}, el, 'an attribute', { beforeAssertion, afterAssertion }) }) test('failure', () => { expect(beforeAssertion).toBeCalledWith({ matcherName: 'toHaveId', expectedValue: 'an attribute', - options: { beforeAssertion, afterAssertion, wait: 0 } + options: { beforeAssertion, afterAssertion } }) expect(result.pass).toBe(false) expect(afterAssertion).toBeCalledWith({ matcherName: 'toHaveId', expectedValue: 'an attribute', - options: { beforeAssertion, afterAssertion, wait: 0 }, + options: { beforeAssertion, afterAssertion }, result }) }) diff --git a/test/matchers/element/toHaveSize.test.ts b/test/matchers/element/toHaveSize.test.ts index 74665b118..de8d86146 100755 --- a/test/matchers/element/toHaveSize.test.ts +++ b/test/matchers/element/toHaveSize.test.ts @@ -1,6 +1,7 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' +import { getExpectMessage, getReceived } from '../../__fixtures__/utils.js' import { toHaveSize } from '../../../src/matchers/element/toHaveSize.js' vi.mock('@wdio/globals') @@ -67,18 +68,15 @@ describe('toHaveSize', () => { expect(el.getSize).toHaveBeenCalledTimes(1) }) - test('not - failure - pass should be true', async () => { + test('not - failure', async () => { const el = await $('sel') el.getSize = vi.fn().mockResolvedValue({ width: 32, height: 32 }) - const result = await toHaveSize.call({ isNot: true }, el, { width: 32, height: 32 }, { wait: 0 }) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to have size + const result = await toHaveSize.call({}, el, { width: 32, height: 32 }, { wait: 0 }) + const received = getReceived(result.message()) -Expected [not]: {"height": 32, "width": 32} -Received : {"height": 32, "width": 32}` - ) + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) test("should return false if sizes don't match", async () => { @@ -105,11 +103,6 @@ Received : {"height": 32, "width": 32}` const result = await toHaveSize.call({}, el, { width: 32, height: 32 }) - expect(result.pass).toBe(false) - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) to have size - -Expected: {"height": 32, "width": 32} -Received: null`) + expect(getExpectMessage(result.message())).toContain('to have size') }) }) diff --git a/test/matchers/element/toHaveStyle.test.ts b/test/matchers/element/toHaveStyle.test.ts index 225e5204e..e7c43f20e 100644 --- a/test/matchers/element/toHaveStyle.test.ts +++ b/test/matchers/element/toHaveStyle.test.ts @@ -1,6 +1,7 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' +import { getExpectMessage, getReceived } from '../../__fixtures__/utils.js' import { toHaveStyle } from '../../../src/matchers/element/toHaveStyle.js' vi.mock('@wdio/globals') @@ -79,36 +80,16 @@ describe('toHaveStyle', () => { expect(el.getCSSProperty).toHaveBeenCalledTimes(3) }) - test('not - failure - pass should be true', async () => { + test('not - failure', async () => { const el = await $('sel') el.getCSSProperty = vi.fn().mockImplementation((property: string) => { return { value: mockStyle[property] } }) const result = await toHaveStyle.call({ isNot: true }, el, mockStyle, { wait: 0 }) + const received = getReceived(result.message()) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to have style - -Expected [not]: {"color": "#000", "font-family": "Faktum", "font-size": "26px"} -Received : {"color": "#000", "font-family": "Faktum", "font-size": "26px"}` - ) - }) - - test('not - success - pass should be false', async () => { - const el = await $('sel') - el.getCSSProperty = vi.fn().mockImplementation((property: string) => { - return { value: mockStyle[property] } - }) - const wrongStyle: { [key: string]: string; } = { - 'font-family': 'Incorrect Font', - 'font-size': '100px', - 'color': '#fff' - } - - const result = await toHaveStyle.bind({ isNot: true })(el, wrongStyle, { wait: 1 }) - - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) test('should return false if styles dont match', async () => { @@ -123,7 +104,7 @@ Received : {"color": "#000", "font-family": "Faktum", "font-size": "26px"}` 'color': '#fff' } - const result = await toHaveStyle.bind({ })(el, wrongStyle, { wait: 1 }) + const result = await toHaveStyle.bind({ isNot: true })(el, wrongStyle, { wait: 1 }) expect(result.pass).toBe(false) }) @@ -133,7 +114,7 @@ Received : {"color": "#000", "font-family": "Faktum", "font-size": "26px"}` return { value: mockStyle[property] } }) - const result = await toHaveStyle.bind({})(el, mockStyle, { wait: 1 }) + const result = await toHaveStyle.bind({ isNot: true })(el, mockStyle, { wait: 1 }) expect(result.pass).toBe(true) }) @@ -143,14 +124,7 @@ Received : {"color": "#000", "font-family": "Faktum", "font-size": "26px"}` const result = await toHaveStyle.call({}, el, 'WebdriverIO' as any) - expect(result.pass).toBe(false) - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) to have style - -Expected: "WebdriverIO" -Received: {"0": "Wrong Value", "1": "Wrong Value", "10": "Wrong Value", "2": "Wrong Value", "3": "Wrong Value", "4": "Wrong Value", "5": "Wrong Value", "6": "Wrong Value", "7": "Wrong Value", "8": "Wrong Value", "9": "Wrong Value"}` - ) - + expect(getExpectMessage(result.message())).toContain('to have style') }) test('success if style matches with ignoreCase', async () => { diff --git a/test/matchers/element/toHaveText.test.ts b/test/matchers/element/toHaveText.test.ts index 92aa72a29..345791d40 100755 --- a/test/matchers/element/toHaveText.test.ts +++ b/test/matchers/element/toHaveText.test.ts @@ -105,59 +105,33 @@ describe('toHaveText', () => { expect(el.getText).toHaveBeenCalledTimes(1) }) - test('not - failure - pass should be true', async () => { + test('not - failure', async () => { const el = await $('sel') - el.getText = vi.fn().mockResolvedValue('WebdriverIO') - - const result = await toHaveText.call({ isNot: true }, el, 'WebdriverIO', { wait: 0 }) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to have text - -Expected [not]: "WebdriverIO" -Received : "WebdriverIO"` - ) - }) - - test('not - success - pass should be false', async () => { - const el = await $('sel') el.getText = vi.fn().mockResolvedValue('WebdriverIO') - const result = await toHaveText.call({ isNot: true }, el, 'not WebdriverIO', { wait: 0 }) - - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` - }) - - test('not with no trim - failure - pass should be true', async () => { - const el = await $('sel') - el.getText = vi.fn().mockResolvedValue(' WebdriverIO ') - - const result = await toHaveText.call({ isNot: true }, el, ' WebdriverIO ', { trim: false, wait: 0 }) - - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to have text + const result = await toHaveText.call({ isNot: true }, el, 'WebdriverIO', { wait: 0 }) + const received = getReceived(result.message()) -Expected [not]: " WebdriverIO " -Received : " WebdriverIO "` - ) + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) - test('not - success - pass should be false', async () => { + test("should return false if texts don't match", async () => { const el = await $('sel') el.getText = vi.fn().mockResolvedValue('WebdriverIO') - const result = await toHaveText.call({ isNot: true }, el, 'not WebdriverIO', { wait: 0 }) + const result = await toHaveText.bind({ isNot: true })(el, 'foobar', { wait: 1 }) - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(result.pass).toBe(false) }) test('should return true if texts match', async () => { const el = await $('sel') el.getText = vi.fn().mockResolvedValue('WebdriverIO') - const result = await toHaveText.bind({})(el, 'WebdriverIO', { wait: 1 }) + const result = await toHaveText.bind({ isNot: true })(el, 'WebdriverIO', { wait: 1 }) + expect(result.pass).toBe(true) }) diff --git a/test/matchers/element/toHaveWidth.test.ts b/test/matchers/element/toHaveWidth.test.ts index 8a13f0700..10ca730c2 100755 --- a/test/matchers/element/toHaveWidth.test.ts +++ b/test/matchers/element/toHaveWidth.test.ts @@ -1,7 +1,7 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' -import { getExpectMessage } from '../../__fixtures__/utils.js' +import { getExpectMessage, getReceived } from '../../__fixtures__/utils.js' import { toHaveWidth } from '../../../src/matchers/element/toHaveWidth.js' vi.mock('@wdio/globals') @@ -81,27 +81,15 @@ describe('toHaveWidth', () => { expect(el.getSize).toHaveBeenCalledTimes(1) }) - test('not - failure - pass should be true', async () => { + test('not - failure', async () => { const el = await $('sel') el.getSize = vi.fn().mockResolvedValue(50) - const result = await toHaveWidth.call({ isNot: true }, el, 50, { wait: 0 }) - expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result.message()).toEqual(`\ -Expect $(\`sel\`) not to have width - -Expected [not]: 50 -Received : 50` - ) - }) - - test('not - success - pass should be false', async () => { - const el = await $('sel') - el.getSize = vi.fn().mockResolvedValue(50) - - const result = await toHaveWidth.call({ isNot: true }, el, 40, { wait: 0 }) + const result = await toHaveWidth.call({}, el, 50, { wait: 0 }) + const received = getReceived(result.message()) - expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + expect(received).not.toContain('not') + expect(result.pass).toBe(true) }) test("should return false if sizes don't match", async () => { diff --git a/test/matchers/elements/toBeElementsArrayOfSize.test.ts b/test/matchers/elements/toBeElementsArrayOfSize.test.ts index e2ed9b5ef..2b68dac36 100644 --- a/test/matchers/elements/toBeElementsArrayOfSize.test.ts +++ b/test/matchers/elements/toBeElementsArrayOfSize.test.ts @@ -46,23 +46,23 @@ describe('toBeElementsArrayOfSize', () => { test('array of size 2', async () => { const beforeAssertion = vi.fn() const afterAssertion = vi.fn() - const result = await toBeElementsArrayOfSize.call({}, els, 2, { beforeAssertion, afterAssertion, wait: 0 }) + const result = await toBeElementsArrayOfSize.call({}, els, 2, { beforeAssertion, afterAssertion }) expect(result.pass).toBe(true) expect(beforeAssertion).toBeCalledWith({ matcherName: 'toBeElementsArrayOfSize', expectedValue: 2, - options: { beforeAssertion, afterAssertion, wait: 0 } + options: { beforeAssertion, afterAssertion } }) expect(afterAssertion).toBeCalledWith({ matcherName: 'toBeElementsArrayOfSize', expectedValue: 2, - options: { beforeAssertion, afterAssertion, wait: 0 }, + options: { beforeAssertion, afterAssertion }, result }) }) test('array of size 5', async () => { els = createMockElementArray(5) - const result = await toBeElementsArrayOfSize.call({}, els, 5, { wait : 0 }) + const result = await toBeElementsArrayOfSize.call({}, els, 5, {}) expect(result.pass).toBe(true) }) }) @@ -71,7 +71,7 @@ describe('toBeElementsArrayOfSize', () => { let result: AssertionResult beforeEach(async () => { - result = await toBeElementsArrayOfSize.call({}, els, 5, { wait: 0 }) + result = await toBeElementsArrayOfSize.call({}, els, 5, {}) }) test('fails', () => { @@ -97,7 +97,7 @@ describe('toBeElementsArrayOfSize', () => { }) test('works if size contains options', async () => { - const result = await toBeElementsArrayOfSize.call({}, els, { lte: 5 }, { wait: 0 }) + const result = await toBeElementsArrayOfSize.call({}, els, { lte: 5 }) expect(result.pass).toBe(true) }) }) @@ -108,9 +108,9 @@ describe('toBeElementsArrayOfSize', () => { ['lte', 1, false], ['gte', 1, true], ['gte', 10, false], - ['gte and lte', { gte: 1, lte: 10, wait: 0 }, true], - ['not gte but is lte', { gte: 10, lte: 10, wait: 0 }, false], - ['not lte but is gte', { gte: 1, lte: 1, wait: 0 }, 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) @@ -173,17 +173,17 @@ describe('toBeElementsArrayOfSize', () => { test('array of size 0', async () => { const beforeAssertion = vi.fn() const afterAssertion = vi.fn() - const result = await toBeElementsArrayOfSize.call({}, elements, 0, { beforeAssertion, afterAssertion, wait: 0 }) + const result = await toBeElementsArrayOfSize.call({}, elements, 0, { beforeAssertion, afterAssertion }) expect(result.pass).toBe(true) expect(beforeAssertion).toBeCalledWith({ matcherName: 'toBeElementsArrayOfSize', expectedValue: 0, - options: { beforeAssertion, afterAssertion, wait: 0 } + options: { beforeAssertion, afterAssertion } }) expect(afterAssertion).toBeCalledWith({ matcherName: 'toBeElementsArrayOfSize', expectedValue: 0, - options: { beforeAssertion, afterAssertion, wait: 0 }, + options: { beforeAssertion, afterAssertion }, result }) }) @@ -193,7 +193,7 @@ describe('toBeElementsArrayOfSize', () => { let result: AssertionResult beforeEach(async () => { - result = await toBeElementsArrayOfSize.call({}, elements, 5, { wait: 0 }) + result = await toBeElementsArrayOfSize.call({}, elements, 5, {}) }) test('fails', () => { @@ -222,17 +222,17 @@ describe('toBeElementsArrayOfSize', () => { test('array of size 1', async () => { const beforeAssertion = vi.fn() const afterAssertion = vi.fn() - const result = await toBeElementsArrayOfSize.call({}, elements, 1, { beforeAssertion, afterAssertion, wait: 0 }) + const result = await toBeElementsArrayOfSize.call({}, elements, 1, { beforeAssertion, afterAssertion }) expect(result.pass).toBe(true) expect(beforeAssertion).toBeCalledWith({ matcherName: 'toBeElementsArrayOfSize', expectedValue: 1, - options: { beforeAssertion, afterAssertion, wait: 0 } + options: { beforeAssertion, afterAssertion } }) expect(afterAssertion).toBeCalledWith({ matcherName: 'toBeElementsArrayOfSize', expectedValue: 1, - options: { beforeAssertion, afterAssertion, wait: 0 }, + options: { beforeAssertion, afterAssertion }, result }) }) @@ -242,7 +242,7 @@ describe('toBeElementsArrayOfSize', () => { let result: AssertionResult beforeEach(async () => { - result = await toBeElementsArrayOfSize.call({}, elements, 5, { wait: 0 }) + result = await toBeElementsArrayOfSize.call({}, elements, 5, {}) }) test('fails', () => { diff --git a/test/matchers/mock/toBeRequested.test.ts b/test/matchers/mock/toBeRequested.test.ts index 05ae9d7cb..237d41a71 100644 --- a/test/matchers/mock/toBeRequested.test.ts +++ b/test/matchers/mock/toBeRequested.test.ts @@ -4,6 +4,8 @@ import type { Matches, Mock } from 'webdriverio' import { toBeRequested } from '../../../src/matchers/mock/toBeRequested.js' +import { getExpected, getExpectMessage, getReceived, removeColors } from '../../__fixtures__/utils.js' + vi.mock('@wdio/globals') class TestMock implements Mock { @@ -67,38 +69,26 @@ describe('toBeRequested', () => { test('not to be called', async () => { const mock: Mock = new TestMock() - // expect(mock).not.toBeRequested() should pass=false + // expect(mock).not.toBeRequested() should pass const result = await toBeRequested.call({ isNot: true }, mock) - expect(result.pass).toBe(false) // success, boolean is inverted later becuase of `.not` + expect(result.pass).toBe(false) mock.calls.push(mockMatch) // expect(mock).not.toBeRequested() should fail const result4 = await toBeRequested.call({ isNot: true }, mock) - expect(result4.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result4.pass).toBe(true) }) test('message', async () => { const mock: Mock = new TestMock() - const result = await toBeRequested(mock) - expect(result.pass).toBe(false) - expect(result.message()).toEqual(`\ -Expect mock to be called - -Expected: ">= 1" -Received: 0` - ) + const message = removeColors((await toBeRequested(mock)).message()) + expect(getExpectMessage(message)).toBe('Expect mock to be called') + expect(getReceived(message)).toBe('Received: 0') + expect(getExpected(message)).toBe('Expected: ">= 1"') - mock.calls.push(mockMatch) const result2 = await toBeRequested.call({ isNot: true }, mock) - - expect(result2.pass).toBe(true) // failure, boolean is inverted later because of `.not` - expect(result2.message()).toEqual(`\ -Expect mock not to be called - -Expected [not]: ">= 1" -Received : 1` - ) + expect(result2.message()).toContain('Expect mock not to be called') }) }) diff --git a/test/matchers/mock/toBeRequestedTimes.test.ts b/test/matchers/mock/toBeRequestedTimes.test.ts index 65df6b0bc..22cd9eb78 100644 --- a/test/matchers/mock/toBeRequestedTimes.test.ts +++ b/test/matchers/mock/toBeRequestedTimes.test.ts @@ -3,6 +3,7 @@ import { vi, test, describe, expect } from 'vitest' import type { Matches, Mock } from 'webdriverio' import { toBeRequestedTimes } from '../../../src/matchers/mock/toBeRequestedTimes.js' +import { removeColors, getReceived, getExpected, getExpectMessage } from '../../__fixtures__/utils.js' vi.mock('@wdio/globals') @@ -101,54 +102,39 @@ describe('toBeRequestedTimes', () => { // expect(mock).not.toBeRequestedTimes(0) should fail const result = await toBeRequestedTimes.call({ isNot: true }, mock, 0) - expect(result.pass).toBe(true) // failure, boolean inverted later because of .not - expect(result.message()).toEqual(`\ -Expect mock not to be called 0 times - -Expected [not]: 0 -Received : 0` - ) + expect(result.pass).toBe(true) // expect(mock).not.toBeRequestedTimes(1) should pass const result2 = await toBeRequestedTimes.call({ isNot: true }, mock, 1) - expect(result2.pass).toBe(false) // success, boolean inverted later because of .not + expect(result2.pass).toBe(false) mock.calls.push(mockMatch) // expect(mock).not.toBeRequestedTimes(0) should pass const result3 = await toBeRequestedTimes.call({ isNot: true }, mock, 0) - expect(result3.pass).toBe(false) // success, boolean inverted later because of .not + expect(result3.pass).toBe(false) // expect(mock).not.toBeRequestedTimes(1) should fail const result4 = await toBeRequestedTimes.call({ isNot: true }, mock, 1) - expect(result4.pass).toBe(true) // failure, boolean inverted later because of .not - expect(result4.message()).toEqual(`\ -Expect mock not to be called 1 time - -Expected [not]: 1 -Received : 1` - ) + expect(result4.pass).toBe(true) }) test('message', async () => { const mock: Mock = new TestMock() - const result = await toBeRequestedTimes.call({}, mock, 0, { wait: 1 }) + const result = await toBeRequestedTimes.call({}, mock, 0) expect(result.message()).toContain('Expect mock to be called 0 times') - const result2 = await toBeRequestedTimes.call({}, mock, 1, { wait: 1 }) + const result2 = await toBeRequestedTimes.call({}, mock, 1) expect(result2.message()).toContain('Expect mock to be called 1 time') - const result3 = await toBeRequestedTimes.call({}, mock, 2, { wait: 1 }) + const result3 = await toBeRequestedTimes.call({}, mock, 2) expect(result3.message()).toContain('Expect mock to be called 2 times') - const result4 = await toBeRequestedTimes.call({}, mock, { gte: 3 }, { wait: 1 }) - expect(result4.pass).toBe(false) - expect(result4.message()).toEqual(`\ -Expect mock to be called times - -Expected: ">= 3" -Received: 0` - ) + const result4 = await toBeRequestedTimes.call({}, mock, { gte: 3 }) + const message4 = removeColors(result4.message()) + expect(getExpectMessage(message4)).toBe('Expect mock to be called times') + expect(getExpected(message4)).toBe('Expected: ">= 3"') + expect(getReceived(message4)).toBe('Received: 0') }) }) diff --git a/test/matchers/mock/toBeRequestedWith.test.ts b/test/matchers/mock/toBeRequestedWith.test.ts index d4c8adab2..0fd51ff70 100644 --- a/test/matchers/mock/toBeRequestedWith.test.ts +++ b/test/matchers/mock/toBeRequestedWith.test.ts @@ -163,7 +163,7 @@ describe('toBeRequestedWith', () => { expect(result.pass).toBe(false) }) - test('wait for NOT - failure with empty params and pass expected to be true', async () => { + test('wait for NOT failure, empty params', async () => { const mock: any = new TestMock() mock.calls.push({ ...mockGet }, { ...mockPost }) setTimeout(() => { @@ -171,16 +171,10 @@ describe('toBeRequestedWith', () => { }, 10) const result = await toBeRequestedWith.call({ isNot: true }, mock, {}) - expect(result.pass).toBe(true) // failure, boolean inverted later because of .not - expect(result.message()).toEqual(`\ -Expect mock not to be called with - -Expected [not]: {} -Received : {}` - ) + expect(result.pass).toBe(true) }) - test('wait for NOT - success with pass expected to be false', async () => { + test('wait for NOT success', async () => { const mock: any = new TestMock() setTimeout(() => { @@ -188,7 +182,7 @@ Received : {}` }, 10) const result = await toBeRequestedWith.call({ isNot: true }, mock, { method: 'DELETE' }) - expect(result.pass).toBe(false) // success, boolean inverted later because of .not + expect(result.pass).toBe(false) }) const scenarios: Scenario[] = [ diff --git a/test/softAssertions.test.ts b/test/softAssertions.test.ts index 647cde302..d524252e5 100644 --- a/test/softAssertions.test.ts +++ b/test/softAssertions.test.ts @@ -21,7 +21,7 @@ describe('Soft Assertions', () => { const softService = SoftAssertService.getInstance() softService.setCurrentTest('test-1', 'test name', 'test file') - await expectWdio.soft(el).toHaveText('Expected Text', { wait: 0 }) + await expectWdio.soft(el).toHaveText('Expected Text') // Verify the failure was recorded const failures = expectWdio.getSoftFailures() @@ -36,7 +36,7 @@ describe('Soft Assertions', () => { softService.setCurrentTest('test-2', 'test name', 'test file') // This should not throw even though it fails - await expectWdio.soft(el).not.toHaveText('Actual Text', { wait: 0 }) + await expectWdio.soft(el).not.toHaveText('Actual Text') // Verify the failure was recorded const failures = expectWdio.getSoftFailures() @@ -50,9 +50,9 @@ describe('Soft Assertions', () => { softService.setCurrentTest('test-3', 'test name', 'test file') // These should not throw even though they fail - await expectWdio.soft(el).toHaveText('First Expected', { wait: 0 }) - await expectWdio.soft(el).toHaveText('Second Expected', { wait: 0 }) - await expectWdio.soft(el).toHaveText('Third Expected', { wait: 0 }) + await expectWdio.soft(el).toHaveText('First Expected') + await expectWdio.soft(el).toHaveText('Second Expected') + await expectWdio.soft(el).toHaveText('Third Expected') // Verify all failures were recorded const failures = expectWdio.getSoftFailures() @@ -170,8 +170,8 @@ describe('Soft Assertions', () => { softService.setCurrentTest('boolean-test', 'boolean test', 'test file') // Test boolean matcher - await expectWdio.soft(el).toBeDisplayed({ wait: 0 }) - await expectWdio.soft(el).toBeClickable({ wait: 0 }) + await expectWdio.soft(el).toBeDisplayed() + await expectWdio.soft(el).toBeClickable() const failures = expectWdio.getSoftFailures() expect(failures.length).toBe(2) @@ -183,7 +183,7 @@ describe('Soft Assertions', () => { const softService = SoftAssertService.getInstance() softService.setCurrentTest('attribute-test', 'attribute test', 'test file') - await expectWdio.soft(el).toHaveAttribute('class', 'expected-class', { wait: 0 }) + await expectWdio.soft(el).toHaveAttribute('class', 'expected-class') const failures = expectWdio.getSoftFailures() expect(failures.length).toBe(1) @@ -194,7 +194,7 @@ describe('Soft Assertions', () => { const softService = SoftAssertService.getInstance() softService.setCurrentTest('options-test', 'options test', 'test file') - await expectWdio.soft(el).toHaveText('Expected', { ignoreCase: true, wait: 0 }) + await expectWdio.soft(el).toHaveText('Expected', { ignoreCase: true, wait: 1000 }) const failures = expectWdio.getSoftFailures() expect(failures.length).toBe(1) @@ -208,12 +208,12 @@ describe('Soft Assertions', () => { // Test 1 softService.setCurrentTest('isolation-test-1', 'test 1', 'file1') - await expectWdio.soft(el).toHaveText('Expected Text 1', { wait: 0 }) + await expectWdio.soft(el).toHaveText('Expected Text 1') expect(expectWdio.getSoftFailures().length).toBe(1) // Test 2 - should have separate failures softService.setCurrentTest('isolation-test-2', 'test 2', 'file2') - await expectWdio.soft(el).toHaveText('Expected Text 2', { wait: 0 }) + await expectWdio.soft(el).toHaveText('Expected Text 2') // Test 2 should only see its own failure expect(expectWdio.getSoftFailures('isolation-test-2').length).toBe(1) @@ -251,9 +251,9 @@ describe('Soft Assertions', () => { // Fire multiple assertions rapidly const promises = [ - expectWdio.soft(el).toHaveText('Expected 1', { wait: 0 }), - expectWdio.soft(el).toBeDisplayed({ wait: 0 }), - expectWdio.soft(el).toBeClickable({ wait: 0 }) + expectWdio.soft(el).toHaveText('Expected 1'), + expectWdio.soft(el).toBeDisplayed(), + expectWdio.soft(el).toBeClickable() ] await Promise.all(promises) @@ -297,7 +297,7 @@ describe('Soft Assertions', () => { softService.setCurrentTest('long-error-test', 'long error', 'test file') const veryLongText = 'A'.repeat(10000) - await expectWdio.soft(el).toHaveText(veryLongText, { wait: 0 }) + await expectWdio.soft(el).toHaveText(veryLongText) const failures = expectWdio.getSoftFailures() expect(failures.length).toBe(1) @@ -305,13 +305,12 @@ describe('Soft Assertions', () => { }) it('should handle null/undefined values gracefully', async () => { - vi.mocked(el.getAttribute).mockResolvedValue(null as any) const softService = SoftAssertService.getInstance() softService.setCurrentTest('null-test', 'null test', 'test file') // Test with null/undefined values - await expectWdio.soft(el).toHaveText(null as any, { wait: 0 }) - await expectWdio.soft(el).toHaveAttribute('class') + await expectWdio.soft(el).toHaveText(null as any) + await expectWdio.soft(el).toHaveAttribute('class', undefined as any) const failures = expectWdio.getSoftFailures() expect(failures.length).toBe(2) @@ -321,7 +320,7 @@ describe('Soft Assertions', () => { const softService = SoftAssertService.getInstance() softService.setCurrentTest('location-test', 'location test', 'test file') - await expectWdio.soft(el).toHaveText('Expected Text', { wait: 0 }) + await expectWdio.soft(el).toHaveText('Expected Text') const failures = expectWdio.getSoftFailures() expect(failures.length).toBe(1) @@ -340,7 +339,7 @@ describe('Soft Assertions', () => { // Generate many failures const promises = [] for (let i = 0; i < 150; i++) { - promises.push(expectWdio.soft(el).toHaveText(`Expected ${i}`), { wait: 0 }) + promises.push(expectWdio.soft(el).toHaveText(`Expected ${i}`)) } await Promise.all(promises) diff --git a/test/utils.test.ts b/test/utils.test.ts index cba27af79..444a8e61f 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,5 +1,5 @@ -import { describe, test, expect, vi } from 'vitest' -import { compareNumbers, compareObject, compareText, compareTextWithArray, waitUntil } from '../src/utils' +import { describe, test, expect } from 'vitest' +import { compareNumbers, compareObject, compareText, compareTextWithArray } from '../src/utils.js' describe('utils', () => { describe('compareText', () => { @@ -158,96 +158,4 @@ describe('utils', () => { expect(compareObject([{ 'foo': 'bar' }], { 'foo': 'bar' }).result).toBe(false) }) }) - - describe('waitUntil', () => { - - describe('should be pass=true for normal success and pass=true for `isNot` failure', () => { - test('should return true when condition is met', async () => { - const condition = vi.fn().mockResolvedValue(true) - - const result = await waitUntil(condition, { wait: 1000, interval: 100 }) - - expect(result).toBe(true) - }) - - test('should return true with wait 0', async () => { - const condition = vi.fn().mockResolvedValue(true) - - const result = await waitUntil(condition, { wait: 0 }) - - expect(result).toBe(true) - }) - - test('should return true when condition is met within wait time', async () => { - const condition = vi.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(false).mockResolvedValueOnce(true) - - const result = await waitUntil(condition, { wait: 1000, interval: 50 }) - - expect(result).toBe(true) - expect(condition).toBeCalledTimes(3) - }) - - test('should return true when condition errors but still is met within wait time', async () => { - const condition = vi.fn().mockRejectedValueOnce(new Error('Test error')).mockRejectedValueOnce(new Error('Test error')).mockResolvedValueOnce(true) - - const result = await waitUntil(condition, { wait: 1000, interval: 50 }) - - expect(result).toBe(true) - expect(condition).toBeCalledTimes(3) - }) - - test('should use default options when not provided', async () => { - const condition = vi.fn().mockResolvedValue(true) - - const result = await waitUntil(condition) - - expect(result).toBe(true) - }) - }) - - describe('should be pass=false for normal failure or pass=false for `isNot` success', () => { - - test('should return false when condition is not met within wait time', async () => { - const condition = vi.fn().mockResolvedValue(false) - - const result = await waitUntil(condition, { wait: 200, interval: 50 }) - - expect(result).toBe(false) - }) - - test('should return false when condition is not met and wait is 0', async () => { - const condition = vi.fn().mockResolvedValue(false) - - const result = await waitUntil(condition, { wait: 0 }) - - expect(result).toBe(false) - }) - - test('should return false if condition throws but still return false', async () => { - const condition = vi.fn().mockRejectedValueOnce(new Error('Always failing')).mockRejectedValueOnce(new Error('Always failing')).mockResolvedValue(false) - - const result = await waitUntil(condition, { wait: 200, interval: 50 }) - - expect(result).toBe(false) - expect(condition).toBeCalledTimes(4) - }) - }) - - describe('when condition throws', () => { - const error = new Error('failing') - - test('should throw with wait', async () => { - const condition = vi.fn().mockRejectedValue(error) - - await expect(() => waitUntil(condition, { wait: 200, interval: 50 })).rejects.toThrowError('failing') - }) - - test('should throw with wait 0', async () => { - const condition = vi.fn().mockRejectedValue(error) - - await expect(() => waitUntil(condition, { wait: 0 })).rejects.toThrowError('failing') - - }) - }) - }) }) diff --git a/types/expect-webdriverio.d.ts b/types/expect-webdriverio.d.ts index 9b729ff34..74963f437 100644 --- a/types/expect-webdriverio.d.ts +++ b/types/expect-webdriverio.d.ts @@ -188,14 +188,11 @@ interface WdioElementOrArrayMatchers<_R, ActualT = unknown> { /** * `WebdriverIO.Element` -> `getProperty` */ - toHaveElementProperty: FnWhenElementOrArrayLike< - ActualT, - ( - property: string, - value?: string | RegExp | WdioAsymmetricMatcher | null, - options?: ExpectWebdriverIO.StringOptions, - ) => Promise - > + toHaveElementProperty: FnWhenElementOrArrayLike, + value?: unknown, + options?: ExpectWebdriverIO.StringOptions + ) => Promise> /** * `WebdriverIO.Element` -> `getProperty` value From 32de2cac8e0ab2c4130d7a31fb490d3817ff7bfd Mon Sep 17 00:00:00 2001 From: dprevost-perso Date: Fri, 30 Jan 2026 08:46:09 -0500 Subject: [PATCH 2/8] Documents clearly today `.not` behavior --- docs/API.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/API.md b/docs/API.md index 9f09e3803..7f9ca71ba 100644 --- a/docs/API.md +++ b/docs/API.md @@ -880,6 +880,29 @@ await expect(elem).toHaveElementClass(/Container/i) In addition to the `expect-webdriverio` matchers you can use builtin Jest's [expect](https://jestjs.io/docs/expect) assertions or [expect/expectAsync](https://jasmine.github.io/api/edge/global.html#expect) for Jasmine. +## Modifiers + +WebdriverIO supports usage of modifiers as `.not` and it will wait until the reverse condition is meet + +```ts +// Wait until the element is no longer present +await expect(element).not.toBeDisplayed() + +// Wait until the text is no more 'some title' +await expect(browser).not.toHaveTitle('some title') +``` + +In case immediate assertion is required, use `{ wait: 0 }` +```ts +// Ensure element is not present right now +await expect(element).not.toBeDisplayed({ wait: 0 }) + +// Ensure the text is not 'some title' right now +await expect(browser).not.toHaveTitle('some title', { wait: 0 }) +``` + +Note: You can pair `.not` with asymmetric matchers, but to enable the wait-until behavior, `.not` must be used directly on the `expect()` call. + ## Asymmetric Matchers WebdriverIO supports usage of asymmetric matchers wherever you compare text values, e.g.: From bbf17886e30225d881cc3badf2b8f25fdcee6c88 Mon Sep 17 00:00:00 2001 From: dprevost-perso Date: Fri, 30 Jan 2026 09:00:34 -0500 Subject: [PATCH 3/8] Keep useful changes --- test/__fixtures__/utils.ts | 5 + test/__mocks__/@wdio/globals.ts | 3 +- test/matchers.test.ts | 386 +++++++++++++++++- test/matchers/beMatchers.test.ts | 44 +- test/matchers/browserMatchers.test.ts | 43 +- test/matchers/element/toBeDisabled.test.ts | 47 ++- test/matchers/element/toBeDisplayed.test.ts | 44 +- test/matchers/element/toHaveChildren.test.ts | 16 +- .../element/toHaveComputedLabel.test.ts | 22 +- .../element/toHaveComputedRole.test.ts | 22 +- .../element/toHaveElementProperty.test.ts | 122 +++++- test/matchers/element/toHaveHTML.test.ts | 42 +- test/matchers/element/toHaveHeight.test.ts | 24 +- test/matchers/element/toHaveId.test.ts | 6 +- test/matchers/element/toHaveSize.test.ts | 21 +- test/matchers/element/toHaveStyle.test.ts | 42 +- test/matchers/element/toHaveText.test.ts | 46 ++- test/matchers/element/toHaveWidth.test.ts | 24 +- .../elements/toBeElementsArrayOfSize.test.ts | 34 +- test/matchers/mock/toBeRequested.test.ts | 30 +- test/matchers/mock/toBeRequestedTimes.test.ts | 40 +- test/matchers/mock/toBeRequestedWith.test.ts | 14 +- test/softAssertions.test.ts | 39 +- test/utils.test.ts | 96 ++++- types/expect-webdriverio.d.ts | 13 +- 25 files changed, 994 insertions(+), 231 deletions(-) diff --git a/test/__fixtures__/utils.ts b/test/__fixtures__/utils.ts index 1149d08d0..95bca72a4 100644 --- a/test/__fixtures__/utils.ts +++ b/test/__fixtures__/utils.ts @@ -2,6 +2,11 @@ export function matcherNameToString(matcherName: string) { return matcherName.replace(/([A-Z])/g, ' $1').toLowerCase() } +export function matcherLastWordName(matcherName: string) { + return matcherName.replace(/^toHave/, '').replace(/^toBe/, '') + .replace(/([A-Z])/g, ' $1').trim().toLowerCase() +} + export function getExpectMessage(msg: string) { return msg.split('\n')[0] } diff --git a/test/__mocks__/@wdio/globals.ts b/test/__mocks__/@wdio/globals.ts index d14bc54d0..d9f4a3b4a 100644 --- a/test/__mocks__/@wdio/globals.ts +++ b/test/__mocks__/@wdio/globals.ts @@ -15,7 +15,7 @@ const getElementMethods = () => ({ isClickable: vi.spyOn({ isClickable: async () => true }, 'isClickable'), isFocused: vi.spyOn({ isFocused: async () => true }, 'isFocused'), isEnabled: vi.spyOn({ isEnabled: async () => true }, 'isEnabled'), - getProperty: vi.spyOn({ getProperty: async (_prop: string) => undefined }, 'getProperty'), + getProperty: vi.spyOn({ getProperty: async (_prop: string) => '1' }, 'getProperty'), getText: vi.spyOn({ getText: async () => ' Valid Text ' }, 'getText'), getHTML: vi.spyOn({ getHTML: async () => { return '' } }, 'getHTML'), getComputedLabel: vi.spyOn({ getComputedLabel: async () => 'Computed Label' }, 'getComputedLabel'), @@ -25,6 +25,7 @@ const getElementMethods = () => ({ if (prop === 'height') { return 50 } return { width: 100, height: 50 } satisfies Size } }, 'getSize') as unknown as WebdriverIO.Element['getSize'], + getAttribute: vi.spyOn({ getAttribute: async (_attr: string) => 'some attribute' }, 'getAttribute'), } satisfies Partial) function $(_selector: string) { diff --git a/test/matchers.test.ts b/test/matchers.test.ts index f96455a01..7cb19b8aa 100644 --- a/test/matchers.test.ts +++ b/test/matchers.test.ts @@ -1,5 +1,8 @@ -import { test, expect, vi } from 'vitest' +import { test, expect, vi, describe } from 'vitest' import { matchers, expect as expectLib } from '../src/index.js' +import { $ } from '@wdio/globals' + +vi.mock('@wdio/globals') const ALL_MATCHERS = [ // browser @@ -71,3 +74,384 @@ test('Generic asymmetric matchers from Expect library should work', () => { expectLib(1).toEqual(expectLib.closeTo(1.0001, 0.0001)) expectLib(['apple', 'banana', 'cherry']).toEqual(expectLib.arrayOf(expectLib.any(String))) }) + +describe('Custom Wdio Matchers Integration Tests', async () => { + + describe('Matchers pass with success with default mocked values', async () => { + const el = await $('selector') + + test('toBe matchers', async () => { + await expectLib(el).toBeDisplayed() + await expectLib(el).toBeExisting() + await expectLib(el).toBeEnabled() + await expectLib(el).toBeClickable() + await expectLib(el).toBeFocused() + await expectLib(el).toBeSelected() + }) + + test('toHave matchers', async () => { + await expectLib(el).toHaveText('Valid Text') + await expectLib(el).toHaveHTML('') + await expectLib(el).toHaveComputedLabel('Computed Label') + await expectLib(el).toHaveComputedRole('Computed Role') + await expectLib(el).toHaveSize({ width: 100, height: 50 }) + await expectLib(el).toHaveHeight(50) + await expectLib(el).toHaveWidth(100) + await expectLib(el).toHaveAttribute('someAttribute', 'some attribute') + await expectLib(el).toHaveAttribute('someAttribute') + await expectLib(el).toHaveAttr('someAttribute', 'some attribute') + await expectLib(el).toHaveElementProperty('someProperty', '1') + }) + }) + + describe('Matchers fails when using `.not` with proper message', async () => { + const el = await $('selector') + + test('toBe matchers', async () => { + await expect(() => expectLib(el).not.toBeDisplayed({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to be displayed + +Expected [not]: "not displayed" +Received : "displayed"` + ) + + await expect(() => expectLib(el).not.toBeExisting({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to be existing + +Expected [not]: "not existing" +Received : "existing"` + ) + + await expect(() => expectLib(el).not.toBeEnabled({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to be enabled + +Expected [not]: "not enabled" +Received : "enabled"` + ) + + await expect(() => expectLib(el).not.toBeClickable({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to be clickable + +Expected [not]: "not clickable" +Received : "clickable"` + ) + + await expect(() => expectLib(el).not.toBeFocused({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to be focused + +Expected [not]: "not focused" +Received : "focused"` + ) + + await expect(() => expectLib(el).not.toBeSelected({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to be selected + +Expected [not]: "not selected" +Received : "selected"` + ) + }) + + test('toHave matchers', async () => { + await expect(() => expectLib(el).not.toHaveText(' Valid Text ', { trim: false, wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to have text + +Expected [not]: " Valid Text " +Received : " Valid Text "` + ) + + await expect(() => expectLib(el).not.toHaveHTML('', { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to have HTML + +Expected [not]: "" +Received : ""` + ) + + await expect(() => expectLib(el).not.toHaveComputedLabel('Computed Label', { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to have computed label + +Expected [not]: "Computed Label" +Received : "Computed Label"` + ) + + await expect(() => expectLib(el).not.toHaveComputedRole('Computed Role', { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to have computed role + +Expected [not]: "Computed Role" +Received : "Computed Role"` + ) + }) + + test('size matchers', async () => { + await expect(() => expectLib(el).not.toHaveSize({ width: 100, height: 50 }, { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to have size + +Expected [not]: {"height": 50, "width": 100} +Received : {"height": 50, "width": 100}` + ) + + await expect(() => expectLib(el).not.toHaveHeight(50, { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to have height + +Expected [not]: 50 +Received : 50` + ) + + await expect(() => expectLib(el).not.toHaveWidth(100, { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to have width + +Expected [not]: 100 +Received : 100` + ) + }) + + test('attribute and property matchers', async () => { + await expect(() => expectLib(el).not.toHaveAttribute('someAttribute', 'some attribute')).rejects.toThrow(`\ +Expect $(\`selector\`) not to have attribute someAttribute + +Expected [not]: "some attribute" +Received : "some attribute"` + ) + + await expect(() => expectLib(el).not.toHaveAttribute('someAttribute')).rejects.toThrow(`\ +Expect $(\`selector\`) not to have attribute someAttribute + +Expected [not]: false +Received : true` + ) + + await expect(() => expectLib(el).not.toHaveAttr('someAttribute', 'some attribute')).rejects.toThrow(`\ +Expect $(\`selector\`) not to have attribute someAttribute + +Expected [not]: "some attribute" +Received : "some attribute"` + ) + await expect(() => expectLib(el).not.toHaveElementProperty('someProperty', '1')).rejects.toThrow(`\ +Expect $(\`selector\`) not to have property someProperty + +Expected [not]: "1" +Received : "1"` + ) + }) + }) + + describe('Matchers fails with proper messages', async () => { + const el = await $('selector') + vi.mocked(el.isDisplayed).mockResolvedValue(false) + vi.mocked(el.isExisting).mockResolvedValue(false) + vi.mocked(el.isEnabled).mockResolvedValue(false) + vi.mocked(el.isClickable).mockResolvedValue(false) + vi.mocked(el.isFocused).mockResolvedValue(false) + vi.mocked(el.isSelected).mockResolvedValue(false) + + test('Ensure toBe matchers throws and show proper failing message', async () => { + await expect(() => expectLib(el).toBeDisplayed({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to be displayed + +Expected: "displayed" +Received: "not displayed"`) + + await expect(() => expectLib(el).toBeExisting({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to be existing + +Expected: "existing" +Received: "not existing"`) + + await expect(() => expectLib(el).toExist({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to exist + +Expected: "exist" +Received: "not exist"`) + + await expect(() => expectLib(el).toBeEnabled({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to be enabled + +Expected: "enabled" +Received: "not enabled"`) + + await expect(() => expectLib(el).toBeClickable({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to be clickable + +Expected: "clickable" +Received: "not clickable"`) + + await expect(() => expectLib(el).toBeFocused({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to be focused + +Expected: "focused" +Received: "not focused"`) + + await expect(() => expectLib(el).toBeSelected({ wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to be selected + +Expected: "selected" +Received: "not selected"`) + + }) + + test('Ensure toHave matchers throws and show proper failing message', async () => { + await expect(() => expectLib(el).toHaveText('Some other text', { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to have text + +Expected: "Some other text" +Received: " Valid Text "`) + + await expect(() => expectLib(el).toHaveHTML('', { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to have HTML + +Expected: "" +Received: ""`) + + await expect(() => expectLib(el).toHaveComputedLabel('Some Other Computed Label', { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to have computed label + +Expected: "Some Other Computed Label" +Received: "Computed Label"`) + + await expect(() => expectLib(el).toHaveComputedRole('Some Other Computed Role', { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to have computed role + +Expected: "Some Other Computed Role" +Received: "Computed Role"`) + + await expect(() => expectLib(el).toHaveElementProperty('someProperty', 'some other value', { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to have property someProperty + +Expected: "some other value" +Received: "1"`) + + }) + + test('Ensure toHaveAttribute matchers throw and show proper failing message', async () => { + await expect(() => expectLib(el).toHaveAttribute('someAttribute', 'some other attribute', { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to have attribute someAttribute + +Expected: "some other attribute" +Received: "some attribute"`) + + vi.mocked(el.getAttribute).mockResolvedValue(null as unknown as string) + await expect(() => expectLib(el).toHaveAttribute('notExistingAttribute')).rejects.toThrow(`\ +Expect $(\`selector\`) to have attribute notExistingAttribute + +Expected: true +Received: false`) + await expect(() => expectLib(el).toHaveAttr('someAttribute', 'some other attribute', { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to have attribute someAttribute + +Expected: "some other attribute" +Received: null`) + }) + + test('Ensure toHaveSize, toHaveHeight, toHaveWidth matchers throw and show proper failing message', async () => { + await expect(() => expectLib(el).toHaveSize({ width: 200, height: 100 }, { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to have size + +- Expected - 2 ++ Received + 2 + + Object { +- "height": 100, +- "width": 200, ++ "height": 50, ++ "width": 100, + }`) + await expect(() => expectLib(el).toHaveHeight(100, { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to have height + +Expected: 100 +Received: 50`) + + await expect(() => expectLib(el).toHaveWidth(200, { wait: 1 })).rejects.toThrow(`\ +Expect $(\`selector\`) to have width + +Expected: 200 +Received: 100`) + }) + + }) + + describe('Matchers pass when using `.not`', async () => { + const el = await $('selector') + vi.mocked(el.isDisplayed).mockResolvedValue(false) + vi.mocked(el.isExisting).mockResolvedValue(false) + vi.mocked(el.isEnabled).mockResolvedValue(false) + vi.mocked(el.isClickable).mockResolvedValue(false) + vi.mocked(el.isFocused).mockResolvedValue(false) + vi.mocked(el.isSelected).mockResolvedValue(false) + + test('toBe matchers', async () => { + await expectLib(el).not.toBeDisplayed({ wait: 1 }) + await expectLib(el).not.toBeExisting({ wait: 1 }) + await expectLib(el).not.toBeEnabled({ wait: 1 }) + await expectLib(el).not.toBeClickable({ wait: 1 }) + await expectLib(el).not.toBeFocused({ wait: 1 }) + await expectLib(el).not.toBeSelected({ wait: 1 }) + }) + + test('toHave matchers', async () => { + await expectLib(el).not.toHaveText('Some other text', { wait: 1 }) + await expectLib(el).not.toHaveHTML('', { wait: 1 }) + await expectLib(el).not.toHaveComputedLabel('Some Other Computed Label', { wait: 1 }) + await expectLib(el).not.toHaveComputedRole('Some Other Computed Role', { wait: 1 }) + await expectLib(el).not.toHaveElementProperty('someProperty', 'some other value', { wait: 1 }) + await expectLib(el).not.toHaveAttribute('someAttribute', 'some other attribute', { wait: 1 }) + await expectLib(el).not.toHaveAttr('someAttribute', 'some other attribute', { wait: 1 }) + await expectLib(el).not.toHaveSize({ width: 200, height: 100 }, { wait: 1 }) + await expectLib(el).not.toHaveHeight(100, { wait: 1 }) + await expectLib(el).not.toHaveWidth(200, { wait: 1 }) + }) + + }) + + describe('Matcher eventually passing', async () => { + + test('when element eventually is displayed, matcher and .not matcher should be consistent', async () => { + const el = await $('selector') + vi.mocked(el.isDisplayed) + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(true) + + // Passes when element becomes displayed + await expectLib(el).toBeDisplayed({ wait: 300, interval: 100 }) + + vi.mocked(el.isDisplayed) + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(true) + + // Should not pass with the same scenario to be consistent + await expect(() => expectLib(el).not.toBeDisplayed({ wait: 300, interval: 100 })).rejects.toThrow(`\ +Expect $(\`selector\`) not to be displayed + +Expected [not]: "not displayed" +Received : "displayed"`) + + expect(el.isDisplayed).toHaveBeenCalledTimes(6) + }) + + test('when element eventually is not displayed, matcher and .not matcher should be consistent', async () => { + const el = await $('selector') + vi.mocked(el.isDisplayed) + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(false) + + // Does not pass since element never becomes displayed + await expect(expectLib(el).toBeDisplayed({ wait: 300, interval: 100 })).rejects.toThrow(`\ +Expect $(\`selector\`) to be displayed + +Expected: "displayed" +Received: "not displayed"`) + + vi.mocked(el.isDisplayed) + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(false) + + // Should pass with the same scenario to be consistent + await expectLib(el).not.toBeDisplayed({ wait: 300, interval: 100 }) + + expect(el.isDisplayed).toHaveBeenCalledTimes(6) + }) + }) +}) diff --git a/test/matchers/beMatchers.test.ts b/test/matchers/beMatchers.test.ts index 4ebf22cd9..c2eba4864 100644 --- a/test/matchers/beMatchers.test.ts +++ b/test/matchers/beMatchers.test.ts @@ -1,6 +1,6 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' -import { getExpectMessage, getReceived, matcherNameToString } from '../__fixtures__/utils.js' +import { matcherLastWordName } from '../__fixtures__/utils.js' import * as Matchers from '../../src/matchers.js' vi.mock('@wdio/globals') @@ -82,56 +82,58 @@ describe('be* matchers', () => { expect(el[elementFnName]).toHaveBeenCalledTimes(1) }) - test('not - failure', async () => { + test('not - failure - pass should be true', async () => { const el = await $('sel') const result = await matcherFn.call({ isNot: true }, el, { wait: 0 }) as ExpectWebdriverIO.AssertionResult - const received = getReceived(result.message()) - expect(received).not.toContain('not') - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to be ${matcherLastWordName(matcherName)} + +Expected [not]: "not ${matcherLastWordName(matcherName)}" +Received : "${matcherLastWordName(matcherName)}"` + ) }) - test('not - success', async () => { + test('not - success - pass should be false', async () => { const el = await $('sel') el[elementFnName] = vi.fn().mockResolvedValue(false) const result = await matcherFn.call({ isNot: true }, el, { wait: 0 }) as ExpectWebdriverIO.AssertionResult - const received = getReceived(result.message()) - expect(received).toContain('not') - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) - test('not - failure (with wait)', async () => { + test('not - failure (with wait) - pass should be true', async () => { const el = await $('sel') const result = await matcherFn.call({ isNot: true }, el, { wait: 1 }) as ExpectWebdriverIO.AssertionResult - const received = getReceived(result.message()) - expect(received).not.toContain('not') - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` }) - test('not - success (with wait)', async () => { + test('not - success (with wait) - pass should be false', async () => { const el = await $('sel') el[elementFnName] = vi.fn().mockResolvedValue(false) - const result = await matcherFn.call({ isNot: true }, el, { wait: 1 }) as ExpectWebdriverIO.AssertionResult - const received = getReceived(result.message()) - expect(received).toContain('not') - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) test('message', async () => { const el = await $('sel') el[elementFnName] = vi.fn().mockResolvedValue(false) - const result = await matcherFn.call({}, el) as ExpectWebdriverIO.AssertionResult - expect(getExpectMessage(result.message())) - .toContain(matcherNameToString(matcherName)) + const result = await matcherFn.call({}, el, { wait: 1 }) as ExpectWebdriverIO.AssertionResult + expect(result.pass).toBe(false) + expect(result.message()).toBe(`\ +Expect $(\`sel\`) to be ${matcherLastWordName(matcherName)} + +Expected: "${matcherLastWordName(matcherName)}" +Received: "not ${matcherLastWordName(matcherName)}"` + ) }) }) }) diff --git a/test/matchers/browserMatchers.test.ts b/test/matchers/browserMatchers.test.ts index 72f9a0a4d..f017b5674 100644 --- a/test/matchers/browserMatchers.test.ts +++ b/test/matchers/browserMatchers.test.ts @@ -1,7 +1,7 @@ import { vi, test, describe, expect } from 'vitest' import { browser } from '@wdio/globals' -import { getExpectMessage, getReceived, matcherNameToString, getExpected } from '../__fixtures__/utils.js' +import { getExpectMessage, matcherNameToString, matcherLastWordName } from '../__fixtures__/utils.js' import * as Matchers from '../../src/matchers.js' vi.mock('@wdio/globals') @@ -28,7 +28,7 @@ describe('browser matchers', () => { expect(browser[browserFnName]).toHaveBeenCalledTimes(3) }) - test('wait but failure', async () => { + test('wait but error', async () => { browser[browserFnName] = vi.fn().mockRejectedValue(new Error('some error')) await expect(() => matcherFn.call({}, browser, validText, { trim: false })) @@ -61,48 +61,47 @@ describe('browser matchers', () => { expect(browser[browserFnName]).toHaveBeenCalledTimes(1) }) - test('not - failure', async () => { + test('not - failure - pass should be true', async () => { + browser[browserFnName] = vi.fn().mockResolvedValue(validText) const result = await matcherFn.call({ isNot: true }, browser, validText, { wait: 0, trim: false }) as ExpectWebdriverIO.AssertionResult - expect(getExpectMessage(result.message())).toContain('not') - expect(getExpected(result.message())).toContain('not') + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect window not to have ${matcherLastWordName(matcherName)} - expect(result.pass).toBe(true) +Expected [not]: " Valid Text " +Received : " Valid Text "` + ) }) - test('not - success', async () => { + test('not - success - pass should be false', async () => { browser[browserFnName] = vi.fn().mockResolvedValue(wrongText) const result = await matcherFn.call({ isNot: true }, browser, validText, { wait: 0 }) as ExpectWebdriverIO.AssertionResult - expect(getExpectMessage(result.message())).toContain('not') - expect(getExpected(result.message())).toContain('Valid') - expect(getReceived(result.message())).toContain('Wrong') - - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) - test('not - failure (with wait)', async () => { + test('not - failure (with wait) - pass should be true', async () => { browser[browserFnName] = vi.fn().mockResolvedValue(validText) const result = await matcherFn.call({ isNot: true }, browser, validText, { wait: 1, trim: false }) as ExpectWebdriverIO.AssertionResult - expect(getExpectMessage(result.message())).toContain('not') - expect(getExpected(result.message())).toContain('not') + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect window not to have ${matcherLastWordName(matcherName)} - expect(result.pass).toBe(true) +Expected [not]: " Valid Text " +Received : " Valid Text "` + ) }) - test('not - success (with wait)', async () => { + test('not - success (with wait) - pass should be false', async () => { browser[browserFnName] = vi.fn().mockResolvedValue(wrongText) const result = await matcherFn.call({ isNot: true }, browser, validText, { wait: 1 }) as ExpectWebdriverIO.AssertionResult - expect(getExpectMessage(result.message())).toContain('not') - expect(getExpected(result.message())).toContain('Valid') - expect(getReceived(result.message())).toContain('Wrong') - - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) test('message', async () => { diff --git a/test/matchers/element/toBeDisabled.test.ts b/test/matchers/element/toBeDisabled.test.ts index f53601413..1b0dac3e4 100644 --- a/test/matchers/element/toBeDisabled.test.ts +++ b/test/matchers/element/toBeDisabled.test.ts @@ -1,7 +1,6 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' -import { getExpectMessage, getReceived } from '../../__fixtures__/utils.js' import { toBeDisabled } from '../../../src/matchers/element/toBeDisabled.js' vi.mock('@wdio/globals') @@ -69,55 +68,65 @@ describe('toBeDisabled', () => { expect(el.isEnabled).toHaveBeenCalledTimes(1) }) - test('not - failure', async () => { + test('not - failure - pass should be true', async () => { const el = await $('sel') el.isEnabled = vi.fn().mockResolvedValue(false) const result = await toBeDisabled.call({ isNot: true }, el, { wait: 0 }) - const received = getReceived(result.message()) - expect(received).not.toContain('not') - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to be disabled + +Expected [not]: "not disabled" +Received : "disabled"` + ) }) - test('not - success', async () => { + test('not - success - pass should be false', async () => { const el = await $('sel') el.isEnabled = vi.fn().mockResolvedValue(true) const result = await toBeDisabled.call({ isNot: true }, el, { wait: 0 }) - const received = getReceived(result.message()) - expect(received).toContain('not') - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) - test('not - failure (with wait)', async () => { + test('not - failure (with wait) - pass should be true', async () => { const el = await $('sel') el.isEnabled = vi.fn().mockResolvedValue(false) const result = await toBeDisabled.call({ isNot: true }, el, { wait: 1 }) - const received = getReceived(result.message()) - expect(received).not.toContain('not') - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to be disabled + +Expected [not]: "not disabled" +Received : "disabled"` + ) }) - test('not - success (with wait)', async () => { + test('not - success (with wait) - pass should be false', async () => { const el = await $('sel') el.isEnabled = vi.fn().mockResolvedValue(true) const result = await toBeDisabled.call({ isNot: true }, el, { wait: 1 }) - const received = getReceived(result.message()) - expect(received).toContain('not') - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) test('message', async () => { const el = await $('sel') - el.isEnabled = vi.fn().mockResolvedValue(false) + el.isEnabled = vi.fn().mockResolvedValue(true) const result = await toBeDisabled.call({}, el) - expect(getExpectMessage(result.message())).toContain('to be disabled') + expect(result.pass).toBe(false) + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) to be disabled + +Expected: "disabled" +Received: "not disabled"` + ) }) }) diff --git a/test/matchers/element/toBeDisplayed.test.ts b/test/matchers/element/toBeDisplayed.test.ts index 84a660674..250ead0bd 100644 --- a/test/matchers/element/toBeDisplayed.test.ts +++ b/test/matchers/element/toBeDisplayed.test.ts @@ -1,7 +1,6 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' -import { getExpectMessage, getReceived } from '../../__fixtures__/utils.js' import { toBeDisplayed } from '../../../src/matchers/element/toBeDisplayed.js' import { executeCommandBe } from '../../../src/utils.js' import { DEFAULT_OPTIONS } from '../../../src/constants.js' @@ -123,45 +122,42 @@ describe('toBeDisplayed', () => { expect(el.isDisplayed).toHaveBeenCalledTimes(1) }) - test('not - failure', async () => { + test('not - failure - pass must be true', async () => { const el = await $('sel') - const result = await toBeDisplayed.call({ isNot: true }, el, { wait: 0 }) - const received = getReceived(result.message()) - expect(received).not.toContain('not') - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` }) - test('not - success', async () => { + test('not - success - pass should be false', async () => { const el = await $('sel') el.isDisplayed = vi.fn().mockResolvedValue(false) const result = await toBeDisplayed.call({ isNot: true }, el, { wait: 0 }) - const received = getReceived(result.message()) - expect(received).toContain('not') - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) - test('not - failure (with wait)', async () => { + test('not - failure (with wait) - pass should be true', async () => { const el = await $('sel') - const result = await toBeDisplayed.call({ isNot: true }, el, { wait: 1 }) - const received = getReceived(result.message()) - expect(received).not.toContain('not') - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to be displayed + +Expected [not]: "not displayed" +Received : "displayed"` + ) }) - test('not - success (with wait)', async () => { + test('not - success (with wait) - pass should be false', async () => { const el = await $('sel') el.isDisplayed = vi.fn().mockResolvedValue(false) const result = await toBeDisplayed.call({ isNot: true }, el, { wait: 1 }) - const received = getReceived(result.message()) expect(el.isDisplayed).toHaveBeenCalledWith( { @@ -175,8 +171,7 @@ describe('toBeDisplayed', () => { wait: 1, interval: DEFAULT_OPTIONS.interval })) - expect(received).toContain('not') - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) test('message', async () => { @@ -184,7 +179,14 @@ describe('toBeDisplayed', () => { el.isDisplayed = vi.fn().mockResolvedValue(false) - const result = await toBeDisplayed.call({}, el) - expect(getExpectMessage(result.message())).toContain('to be displayed') + const result = await toBeDisplayed.call({}, el, { wait: 0 }) + + expect(result.pass).toBe(false) + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) to be displayed + +Expected: "displayed" +Received: "not displayed"` + ) }) }) diff --git a/test/matchers/element/toHaveChildren.test.ts b/test/matchers/element/toHaveChildren.test.ts index f37bb3d98..4699cb5dc 100644 --- a/test/matchers/element/toHaveChildren.test.ts +++ b/test/matchers/element/toHaveChildren.test.ts @@ -66,17 +66,25 @@ describe('toHaveChildren', () => { expect(result.pass).toBe(false) }) - test('.not exact value - failure', async () => { + test('.not exact value - failure - pass should be true', async () => { const el = await $('sel') const result = await toHaveChildren.bind({ isNot: true })(el, { eq: 2, wait: 0 }) - expect(result.pass).toBe(true) + + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to have children + +Expected [not]: 2 +Received : 2`) + }) - test('.not exact value - success', async () => { + test('.not exact value - success - pass should be false', async () => { const el = await $('sel') const result = await toHaveChildren.bind({ isNot: true })(el, { eq: 3, wait: 1 }) - expect(result.pass).toBe(false) + + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) }) diff --git a/test/matchers/element/toHaveComputedLabel.test.ts b/test/matchers/element/toHaveComputedLabel.test.ts index ae8c2a1df..02f95a5b2 100644 --- a/test/matchers/element/toHaveComputedLabel.test.ts +++ b/test/matchers/element/toHaveComputedLabel.test.ts @@ -67,30 +67,35 @@ describe('toHaveComputedLabel', () => { expect(el.getComputedLabel).toHaveBeenCalledTimes(1) }) - test('not - failure', async () => { + test('not - failure - pass should be true', async () => { const el = await $('sel') el.getComputedLabel = vi.fn().mockResolvedValue('WebdriverIO') const result = await toHaveComputedLabel.call({ isNot: true }, el, 'WebdriverIO', { wait: 0 }) - const received = getReceived(result.message()) - expect(received).not.toContain('not') - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to have computed label + +Expected [not]: "WebdriverIO" +Received : "WebdriverIO"` + ) }) - test("should return false if computed labels don't match", async () => { + test('not - success - pass should be false', async () => { const el = await $('sel') el.getComputedLabel = vi.fn().mockResolvedValue('WebdriverIO') - const result = await toHaveComputedLabel.bind({ isNot: true })(el, 'foobar', { wait: 1 }) - expect(result.pass).toBe(false) + const result = await toHaveComputedLabel.call({ isNot: true }, el, 'not WebdriverIO', { wait: 0 }) + + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) test('should return true if computed labels match', async () => { const el = await $('sel') el.getComputedLabel = vi.fn().mockResolvedValue('WebdriverIO') - const result = await toHaveComputedLabel.bind({ isNot: true })(el, 'WebdriverIO', { wait: 1 }) + const result = await toHaveComputedLabel.bind({})(el, 'WebdriverIO', { wait: 1 }) expect(result.pass).toBe(true) }) @@ -101,6 +106,7 @@ describe('toHaveComputedLabel', () => { const result = await toHaveComputedLabel.bind({})(el, 'BrowserdriverIO', { replace: ['Web', 'Browser'], }) + expect(result.pass).toBe(true) }) diff --git a/test/matchers/element/toHaveComputedRole.test.ts b/test/matchers/element/toHaveComputedRole.test.ts index a2b143744..f01df18af 100644 --- a/test/matchers/element/toHaveComputedRole.test.ts +++ b/test/matchers/element/toHaveComputedRole.test.ts @@ -69,31 +69,35 @@ describe('toHaveComputedcomputed role', () => { expect(el.getComputedRole).toHaveBeenCalledTimes(1) }) - test('not - failure', async () => { + test('not - failure - pass should be true', async () => { const el = await $('sel') el.getComputedRole = vi.fn().mockResolvedValueOnce('WebdriverIO') const result = await toHaveComputedRole.call({ isNot: true }, el, 'WebdriverIO', { wait: 0 }) - const received = getReceived(result.message()) - expect(received).not.toContain('not') - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to have computed role + +Expected [not]: "WebdriverIO" +Received : "WebdriverIO"` + ) }) - test("should return false if computed roles don't match", async () => { + test('not - success - pass should be false', async () => { const el = await $('sel') el.getComputedRole = vi.fn().mockResolvedValueOnce('WebdriverIO') - const result = await toHaveComputedRole.bind({ isNot: true })(el, 'foobar', { wait: 1 }) - expect(result.pass).toBe(false) + const result = await toHaveComputedRole.call({ isNot: true }, el, 'not WebdriverIO', { wait: 0 }) + + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) test('should return true if computed roles match', async () => { const el = await $('sel') el.getComputedRole = vi.fn().mockResolvedValueOnce('WebdriverIO') - const result = await toHaveComputedRole.bind({ isNot: true })(el, 'WebdriverIO', { wait: 1 }) - + const result = await toHaveComputedRole.bind({})(el, 'WebdriverIO', { wait: 1 }) expect(result.pass).toBe(true) }) diff --git a/test/matchers/element/toHaveElementProperty.test.ts b/test/matchers/element/toHaveElementProperty.test.ts index 977e75b1e..affb68bf3 100644 --- a/test/matchers/element/toHaveElementProperty.test.ts +++ b/test/matchers/element/toHaveElementProperty.test.ts @@ -45,18 +45,41 @@ describe('toHaveElementProperty', () => { const el = await $('sel') el.getProperty = vi.fn().mockResolvedValue('iphone') - const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', 'foobar', { wait: 1 }) + const result = await toHaveElementProperty.bind({})(el, 'property', 'foobar', { wait: 1 }) expect(result.pass).toBe(false) }) + test('should return success (false) if values dont match when isNot is true', async () => { + const el = await $('sel') + el.getProperty = vi.fn().mockResolvedValue('iphone') + + const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', 'foobar', { wait: 1 }) + + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + }) + test('should return true if values match', async () => { const el = await $('sel') + el.getProperty = vi.fn().mockResolvedValue('iphone') + const result = await toHaveElementProperty.bind({})(el, 'property', 'iphone', { wait: 1 }) + expect(result.pass).toBe(true) + }) + + test('should return failure (true) if values match when isNot is true', async () => { + const el = await $('sel') el.getProperty = vi.fn().mockResolvedValue('iphone') const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', 'iphone', { wait: 1 }) - expect(result.pass).toBe(true) + + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to have property property + +Expected [not]: "iphone" +Received : "iphone"` + ) }) test('with RegExp should return true if values match', async () => { @@ -68,31 +91,112 @@ describe('toHaveElementProperty', () => { expect(result.pass).toBe(true) }) - test('should return false for null input', async () => { + test.for([ + { propertyActualValue: null }, + { propertyActualValue: undefined }] + )('return false for not defined actual if expected is defined since property does not exist', async ( { propertyActualValue }) => { + const el = await $('sel') + el.getProperty = vi.fn().mockResolvedValue(propertyActualValue) + + const result = await toHaveElementProperty.bind({})(el, 'property', 'iphone', { wait: 1 }) + expect(result.pass).toBe(false) + }) + + test.for([ + { propertyActualValue: null }, + { propertyActualValue: undefined }] + )('return success (false) for not defined actual and defined expected when isNot is true since property does not exist', async ({ propertyActualValue }) => { const el = await $('sel') - el.getProperty = vi.fn().mockResolvedValue(undefined) + el.getProperty = vi.fn().mockResolvedValue(propertyActualValue) const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', 'iphone', { wait: 1 }) - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) - test('should return true if value is null', async () => { + test.for([ + { expectedValue: null }, + // { expectedValue: undefined } // fails a bug? + ] + )('should return true when property does exist by passing an not defined expected value', async ( { expectedValue }) => { const el = await $('sel') el.getProperty = vi.fn().mockResolvedValue('Test Value') - const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', null as any) + const result = await toHaveElementProperty.bind({})(el, 'property', expectedValue) expect(result.pass).toBe(true) }) + test.for([ + { expectedValue: null }, + //{ expectedValue: undefined } // fails a bug? + ] + )('should return failure (true) if property exists by passing not defined expected value when isNot is true', async ( { expectedValue }) => { + const el = await $('sel') + el.getProperty = vi.fn().mockResolvedValue('Test Value') + + const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', expectedValue) + + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + }) + + // Bug? When requesting to have element property and it does exist should we return true here? + test.skip('return true if property is present', async () => { + const el = await $('sel') + el.getProperty = vi.fn().mockResolvedValue('Test Value') + + const result = await toHaveElementProperty.bind({})(el, 'property') + expect(result.pass).toBe(true) + }) + + // Bug? When requesting to not have element property and it does exist should we have a failure (pass=true? + test.skip('return failure (true) if property is present when isNot is true', async () => { + const el = await $('sel') + el.getProperty = vi.fn().mockResolvedValue('Test Value') + + const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property') + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + }) + + test.for([ + { expectedValue: null }, + { expectedValue: undefined } + ] + )('return false if property is not present', async ({ expectedValue }) => { + const el = await $('sel') + el.getProperty = vi.fn().mockResolvedValue(expectedValue) + + const result = await toHaveElementProperty.bind({})(el, 'property') + expect(result.pass).toBe(false) + }) + test.for([ + { expectedValue: null }, + { expectedValue: undefined } + ] + )('return success (false) if value is not present when isNot is true', async ({ expectedValue }) => { + const el = await $('sel') + el.getProperty = vi.fn().mockResolvedValue(expectedValue) + + const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property') + + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + }) + test('should return false if value is non-string', async () => { const el = await $('sel') el.getProperty = vi.fn().mockResolvedValue(5) + const result = await toHaveElementProperty.bind({})(el, 'property', 'Test Value') + expect(result.pass).toBe(false) + }) + + test('should return success (false) if value is non-string when isNot is true', async () => { + const el = await $('sel') + el.getProperty = vi.fn().mockResolvedValue(5) + const result = await toHaveElementProperty.bind({ isNot: true })(el, 'property', 'Test Value') - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) describe('failure with RegExp when value does not match', () => { @@ -102,7 +206,7 @@ describe('toHaveElementProperty', () => { const el = await $('sel') el.getProperty = vi.fn().mockResolvedValue('iphone') - result = await toHaveElementProperty.call({}, el, 'property', /WDIO/) + result = await toHaveElementProperty.call({}, el, 'property', /WDIO/, { wait: 1 }) }) test('failure', () => { diff --git a/test/matchers/element/toHaveHTML.test.ts b/test/matchers/element/toHaveHTML.test.ts index 5e380d012..f8dbbd2a2 100755 --- a/test/matchers/element/toHaveHTML.test.ts +++ b/test/matchers/element/toHaveHTML.test.ts @@ -70,29 +70,59 @@ describe('toHaveHTML', () => { expect(element.getHTML).toHaveBeenCalledTimes(1) }) - test('not - failure', async () => { + test('not - failure - pass should be true', async () => { const element = await $('sel') element.getHTML = vi.fn().mockResolvedValue('
foo
') + const result = await toHaveHTML.call({ isNot: true }, element, '
foo
', { wait: 0 }) - const received = getReceived(result.message()) - expect(received).not.toContain('not') - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to have HTML + +Expected [not]: "
foo
" +Received : "
foo
"` + ) + }) + + test('not - success - pass should be false', async () => { + const el = await $('sel') + el.getHTML = vi.fn().mockResolvedValue('
foo
') + + const result = await toHaveHTML.call({ isNot: true }, el, '
Notfoo
', { wait: 0 }) + + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) test("should return false if htmls don't match", async () => { const element = await $('sel') element.getHTML = vi.fn().mockResolvedValue('
foo
') - const result = await toHaveHTML.bind({ isNot: true })(element, 'foobar', { wait: 1 }) + const result = await toHaveHTML.bind({})(element, 'foobar', { wait: 1 }) expect(result.pass).toBe(false) }) + test("should suceeds (false) if htmls don't match when isNot is true", async () => { + const element = await $('sel') + element.getHTML = vi.fn().mockReturnValue('
foo
') + + const result = await toHaveHTML.bind({ isNot: true })(element, 'foobar', { wait: 1 }) + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + }) + + test('should fails (pass=true) if htmls match when isNot is true', async () => { + const element = await $('sel') + element.getHTML = vi.fn().mockReturnValue('
foo
') + + const result = await toHaveHTML.bind({ isNot: true })(element, '
foo
', { wait: 1 }) + expect(result.pass).toBe(true) // success, boolean is inverted later because of `.not` + }) + test('should return true if htmls match', async () => { const element = await $('sel') element.getHTML = vi.fn().mockResolvedValue('
foo
') - const result = await toHaveHTML.bind({ isNot: true })(element, '
foo
', { wait: 1 }) + const result = await toHaveHTML.bind({})(element, '
foo
', { wait: 1 }) expect(result.pass).toBe(true) }) diff --git a/test/matchers/element/toHaveHeight.test.ts b/test/matchers/element/toHaveHeight.test.ts index 83f289cba..8f6134cf7 100755 --- a/test/matchers/element/toHaveHeight.test.ts +++ b/test/matchers/element/toHaveHeight.test.ts @@ -1,7 +1,7 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' -import { getExpectMessage, getReceived } from '../../__fixtures__/utils.js' +import { getExpectMessage } from '../../__fixtures__/utils.js' import { toHaveHeight } from '../../../src/matchers/element/toHaveHeight.js' vi.mock('@wdio/globals') @@ -84,15 +84,27 @@ describe('toHaveHeight', () => { expect(el.getSize).toHaveBeenCalledTimes(1) }) - test('not - failure', async () => { + test('not - failure - pass should be true', async () => { const el = await $('sel') el.getSize = vi.fn().mockResolvedValue(32) + const result = await toHaveHeight.call({ isNot: true }, el, 32, { wait: 0 }) - const result = await toHaveHeight.call({}, el, 32, { wait: 0 }) - const received = getReceived(result.message()) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to have height - expect(received).not.toContain('not') - expect(result.pass).toBe(true) +Expected [not]: 32 +Received : 32` + ) + }) + + test('not - success - pass should be false', async () => { + const el = await $('sel') + el.getSize = vi.fn().mockResolvedValue(31) + + const result = await toHaveHeight.call({ isNot: true }, el, 32, { wait: 0 }) + + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) test("should return false if sizes don't match", async () => { diff --git a/test/matchers/element/toHaveId.test.ts b/test/matchers/element/toHaveId.test.ts index 382fa3141..3c67a939c 100644 --- a/test/matchers/element/toHaveId.test.ts +++ b/test/matchers/element/toHaveId.test.ts @@ -31,20 +31,20 @@ describe('toHaveId', () => { const afterAssertion = vi.fn() beforeEach(async () => { - result = await toHaveId.call({}, el, 'an attribute', { beforeAssertion, afterAssertion }) + result = await toHaveId.call({}, el, 'an attribute', { beforeAssertion, afterAssertion, wait: 0 }) }) test('failure', () => { expect(beforeAssertion).toBeCalledWith({ matcherName: 'toHaveId', expectedValue: 'an attribute', - options: { beforeAssertion, afterAssertion } + options: { beforeAssertion, afterAssertion, wait: 0 } }) expect(result.pass).toBe(false) expect(afterAssertion).toBeCalledWith({ matcherName: 'toHaveId', expectedValue: 'an attribute', - options: { beforeAssertion, afterAssertion }, + options: { beforeAssertion, afterAssertion, wait: 0 }, result }) }) diff --git a/test/matchers/element/toHaveSize.test.ts b/test/matchers/element/toHaveSize.test.ts index de8d86146..74665b118 100755 --- a/test/matchers/element/toHaveSize.test.ts +++ b/test/matchers/element/toHaveSize.test.ts @@ -1,7 +1,6 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' -import { getExpectMessage, getReceived } from '../../__fixtures__/utils.js' import { toHaveSize } from '../../../src/matchers/element/toHaveSize.js' vi.mock('@wdio/globals') @@ -68,15 +67,18 @@ describe('toHaveSize', () => { expect(el.getSize).toHaveBeenCalledTimes(1) }) - test('not - failure', async () => { + test('not - failure - pass should be true', async () => { const el = await $('sel') el.getSize = vi.fn().mockResolvedValue({ width: 32, height: 32 }) + const result = await toHaveSize.call({ isNot: true }, el, { width: 32, height: 32 }, { wait: 0 }) - const result = await toHaveSize.call({}, el, { width: 32, height: 32 }, { wait: 0 }) - const received = getReceived(result.message()) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to have size - expect(received).not.toContain('not') - expect(result.pass).toBe(true) +Expected [not]: {"height": 32, "width": 32} +Received : {"height": 32, "width": 32}` + ) }) test("should return false if sizes don't match", async () => { @@ -103,6 +105,11 @@ describe('toHaveSize', () => { const result = await toHaveSize.call({}, el, { width: 32, height: 32 }) - expect(getExpectMessage(result.message())).toContain('to have size') + expect(result.pass).toBe(false) + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) to have size + +Expected: {"height": 32, "width": 32} +Received: null`) }) }) diff --git a/test/matchers/element/toHaveStyle.test.ts b/test/matchers/element/toHaveStyle.test.ts index e7c43f20e..225e5204e 100644 --- a/test/matchers/element/toHaveStyle.test.ts +++ b/test/matchers/element/toHaveStyle.test.ts @@ -1,7 +1,6 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' -import { getExpectMessage, getReceived } from '../../__fixtures__/utils.js' import { toHaveStyle } from '../../../src/matchers/element/toHaveStyle.js' vi.mock('@wdio/globals') @@ -80,16 +79,36 @@ describe('toHaveStyle', () => { expect(el.getCSSProperty).toHaveBeenCalledTimes(3) }) - test('not - failure', async () => { + test('not - failure - pass should be true', async () => { const el = await $('sel') el.getCSSProperty = vi.fn().mockImplementation((property: string) => { return { value: mockStyle[property] } }) const result = await toHaveStyle.call({ isNot: true }, el, mockStyle, { wait: 0 }) - const received = getReceived(result.message()) - expect(received).not.toContain('not') - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to have style + +Expected [not]: {"color": "#000", "font-family": "Faktum", "font-size": "26px"} +Received : {"color": "#000", "font-family": "Faktum", "font-size": "26px"}` + ) + }) + + test('not - success - pass should be false', async () => { + const el = await $('sel') + el.getCSSProperty = vi.fn().mockImplementation((property: string) => { + return { value: mockStyle[property] } + }) + const wrongStyle: { [key: string]: string; } = { + 'font-family': 'Incorrect Font', + 'font-size': '100px', + 'color': '#fff' + } + + const result = await toHaveStyle.bind({ isNot: true })(el, wrongStyle, { wait: 1 }) + + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) test('should return false if styles dont match', async () => { @@ -104,7 +123,7 @@ describe('toHaveStyle', () => { 'color': '#fff' } - const result = await toHaveStyle.bind({ isNot: true })(el, wrongStyle, { wait: 1 }) + const result = await toHaveStyle.bind({ })(el, wrongStyle, { wait: 1 }) expect(result.pass).toBe(false) }) @@ -114,7 +133,7 @@ describe('toHaveStyle', () => { return { value: mockStyle[property] } }) - const result = await toHaveStyle.bind({ isNot: true })(el, mockStyle, { wait: 1 }) + const result = await toHaveStyle.bind({})(el, mockStyle, { wait: 1 }) expect(result.pass).toBe(true) }) @@ -124,7 +143,14 @@ describe('toHaveStyle', () => { const result = await toHaveStyle.call({}, el, 'WebdriverIO' as any) - expect(getExpectMessage(result.message())).toContain('to have style') + expect(result.pass).toBe(false) + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) to have style + +Expected: "WebdriverIO" +Received: {"0": "Wrong Value", "1": "Wrong Value", "10": "Wrong Value", "2": "Wrong Value", "3": "Wrong Value", "4": "Wrong Value", "5": "Wrong Value", "6": "Wrong Value", "7": "Wrong Value", "8": "Wrong Value", "9": "Wrong Value"}` + ) + }) test('success if style matches with ignoreCase', async () => { diff --git a/test/matchers/element/toHaveText.test.ts b/test/matchers/element/toHaveText.test.ts index 345791d40..92aa72a29 100755 --- a/test/matchers/element/toHaveText.test.ts +++ b/test/matchers/element/toHaveText.test.ts @@ -105,33 +105,59 @@ describe('toHaveText', () => { expect(el.getText).toHaveBeenCalledTimes(1) }) - test('not - failure', async () => { + test('not - failure - pass should be true', async () => { const el = await $('sel') - el.getText = vi.fn().mockResolvedValue('WebdriverIO') const result = await toHaveText.call({ isNot: true }, el, 'WebdriverIO', { wait: 0 }) - const received = getReceived(result.message()) - expect(received).not.toContain('not') - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to have text + +Expected [not]: "WebdriverIO" +Received : "WebdriverIO"` + ) }) - test("should return false if texts don't match", async () => { + test('not - success - pass should be false', async () => { const el = await $('sel') el.getText = vi.fn().mockResolvedValue('WebdriverIO') - const result = await toHaveText.bind({ isNot: true })(el, 'foobar', { wait: 1 }) + const result = await toHaveText.call({ isNot: true }, el, 'not WebdriverIO', { wait: 0 }) - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) - test('should return true if texts match', async () => { + test('not with no trim - failure - pass should be true', async () => { + const el = await $('sel') + el.getText = vi.fn().mockResolvedValue(' WebdriverIO ') + + const result = await toHaveText.call({ isNot: true }, el, ' WebdriverIO ', { trim: false, wait: 0 }) + + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to have text + +Expected [not]: " WebdriverIO " +Received : " WebdriverIO "` + ) + }) + + test('not - success - pass should be false', async () => { const el = await $('sel') el.getText = vi.fn().mockResolvedValue('WebdriverIO') - const result = await toHaveText.bind({ isNot: true })(el, 'WebdriverIO', { wait: 1 }) + const result = await toHaveText.call({ isNot: true }, el, 'not WebdriverIO', { wait: 0 }) + + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` + }) + + test('should return true if texts match', async () => { + const el = await $('sel') + el.getText = vi.fn().mockResolvedValue('WebdriverIO') + const result = await toHaveText.bind({})(el, 'WebdriverIO', { wait: 1 }) expect(result.pass).toBe(true) }) diff --git a/test/matchers/element/toHaveWidth.test.ts b/test/matchers/element/toHaveWidth.test.ts index 10ca730c2..8a13f0700 100755 --- a/test/matchers/element/toHaveWidth.test.ts +++ b/test/matchers/element/toHaveWidth.test.ts @@ -1,7 +1,7 @@ import { vi, test, describe, expect } from 'vitest' import { $ } from '@wdio/globals' -import { getExpectMessage, getReceived } from '../../__fixtures__/utils.js' +import { getExpectMessage } from '../../__fixtures__/utils.js' import { toHaveWidth } from '../../../src/matchers/element/toHaveWidth.js' vi.mock('@wdio/globals') @@ -81,15 +81,27 @@ describe('toHaveWidth', () => { expect(el.getSize).toHaveBeenCalledTimes(1) }) - test('not - failure', async () => { + test('not - failure - pass should be true', async () => { const el = await $('sel') el.getSize = vi.fn().mockResolvedValue(50) + const result = await toHaveWidth.call({ isNot: true }, el, 50, { wait: 0 }) - const result = await toHaveWidth.call({}, el, 50, { wait: 0 }) - const received = getReceived(result.message()) + expect(result.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result.message()).toEqual(`\ +Expect $(\`sel\`) not to have width - expect(received).not.toContain('not') - expect(result.pass).toBe(true) +Expected [not]: 50 +Received : 50` + ) + }) + + test('not - success - pass should be false', async () => { + const el = await $('sel') + el.getSize = vi.fn().mockResolvedValue(50) + + const result = await toHaveWidth.call({ isNot: true }, el, 40, { wait: 0 }) + + expect(result.pass).toBe(false) // success, boolean is inverted later because of `.not` }) test("should return false if sizes don't match", async () => { diff --git a/test/matchers/elements/toBeElementsArrayOfSize.test.ts b/test/matchers/elements/toBeElementsArrayOfSize.test.ts index 2b68dac36..e2ed9b5ef 100644 --- a/test/matchers/elements/toBeElementsArrayOfSize.test.ts +++ b/test/matchers/elements/toBeElementsArrayOfSize.test.ts @@ -46,23 +46,23 @@ describe('toBeElementsArrayOfSize', () => { test('array of size 2', async () => { const beforeAssertion = vi.fn() const afterAssertion = vi.fn() - const result = await toBeElementsArrayOfSize.call({}, els, 2, { beforeAssertion, afterAssertion }) + const result = await toBeElementsArrayOfSize.call({}, els, 2, { beforeAssertion, afterAssertion, wait: 0 }) expect(result.pass).toBe(true) expect(beforeAssertion).toBeCalledWith({ matcherName: 'toBeElementsArrayOfSize', expectedValue: 2, - options: { beforeAssertion, afterAssertion } + options: { beforeAssertion, afterAssertion, wait: 0 } }) expect(afterAssertion).toBeCalledWith({ matcherName: 'toBeElementsArrayOfSize', expectedValue: 2, - options: { beforeAssertion, afterAssertion }, + options: { beforeAssertion, afterAssertion, wait: 0 }, result }) }) test('array of size 5', async () => { els = createMockElementArray(5) - const result = await toBeElementsArrayOfSize.call({}, els, 5, {}) + const result = await toBeElementsArrayOfSize.call({}, els, 5, { wait : 0 }) expect(result.pass).toBe(true) }) }) @@ -71,7 +71,7 @@ describe('toBeElementsArrayOfSize', () => { let result: AssertionResult beforeEach(async () => { - result = await toBeElementsArrayOfSize.call({}, els, 5, {}) + result = await toBeElementsArrayOfSize.call({}, els, 5, { wait: 0 }) }) test('fails', () => { @@ -97,7 +97,7 @@ describe('toBeElementsArrayOfSize', () => { }) test('works if size contains options', async () => { - const result = await toBeElementsArrayOfSize.call({}, els, { lte: 5 }) + const result = await toBeElementsArrayOfSize.call({}, els, { lte: 5 }, { wait: 0 }) expect(result.pass).toBe(true) }) }) @@ -108,9 +108,9 @@ describe('toBeElementsArrayOfSize', () => { ['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], + ['gte and lte', { gte: 1, lte: 10, wait: 0 }, true], + ['not gte but is lte', { gte: 10, lte: 10, wait: 0 }, false], + ['not lte but is gte', { gte: 1, lte: 1, wait: 0 }, 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) @@ -173,17 +173,17 @@ describe('toBeElementsArrayOfSize', () => { test('array of size 0', async () => { const beforeAssertion = vi.fn() const afterAssertion = vi.fn() - const result = await toBeElementsArrayOfSize.call({}, elements, 0, { beforeAssertion, afterAssertion }) + const result = await toBeElementsArrayOfSize.call({}, elements, 0, { beforeAssertion, afterAssertion, wait: 0 }) expect(result.pass).toBe(true) expect(beforeAssertion).toBeCalledWith({ matcherName: 'toBeElementsArrayOfSize', expectedValue: 0, - options: { beforeAssertion, afterAssertion } + options: { beforeAssertion, afterAssertion, wait: 0 } }) expect(afterAssertion).toBeCalledWith({ matcherName: 'toBeElementsArrayOfSize', expectedValue: 0, - options: { beforeAssertion, afterAssertion }, + options: { beforeAssertion, afterAssertion, wait: 0 }, result }) }) @@ -193,7 +193,7 @@ describe('toBeElementsArrayOfSize', () => { let result: AssertionResult beforeEach(async () => { - result = await toBeElementsArrayOfSize.call({}, elements, 5, {}) + result = await toBeElementsArrayOfSize.call({}, elements, 5, { wait: 0 }) }) test('fails', () => { @@ -222,17 +222,17 @@ describe('toBeElementsArrayOfSize', () => { test('array of size 1', async () => { const beforeAssertion = vi.fn() const afterAssertion = vi.fn() - const result = await toBeElementsArrayOfSize.call({}, elements, 1, { beforeAssertion, afterAssertion }) + const result = await toBeElementsArrayOfSize.call({}, elements, 1, { beforeAssertion, afterAssertion, wait: 0 }) expect(result.pass).toBe(true) expect(beforeAssertion).toBeCalledWith({ matcherName: 'toBeElementsArrayOfSize', expectedValue: 1, - options: { beforeAssertion, afterAssertion } + options: { beforeAssertion, afterAssertion, wait: 0 } }) expect(afterAssertion).toBeCalledWith({ matcherName: 'toBeElementsArrayOfSize', expectedValue: 1, - options: { beforeAssertion, afterAssertion }, + options: { beforeAssertion, afterAssertion, wait: 0 }, result }) }) @@ -242,7 +242,7 @@ describe('toBeElementsArrayOfSize', () => { let result: AssertionResult beforeEach(async () => { - result = await toBeElementsArrayOfSize.call({}, elements, 5, {}) + result = await toBeElementsArrayOfSize.call({}, elements, 5, { wait: 0 }) }) test('fails', () => { diff --git a/test/matchers/mock/toBeRequested.test.ts b/test/matchers/mock/toBeRequested.test.ts index 237d41a71..05ae9d7cb 100644 --- a/test/matchers/mock/toBeRequested.test.ts +++ b/test/matchers/mock/toBeRequested.test.ts @@ -4,8 +4,6 @@ import type { Matches, Mock } from 'webdriverio' import { toBeRequested } from '../../../src/matchers/mock/toBeRequested.js' -import { getExpected, getExpectMessage, getReceived, removeColors } from '../../__fixtures__/utils.js' - vi.mock('@wdio/globals') class TestMock implements Mock { @@ -69,26 +67,38 @@ describe('toBeRequested', () => { test('not to be called', async () => { const mock: Mock = new TestMock() - // expect(mock).not.toBeRequested() should pass + // expect(mock).not.toBeRequested() should pass=false const result = await toBeRequested.call({ isNot: true }, mock) - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean is inverted later becuase of `.not` mock.calls.push(mockMatch) // expect(mock).not.toBeRequested() should fail const result4 = await toBeRequested.call({ isNot: true }, mock) - expect(result4.pass).toBe(true) + expect(result4.pass).toBe(true) // failure, boolean is inverted later because of `.not` }) test('message', async () => { const mock: Mock = new TestMock() - const message = removeColors((await toBeRequested(mock)).message()) - expect(getExpectMessage(message)).toBe('Expect mock to be called') - expect(getReceived(message)).toBe('Received: 0') - expect(getExpected(message)).toBe('Expected: ">= 1"') + const result = await toBeRequested(mock) + expect(result.pass).toBe(false) + expect(result.message()).toEqual(`\ +Expect mock to be called + +Expected: ">= 1" +Received: 0` + ) + mock.calls.push(mockMatch) const result2 = await toBeRequested.call({ isNot: true }, mock) - expect(result2.message()).toContain('Expect mock not to be called') + + expect(result2.pass).toBe(true) // failure, boolean is inverted later because of `.not` + expect(result2.message()).toEqual(`\ +Expect mock not to be called + +Expected [not]: ">= 1" +Received : 1` + ) }) }) diff --git a/test/matchers/mock/toBeRequestedTimes.test.ts b/test/matchers/mock/toBeRequestedTimes.test.ts index 22cd9eb78..65df6b0bc 100644 --- a/test/matchers/mock/toBeRequestedTimes.test.ts +++ b/test/matchers/mock/toBeRequestedTimes.test.ts @@ -3,7 +3,6 @@ import { vi, test, describe, expect } from 'vitest' import type { Matches, Mock } from 'webdriverio' import { toBeRequestedTimes } from '../../../src/matchers/mock/toBeRequestedTimes.js' -import { removeColors, getReceived, getExpected, getExpectMessage } from '../../__fixtures__/utils.js' vi.mock('@wdio/globals') @@ -102,39 +101,54 @@ describe('toBeRequestedTimes', () => { // expect(mock).not.toBeRequestedTimes(0) should fail const result = await toBeRequestedTimes.call({ isNot: true }, mock, 0) - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean inverted later because of .not + expect(result.message()).toEqual(`\ +Expect mock not to be called 0 times + +Expected [not]: 0 +Received : 0` + ) // expect(mock).not.toBeRequestedTimes(1) should pass const result2 = await toBeRequestedTimes.call({ isNot: true }, mock, 1) - expect(result2.pass).toBe(false) + expect(result2.pass).toBe(false) // success, boolean inverted later because of .not mock.calls.push(mockMatch) // expect(mock).not.toBeRequestedTimes(0) should pass const result3 = await toBeRequestedTimes.call({ isNot: true }, mock, 0) - expect(result3.pass).toBe(false) + expect(result3.pass).toBe(false) // success, boolean inverted later because of .not // expect(mock).not.toBeRequestedTimes(1) should fail const result4 = await toBeRequestedTimes.call({ isNot: true }, mock, 1) - expect(result4.pass).toBe(true) + expect(result4.pass).toBe(true) // failure, boolean inverted later because of .not + expect(result4.message()).toEqual(`\ +Expect mock not to be called 1 time + +Expected [not]: 1 +Received : 1` + ) }) test('message', async () => { const mock: Mock = new TestMock() - const result = await toBeRequestedTimes.call({}, mock, 0) + const result = await toBeRequestedTimes.call({}, mock, 0, { wait: 1 }) expect(result.message()).toContain('Expect mock to be called 0 times') - const result2 = await toBeRequestedTimes.call({}, mock, 1) + const result2 = await toBeRequestedTimes.call({}, mock, 1, { wait: 1 }) expect(result2.message()).toContain('Expect mock to be called 1 time') - const result3 = await toBeRequestedTimes.call({}, mock, 2) + const result3 = await toBeRequestedTimes.call({}, mock, 2, { wait: 1 }) expect(result3.message()).toContain('Expect mock to be called 2 times') - const result4 = await toBeRequestedTimes.call({}, mock, { gte: 3 }) - const message4 = removeColors(result4.message()) - expect(getExpectMessage(message4)).toBe('Expect mock to be called times') - expect(getExpected(message4)).toBe('Expected: ">= 3"') - expect(getReceived(message4)).toBe('Received: 0') + const result4 = await toBeRequestedTimes.call({}, mock, { gte: 3 }, { wait: 1 }) + expect(result4.pass).toBe(false) + expect(result4.message()).toEqual(`\ +Expect mock to be called times + +Expected: ">= 3" +Received: 0` + ) }) }) diff --git a/test/matchers/mock/toBeRequestedWith.test.ts b/test/matchers/mock/toBeRequestedWith.test.ts index 0fd51ff70..d4c8adab2 100644 --- a/test/matchers/mock/toBeRequestedWith.test.ts +++ b/test/matchers/mock/toBeRequestedWith.test.ts @@ -163,7 +163,7 @@ describe('toBeRequestedWith', () => { expect(result.pass).toBe(false) }) - test('wait for NOT failure, empty params', async () => { + test('wait for NOT - failure with empty params and pass expected to be true', async () => { const mock: any = new TestMock() mock.calls.push({ ...mockGet }, { ...mockPost }) setTimeout(() => { @@ -171,10 +171,16 @@ describe('toBeRequestedWith', () => { }, 10) const result = await toBeRequestedWith.call({ isNot: true }, mock, {}) - expect(result.pass).toBe(true) + expect(result.pass).toBe(true) // failure, boolean inverted later because of .not + expect(result.message()).toEqual(`\ +Expect mock not to be called with + +Expected [not]: {} +Received : {}` + ) }) - test('wait for NOT success', async () => { + test('wait for NOT - success with pass expected to be false', async () => { const mock: any = new TestMock() setTimeout(() => { @@ -182,7 +188,7 @@ describe('toBeRequestedWith', () => { }, 10) const result = await toBeRequestedWith.call({ isNot: true }, mock, { method: 'DELETE' }) - expect(result.pass).toBe(false) + expect(result.pass).toBe(false) // success, boolean inverted later because of .not }) const scenarios: Scenario[] = [ diff --git a/test/softAssertions.test.ts b/test/softAssertions.test.ts index d524252e5..647cde302 100644 --- a/test/softAssertions.test.ts +++ b/test/softAssertions.test.ts @@ -21,7 +21,7 @@ describe('Soft Assertions', () => { const softService = SoftAssertService.getInstance() softService.setCurrentTest('test-1', 'test name', 'test file') - await expectWdio.soft(el).toHaveText('Expected Text') + await expectWdio.soft(el).toHaveText('Expected Text', { wait: 0 }) // Verify the failure was recorded const failures = expectWdio.getSoftFailures() @@ -36,7 +36,7 @@ describe('Soft Assertions', () => { softService.setCurrentTest('test-2', 'test name', 'test file') // This should not throw even though it fails - await expectWdio.soft(el).not.toHaveText('Actual Text') + await expectWdio.soft(el).not.toHaveText('Actual Text', { wait: 0 }) // Verify the failure was recorded const failures = expectWdio.getSoftFailures() @@ -50,9 +50,9 @@ describe('Soft Assertions', () => { softService.setCurrentTest('test-3', 'test name', 'test file') // These should not throw even though they fail - await expectWdio.soft(el).toHaveText('First Expected') - await expectWdio.soft(el).toHaveText('Second Expected') - await expectWdio.soft(el).toHaveText('Third Expected') + await expectWdio.soft(el).toHaveText('First Expected', { wait: 0 }) + await expectWdio.soft(el).toHaveText('Second Expected', { wait: 0 }) + await expectWdio.soft(el).toHaveText('Third Expected', { wait: 0 }) // Verify all failures were recorded const failures = expectWdio.getSoftFailures() @@ -170,8 +170,8 @@ describe('Soft Assertions', () => { softService.setCurrentTest('boolean-test', 'boolean test', 'test file') // Test boolean matcher - await expectWdio.soft(el).toBeDisplayed() - await expectWdio.soft(el).toBeClickable() + await expectWdio.soft(el).toBeDisplayed({ wait: 0 }) + await expectWdio.soft(el).toBeClickable({ wait: 0 }) const failures = expectWdio.getSoftFailures() expect(failures.length).toBe(2) @@ -183,7 +183,7 @@ describe('Soft Assertions', () => { const softService = SoftAssertService.getInstance() softService.setCurrentTest('attribute-test', 'attribute test', 'test file') - await expectWdio.soft(el).toHaveAttribute('class', 'expected-class') + await expectWdio.soft(el).toHaveAttribute('class', 'expected-class', { wait: 0 }) const failures = expectWdio.getSoftFailures() expect(failures.length).toBe(1) @@ -194,7 +194,7 @@ describe('Soft Assertions', () => { const softService = SoftAssertService.getInstance() softService.setCurrentTest('options-test', 'options test', 'test file') - await expectWdio.soft(el).toHaveText('Expected', { ignoreCase: true, wait: 1000 }) + await expectWdio.soft(el).toHaveText('Expected', { ignoreCase: true, wait: 0 }) const failures = expectWdio.getSoftFailures() expect(failures.length).toBe(1) @@ -208,12 +208,12 @@ describe('Soft Assertions', () => { // Test 1 softService.setCurrentTest('isolation-test-1', 'test 1', 'file1') - await expectWdio.soft(el).toHaveText('Expected Text 1') + await expectWdio.soft(el).toHaveText('Expected Text 1', { wait: 0 }) expect(expectWdio.getSoftFailures().length).toBe(1) // Test 2 - should have separate failures softService.setCurrentTest('isolation-test-2', 'test 2', 'file2') - await expectWdio.soft(el).toHaveText('Expected Text 2') + await expectWdio.soft(el).toHaveText('Expected Text 2', { wait: 0 }) // Test 2 should only see its own failure expect(expectWdio.getSoftFailures('isolation-test-2').length).toBe(1) @@ -251,9 +251,9 @@ describe('Soft Assertions', () => { // Fire multiple assertions rapidly const promises = [ - expectWdio.soft(el).toHaveText('Expected 1'), - expectWdio.soft(el).toBeDisplayed(), - expectWdio.soft(el).toBeClickable() + expectWdio.soft(el).toHaveText('Expected 1', { wait: 0 }), + expectWdio.soft(el).toBeDisplayed({ wait: 0 }), + expectWdio.soft(el).toBeClickable({ wait: 0 }) ] await Promise.all(promises) @@ -297,7 +297,7 @@ describe('Soft Assertions', () => { softService.setCurrentTest('long-error-test', 'long error', 'test file') const veryLongText = 'A'.repeat(10000) - await expectWdio.soft(el).toHaveText(veryLongText) + await expectWdio.soft(el).toHaveText(veryLongText, { wait: 0 }) const failures = expectWdio.getSoftFailures() expect(failures.length).toBe(1) @@ -305,12 +305,13 @@ describe('Soft Assertions', () => { }) it('should handle null/undefined values gracefully', async () => { + vi.mocked(el.getAttribute).mockResolvedValue(null as any) const softService = SoftAssertService.getInstance() softService.setCurrentTest('null-test', 'null test', 'test file') // Test with null/undefined values - await expectWdio.soft(el).toHaveText(null as any) - await expectWdio.soft(el).toHaveAttribute('class', undefined as any) + await expectWdio.soft(el).toHaveText(null as any, { wait: 0 }) + await expectWdio.soft(el).toHaveAttribute('class') const failures = expectWdio.getSoftFailures() expect(failures.length).toBe(2) @@ -320,7 +321,7 @@ describe('Soft Assertions', () => { const softService = SoftAssertService.getInstance() softService.setCurrentTest('location-test', 'location test', 'test file') - await expectWdio.soft(el).toHaveText('Expected Text') + await expectWdio.soft(el).toHaveText('Expected Text', { wait: 0 }) const failures = expectWdio.getSoftFailures() expect(failures.length).toBe(1) @@ -339,7 +340,7 @@ describe('Soft Assertions', () => { // Generate many failures const promises = [] for (let i = 0; i < 150; i++) { - promises.push(expectWdio.soft(el).toHaveText(`Expected ${i}`)) + promises.push(expectWdio.soft(el).toHaveText(`Expected ${i}`), { wait: 0 }) } await Promise.all(promises) diff --git a/test/utils.test.ts b/test/utils.test.ts index 444a8e61f..cba27af79 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,5 +1,5 @@ -import { describe, test, expect } from 'vitest' -import { compareNumbers, compareObject, compareText, compareTextWithArray } from '../src/utils.js' +import { describe, test, expect, vi } from 'vitest' +import { compareNumbers, compareObject, compareText, compareTextWithArray, waitUntil } from '../src/utils' describe('utils', () => { describe('compareText', () => { @@ -158,4 +158,96 @@ describe('utils', () => { expect(compareObject([{ 'foo': 'bar' }], { 'foo': 'bar' }).result).toBe(false) }) }) + + describe('waitUntil', () => { + + describe('should be pass=true for normal success and pass=true for `isNot` failure', () => { + test('should return true when condition is met', async () => { + const condition = vi.fn().mockResolvedValue(true) + + const result = await waitUntil(condition, { wait: 1000, interval: 100 }) + + expect(result).toBe(true) + }) + + test('should return true with wait 0', async () => { + const condition = vi.fn().mockResolvedValue(true) + + const result = await waitUntil(condition, { wait: 0 }) + + expect(result).toBe(true) + }) + + test('should return true when condition is met within wait time', async () => { + const condition = vi.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(false).mockResolvedValueOnce(true) + + const result = await waitUntil(condition, { wait: 1000, interval: 50 }) + + expect(result).toBe(true) + expect(condition).toBeCalledTimes(3) + }) + + test('should return true when condition errors but still is met within wait time', async () => { + const condition = vi.fn().mockRejectedValueOnce(new Error('Test error')).mockRejectedValueOnce(new Error('Test error')).mockResolvedValueOnce(true) + + const result = await waitUntil(condition, { wait: 1000, interval: 50 }) + + expect(result).toBe(true) + expect(condition).toBeCalledTimes(3) + }) + + test('should use default options when not provided', async () => { + const condition = vi.fn().mockResolvedValue(true) + + const result = await waitUntil(condition) + + expect(result).toBe(true) + }) + }) + + describe('should be pass=false for normal failure or pass=false for `isNot` success', () => { + + test('should return false when condition is not met within wait time', async () => { + const condition = vi.fn().mockResolvedValue(false) + + const result = await waitUntil(condition, { wait: 200, interval: 50 }) + + expect(result).toBe(false) + }) + + test('should return false when condition is not met and wait is 0', async () => { + const condition = vi.fn().mockResolvedValue(false) + + const result = await waitUntil(condition, { wait: 0 }) + + expect(result).toBe(false) + }) + + test('should return false if condition throws but still return false', async () => { + const condition = vi.fn().mockRejectedValueOnce(new Error('Always failing')).mockRejectedValueOnce(new Error('Always failing')).mockResolvedValue(false) + + const result = await waitUntil(condition, { wait: 200, interval: 50 }) + + expect(result).toBe(false) + expect(condition).toBeCalledTimes(4) + }) + }) + + describe('when condition throws', () => { + const error = new Error('failing') + + test('should throw with wait', async () => { + const condition = vi.fn().mockRejectedValue(error) + + await expect(() => waitUntil(condition, { wait: 200, interval: 50 })).rejects.toThrowError('failing') + }) + + test('should throw with wait 0', async () => { + const condition = vi.fn().mockRejectedValue(error) + + await expect(() => waitUntil(condition, { wait: 0 })).rejects.toThrowError('failing') + + }) + }) + }) }) diff --git a/types/expect-webdriverio.d.ts b/types/expect-webdriverio.d.ts index 74963f437..9b729ff34 100644 --- a/types/expect-webdriverio.d.ts +++ b/types/expect-webdriverio.d.ts @@ -188,11 +188,14 @@ interface WdioElementOrArrayMatchers<_R, ActualT = unknown> { /** * `WebdriverIO.Element` -> `getProperty` */ - toHaveElementProperty: FnWhenElementOrArrayLike, - value?: unknown, - options?: ExpectWebdriverIO.StringOptions - ) => Promise> + toHaveElementProperty: FnWhenElementOrArrayLike< + ActualT, + ( + property: string, + value?: string | RegExp | WdioAsymmetricMatcher | null, + options?: ExpectWebdriverIO.StringOptions, + ) => Promise + > /** * `WebdriverIO.Element` -> `getProperty` value From 705713c79c8ffc8b3781d1ebad02daa0d67bba27 Mon Sep 17 00:00:00 2001 From: dprevost-perso Date: Fri, 30 Jan 2026 09:29:07 -0500 Subject: [PATCH 4/8] Add clear waitUntil UT + add integrated test representing the issue --- src/matchers/element/toHaveElementProperty.ts | 2 +- test/matchers.test.ts | 29 ++- test/utils.test.ts | 197 +++++++++++++----- 3 files changed, 175 insertions(+), 53 deletions(-) diff --git a/src/matchers/element/toHaveElementProperty.ts b/src/matchers/element/toHaveElementProperty.ts index cdf4146b0..958939ac2 100644 --- a/src/matchers/element/toHaveElementProperty.ts +++ b/src/matchers/element/toHaveElementProperty.ts @@ -36,7 +36,7 @@ async function condition( export async function toHaveElementProperty( received: WdioElementMaybePromise, property: string, - value?: string | RegExp | WdioAsymmetricMatcher, + value?: string | RegExp | WdioAsymmetricMatcher | null, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS ) { const isNot = this.isNot diff --git a/test/matchers.test.ts b/test/matchers.test.ts index 7cb19b8aa..4d89f74c0 100644 --- a/test/matchers.test.ts +++ b/test/matchers.test.ts @@ -402,7 +402,8 @@ Received: 100`) }) - describe('Matcher eventually passing', async () => { + // Skipped since even through logically correct, this is not too user friendly adn breaks today current expected behavior, see https://github.com/webdriverio/expect-webdriverio/issues/2013 + describe.skip('Matcher eventually passing', async () => { test('when element eventually is displayed, matcher and .not matcher should be consistent', async () => { const el = await $('selector') @@ -454,4 +455,30 @@ Received: "not displayed"`) expect(el.isDisplayed).toHaveBeenCalledTimes(6) }) }) + + describe('Matchers should cover real life scenarios', async () => { + test('Using toBeDisplayed and not.toBeDisplayed before and after a component is being discarded should work easily', async () => { + const el = await $('selector') + + // Element takes time to display + vi.mocked(el.isDisplayed) + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(false) + .mockResolvedValueOnce(true) + + // Passes when element becomes displayed + await expectLib(el).toBeDisplayed() + + // The element ok button is clicked and the component is discarded... + + // ...but the element takes time to be removed from the DOM (at least 500 ms in real life) + vi.mocked(el.isDisplayed) + .mockResolvedValueOnce(true) + .mockResolvedValueOnce(true) + .mockResolvedValueOnce(false) + + // We should be able to assert that the element is no longer displayed by default without additional code of configuration + await expectLib(el).not.toBeDisplayed() + }) + }) }) diff --git a/test/utils.test.ts b/test/utils.test.ts index cba27af79..fa3349c8a 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -159,94 +159,189 @@ describe('utils', () => { }) }) - describe('waitUntil', () => { + describe(waitUntil, () => { - describe('should be pass=true for normal success and pass=true for `isNot` failure', () => { - test('should return true when condition is met', async () => { - const condition = vi.fn().mockResolvedValue(true) + describe('given we should wait for the condition to be met (modifier `.not` is not used)', () => { + const isNot = undefined + describe('should be pass=true for normal success', () => { + test('should return true when condition is met', async () => { + const condition = vi.fn().mockResolvedValue(true) - const result = await waitUntil(condition, { wait: 1000, interval: 100 }) + const result = await waitUntil(condition, isNot, { wait: 1000, interval: 100 }) - expect(result).toBe(true) - }) + expect(result).toBe(true) + }) - test('should return true with wait 0', async () => { - const condition = vi.fn().mockResolvedValue(true) + test('should return true with wait 0', async () => { + const condition = vi.fn().mockResolvedValue(true) - const result = await waitUntil(condition, { wait: 0 }) + const result = await waitUntil(condition, isNot, { wait: 0 }) - expect(result).toBe(true) - }) + expect(result).toBe(true) + }) + + test('should return true when condition is met within wait time', async () => { + const condition = vi.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(false).mockResolvedValueOnce(true) + + const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 }) + + expect(result).toBe(true) + expect(condition).toBeCalledTimes(3) + }) - test('should return true when condition is met within wait time', async () => { - const condition = vi.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(false).mockResolvedValueOnce(true) + test('should return true when condition errors but still is met within wait time', async () => { + const condition = vi.fn().mockRejectedValueOnce(new Error('Test error')).mockRejectedValueOnce(new Error('Test error')).mockResolvedValueOnce(true) - const result = await waitUntil(condition, { wait: 1000, interval: 50 }) + const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 }) - expect(result).toBe(true) - expect(condition).toBeCalledTimes(3) + expect(result).toBe(true) + expect(condition).toBeCalledTimes(3) + }) + + test('should use default options when not provided', async () => { + const condition = vi.fn().mockResolvedValue(true) + + const result = await waitUntil(condition) + + expect(result).toBe(true) + }) }) - test('should return true when condition errors but still is met within wait time', async () => { - const condition = vi.fn().mockRejectedValueOnce(new Error('Test error')).mockRejectedValueOnce(new Error('Test error')).mockResolvedValueOnce(true) + describe('should be pass=false for normal failure', () => { + + test('should return false when condition is not met within wait time', async () => { + const condition = vi.fn().mockResolvedValue(false) - const result = await waitUntil(condition, { wait: 1000, interval: 50 }) + const result = await waitUntil(condition, isNot, { wait: 200, interval: 50 }) - expect(result).toBe(true) - expect(condition).toBeCalledTimes(3) + expect(result).toBe(false) + }) + + test('should return false when condition is not met and wait is 0', async () => { + const condition = vi.fn().mockResolvedValue(false) + + const result = await waitUntil(condition, isNot, { wait: 0 }) + + expect(result).toBe(false) + }) + + test('should return false if condition throws but still return false', async () => { + const condition = vi.fn().mockRejectedValueOnce(new Error('Always failing')).mockRejectedValueOnce(new Error('Always failing')).mockResolvedValue(false) + + const result = await waitUntil(condition, isNot, { wait: 200, interval: 50 }) + + expect(result).toBe(false) + expect(condition).toBeCalledTimes(4) + }) }) - test('should use default options when not provided', async () => { - const condition = vi.fn().mockResolvedValue(true) + describe('when condition throws', () => { + const error = new Error('failing') + + test('should throw with wait', async () => { + const condition = vi.fn().mockRejectedValue(error) + + await expect(() => waitUntil(condition, isNot, { wait: 200, interval: 50 })).rejects.toThrowError('failing') + }) + + test('should throw with wait 0', async () => { + const condition = vi.fn().mockRejectedValue(error) - const result = await waitUntil(condition) + await expect(() => waitUntil(condition, isNot, { wait: 0 })).rejects.toThrowError('failing') - expect(result).toBe(true) + }) }) }) - describe('should be pass=false for normal failure or pass=false for `isNot` success', () => { + describe('given we should wait for the reverse condition to meet since element state can take time to update (modifier `.not` is true to for reverse condition)', () => { + const isNot = true + describe('should be pass=false for normal success', () => { + test('should return false when condition is met', async () => { + const condition = vi.fn().mockResolvedValue(false) - test('should return false when condition is not met within wait time', async () => { - const condition = vi.fn().mockResolvedValue(false) + const result = await waitUntil(condition, isNot, { wait: 1000, interval: 100 }) - const result = await waitUntil(condition, { wait: 200, interval: 50 }) + expect(result).toBe(false) + }) - expect(result).toBe(false) - }) + test('should return false with wait 0', async () => { + const condition = vi.fn().mockResolvedValue(false) - test('should return false when condition is not met and wait is 0', async () => { - const condition = vi.fn().mockResolvedValue(false) + const result = await waitUntil(condition, isNot, { wait: 0 }) - const result = await waitUntil(condition, { wait: 0 }) + expect(result).toBe(false) + }) - expect(result).toBe(false) - }) + test('should return false when condition is met within wait time', async () => { + const condition = vi.fn().mockResolvedValueOnce(true).mockResolvedValueOnce(true).mockResolvedValueOnce(false) + + const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 }) + + expect(result).toBe(false) // success for .not, boolean is inverted later by jest's expect library + expect(condition).toBeCalledTimes(3) + }) + + test('should return false when condition errors but still is met within wait time', async () => { + const condition = vi.fn().mockRejectedValueOnce(new Error('Test error')).mockRejectedValueOnce(new Error('Test error')).mockResolvedValueOnce(false) - test('should return false if condition throws but still return false', async () => { - const condition = vi.fn().mockRejectedValueOnce(new Error('Always failing')).mockRejectedValueOnce(new Error('Always failing')).mockResolvedValue(false) + const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 }) - const result = await waitUntil(condition, { wait: 200, interval: 50 }) + expect(result).toBe(false) + expect(condition).toBeCalledTimes(3) + }) - expect(result).toBe(false) - expect(condition).toBeCalledTimes(4) + test('should use default options when not provided', async () => { + const condition = vi.fn().mockResolvedValue(false) + + const result = await waitUntil(condition, isNot) + + expect(result).toBe(false) + }) }) - }) - describe('when condition throws', () => { - const error = new Error('failing') + describe('should be pass=true for normal failure', () => { + + test('should return true when condition is not met within wait time', async () => { + const condition = vi.fn().mockResolvedValue(true) - test('should throw with wait', async () => { - const condition = vi.fn().mockRejectedValue(error) + const result = await waitUntil(condition, isNot, { wait: 200, interval: 50 }) - await expect(() => waitUntil(condition, { wait: 200, interval: 50 })).rejects.toThrowError('failing') + expect(result).toBe(true) + }) + + test('should return true when condition is not met and wait is 0', async () => { + const condition = vi.fn().mockResolvedValue(true) + + const result = await waitUntil(condition, isNot, { wait: 0 }) + + expect(result).toBe(true) + }) + + test('should return true if condition throws but still return true', async () => { + const condition = vi.fn().mockRejectedValueOnce(new Error('Always failing')).mockRejectedValueOnce(new Error('Always failing')).mockResolvedValue(true) + + const result = await waitUntil(condition, isNot, { wait: 200, interval: 50 }) + + expect(result).toBe(true) + expect(condition).toBeCalledTimes(4) + }) }) - test('should throw with wait 0', async () => { - const condition = vi.fn().mockRejectedValue(error) + describe('when condition throws', () => { + const error = new Error('failing') + + test('should throw with wait', async () => { + const condition = vi.fn().mockRejectedValue(error) + + await expect(() => waitUntil(condition, isNot, { wait: 200, interval: 50 })).rejects.toThrowError('failing') + }) + + test('should throw with wait 0', async () => { + const condition = vi.fn().mockRejectedValue(error) - await expect(() => waitUntil(condition, { wait: 0 })).rejects.toThrowError('failing') + await expect(() => waitUntil(condition, isNot, { wait: 0 })).rejects.toThrowError('failing') + }) }) }) }) From 5af55a1179180bb12676cb05134eb5d32d874da2 Mon Sep 17 00:00:00 2001 From: dprevost-perso Date: Fri, 30 Jan 2026 09:32:48 -0500 Subject: [PATCH 5/8] Keep relevant comment --- src/matchers/element/toHaveElementProperty.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/matchers/element/toHaveElementProperty.ts b/src/matchers/element/toHaveElementProperty.ts index 958939ac2..2c67f38d2 100644 --- a/src/matchers/element/toHaveElementProperty.ts +++ b/src/matchers/element/toHaveElementProperty.ts @@ -17,10 +17,13 @@ async function condition( const { asString = false } = options let prop = await el.getProperty(property) + + // As specified in the w3c spec, cases where property does not exist if (prop === null || prop === undefined) { return { result: false, value: prop } } + // As specified in the w3c spec, cases where property simply exists, missing undefined here? if (value === null) { return { result: true, value: prop } } From e8ee6bd7e05ec2b5d3a979b18445a1ac850e0364 Mon Sep 17 00:00:00 2001 From: dprevost-perso Date: Fri, 30 Jan 2026 09:41:14 -0500 Subject: [PATCH 6/8] More stable tests sometimes when run too fast, it triggers conditions one more time than expected --- test/utils.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/utils.test.ts b/test/utils.test.ts index fa3349c8a..377628621 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -183,7 +183,7 @@ describe('utils', () => { test('should return true when condition is met within wait time', async () => { const condition = vi.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(false).mockResolvedValueOnce(true) - const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 }) + const result = await waitUntil(condition, isNot, { wait: 990, interval: 50 }) expect(result).toBe(true) expect(condition).toBeCalledTimes(3) @@ -192,7 +192,7 @@ describe('utils', () => { test('should return true when condition errors but still is met within wait time', async () => { const condition = vi.fn().mockRejectedValueOnce(new Error('Test error')).mockRejectedValueOnce(new Error('Test error')).mockResolvedValueOnce(true) - const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 }) + const result = await waitUntil(condition, isNot, { wait: 990, interval: 50 }) expect(result).toBe(true) expect(condition).toBeCalledTimes(3) @@ -228,7 +228,7 @@ describe('utils', () => { test('should return false if condition throws but still return false', async () => { const condition = vi.fn().mockRejectedValueOnce(new Error('Always failing')).mockRejectedValueOnce(new Error('Always failing')).mockResolvedValue(false) - const result = await waitUntil(condition, isNot, { wait: 200, interval: 50 }) + const result = await waitUntil(condition, isNot, { wait: 180, interval: 50 }) expect(result).toBe(false) expect(condition).toBeCalledTimes(4) @@ -275,7 +275,7 @@ describe('utils', () => { test('should return false when condition is met within wait time', async () => { const condition = vi.fn().mockResolvedValueOnce(true).mockResolvedValueOnce(true).mockResolvedValueOnce(false) - const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 }) + const result = await waitUntil(condition, isNot, { wait: 990, interval: 50 }) expect(result).toBe(false) // success for .not, boolean is inverted later by jest's expect library expect(condition).toBeCalledTimes(3) @@ -284,7 +284,7 @@ describe('utils', () => { test('should return false when condition errors but still is met within wait time', async () => { const condition = vi.fn().mockRejectedValueOnce(new Error('Test error')).mockRejectedValueOnce(new Error('Test error')).mockResolvedValueOnce(false) - const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 }) + const result = await waitUntil(condition, isNot, { wait: 990, interval: 50 }) expect(result).toBe(false) expect(condition).toBeCalledTimes(3) @@ -320,7 +320,7 @@ describe('utils', () => { test('should return true if condition throws but still return true', async () => { const condition = vi.fn().mockRejectedValueOnce(new Error('Always failing')).mockRejectedValueOnce(new Error('Always failing')).mockResolvedValue(true) - const result = await waitUntil(condition, isNot, { wait: 200, interval: 50 }) + const result = await waitUntil(condition, isNot, { wait: 190, interval: 50 }) expect(result).toBe(true) expect(condition).toBeCalledTimes(4) From bca2eef36ad8e754d8f03295e0fc032ee8ed1c19 Mon Sep 17 00:00:00 2001 From: David Prevost <77302423+dprevost-LMI@users.noreply.github.com> Date: Fri, 30 Jan 2026 16:59:16 -0500 Subject: [PATCH 7/8] Apply suggestions from code review --- test/matchers.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/matchers.test.ts b/test/matchers.test.ts index 4d89f74c0..fe65896f0 100644 --- a/test/matchers.test.ts +++ b/test/matchers.test.ts @@ -402,7 +402,7 @@ Received: 100`) }) - // Skipped since even through logically correct, this is not too user friendly adn breaks today current expected behavior, see https://github.com/webdriverio/expect-webdriverio/issues/2013 + // Skipped since even though logically correct, this is not too user-friendly and breaks today's current expected behaviour, see https://github.com/webdriverio/expect-webdriverio/issues/2013 describe.skip('Matcher eventually passing', async () => { test('when element eventually is displayed, matcher and .not matcher should be consistent', async () => { @@ -477,7 +477,7 @@ Received: "not displayed"`) .mockResolvedValueOnce(true) .mockResolvedValueOnce(false) - // We should be able to assert that the element is no longer displayed by default without additional code of configuration + // We should be able to assert that the element is no longer displayed by default without additional code or configuration await expectLib(el).not.toBeDisplayed() }) }) From 5ac87215bcd4dc16f11e4e7c29d9cdc4c9c8d903 Mon Sep 17 00:00:00 2001 From: David Prevost <77302423+dprevost-LMI@users.noreply.github.com> Date: Fri, 30 Jan 2026 16:59:58 -0500 Subject: [PATCH 8/8] Update test/matchers.test.ts --- test/matchers.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/matchers.test.ts b/test/matchers.test.ts index fe65896f0..a1534d539 100644 --- a/test/matchers.test.ts +++ b/test/matchers.test.ts @@ -471,7 +471,7 @@ Received: "not displayed"`) // The element ok button is clicked and the component is discarded... - // ...but the element takes time to be removed from the DOM (at least 500 ms in real life) + // ...but the element takes time to be removed from the DOM (below 500 ms in real life) vi.mocked(el.isDisplayed) .mockResolvedValueOnce(true) .mockResolvedValueOnce(true)