expect-webdriverio 6.1.0 → 8.0.0-alpha.0

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 (56) hide show
  1. package/README.md +22 -7
  2. package/jasmine-wdio-expect-async.d.ts +1 -1
  3. package/jest.d.ts +4 -2
  4. package/lib/api/index.js +2 -0
  5. package/lib/constants.js +0 -6
  6. package/lib/index.js +3 -13
  7. package/lib/jasmineUtils.js +2 -2
  8. package/lib/matchers/asymmetrics/asymmetricsUtils.js +10 -0
  9. package/lib/matchers/asymmetrics/multiRemote.js +36 -0
  10. package/lib/matchers/asymmetrics/oneOf.js +2 -2
  11. package/lib/matchers/browser/toHaveClipboardText.js +24 -10
  12. package/lib/matchers/browser/toHaveLocalStorageItem.js +23 -22
  13. package/lib/matchers/browser/toHaveTitle.js +16 -6
  14. package/lib/matchers/browser/toHaveUrl.js +16 -6
  15. package/lib/matchers/element/toBeClickable.js +3 -2
  16. package/lib/matchers/element/toBeDisabled.js +3 -2
  17. package/lib/matchers/element/toBeDisplayed.js +3 -2
  18. package/lib/matchers/element/toBeDisplayedInViewport.js +3 -2
  19. package/lib/matchers/element/toBeEnabled.js +3 -2
  20. package/lib/matchers/element/toBeExisting.js +5 -2
  21. package/lib/matchers/element/toBeFocused.js +3 -2
  22. package/lib/matchers/element/toBeSelected.js +4 -4
  23. package/lib/matchers/element/toHaveAttribute.js +4 -16
  24. package/lib/matchers/element/toHaveChildren.js +9 -14
  25. package/lib/matchers/element/toHaveComputedLabel.js +4 -6
  26. package/lib/matchers/element/toHaveComputedRole.js +4 -6
  27. package/lib/matchers/element/toHaveElementClass.js +5 -10
  28. package/lib/matchers/element/toHaveElementProperty.js +4 -9
  29. package/lib/matchers/element/toHaveHTML.js +6 -8
  30. package/lib/matchers/element/toHaveHeight.js +6 -7
  31. package/lib/matchers/element/toHaveHref.js +6 -3
  32. package/lib/matchers/element/toHaveId.js +3 -2
  33. package/lib/matchers/element/toHaveSize.js +3 -4
  34. package/lib/matchers/element/toHaveStyle.js +3 -4
  35. package/lib/matchers/element/toHaveText.js +5 -8
  36. package/lib/matchers/element/toHaveValue.js +1 -1
  37. package/lib/matchers/element/toHaveWidth.js +6 -7
  38. package/lib/matchers/elements/toBeElementsArrayOfSize.js +71 -6
  39. package/lib/matchers/mock/toBeRequested.js +1 -1
  40. package/lib/matchers/mock/toBeRequestedTimes.js +30 -7
  41. package/lib/matchers/mock/toBeRequestedWith.js +40 -15
  42. package/lib/matchers/snapshot.js +38 -7
  43. package/lib/matchers.js +32 -32
  44. package/lib/snapshot.js +1 -8
  45. package/lib/tsconfig.build.tsbuildinfo +1 -1
  46. package/lib/util/elementsUtil.js +35 -6
  47. package/lib/util/executeBrowserCommand.js +37 -0
  48. package/lib/util/executeCommand.js +101 -52
  49. package/lib/util/formatMessage.js +81 -7
  50. package/lib/util/multiRemoteUtils.js +93 -0
  51. package/lib/util/numberOptionsUtil.js +19 -24
  52. package/lib/util/refetchElements.js +24 -2
  53. package/lib/utils.js +16 -111
  54. package/package.json +37 -40
  55. package/types/expect-global.d.ts +1 -2
  56. package/types/expect-webdriverio.d.ts +386 -232
@@ -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,
@@ -29,10 +29,9 @@ export async function toHaveAttributeAndValue(received, attribute, expectedValue
29
29
  return conditionAttributeValueMatchWithExpected(element, attribute, values, options);
30
30
  },
31
31
  context: { isNot, iteration },
32
- strategy: 'NewStrictMultipleElements',
33
32
  });
34
33
  }, isNot, { wait: options.wait, interval: options.interval });
