expect-webdriverio 6.1.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +16 -5
  2. package/jest.d.ts +4 -2
  3. package/lib/api/index.js +2 -0
  4. package/lib/index.js +2 -0
  5. package/lib/matchers/asymmetrics/asymmetricsUtils.js +10 -0
  6. package/lib/matchers/asymmetrics/multiRemote.js +36 -0
  7. package/lib/matchers/browser/toHaveClipboardText.js +18 -8
  8. package/lib/matchers/browser/toHaveLocalStorageItem.js +22 -11
  9. package/lib/matchers/browser/toHaveTitle.js +16 -6
  10. package/lib/matchers/browser/toHaveUrl.js +16 -6
  11. package/lib/matchers/element/toHaveAttribute.js +2 -2
  12. package/lib/matchers/element/toHaveChildren.js +4 -3
  13. package/lib/matchers/element/toHaveComputedLabel.js +4 -4
  14. package/lib/matchers/element/toHaveComputedRole.js +4 -4
  15. package/lib/matchers/element/toHaveElementClass.js +4 -4
  16. package/lib/matchers/element/toHaveElementProperty.js +4 -4
  17. package/lib/matchers/element/toHaveHTML.js +4 -4
  18. package/lib/matchers/element/toHaveHeight.js +2 -2
  19. package/lib/matchers/element/toHaveSize.js +3 -3
  20. package/lib/matchers/element/toHaveStyle.js +3 -3
  21. package/lib/matchers/element/toHaveText.js +5 -5
  22. package/lib/matchers/element/toHaveValue.js +1 -1
  23. package/lib/matchers/element/toHaveWidth.js +2 -2
  24. package/lib/matchers/elements/toBeElementsArrayOfSize.js +70 -3
  25. package/lib/matchers/mock/toBeRequestedTimes.js +28 -5
  26. package/lib/matchers/mock/toBeRequestedWith.js +40 -12
  27. package/lib/matchers/snapshot.js +40 -11
  28. package/lib/tsconfig.build.tsbuildinfo +1 -1
  29. package/lib/util/elementsUtil.js +34 -6
  30. package/lib/util/executeBrowserCommand.js +37 -0
  31. package/lib/util/executeCommand.js +107 -19
  32. package/lib/util/formatMessage.js +80 -6
  33. package/lib/util/multiRemoteUtils.js +85 -0
  34. package/lib/util/numberOptionsUtil.js +6 -0
  35. package/lib/util/refetchElements.js +24 -2
  36. package/lib/util/stackUtil.js +7 -0
  37. package/lib/utils.js +7 -2
  38. package/package.json +3 -3
  39. package/types/expect-webdriverio.d.ts +384 -39
package/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  # expect-webdriverio [![Test](https://github.com/webdriverio/expect-webdriverio/actions/workflows/test.yml/badge.svg)](https://github.com/webdriverio/expect-webdriverio/actions/workflows/test.yml)
2
2
 
3
- ###### [API](docs/API.md) | [TypeScript / JS Autocomplete](docs/Types.md) | [Examples](docs/Examples.md) | [Extending Matchers](docs/CustomMatchers.md)
3
+ ###### [API](docs/API.md) | [Multiple Elements](docs/MultipleElements.md) | [Multi-remote](docs/MultiRemote.md) | [TypeScript / JS Autocomplete](docs/Types.md) | [Examples](docs/Examples.md) | [Extending Matchers](docs/CustomMatchers.md)
4
4
 
