From 96b86f39fabb60412b35c2c23c28ae3b151ec34b Mon Sep 17 00:00:00 2001 From: Mrunal chaudhari Date: Tue, 30 Dec 2025 21:42:51 +0530 Subject: [PATCH] fix: handle array return values in multiremote for all matchers --- package-lock.json | 13 +++++++++ src/matchers/browser/toHaveTitle.ts | 17 +++++++++-- src/matchers/browser/toHaveUrl.ts | 17 +++++++++-- src/matchers/element/toHaveAttribute.ts | 24 ++++++++++++++- src/matchers/element/toHaveElementProperty.ts | 29 +++++++++++++++++++ src/matchers/element/toHaveText.ts | 19 +++++++++--- 6 files changed, 108 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index af5650fe4..3a42fa245 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,6 +90,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2226,6 +2227,7 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -3026,6 +3028,7 @@ "integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.50.1", @@ -3065,6 +3068,7 @@ "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.50.1", "@typescript-eslint/types": "8.50.1", @@ -3501,6 +3505,7 @@ "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", "license": "MIT", + "peer": true, "dependencies": { "chalk": "^5.1.2", "loglevel": "^1.6.0", @@ -3626,6 +3631,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4115,6 +4121,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5106,6 +5113,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7006,6 +7014,7 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -9482,6 +9491,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9614,6 +9624,7 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -9689,6 +9700,7 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", @@ -9861,6 +9873,7 @@ "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.21.0.tgz", "integrity": "sha512-7teaXajOuNdn2UyyKlqMLssJjf0vDEih+Lo+tE/gHOt/P+mB8CinZym4PGtsriZLcyt4xV+Cun3hDmXM+pL26A==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "^20.11.30", "@types/sinonjs__fake-timers": "^8.1.5", diff --git a/src/matchers/browser/toHaveTitle.ts b/src/matchers/browser/toHaveTitle.ts index 4c18dd7f8..106d39a5e 100644 --- a/src/matchers/browser/toHaveTitle.ts +++ b/src/matchers/browser/toHaveTitle.ts @@ -1,4 +1,4 @@ -import { waitUntil, enhanceError, compareText } from '../../utils.js' +import { waitUntil, enhanceError, compareText, compareTextWithArray } from '../../utils.js' import { DEFAULT_OPTIONS } from '../../constants.js' export async function toHaveTitle( @@ -17,9 +17,20 @@ export async function toHaveTitle( let actual const pass = await waitUntil(async () => { - actual = await browser.getTitle() + const result = await browser.getTitle() + actual = result - return compareText(actual, expectedValue, options).result + if (Array.isArray(result)) { + const results = result.map((item) => { + return Array.isArray(expectedValue) + ? compareTextWithArray(item, expectedValue, options).result + : compareText(item, expectedValue, options).result + }) + + return results.every((res) => res) + } + + return compareText(result, expectedValue, options).result }, isNot, options) const message = enhanceError('window', expectedValue, actual, this, verb, expectation, '', options) diff --git a/src/matchers/browser/toHaveUrl.ts b/src/matchers/browser/toHaveUrl.ts index 06719ac5d..6b4f1bfc1 100644 --- a/src/matchers/browser/toHaveUrl.ts +++ b/src/matchers/browser/toHaveUrl.ts @@ -1,4 +1,4 @@ -import { waitUntil, enhanceError, compareText } from '../../utils.js' +import { waitUntil, enhanceError, compareText, compareTextWithArray } from '../../utils.js' import { DEFAULT_OPTIONS } from '../../constants.js' export async function toHaveUrl( @@ -17,9 +17,20 @@ export async function toHaveUrl( let actual const pass = await waitUntil(async () => { - actual = await browser.getUrl() + const result = await browser.getUrl() + actual = result - return compareText(actual, expectedValue, options).result + if (Array.isArray(result)) { + const results = result.map((item) => { + return Array.isArray(expectedValue) + ? compareTextWithArray(item, expectedValue, options).result + : compareText(item, expectedValue, options).result + }) + + return results.every((res) => res) + } + + return compareText(result, expectedValue, options).result }, isNot, options) const message = enhanceError('window', expectedValue, actual, this, verb, expectation, '', options) diff --git a/src/matchers/element/toHaveAttribute.ts b/src/matchers/element/toHaveAttribute.ts index 9d42293e7..42269f553 100644 --- a/src/matchers/element/toHaveAttribute.ts +++ b/src/matchers/element/toHaveAttribute.ts @@ -2,6 +2,7 @@ import { DEFAULT_OPTIONS } from '../../constants.js' import type { WdioElementMaybePromise } from '../../types.js' import { compareText, + compareTextWithArray, enhanceError, executeCommand, waitUntil, @@ -10,15 +11,36 @@ import { async function conditionAttr(el: WebdriverIO.Element, attribute: string) { const attr = await el.getAttribute(attribute) + if (Array.isArray(attr)) { + return { + result: attr.every(a => typeof a === 'string'), + value: attr + } + } if (typeof attr !== 'string') { return { result: false, value: attr } } return { result: true, value: attr } - } async function conditionAttrAndValue(el: WebdriverIO.Element, attribute: string, value: string | RegExp | WdioAsymmetricMatcher, options: ExpectWebdriverIO.StringOptions) { const attr = await el.getAttribute(attribute) + if (Array.isArray(attr)) { + const results = attr.map((item) => { + if (typeof item !== 'string') { + return { result: false, value: item } + } + return Array.isArray(value) + ? compareTextWithArray(item, value, options) + : compareText(item, value, options) + }) + + return { + result: results.every((res) => res.result), + value: attr + } + } + if (typeof attr !== 'string') { return { result: false, value: attr } } diff --git a/src/matchers/element/toHaveElementProperty.ts b/src/matchers/element/toHaveElementProperty.ts index cdf4146b0..554260035 100644 --- a/src/matchers/element/toHaveElementProperty.ts +++ b/src/matchers/element/toHaveElementProperty.ts @@ -2,6 +2,7 @@ import { DEFAULT_OPTIONS } from '../../constants.js' import type { WdioElementMaybePromise } from '../../types.js' import { compareText, + compareTextWithArray, enhanceError, executeCommand, waitUntil, @@ -21,6 +22,34 @@ async function condition( return { result: false, value: prop } } + if (Array.isArray(prop)) { + // existence check + if (value === null) { + return { result: prop.every(p => p === null), value: prop } + } + + const results = prop.map((item) => { + if (item === null || item === undefined) { + return { result: false, value: item } + } + if (value === null) { + return { result: item === null, value: item } + } + if (!(value instanceof RegExp) && typeof item !== 'string' && !asString) { + return { result: item === value, value: item } + } + const itemStr = item.toString() + return Array.isArray(value) + ? compareTextWithArray(itemStr, value as string[], options) + : compareText(itemStr, value as string | RegExp | WdioAsymmetricMatcher, options) + }) + + return { + result: results.every((res) => res.result), + value: prop + } + } + if (value === null) { return { result: true, value: prop } } diff --git a/src/matchers/element/toHaveText.ts b/src/matchers/element/toHaveText.ts index 82d4450c7..21eae8700 100644 --- a/src/matchers/element/toHaveText.ts +++ b/src/matchers/element/toHaveText.ts @@ -25,10 +25,21 @@ async function condition(el: WebdriverIO.Element | WebdriverIO.ElementArray, tex checkAllValuesMatchCondition = resultArray.every(Boolean) } else { const actualText = await (el as WebdriverIO.Element).getText() - actualTextArray.push(actualText) - checkAllValuesMatchCondition = Array.isArray(text) - ? compareTextWithArray(actualText, text, options).result - : compareText(actualText, text, options).result + if (Array.isArray(actualText)) { + for (const value of actualText) { + actualTextArray.push(value) + const result = Array.isArray(text) + ? compareTextWithArray(value, text, options).result + : compareText(value, text, options).result + resultArray.push(result) + } + checkAllValuesMatchCondition = resultArray.every(Boolean) + } else { + actualTextArray.push(actualText) + checkAllValuesMatchCondition = Array.isArray(text) + ? compareTextWithArray(actualText, text, options).result + : compareText(actualText, text, options).result + } } return {