35
- const expected = wrapExpectedWithArray(el, attr, expectedValue);
34
+ const expected = expectedValues ?? wrapExpectedWithArray(el, attr, expectedValue);
36
35
  const message = enhanceError(el, expected, attr, { isNot, isSome }, verb, expectation, attribute, options);
37
36
  return {
38
37
  pass,
@@ -40,23 +39,13 @@ export async function toHaveAttributeAndValue(received, attribute, expectedValue
40
39
  };
41
40
  }
42
41
  export async function toHaveAttribute(received, attribute, value, options = DEFAULT_OPTIONS) {
43
- const matcherName = 'toHaveAttribute';
44
- const paramsCount = arguments.length;
42
+ const { matcherName = 'toHaveAttribute' } = this;
45
43
  await options.beforeAssertion?.({
46
44
  matcherName,
47
45
  expectedValue: [attribute, value],
48
46
  options,
49
47
  });
50
- let expectedValue;
51
- if (value === undefined) {
52
- if (paramsCount > 2) {
53
- console.warn('Using undefined or null as value for toHaveAttribute is deprecated and will be removed in v6.0.0. Please omit the third argument entirely or use toHaveAttribute(el, attribute, wdioExpect.anything(), options).');
54
- }
55
- expectedValue = expect.anything();
56
- }
57
- else {
58
- expectedValue = value;
59
- }
48
+ const expectedValue = value ?? expect.anything();
60
49
  const result = await toHaveAttributeAndValue.call(this, received, attribute, expectedValue, options);
61
50
  await options.afterAssertion?.({
62
51
  matcherName,
@@ -66,4 +55,3 @@ export async function toHaveAttribute(received, attribute, value, options = DEFA
66
55
  });
67
56
  return result;
68
57
  }
69
- export const toHaveAttr = toHaveAttribute;
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_OPTIONS } from '../../constants.js';
2
2
  import { executeCommandWithStrategy } from '../../util/executeCommand.js';
3
- import { isEmptyOrLegacyNumberOptions, validateNumberArrayAndExtractOptions } from '../../util/numberOptionsUtil.js';
3
+ import { validateNumberMatcherArray } from '../../util/numberOptionsUtil.js';
4
4
  import { enhanceError, waitUntil, wrapExpectedWithArray } from '../../utils.js';
5
5
  async function condition(el, expectedValue) {
6
6
  const children = await el.$$('./*').getElements();
@@ -12,36 +12,31 @@ async function condition(el, expectedValue) {
12
12
  actual: children?.length
13
13
  };
14
14
  }
15
- export async function toHaveChildren(received, expectedValueOrOptions, options = DEFAULT_OPTIONS) {
15
+ export async function toHaveChildren(received, expectedValue, options = DEFAULT_OPTIONS) {
16
16
  const { expectation = 'children', verb = 'have', isNot, matcherName = 'toHaveChildren' } = this;
17
- const paramsCount = arguments.length;
18
- if (paramsCount > 1 && (expectedValueOrOptions === undefined || isEmptyOrLegacyNumberOptions(expectedValueOrOptions))) {
19
- 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
17
  await options.beforeAssertion?.({
22
18
  matcherName,
23
- expectedValue: expectedValueOrOptions,
19
+ expectedValue,
24
20
  options,
25
21
  });
26
- const { numberMatcher: expectedNumber, commandOptions } = validateNumberArrayAndExtractOptions(expectedValueOrOptions, options, { supportDefaultAsGteThen1: true });
27
- const { success: pass, actual: children, subject, context: { isSome } = {} } = await waitUntil(async (iteration) => {
22
+ const expectedNumber = validateNumberMatcherArray(expectedValue, { supportDefaultAsGteThen1: true });
23
+ const { success: pass, actual: children, subject, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
28
24
  return await executeCommandWithStrategy({
29
25
  unresolvedElements: received,
30
26
  expectedValues: expectedNumber,
31
27
  singleElementCompare: (element, expectedValue) => condition(element, expectedValue),
32
28
  context: { isNot, iteration },
33
- strategy: 'NewStrictMultipleElements',
34
29
  });
35
- }, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
36
- const expectedArray = wrapExpectedWithArray(subject, children, expectedNumber);
37
- const message = enhanceError(subject, expectedArray, children, { isNot, isSome }, verb, expectation, '', commandOptions);
30
+ }, isNot, { wait: options.wait, interval: options.interval });
31
+ const expectedArray = expected ?? wrapExpectedWithArray(subject, children, expectedNumber);
32
+ const message = enhanceError(subject, expectedArray, children, { isNot, isSome }, verb, expectation, '', options);
38
33
  const result = {
39
34
  pass,
40
35
  message: () => message
41
36
  };
42
37
  await options.afterAssertion?.({
43
38
  matcherName,
44
- expectedValue: expectedValueOrOptions,
39
+ expectedValue,
45
40
  options,
46
41
  result
47
42
  });
@@ -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,18 +14,16 @@ 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,
21
21
  expectedValues: expectedValue,
22
22
  singleElementCompare: (element, expectedValue) => singleElementCompare(element, expectedValue, options),
23
23
  context: { isNot, iteration },
24
- strategy: 'NewStrictMultipleElements',
25
- strictConfiguration: { allowArrayWithSingleElement: true }
26
24
  });
27
25
  }, isNot, { wait: options.wait, interval: options.interval });