5
5
  > [WebdriverIO](https://webdriver.io/) assertion library inspired by [expect](https://www.npmjs.com/package/expect)
6
6
 
7
7
  ## Key Features
8
8
 
9
- - [Waits](#default-options) for expectations to succeed
10
- - Supports single element `$()` & multiple elements `$$()`
9
+ - [Waits](docs/API.md#default-options) for expectations to succeed
10
+ - Supports single element `$()` & [multiple elements](docs/MultipleElements.md) `$$()`
11
+ - Supports [multi-remote](docs/MultiRemote.md) browsers & elements, with one expected value per browser instance
11
12
  - Detailed [error messages](#error-messages)
12
13
  - Works in Mocha, Cucumber, Jest, and Jasmine
13
14
  - Built-in [types](docs/Types.md) for TypeScript and JS autocompletion
@@ -21,7 +22,7 @@
21
22
  - **Cucumber**: `npm install @wdio/cucumber-framework`
22
23
  - **Jest**: No adapter needed — see [Jest Framework section](docs/Framework.md#jest)
23
24
 
24
- **Note:** [WebdriverIO](https://github.com/webdriverio/webdriverio) `v9.0.0` or higher is required!
25
+ **Note:** [WebdriverIO](https://github.com/webdriverio/webdriverio) `v9.31.5` or higher is required!
25
26
 
26
27
  ## Usage
27
28
 
@@ -36,6 +37,17 @@ await expect($$('buttons')).toBeDisplayed()
36
37
 
37
38
  See more [Examples](docs/Examples.md).
38
39
 
40
+ #### Multi-remote
41
+
42
+ With [multi-remote](https://webdriver.io/docs/multiremote), assertions check every browser instance, with a single expected value or one per instance:
43
+
44
+ ```js
45
+ await expect(multiRemoteBrowser).toHaveTitle('WebdriverIO')
46
+ await expect(multiRemoteBrowser.$('h1')).toHaveText(expect.multiRemote({ chrome: 'Welcome', firefox: 'Bienvenue' }))
47
+ ```
48
+
49
+ Some feature flags and WebdriverIO environment variables are required or recommended, see [Multi-remote Support](docs/MultiRemote.md#requirements--configuration).
50
+
39
51
  #### Local & Browser Runners
40
52
  - **Local Runner**: Fully compatible. You can leverage your test framework adapter as mentioned above.
41
53
  - **Browser Runner**: Designed for component frameworks like React, Preact, Vue.js, Svelte, and SolidJS, with a few known limitations. For details, see the [Browser Runner Framework section](docs/Framework.md#browser-runner).
@@ -86,4 +98,3 @@ First of all, **feel free to raise an issue with your suggestions or help with P
86
98
  ### Planned
87
99
 
88
100
  - Cookie matchers
89
- - Multiremote support (in progress)
package/jest.d.ts CHANGED
@@ -19,16 +19,18 @@ declare namespace jest {
19
19
 
20
20
  /**
21
21
  * snapshot matcher
22
+ * A plain `Element[]` or `MultiRemoteElement[]` is only recognized from its elements, so an empty one is snapshotted synchronously
22
23
  * @param label optional snapshot label
23
24
  */
24
- toMatchSnapshot(label?: string): T extends WdioElementOrPromiseLike ? Promise<void> : R;
25
+ toMatchSnapshot(label?: string): T extends WdioElementOrPromiseLike | WebdriverIO.MultiRemoteElement | WebdriverIO.ElementArray ? Promise<void> : T extends WebdriverIO.Element[] | WebdriverIO.MultiRemoteElement[] ? Promise<void> | R : R;
25
26
 
26
27
  /**
27
28
  * inline snapshot matcher
29
+ * A plain `Element[]` or `MultiRemoteElement[]` is only recognized from its elements, so an empty one is snapshotted synchronously
28
30
  * @param snapshot snapshot string (autogenerated if not specified)
29
31
  * @param label optional snapshot label
30
32
  */
31
- toMatchInlineSnapshot(snapshot?: string, label?: string): T extends WdioElementOrPromiseLike ? Promise<void> : R;
33
+ toMatchInlineSnapshot(snapshot?: string, label?: string): T extends WdioElementOrPromiseLike | WebdriverIO.MultiRemoteElement | WebdriverIO.ElementArray ? Promise<void> : T extends WebdriverIO.Element[] | WebdriverIO.MultiRemoteElement[] ? Promise<void> | R : R;
32
34
  }
33
35
 
34
36
  interface Expect extends ExpectWebdriverIO.Expect {}
package/lib/api/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  import { some as wdioSome } from '../matchers/modifiers/some.js';
2
+ import { multiRemote as wdioMultiRemote } from '../matchers/asymmetrics/multiRemote.js';
2
3
  export const some = wdioSome;
4
+ export const multiRemote = wdioMultiRemote;
package/lib/index.js CHANGED
@@ -4,6 +4,7 @@ import { DEFAULT_OPTIONS, defaultOptionsList } from './constants.js';
4
4
  import createSoftExpect from './softExpect.js';
5
5
  import { SoftAssertService } from './softAssert.js';
6
6
  import { oneOf } from './matchers/asymmetrics/oneOf.js';
7
+ import { multiRemote } from './matchers/asymmetrics/multiRemote.js';
7
8
  import { getGlobalSingleton } from './util/globalSingleton.js';
8
9
  function createSharedExpectSetup() {
9
10
  const wdioCustomMatchers = {};
@@ -28,6 +29,7 @@ function createSharedExpectSetup() {
28
29
  const wdioExpect = expectLib;
29
30
  wdioExpect.extend(filteredWdioMatchers);
30
31
  wdioExpect.oneOf = oneOf;
32
+ wdioExpect.multiRemote = multiRemote;
31
33
  Object.defineProperty(wdioExpect, 'soft', {
32
34
  configurable: true,
33
35
  value: (actual) => createSoftExpect(actual)
@@ -5,10 +5,20 @@ export const buildWdioAsymmetricMatchersWithOptions = (expectedValue, options) =
5
5
  if (Array.isArray(expectedValue)) {
6
6
  return expectedValue.map((value) => buildOneAsymmetricMatcherWithOptions(value, options));
7
7
  }
8
+ if (isPlainObject(expectedValue)) {
9
+ return Object.fromEntries(Object.entries(expectedValue).map(([key, value]) => [key, buildWdioAsymmetricMatchersWithOptions(value, options)]));
10
+ }
8
11
  return buildOneAsymmetricMatcherWithOptions(expectedValue, options);
9
12
  }
10
13
  return expectedValue;
11
14
  };
15
+ const isPlainObject = (value) => {
16
+ if (typeof value !== 'object' || value === null || isAsymmetricMatcher(value)) {
17
+ return false;
18
+ }
19
+ const prototype = Object.getPrototypeOf(value);
20
+ return prototype === Object.prototype || prototype === null;
21
+ };
12
22
  const buildOneAsymmetricMatcherWithOptions = (expectedValue, options) => {
13
23
  if (options) {
14
24
  if (isAsymmetricMatcher(expectedValue) && 'withOptions' in expectedValue && typeof expectedValue.withOptions === 'function') {
@@ -0,0 +1,36 @@
1
+ import { equals } from '../../jasmineUtils.js';
2
+ import { hasSameInstanceNames, isMultiRemoteValues, MULTI_REMOTE_MATCHER_SYMBOL } from '../../util/multiRemoteUtils.js';
3
+ import { buildWdioAsymmetricMatchersWithOptions, WdioAsymmetricMatchers } from './asymmetricsUtils.js';
4
+ export class MultiRemoteMatcher extends WdioAsymmetricMatchers {
5
+ [MULTI_REMOTE_MATCHER_SYMBOL] = true;
6
+ constructor(sample) {
7
+ if (!isMultiRemoteValues(sample)) {
8
+ throw new TypeError(`expect.multiRemote() expects an object with one expected value per multi-remote instance, e.g. { chrome: 'A', firefox: 'B' }. Received: ${String(sample)}`);
9
+ }
10
+ super(sample);
11
+ }
12
+ get values() {
13
+ return this.sample;
14
+ }
15
+ asymmetricMatch(actual) {
16
+ return isMultiRemoteValues(actual)
17
+ && hasSameInstanceNames(actual, Object.keys(this.sample))
18
+ && Object.entries(this.sample).every(([instance, expected]) => equals(actual[instance], expected));
19
+ }
20
+ withOptions(options) {
21
+ return new MultiRemoteMatcher(buildWdioAsymmetricMatchersWithOptions({ ...this.sample }, options));
22
+ }
23
+ toAsymmetricMatcher() {
24
+ const values = Object.entries(this.sample).map(([instance, expected]) => `${instance}: ${formatValue(expected)}`);
25
+ return `multiRemote<${values.join(', ')}>`;
26
+ }
27
+ }
28
+ const formatValue = (value) => {
29
+ if (value && typeof value === 'object' && 'toAsymmetricMatcher' in value && typeof value.toAsymmetricMatcher === 'function') {
30
+ return value.toAsymmetricMatcher();
31
+ }
32
+ return value instanceof RegExp ? value.toString() : JSON.stringify(value) ?? String(value);
33
+ };
34
+ export function multiRemote(values) {
35
+ return new MultiRemoteMatcher(values);
36
+ }
@@ -1,6 +1,8 @@
1
1
  import logger from '@wdio/logger';
2
- import { waitUntil, enhanceError, compareText } from '../../utils.js';
2
+ import { waitUntil, enhanceError, compareTextOrOneOf } from '../../utils.js';
3
3
  import { DEFAULT_OPTIONS } from '../../constants.js';
4
+ import { executeBrowserCommand } from '../../util/executeBrowserCommand.js';
5
+ import { buildWdioAsymmetricMatchersWithOptions } from '../asymmetrics/asymmetricsUtils.js';
4
6
  const log = logger('expect-webdriverio');
5
7
  export async function toHaveClipboardText(browser, expectedValue, options = DEFAULT_OPTIONS) {
6
8
  const { expectation = 'clipboard text', verb = 'have', isNot, matcherName = 'toHaveClipboardText' } = this;
@@ -9,14 +11,16 @@ export async function toHaveClipboardText(browser, expectedValue, options = DEFA
9
11
  expectedValue,
10
12
  options,
11
13
  });
12
- const { actual, success: pass } = await waitUntil(async () => {
13
- await browser.setPermissions({ name: 'clipboard-read' }, 'granted')
14
- .catch((err) => log.warn(`Couldn't set clipboard permissions: ${err}`));
15
- const actual = await browser.execute(() => window.navigator.clipboard.readText());
16
- const compareResult = compareText(actual, expectedValue, options);
17
- return { actual, success: compareResult.success, subject: browser };
14
+ const expectedWithOptions = buildWdioAsymmetricMatchersWithOptions(expectedValue, options);
15
+ const { actual, success: pass, subject, expected } = await waitUntil(async () => {
16
+ return await executeBrowserCommand({
17
+ browser,
18
+ isNot,
19
+ expectedValue: expectedWithOptions,
20
+ compare: (browser, expectedValue) => compareClipboardText(browser, expectedValue, options),
21
+ });
18
22
  }, isNot, { wait: options.wait, interval: options.interval });
19
- const message = enhanceError('browser', expectedValue, actual, this, verb, expectation, '', options);
23
+ const message = enhanceError(subject, expected, actual, this, verb, expectation, '', options);
20
24
  const result = {
21
25
  pass,
22
26
  message: () => message
@@ -29,3 +33,9 @@ export async function toHaveClipboardText(browser, expectedValue, options = DEFA
29
33
  });
30
34
  return result;
31
35
  }
36
+ const compareClipboardText = async (browser, expectedValue, options) => {
37
+ await browser.setPermissions({ name: 'clipboard-read' }, 'granted')
38
+ .catch((err) => log.warn(`Couldn't set clipboard permissions: ${err}`));
39
+ const actual = await browser.execute(() => window.navigator.clipboard.readText());
40
+ return compareTextOrOneOf(actual, expectedValue, options);
41
+ };
@@ -1,6 +1,8 @@
1
- import { waitUntil, enhanceError, compareText } from '../../utils.js';
1
+ import { waitUntil, enhanceError, compareTextOrOneOf } from '../../utils.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
3
  import { expect } from 'expect';
4
+ import { executeBrowserCommand } from '../../util/executeBrowserCommand.js';
5
+ import { buildWdioAsymmetricMatchersWithOptions } from '../asymmetrics/asymmetricsUtils.js';
4
6
  export async function toHaveLocalStorageItem(browser, key, expectedValue, options = DEFAULT_OPTIONS) {
5
7
  const { expectation = 'localStorage item', verb = 'have', isNot, matcherName = 'toHaveLocalStorageItem' } = this;
6
8
  await options.beforeAssertion?.({
@@ -19,17 +21,16 @@ export async function toHaveLocalStorageItem(browser, key, expectedValue, option
19
21
  else {
20
22
  expected = expectedValue;
21
23
  }
22
- const { actual, success: pass } = await waitUntil(async () => {
23
- const actual = await browser.execute((storageKey) => {
24
- return localStorage.getItem(storageKey);
25
- }, key);
26
- if (actual === null) {
27
- return { actual, success: false, subject: browser };
28
- }
29
- const compareResult = compareText(actual, expected, options);
30
- return { actual, success: compareResult.success, subject: browser };
24
+ const expectedWithOptions = buildWdioAsymmetricMatchersWithOptions(expected, options);
25
+ const { actual, success: pass, subject, expected: expectedValues } = await waitUntil(async () => {
26
+ return await executeBrowserCommand({
27
+ browser,
28
+ isNot,
29
+ expectedValue: expectedWithOptions,
30
+ compare: (browser, expectedValue) => compareStorageItem(browser, key, expectedValue, options),
31
+ });
31
32
  }, isNot, { wait: options.wait, interval: options.interval });
32
- const message = enhanceError('browser', expected, actual, this, verb, expectation, key, options);
33
+ const message = enhanceError(subject, expectedValues, actual, this, verb, expectation, key, options);
33
34
  const result = {
34
35
  pass,
35
36
  message: () => message
@@ -42,3 +43,13 @@ export async function toHaveLocalStorageItem(browser, key, expectedValue, option
42
43
  });
43
44
  return result;
44
45
  }
46
+ const compareStorageItem = async (browser, key, expected, options) => {
47
+ const actual = await browser.execute((storageKey) => {
48
+ return localStorage.getItem(storageKey);
49
+ }, key);
50
+ if (actual === null) {
51
+ return { actual, success: false };
52
+ }
53
+ const compareResult = compareTextOrOneOf(actual, expected, options);
54
+ return { actual, success: compareResult.success };
55
+ };
@@ -1,5 +1,7 @@
1
- import { waitUntil, enhanceError, compareText } from '../../utils.js';
1
+ import { waitUntil, enhanceError, compareTextOrOneOf } from '../../utils.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
+ import { executeBrowserCommand } from '../../util/executeBrowserCommand.js';
4
+ import { buildWdioAsymmetricMatchersWithOptions } from '../asymmetrics/asymmetricsUtils.js';
3
5
  export async function toHaveTitle(browser, expectedValue, options = DEFAULT_OPTIONS) {
4
6
  const { expectation = 'title', verb = 'have', isNot, matcherName = 'toHaveTitle' } = this;
5
7
  await options.beforeAssertion?.({
@@ -7,12 +9,16 @@ export async function toHaveTitle(browser, expectedValue, options = DEFAULT_OPTI
7
9
  expectedValue,
8
10
  options,
9
11
  });
10
- const { actual, success } = await waitUntil(async () => {
11
- const actual = await browser.getTitle();
12
- const compareResult = compareText(actual, expectedValue, options);
13
- return { actual, success: compareResult.success, subject: browser };
12
+ const expectedWithOptions = buildWdioAsymmetricMatchersWithOptions(expectedValue, options);
13
+ const { actual, success, subject, expected } = await waitUntil(async () => {
14
+ return await executeBrowserCommand({
15
+ browser,
16
+ isNot,
17
+ expectedValue: expectedWithOptions,
18
+ compare: (browser, expectedValue) => compareTitle(browser, expectedValue, options),
19
+ });
14
20
  }, isNot, { wait: options.wait, interval: options.interval });
15
- const message = enhanceError('window', expectedValue, actual, this, verb, expectation, '', options);
21
+ const message = enhanceError(subject, expected, actual, { isNot, browserTargetType: 'window' }, verb, expectation, '', options);
16
22
  const result = {
17
23
  pass: success,
18
24
  message: () => message
@@ -25,3 +31,7 @@ export async function toHaveTitle(browser, expectedValue, options = DEFAULT_OPTI
25
31
  });
26
32
  return result;
27
33
  }
34
+ const compareTitle = async (browser, expectedValue, options) => {
35
+ const actual = await browser.getTitle();
36
+ return compareTextOrOneOf(actual, expectedValue, options);
37
+ };
@@ -1,5 +1,7 @@
1
- import { waitUntil, enhanceError, compareText } from '../../utils.js';
1
+ import { waitUntil, enhanceError, compareTextOrOneOf } from '../../utils.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
+ import { executeBrowserCommand } from '../../util/executeBrowserCommand.js';
4
+ import { buildWdioAsymmetricMatchersWithOptions } from '../asymmetrics/asymmetricsUtils.js';
3
5
  export async function toHaveUrl(browser, expectedValue, options = DEFAULT_OPTIONS) {
4
6
  const { expectation = 'url', verb = 'have', isNot, matcherName = 'toHaveUrl' } = this;
5
7
  await options.beforeAssertion?.({
@@ -7,12 +9,16 @@ export async function toHaveUrl(browser, expectedValue, options = DEFAULT_OPTION
7
9
  expectedValue,
8
10
  options,
9
11
  });
10
- const { success: pass, actual } = await waitUntil(async () => {
11
- const actual = await browser.getUrl();
12
- const compareResult = compareText(actual, expectedValue, options);
13
- return { actual, success: compareResult.success, subject: browser };
12
+ const expectedWithOptions = buildWdioAsymmetricMatchersWithOptions(expectedValue, options);
13
+ const { success: pass, actual, subject, expected } = await waitUntil(async () => {
14
+ return await executeBrowserCommand({
15
+ browser,
16
+ isNot,
17
+ expectedValue: expectedWithOptions,
18
+ compare: (browser, expectedValue) => compareUrl(browser, expectedValue, options),
19
+ });
14
20
  }, isNot, { wait: options.wait, interval: options.interval });
15
- const message = enhanceError('window', expectedValue, actual, this, verb, expectation, '', options);
21
+ const message = enhanceError(subject, expected, actual, { isNot, browserTargetType: 'window' }, verb, expectation, '', options);
16
22
  const result = {
17
23
  pass,
18
24
  message: () => message
@@ -25,3 +31,7 @@ export async function toHaveUrl(browser, expectedValue, options = DEFAULT_OPTION
25
31
  });
26
32
  return result;
27
33
  }
34
+ const compareUrl = async (browser, expectedValue, options) => {
35
+ const actual = await browser.getUrl();
36
+ return compareTextOrOneOf(actual, expectedValue, options);
37
+ };
@@ -20,7 +20,7 @@ async function conditionAttributeValueMatchWithExpected(el, attribute, expectedV
20
20
  export async function toHaveAttributeAndValue(received, attribute, expectedValue, options = DEFAULT_OPTIONS) {
21
21
  const { expectation = 'attribute', verb = 'have', isNot } = this;
22
22
  expectedValue = buildWdioAsymmetricMatchersWithOptions(expectedValue, options);
23
- const { success: pass, actual: attr, subject: el, context: { isSome } = {} } = await waitUntil(async (iteration) => {
23
+ const { success: pass, actual: attr, subject: el, context: { isSome } = {}, expected: expectedValues } = await waitUntil(async (iteration) => {
24
24
  return await executeCommandWithStrategy({
25
25
  unresolvedElements: received,
26
26
  supportsArrayContaining: true,
@@ -32,7 +32,7 @@ export async function toHaveAttributeAndValue(received, attribute, expectedValue
32
32
  strategy: 'NewStrictMultipleElements',
33
33
  });
34
34
  }, isNot, { wait: options.wait, interval: options.interval });
35
- const expected = wrapExpectedWithArray(el, attr, expectedValue);
35
+ const expected = expectedValues ?? wrapExpectedWithArray(el, attr, expectedValue);
36
36
  const message = enhanceError(el, expected, attr, { isNot, isSome }, verb, expectation, attribute, options);
37
37
  return {
38
38
  pass,
@@ -1,6 +1,7 @@
1
1
  import { DEFAULT_OPTIONS } from '../../constants.js';
2
2
  import { executeCommandWithStrategy } from '../../util/executeCommand.js';
3
3
  import { isEmptyOrLegacyNumberOptions, validateNumberArrayAndExtractOptions } from '../../util/numberOptionsUtil.js';
4
+ import { isMultiRemoteMatcher } from '../../util/multiRemoteUtils.js';
4
5
  import { enhanceError, waitUntil, wrapExpectedWithArray } from '../../utils.js';
5
6
  async function condition(el, expectedValue) {
6
7
  const children = await el.$$('./*').getElements();
@@ -15,7 +16,7 @@ async function condition(el, expectedValue) {
15
16
  export async function toHaveChildren(received, expectedValueOrOptions, options = DEFAULT_OPTIONS) {
16
17
  const { expectation = 'children', verb = 'have', isNot, matcherName = 'toHaveChildren' } = this;
17
18
  const paramsCount = arguments.length;
18
- if (paramsCount > 1 && (expectedValueOrOptions === undefined || isEmptyOrLegacyNumberOptions(expectedValueOrOptions))) {
19
+ if (paramsCount > 1 && (expectedValueOrOptions === undefined || (isEmptyOrLegacyNumberOptions(expectedValueOrOptions) && !isMultiRemoteMatcher(expectedValueOrOptions)))) {
19
20
  console.warn('Passing undefined or NumberOptions as the second argument to toHaveChildren is deprecated. Use a NumberMatcher instead. For example, `expect(el).toHaveChildren({ gte: 1 }, options)`');
20
21
  }
21
22
  await options.beforeAssertion?.({
@@ -24,7 +25,7 @@ export async function toHaveChildren(received, expectedValueOrOptions, options =
24
25
  options,
25
26
  });
26
27
  const { numberMatcher: expectedNumber, commandOptions } = validateNumberArrayAndExtractOptions(expectedValueOrOptions, options, { supportDefaultAsGteThen1: true });
27
- const { success: pass, actual: children, subject, context: { isSome } = {} } = await waitUntil(async (iteration) => {
28
+ const { success: pass, actual: children, subject, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
28
29
  return await executeCommandWithStrategy({
29
30
  unresolvedElements: received,
30
31
  expectedValues: expectedNumber,
@@ -33,7 +34,7 @@ export async function toHaveChildren(received, expectedValueOrOptions, options =
33
34
  strategy: 'NewStrictMultipleElements',
34
35
  });
35
36
  }, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
36
- const expectedArray = wrapExpectedWithArray(subject, children, expectedNumber);
37
+ const expectedArray = expected ?? wrapExpectedWithArray(subject, children, expectedNumber);
37
38
  const message = enhanceError(subject, expectedArray, children, { isNot, isSome }, verb, expectation, '', commandOptions);
38
39
  const result = {
39
40
  pass,
@@ -1,10 +1,10 @@
1
1
  import { DEFAULT_OPTIONS } from '../../constants.js';
2
2
  import { executeCommandWithStrategy } from '../../util/executeCommand.js';
3
- import { compareTextOrArray, enhanceError, waitUntil, wrapExpectedWithArray } from '../../utils.js';
3
+ import { compareTextOrOneOf, enhanceError, waitUntil, wrapExpectedWithArray } from '../../utils.js';
4
4
  import { buildWdioAsymmetricMatchersWithOptions } from '../asymmetrics/asymmetricsUtils.js';
5
5
  async function singleElementCompare(element, label, options) {
6
6
  const actualLabel = await element.getComputedLabel();
7
- return compareTextOrArray(actualLabel, label, options);
7
+ return compareTextOrOneOf(actualLabel, label, options);
8
8
  }
9
9
  export async function toHaveComputedLabel(received, expectedValue, options = DEFAULT_OPTIONS) {
10
10
  const { expectation = 'computed label', verb = 'have', isNot, matcherName = 'toHaveComputedLabel' } = this;
@@ -14,7 +14,7 @@ export async function toHaveComputedLabel(received, expectedValue, options = DEF
14
14
  options,
15
15
  });
16
16
  expectedValue = buildWdioAsymmetricMatchersWithOptions(expectedValue, options);
17
- const { success: pass, actual: actualLabel, subject: el, context: { isSome } = {} } = await waitUntil(async (iteration) => {
17
+ const { success: pass, actual: actualLabel, subject: el, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
18
18
  return await executeCommandWithStrategy({
19
19
  unresolvedElements: received,
20
20
  supportsArrayContaining: true,
@@ -25,7 +25,7 @@ export async function toHaveComputedLabel(received, expectedValue, options = DEF
25
25
  strictConfiguration: { allowArrayWithSingleElement: true }
26
26
  });
27
27
  }, isNot, { wait: options.wait, interval: options.interval });
28
- const message = enhanceError(el, wrapExpectedWithArray(el, actualLabel, expectedValue), actualLabel, { isNot, isSome }, verb, expectation, '', options);
28
+ const message = enhanceError(el, expected ?? wrapExpectedWithArray(el, actualLabel, expectedValue), actualLabel, { isNot, isSome }, verb, expectation, '', options);
29
29
  const result = {
30
30
  pass,
31
31
  message: () => message
@@ -1,10 +1,10 @@
1
1
  import { DEFAULT_OPTIONS } from '../../constants.js';
2
2
  import { executeCommandWithStrategy } from '../../util/executeCommand.js';
3
- import { compareTextOrArray, enhanceError, waitUntil, wrapExpectedWithArray } from '../../utils.js';
3
+ import { compareTextOrOneOf, enhanceError, waitUntil, wrapExpectedWithArray } from '../../utils.js';
4
4
  import { buildWdioAsymmetricMatchersWithOptions } from '../asymmetrics/asymmetricsUtils.js';
5
5
  async function singleElementCompare(element, role, options) {
6
6
  const actualRole = await element.getComputedRole();
7
- return compareTextOrArray(actualRole, role, options);
7
+ return compareTextOrOneOf(actualRole, role, options);
8
8
  }
9
9
  export async function toHaveComputedRole(received, expectedValue, options = DEFAULT_OPTIONS) {
10
10
  const { expectation = 'computed role', verb = 'have', isNot, matcherName = 'toHaveComputedRole' } = this;
@@ -14,7 +14,7 @@ export async function toHaveComputedRole(received, expectedValue, options = DEFA
14
14
  options,
15
15
  });
16
16
  expectedValue = buildWdioAsymmetricMatchersWithOptions(expectedValue, options);
17
- const { success: pass, actual: actualRole, subject: el, context: { isSome } = {} } = await waitUntil(async (iteration) => {
17
+ const { success: pass, actual: actualRole, subject: el, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
18
18
  return await executeCommandWithStrategy({
19
19
  unresolvedElements: received,
20
20
  supportsArrayContaining: true,
@@ -25,7 +25,7 @@ export async function toHaveComputedRole(received, expectedValue, options = DEFA
25
25
  strictConfiguration: { allowArrayWithSingleElement: true }
26
26
  });
27
27
  }, isNot, { wait: options.wait, interval: options.interval });
28
- const message = enhanceError(el, wrapExpectedWithArray(el, actualRole, expectedValue), actualRole, { isNot, isSome }, verb, expectation, '', options);
28
+ const message = enhanceError(el, expected ?? wrapExpectedWithArray(el, actualRole, expectedValue), actualRole, { isNot, isSome }, verb, expectation, '', options);
29
29
  const result = {
30
30
  pass,
31
31
  message: () => message
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_OPTIONS } from '../../constants.js';
2
2
  import { executeCommandWithStrategy } from '../../util/executeCommand.js';
3
- import { compareText, compareTextOrArray, enhanceError, isAsymmetricMatcher, waitUntil, wrapExpectedWithArray } from '../../utils.js';
3
+ import { compareText, compareTextOrOneOf, enhanceError, isAsymmetricMatcher, waitUntil, wrapExpectedWithArray } from '../../utils.js';
4
4
  async function singleElementCompare(el, attribute, value, options) {
5
5
  const actualClass = await el.getAttribute(attribute);
6
6
  if (value === undefined) {
@@ -14,7 +14,7 @@ async function singleElementCompare(el, attribute, value, options) {
14
14
  }
15
15
  const classes = actualClass.split(' ');
16
16
  const isValueInClasses = classes.some((clazz) => {
17
- return compareTextOrArray(clazz, value, options).success;
17
+ return compareTextOrOneOf(clazz, value, options).success;
18
18
  });
19
19
  return {
20
20
  success: isValueInClasses,
@@ -32,7 +32,7 @@ export async function toHaveElementClass(received, expectedValue, options = DEFA
32
32
  options,
33
33
  });
34
34
  const attribute = 'class';
35
- const { success: pass, actual: attr, subject: el, context: { isSome } = {} } = await waitUntil(async (iteration) => {
35
+ const { success: pass, actual: attr, subject: el, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
36
36
  return await executeCommandWithStrategy({
37
37
  unresolvedElements: received,
38
38
  supportsArrayContaining: true,
@@ -43,7 +43,7 @@ export async function toHaveElementClass(received, expectedValue, options = DEFA
43
43
  strictConfiguration: { allowArrayWithSingleElement: true }
44
44
  });
45
45
  }, isNot, { wait: options.wait, interval: options.interval });
46
- const message = enhanceError(el, wrapExpectedWithArray(el, attr, expectedValue), attr, { isNot, isSome }, verb, expectation, '', options);
46
+ const message = enhanceError(el, expected ?? wrapExpectedWithArray(el, attr, expectedValue), attr, { isNot, isSome }, verb, expectation, '', options);
47
47
  const result = {
48
48
  pass,
49
49
  message: () => message
@@ -20,7 +20,7 @@ async function condition(el, property, expectedValue, options = DEFAULT_OPTIONS)
20
20
  return compareText(propertyValue.toString(), expectedValue, options);
21
21
  }
22
22
  export async function toHaveElementProperty(received, property, value, options = DEFAULT_OPTIONS) {
23
- const { expectation = 'property', verb = 'have', isNot, matcherName = 'toHaveElementProperty' } = this;
23
+ const { expectation = 'property', verb = 'have', isNot, matcherName = 'toHaveElementProperty', allowObjectExpectedValue = true } = this;
24
24
  const paramsCount = arguments.length;
25
25
  if (value === undefined || value === null) {
26
26
  if (paramsCount > 2) {
@@ -34,7 +34,7 @@ export async function toHaveElementProperty(received, property, value, options =
34
34
  options,
35
35
  });
36
36
  value = buildWdioAsymmetricMatchersWithOptions(value, options);
37
- const { success: pass, actual: actualProppertyValue, subject: elements, context: { isSome } = {} } = await waitUntil(async (iteration) => {
37
+ const { success: pass, actual: actualProppertyValue, subject: elements, context: { isSome } = {}, expected: expectedValues } = await waitUntil(async (iteration) => {
38
38
  return await executeCommandWithStrategy({
39
39
  unresolvedElements: received,
40
40
  supportsArrayContaining: true,
@@ -44,10 +44,10 @@ export async function toHaveElementProperty(received, property, value, options =
44
44
  },
45
45
  context: { isNot, iteration },
46
46
  strategy: 'NewStrictMultipleElements',
47
- strictConfiguration: { allowArrayWithSingleElement: false }
47
+ strictConfiguration: { allowArrayWithSingleElement: false, allowObjectExpectedValue }
48
48
  });
49
49
  }, isNot, { wait: options.wait, interval: options.interval });
50
- const expected = wrapExpectedWithArray(elements, actualProppertyValue, value);
50
+ const expected = expectedValues ?? wrapExpectedWithArray(elements, actualProppertyValue, value);
51
51
  const message = enhanceError(elements, expected, actualProppertyValue, { isNot, isSome }, verb, expectation, property, options);
52
52
  const result = {
53
53
  pass,
@@ -1,10 +1,10 @@
1
1
  import { DEFAULT_OPTIONS } from '../../constants.js';
2
- import { compareTextOrArray, enhanceError, waitUntil, wrapExpectedWithArray } from '../../utils.js';
2
+ import { compareTextOrOneOf, enhanceError, waitUntil, wrapExpectedWithArray } from '../../utils.js';
3
3
  import { executeCommandWithStrategy } from '../../util/executeCommand.js';
4
4
  import { buildWdioAsymmetricMatchersWithOptions } from '../asymmetrics/asymmetricsUtils.js';
5
5
  async function singleElementCompare(el, html, options) {
6
6
  const actualHTML = await el.getHTML(options);
7
- return compareTextOrArray(actualHTML, html, options);
7
+ return compareTextOrOneOf(actualHTML, html, options);
8
8
  }
9
9
  export async function toHaveHTML(received, expectedValue, options = DEFAULT_OPTIONS) {
10
10
  const { expectation = 'HTML', verb = 'have', isNot, matcherName = 'toHaveHTML' } = this;
@@ -14,7 +14,7 @@ export async function toHaveHTML(received, expectedValue, options = DEFAULT_OPTI
14
14
  expectedValue,
15
15
  options,
16
16
  });
17
- const { success: pass, actual: actualHTML, subject: elements, context: { isSome } = {} } = await waitUntil(async (iteration) => {
17
+ const { success: pass, actual: actualHTML, subject: elements, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
18
18
  const result = await executeCommandWithStrategy({
19
19
  unresolvedElements: received,
20
20
  supportsArrayContaining: true,
@@ -26,7 +26,7 @@ export async function toHaveHTML(received, expectedValue, options = DEFAULT_OPTI
26
26
  });
27
27
  return result;
28
28
  }, isNot, { wait: options.wait, interval: options.interval });
29
- const expectedValues = wrapExpectedWithArray(elements, actualHTML, expectedValue);
29
+ const expectedValues = expected ?? wrapExpectedWithArray(elements, actualHTML, expectedValue);
30
30
  const message = enhanceError(elements, expectedValues, actualHTML, { isNot, isSome }, verb, expectation, '', options);
31
31
  const result = {
32
32
  pass,
@@ -18,7 +18,7 @@ export async function toHaveHeight(received, expectedValue, options = DEFAULT_OP
18
18
  options,
19
19
  });
20
20
  const { numberMatcher: expectedNumber, commandOptions } = validateNumberArrayAndExtractOptions(expectedValue, options);
21
- const { success: pass, actual: actualHeight, subject: elements, context: { isSome } = {} } = await waitUntil(async (iteration) => {
21
+ const { success: pass, actual: actualHeight, subject: elements, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
22
22
  return await executeCommandWithStrategy({
23
23
  unresolvedElements: received,
24
24
  expectedValues: expectedNumber,
@@ -28,7 +28,7 @@ export async function toHaveHeight(received, expectedValue, options = DEFAULT_OP
28
28
  strictConfiguration: { allowArrayWithSingleElement: false }
29
29
  });
30
30
  }, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
31
- const expectedValues = wrapExpectedWithArray(elements, actualHeight, expectedNumber);
31
+ const expectedValues = expected ?? wrapExpectedWithArray(elements, actualHeight, expectedNumber);
32
32
  const message = enhanceError(elements, expectedValues, actualHeight, { isNot, isSome }, verb, expectation, '', commandOptions);
33
33
  const result = {
34
34
  pass,
@@ -12,17 +12,17 @@ export async function toHaveSize(received, expectedValue, options = DEFAULT_OPTI
12
12
  expectedValue,
13
13
  options,
14
14
  });
15
- const { success: pass, actual: actualSize, subject: el, context: { isSome } = {} } = await waitUntil(async (iteration) => {
15
+ const { success: pass, actual: actualSize, subject: el, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
16
16
  return await executeCommandWithStrategy({
17
17
  unresolvedElements: received,
18
18
  expectedValues: expectedValue,
19
19
  singleElementCompare: (element, expectedSize) => condition(element, expectedSize),
20
20
  context: { isNot, iteration },
21
21
  strategy: 'NewStrictMultipleElements',
22
- strictConfiguration: { allowArrayWithSingleElement: false }
22
+ strictConfiguration: { allowArrayWithSingleElement: false, allowObjectExpectedValue: true }
23
23
  });
24
24
  }, isNot, { wait: options.wait, interval: options.interval });
25
- const message = enhanceError(el, wrapExpectedWithArray(el, actualSize, expectedValue), actualSize, { isNot, isSome }, verb, expectation, '', options);
25
+ const message = enhanceError(el, expected ?? wrapExpectedWithArray(el, actualSize, expectedValue), actualSize, { isNot, isSome }, verb, expectation, '', options);
26
26
  const result = {
27
27
  pass,
28
28
  message: () => message
@@ -14,17 +14,17 @@ export async function toHaveStyle(received, expectedValue, options = DEFAULT_OPT
14
14
  expectedValue,
15
15
  options,
16
16
  });
17
- const { success: pass, actual: actualStyle, subject: el, context: { isSome } = {} } = await waitUntil(async (iteration) => {
17
+ const { success: pass, actual: actualStyle, subject: el, context: { isSome } = {}, expected: expectedValues } = await waitUntil(async (iteration) => {
18
18
  return await executeCommandWithStrategy({
19
19
  unresolvedElements: received,
20
20
  expectedValues: expectedValue,
21
21
  singleElementCompare: (element, expectedValues) => condition(element, expectedValues, options),
22
22
  context: { isNot, iteration },
23
23
  strategy: 'NewStrictMultipleElements',
24
- strictConfiguration: { allowArrayWithSingleElement: false }
24
+ strictConfiguration: { allowArrayWithSingleElement: false, allowObjectExpectedValue: true }
25
25
  });
26
26
  }, isNot, { wait: options.wait, interval: options.interval });
27
- const expected = wrapExpectedWithArray(el, actualStyle, expectedValue);
27
+ const expected = expectedValues ?? wrapExpectedWithArray(el, actualStyle, expectedValue);
28
28
  const message = enhanceError(el, expected, actualStyle, { isNot, isSome }, verb, expectation, '', options);
29
29
  const result = {
30
30
  pass,