28
- const message = enhanceError(el, wrapExpectedWithArray(el, actualLabel, expectedValue), actualLabel, { isNot, isSome }, verb, expectation, '', options);
26
+ const message = enhanceError(el, expected ?? wrapExpectedWithArray(el, actualLabel, expectedValue), actualLabel, { isNot, isSome }, verb, expectation, '', options);
29
27
  const result = {
30
28
  pass,
31
29
  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,18 +14,16 @@ 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,
21
21
  expectedValues: expectedValue,
22
22
  singleElementCompare: (element, expectedValue) => singleElementCompare(element, expectedValue, options),
23
23
  context: { isNot, iteration },
24
- strategy: 'NewStrictMultipleElements',
25
- strictConfiguration: { allowArrayWithSingleElement: true }
26
24
  });
27
25
  }, isNot, { wait: options.wait, interval: options.interval });
28
- const message = enhanceError(el, wrapExpectedWithArray(el, actualRole, expectedValue), actualRole, { isNot, isSome }, verb, expectation, '', options);
26
+ const message = enhanceError(el, expected ?? wrapExpectedWithArray(el, actualRole, expectedValue), actualRole, { isNot, isSome }, verb, expectation, '', options);
29
27
  const result = {
30
28
  pass,
31
29
  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, 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) {
@@ -13,17 +13,13 @@ async function singleElementCompare(el, attribute, value, options) {
13
13
  return compareText(actualClass, value, options);
14
14
  }
15
15
  const classes = actualClass.split(' ');
16
- const isValueInClasses = classes.some((clazz) => {
17
- return compareTextOrArray(clazz, value, options).success;
18
- });
16
+ const values = Array.isArray(value) ? value : [value];
17
+ const isValueInClasses = classes.some((clazz) => values.some((expected) => compareText(clazz, expected, options).success));
19
18
  return {
20
19
  success: isValueInClasses,
21
20
  actual: actualClass
22
21
  };
23
22
  }
24
- export function toHaveClass(...args) {
25
- return toHaveElementClass.call(this || {}, ...args);
26
- }
27
23
  export async function toHaveElementClass(received, expectedValue, options = DEFAULT_OPTIONS) {
28
24
  const { expectation = 'class', verb = 'have', isNot, matcherName = 'toHaveElementClass' } = this;
29
25
  await options.beforeAssertion?.({
@@ -32,18 +28,17 @@ export async function toHaveElementClass(received, expectedValue, options = DEFA
32
28
  options,
33
29
  });
34
30
  const attribute = 'class';
35
- const { success: pass, actual: attr, subject: el, context: { isSome } = {} } = await waitUntil(async (iteration) => {
31
+ const { success: pass, actual: attr, subject: el, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
36
32
  return await executeCommandWithStrategy({
37
33
  unresolvedElements: received,
38
34
  supportsArrayContaining: true,
39
35
  expectedValues: expectedValue,
40
36
  singleElementCompare: (element, expectedValue) => singleElementCompare(element, attribute, expectedValue, options),
41
37
  context: { isNot, iteration },
42
- strategy: 'NewStrictMultipleElements',
43
38
  strictConfiguration: { allowArrayWithSingleElement: true }
44
39
  });
45
40
  }, isNot, { wait: options.wait, interval: options.interval });
46
- const message = enhanceError(el, wrapExpectedWithArray(el, attr, expectedValue), attr, { isNot, isSome }, verb, expectation, '', options);
41
+ const message = enhanceError(el, expected ?? wrapExpectedWithArray(el, attr, expectedValue), attr, { isNot, isSome }, verb, expectation, '', options);
47
42
  const result = {
48
43
  pass,
49
44
  message: () => message
@@ -20,12 +20,8 @@ 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;
24
- const paramsCount = arguments.length;
23
+ const { expectation = 'property', verb = 'have', isNot, matcherName = 'toHaveElementProperty', allowObjectExpectedValue = true } = this;
25
24
  if (value === undefined || value === null) {
26
- if (paramsCount > 2) {
27
- console.warn('Using undefined or null as value for toHaveElementProperty is deprecated and will be removed in v6.0.0. Please omit the third argument entirely or use toHaveElementProperty(el, property, object.anything(), options).');
28
- }
29
25
  value = expect.anything();
30
26
  }
31
27
  await options.beforeAssertion?.({
@@ -34,7 +30,7 @@ export async function toHaveElementProperty(received, property, value, options =
34
30
  options,
35
31
  });
36
32
  value = buildWdioAsymmetricMatchersWithOptions(value, options);
37
- const { success: pass, actual: actualProppertyValue, subject: elements, context: { isSome } = {} } = await waitUntil(async (iteration) => {
33
+ const { success: pass, actual: actualProppertyValue, subject: elements, context: { isSome } = {}, expected: expectedValues } = await waitUntil(async (iteration) => {
38
34
  return await executeCommandWithStrategy({
39
35
  unresolvedElements: received,
40
36
  supportsArrayContaining: true,
@@ -43,11 +39,10 @@ export async function toHaveElementProperty(received, property, value, options =
43
39
  return condition(element, property, expectedValue, options);
44
40
  },
45
41
  context: { isNot, iteration },
46
- strategy: 'NewStrictMultipleElements',
47
- strictConfiguration: { allowArrayWithSingleElement: false }
42
+ strictConfiguration: { allowArrayWithSingleElement: false, allowObjectExpectedValue }
48
43
  });
49
44
  }, isNot, { wait: options.wait, interval: options.interval });
50
- const expected = wrapExpectedWithArray(elements, actualProppertyValue, value);
45
+ const expected = expectedValues ?? wrapExpectedWithArray(elements, actualProppertyValue, value);
51
46
  const message = enhanceError(elements, expected, actualProppertyValue, { isNot, isSome }, verb, expectation, property, options);
52
47
  const result = {
53
48
  pass,
@@ -1,32 +1,30 @@
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;
11
- expectedValue = buildWdioAsymmetricMatchersWithOptions(expectedValue, options);
12
11
  await options.beforeAssertion?.({
13
12
  matcherName,
14
13
  expectedValue,
15
14
  options,
16
15
  });
17
- const { success: pass, actual: actualHTML, subject: elements, context: { isSome } = {} } = await waitUntil(async (iteration) => {
16
+ const expectedWithOptions = buildWdioAsymmetricMatchersWithOptions(expectedValue, options);
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,
21
- expectedValues: expectedValue,
21
+ expectedValues: expectedWithOptions,
22
22
  singleElementCompare: (element, expectedValue) => singleElementCompare(element, expectedValue, options),
23
23
  context: { isNot, iteration },
24
- strategy: 'NewStrictMultipleElements',
25
- strictConfiguration: { allowArrayWithSingleElement: true }
26
24
  });
27
25
  return result;
28
26
  }, isNot, { wait: options.wait, interval: options.interval });
29
- const expectedValues = wrapExpectedWithArray(elements, actualHTML, expectedValue);
27
+ const expectedValues = expected ?? wrapExpectedWithArray(elements, actualHTML, expectedWithOptions);
30
28
  const message = enhanceError(elements, expectedValues, actualHTML, { isNot, isSome }, verb, expectation, '', options);
31
29
  const result = {
32
30
  pass,
@@ -1,7 +1,7 @@
1
1
  import { DEFAULT_OPTIONS } from '../../constants.js';
2
2
  import { wrapExpectedWithArray } from '../../util/elementsUtil.js';
3
3
  import { executeCommandWithStrategy } from '../../util/executeCommand.js';
4
- import { validateNumberArrayAndExtractOptions } from '../../util/numberOptionsUtil.js';
4
+ import { validateNumberMatcherArray } from '../../util/numberOptionsUtil.js';
5
5
  import { enhanceError, waitUntil, } from '../../utils.js';
6
6
  async function condition(el, expectedNumber) {
7
7
  const actualHeight = await el.getSize('height');
@@ -17,19 +17,18 @@ export async function toHaveHeight(received, expectedValue, options = DEFAULT_OP
17
17
  expectedValue,
18
18
  options,
19
19
  });
20
- const { numberMatcher: expectedNumber, commandOptions } = validateNumberArrayAndExtractOptions(expectedValue, options);
21
- const { success: pass, actual: actualHeight, subject: elements, context: { isSome } = {} } = await waitUntil(async (iteration) => {
20
+ const expectedNumber = validateNumberMatcherArray(expectedValue);
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,
25
25
  singleElementCompare: (element, expectedNumber) => condition(element, expectedNumber),
26
26
  context: { isNot, iteration },
27
- strategy: 'NewStrictMultipleElements',
28
27
  strictConfiguration: { allowArrayWithSingleElement: false }
29
28
  });
30
- }, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
31
- const expectedValues = wrapExpectedWithArray(elements, actualHeight, expectedNumber);
32
- const message = enhanceError(elements, expectedValues, actualHeight, { isNot, isSome }, verb, expectation, '', commandOptions);
29
+ }, isNot, { wait: options.wait, interval: options.interval });
30
+ const expectedValues = expected ?? wrapExpectedWithArray(elements, actualHeight, expectedNumber);
31
+ const message = enhanceError(elements, expectedValues, actualHeight, { isNot, isSome }, verb, expectation, '', options);
33
32
  const result = {
34
33
  pass,
35
34
  message: () => message
@@ -1,18 +1,21 @@
1
1
  import { toHaveAttributeAndValue } from './toHaveAttribute.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
3
  export async function toHaveHref(el, expectedValue, options = DEFAULT_OPTIONS) {
4
+ const { matcherName = 'toHaveHref' } = this;
4
5
  await options.beforeAssertion?.({
5
- matcherName: 'toHaveHref',
6
+ matcherName,
6
7
  expectedValue,
7
8
  options,
8
9
  });
9
10
  const result = await toHaveAttributeAndValue.call(this, el, 'href', expectedValue, options);
10
11
  await options.afterAssertion?.({
11
- matcherName: 'toHaveHref',
12
+ matcherName,
12
13
  expectedValue,
13
14
  options,
14
15
  result
15
16
  });
16
17
  return result;
17
18
  }
18
- export const toHaveLink = toHaveHref;
19
+ export const toHaveLink = function toHaveLink(...args) {
20
+ return toHaveHref.call({ matcherName: 'toHaveLink', ...this }, ...args);
21
+ };
@@ -1,14 +1,15 @@
1
1
  import { toHaveAttributeAndValue } from './toHaveAttribute.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
3
  export async function toHaveId(el, expectedValue, options = DEFAULT_OPTIONS) {
4
+ const { matcherName = 'toHaveId' } = this;
4
5
  await options.beforeAssertion?.({
5
- matcherName: 'toHaveId',
6
+ matcherName,
6
7
  expectedValue,
7
8
  options,
8
9
  });
9
10
  const result = await toHaveAttributeAndValue.call(this, el, 'id', expectedValue, options);
10
11
  await options.afterAssertion?.({
11
- matcherName: 'toHaveId',
12
+ matcherName,
12
13
  expectedValue,
13
14
  options,
14
15
  result
@@ -12,17 +12,16 @@ 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
- strategy: 'NewStrictMultipleElements',
22
- strictConfiguration: { allowArrayWithSingleElement: false }
21
+ strictConfiguration: { allowArrayWithSingleElement: false, allowObjectExpectedValue: true }
23
22
  });
24
23
  }, isNot, { wait: options.wait, interval: options.interval });
25
- const message = enhanceError(el, wrapExpectedWithArray(el, actualSize, expectedValue), actualSize, { isNot, isSome }, verb, expectation, '', options);
24
+ const message = enhanceError(el, expected ?? wrapExpectedWithArray(el, actualSize, expectedValue), actualSize, { isNot, isSome }, verb, expectation, '', options);
26
25
  const result = {
27
26
  pass,
28
27
  message: () => message
@@ -14,17 +14,16 @@ 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
- strategy: 'NewStrictMultipleElements',
24
- strictConfiguration: { allowArrayWithSingleElement: false }
23
+ strictConfiguration: { allowArrayWithSingleElement: false, allowObjectExpectedValue: true }
25
24
  });
26
25
  }, isNot, { wait: options.wait, interval: options.interval });
27
- const expected = wrapExpectedWithArray(el, actualStyle, expectedValue);
26
+ const expected = expectedValues ?? wrapExpectedWithArray(el, actualStyle, expectedValue);
28
27
  const message = enhanceError(el, expected, actualStyle, { isNot, isSome }, verb, expectation, '', options);
29
28
  const result = {
30
29
  pass,
@@ -1,11 +1,11 @@
1
1
  import { DEFAULT_OPTIONS } from '../../constants.js';
2
- import { compareTextOrArray, enhanceError, getFeatureFlagValue, isArrayContainingMatcher, waitUntil, } from '../../utils.js';
2
+ import { compareTextOrOneOf, enhanceError, isArrayContainingMatcher, waitUntil, } from '../../utils.js';
3
3
  import { executeCommandWithStrategy } from '../../util/executeCommand.js';
4
4
  import { fillSingleExpectedForElementArray } from '../../util/elementsUtil.js';
5
5
  import { buildWdioAsymmetricMatchersWithOptions } from '../asymmetrics/asymmetricsUtils.js';
6
6
  async function compareElement(el, expectedText, options) {
7
7
  const actualText = await el.getText();
8
- return compareTextOrArray(actualText, expectedText, options);
8
+ return compareTextOrOneOf(actualText, expectedText, options);
9
9
  }
10
10
  export async function toHaveText(received, expectedValue, options = DEFAULT_OPTIONS) {
11
11
  const { expectation = 'text', verb = 'have', isNot, matcherName = 'toHaveText' } = this;
@@ -15,8 +15,7 @@ export async function toHaveText(received, expectedValue, options = DEFAULT_OPTI
15
15
  options,
16
16
  });
17
17
  expectedValue = buildWdioAsymmetricMatchersWithOptions(expectedValue, options);
18
- const isNewStrictCompare = getFeatureFlagValue(options, 'useToHaveTextStrictMultiElementsCompareStrategy');
19
- const { success: pass, actual: actualText, subject: subject, context: { isSome } = {} } = await waitUntil(async (iteration) => {
18
+ const { success: pass, actual: actualText, subject, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
20
19
  return await executeCommandWithStrategy({
21
20
  unresolvedElements: received,
22
21
  expectedValues: expectedValue,
@@ -25,15 +24,13 @@ export async function toHaveText(received, expectedValue, options = DEFAULT_OPTI
25
24
  return compareElement(element, values, options);
26
25
  },
27
26
  context: { isNot, iteration },
28
- strategy: isNewStrictCompare ? 'NewStrictMultipleElements' : 'LegacyLooseMultipleElements',
29
- strictConfiguration: { allowArrayWithSingleElement: true }
30
27
  });
31
28
  }, isNot, { wait: options.wait, interval: options.interval });
32
29
  if (isArrayContainingMatcher(expectedValue) && actualText === undefined) {
33
30
  throw new Error('toHaveText with arrayContaining requires an array of elements');
34
31
  }
35
- const expected = fillSingleExpectedForElementArray(subject, expectedValue);
36
- const message = enhanceError(subject, expected, actualText, { isNot, isSome }, verb, expectation, '', options);
32
+ const finalExpected = expected ?? fillSingleExpectedForElementArray(subject, expectedValue);
33
+ const message = enhanceError(subject, finalExpected, actualText, { isNot, isSome }, verb, expectation, '', options);
37
34
  const result = {
38
35
  pass,
39
36
  message: () => message
@@ -1,5 +1,5 @@
1
1
  import { toHaveElementProperty } from './toHaveElementProperty.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
3
  export function toHaveValue(el, value, options = DEFAULT_OPTIONS) {
4
- return toHaveElementProperty.call(this, el, 'value', value, options);
4
+ return toHaveElementProperty.call({ matcherName: 'toHaveValue', ...this, allowObjectExpectedValue: false }, el, 'value', value, options);
5
5
  }
@@ -1,7 +1,7 @@
1
1
  import { DEFAULT_OPTIONS } from '../../constants.js';
2
2
  import { wrapExpectedWithArray } from '../../util/elementsUtil.js';
3
3
  import { executeCommandWithStrategy } from '../../util/executeCommand.js';
4
- import { validateNumberArrayAndExtractOptions } from '../../util/numberOptionsUtil.js';
4
+ import { validateNumberMatcherArray } from '../../util/numberOptionsUtil.js';
5
5
  import { enhanceError, waitUntil, } from '../../utils.js';
6
6
  async function condition(el, expectedNumber) {
7
7
  const actualWidth = await el.getSize('width');
@@ -17,19 +17,18 @@ export async function toHaveWidth(received, expectedValue, options = DEFAULT_OPT
17
17
  expectedValue,
18
18
  options,
19
19
  });
20
- const { numberMatcher: expectedNumber, commandOptions } = validateNumberArrayAndExtractOptions(expectedValue, options);
21
- const { success: pass, actual: actualWidth, subject: elements, context: { isSome } = {} } = await waitUntil(async (iteration) => {
20
+ const expectedNumber = validateNumberMatcherArray(expectedValue);
21
+ const { success: pass, actual: actualWidth, subject: elements, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
22
22
  return await executeCommandWithStrategy({
23
23
  unresolvedElements: received,
24
24
  expectedValues: expectedNumber,
25
25
  singleElementCompare: (element, expectedNumber) => condition(element, expectedNumber),
26
26
  context: { isNot, iteration },
27
- strategy: 'NewStrictMultipleElements',
28
27
  strictConfiguration: { allowArrayWithSingleElement: false }
29
28
  });
30
- }, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
31
- const expectedValues = wrapExpectedWithArray(elements, actualWidth, expectedNumber);
32
- const message = enhanceError(elements, expectedValues, actualWidth, { isNot, isSome }, verb, expectation, '', commandOptions);
29
+ }, isNot, { wait: options.wait, interval: options.interval });
30
+ const expectedValues = expected ?? wrapExpectedWithArray(elements, actualWidth, expectedNumber);
31
+ const message = enhanceError(elements, expectedValues, actualWidth, { isNot, isSome }, verb, expectation, '', options);
33
32
  const result = {
34
33
  pass,
35
34
  message: () => message
@@ -1,8 +1,9 @@
1
1
  import { waitUntil, enhanceError, } from '../../utils.js';
2
- import { refetchElements, syncronizeElements } from '../../util/refetchElements.js';
2
+ import { refetchElements, synchronizeElementArray, syncronizeElements } from '../../util/refetchElements.js';
3
3
  import { DEFAULT_OPTIONS } from '../../constants.js';
4
- import { validateNumberAndExtractOptions } from '../../util/numberOptionsUtil.js';
5
- import { awaitElementArray, isStrictlyElementArray } from '../../util/elementsUtil.js';
4
+ import { validateNumberMatcher } from '../../util/numberOptionsUtil.js';
5
+ import { awaitElementArray, isMultiRemoteElementArray, isMultiRemoteElements, isMultiRemoteElementsLike, isStrictlyElementArray } from '../../util/elementsUtil.js';
6
+ import { getElementsPerInstance, getGlobalMultiRemoteInstanceNames, hasSameInstanceNames, isGlobalBrowserSingleRemote, isMultiRemoteMatcher } from '../../util/multiRemoteUtils.js';
6
7
  export async function toBeElementsArrayOfSize(received, expectedValue, options = DEFAULT_OPTIONS) {
7
8
  const { expectation = 'elements array of size', verb = 'be', isNot, matcherName = 'toBeElementsArrayOfSize' } = this;
8
9
  await options.beforeAssertion?.({
@@ -10,8 +11,17 @@ export async function toBeElementsArrayOfSize(received, expectedValue, options =
10
11
  expectedValue,
11
12
  options,
12
13
  });
13
- const { numberMatcher: expectedNumber, commandOptions } = validateNumberAndExtractOptions(expectedValue, options);
14
14
  let { elements, other } = await awaitElementArray(received);
15
+ const awaitedMultiRemote = other ?? elements;
16
+ const isEmptyWithPerInstanceSizes = Array.isArray(awaitedMultiRemote) && awaitedMultiRemote.length === 0
17
+ && !isStrictlyElementArray(awaitedMultiRemote) && !isGlobalBrowserSingleRemote()
18
+ && getPerInstanceSizes(expectedValue) !== undefined;
19
+ if (isMultiRemoteElementsLike(awaitedMultiRemote) || isEmptyWithPerInstanceSizes) {
20
+ const result = await multiRemoteElementsArrayOfSize(awaitedMultiRemote, expectedValue, options, { context: this, verb, expectation });
21
+ await options.afterAssertion?.({ matcherName, expectedValue, options, result });
22
+ return result;
23
+ }
24
+ const expectedNumber = validateNumberMatcher(expectedValue);
15
25
  const originalLength = elements ? elements.length : undefined;
16
26
  const { success: pass } = await waitUntil(async (iteration) => {
17
27
  if (!elements) {
@@ -25,12 +35,12 @@ export async function toBeElementsArrayOfSize(received, expectedValue, options =
25
35
  return { success: isPassing, subject: elements, actual: elements.length };
26
36
  }
27
37
  return { success: false, subject: elements, actual: elements.length };
28
- }, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
38
+ }, isNot, { wait: options.wait, interval: options.interval });
29
39
  if (pass && originalLength !== undefined && elements !== received && (isStrictlyElementArray(received) || received instanceof Promise) && isStrictlyElementArray(elements)) {
30
40
  await syncronizeElements(received, elements);
31
41
  }
32
42
  const actual = originalLength;
33
- const message = enhanceError(elements ?? other, expectedNumber, actual, this, verb, expectation, '', commandOptions);
43
+ const message = enhanceError(elements ?? other, expectedNumber, actual, this, verb, expectation, '', options);
34
44
  const result = {
35
45
  pass,
36
46
  message: () => message
@@ -43,3 +53,58 @@ export async function toBeElementsArrayOfSize(received, expectedValue, options =
43
53
  });
44
54
  return result;
45
55
  }
56
+ const multiRemoteElementsArrayOfSize = async (received, expectedValue, options, { context, verb, expectation }) => {
57
+ const { isNot } = context;
58
+ const perInstanceSizes = getPerInstanceSizes(expectedValue);
59
+ const instances = getMultiRemoteInstanceNames(received)
60
+ ?? getGlobalMultiRemoteInstanceNames()
61
+ ?? (perInstanceSizes ? Object.keys(perInstanceSizes) : []);
62
+ let expected;
63
+ let instanceMismatch = false;
64
+ if (perInstanceSizes) {
65
+ instanceMismatch = !hasSameInstanceNames(perInstanceSizes, instances);
66
+ expected = Object.fromEntries(Object.entries(perInstanceSizes).map(([name, value]) => [name, validateNumberMatcher(value)]));
67
+ }
68
+ else {
69
+ const numberMatcher = validateNumberMatcher(expectedValue);
70
+ expected = Object.fromEntries(instances.map((name) => [name, numberMatcher]));
71
+ }
72
+ let refetchSource = received;
73
+ let elements = received;
74
+ let actual = countElementsPerInstance(elements, instances);
75
+ const { success: pass } = await waitUntil(async (iteration) => {
76
+ if (iteration > 0) {
77
+ elements = await refetchElements(refetchSource);
78
+ if (elements.length > 0 || !isMultiRemoteElements(refetchSource)) {
79
+ refetchSource = elements;
80
+ }
81
+ actual = countElementsPerInstance(elements, instances);
82
+ }
83
+ if (instanceMismatch) {
84
+ return { success: !!isNot, subject: refetchSource, actual, abort: true };
85
+ }
86
+ const matches = (name) => expected[name].asymmetricMatch(actual[name]);
87
+ const success = isNot ? instances.some(matches) : instances.every(matches);
88
+ return { success, subject: refetchSource, actual };
89
+ }, isNot, { wait: options.wait, interval: options.interval });
90
+ if (pass && elements !== received) {
91
+ synchronizeElementArray(received, elements);
92
+ }
93
+ const message = enhanceError(refetchSource, expected, actual, { isNot }, verb, expectation, '', options);
94
+ return { pass, message: () => message };
95
+ };
96
+ const getPerInstanceSizes = (value) => {
97
+ return isMultiRemoteMatcher(value) ? value.sample : undefined;
98
+ };
99
+ const getMultiRemoteInstanceNames = (elements) => {
100
+ const first = elements[0];
101
+ if (first) {
102
+ return first.instances;
103
+ }
104
+ const parent = isMultiRemoteElementArray(elements) ? elements.parent : undefined;
105
+ return parent?.instances;
106
+ };
107
+ const countElementsPerInstance = (elements, instances) => {
108
+ const elementsPerInstance = getElementsPerInstance(elements, instances);
109
+ return Object.fromEntries(instances.map((name) => [name, elementsPerInstance[name].length]));
110
+ };
@@ -1,5 +1,5 @@
1
1
  import { toBeRequestedTimes } from './toBeRequestedTimes.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
3
  export function toBeRequested(received, options = DEFAULT_OPTIONS) {
4
- return toBeRequestedTimes.call({ ...(this || {}), expectation: 'called' }, received, { gte: 1 }, options);
4
+ return toBeRequestedTimes.call({ matcherName: 'toBeRequested', ...(this || {}), expectation: 'called' }, received, { gte: 1 }, options);
5
5
  }