expect-webdriverio 6.0.10 → 6.1.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.
package/lib/constants.js CHANGED
@@ -1,21 +1,31 @@
1
- export const DEFAULT_FEATURE_FLAGS = {
2
- useToHaveTextStrictMultiElementsCompareStrategy: false
3
- };
4
- export const DEFAULT_OPTIONS = {
5
- wait: 2000,
6
- interval: 100,
7
- beforeAssertion: async () => { },
8
- afterAssertion: async () => { },
9
- featureFlags: DEFAULT_FEATURE_FLAGS
10
- };
11
- export const DEFAULT_OPTIONS_TO_BE_DISPLAYED = {
12
- ...DEFAULT_OPTIONS,
13
- withinViewport: false,
14
- contentVisibilityAuto: true,
15
- opacityProperty: true,
16
- visibilityProperty: true
17
- };
18
- export const defaultOptionsList = [
19
- DEFAULT_OPTIONS,
20
- DEFAULT_OPTIONS_TO_BE_DISPLAYED
21
- ];
1
+ import { getGlobalSingleton } from './util/globalSingleton.js';
2
+ function createSharedConstants() {
3
+ const DEFAULT_FEATURE_FLAGS = {
4
+ useToHaveTextStrictMultiElementsCompareStrategy: false
5
+ };
6
+ const DEFAULT_OPTIONS = {
7
+ wait: 2000,
8
+ interval: 100,
9
+ beforeAssertion: async () => { },
10
+ afterAssertion: async () => { },
11
+ featureFlags: DEFAULT_FEATURE_FLAGS
12
+ };
13
+ const DEFAULT_OPTIONS_TO_BE_DISPLAYED = {
14
+ ...DEFAULT_OPTIONS,
15
+ withinViewport: false,
16
+ contentVisibilityAuto: true,
17
+ opacityProperty: true,
18
+ visibilityProperty: true
19
+ };
20
+ return {
21
+ DEFAULT_FEATURE_FLAGS,
22
+ DEFAULT_OPTIONS,
23
+ DEFAULT_OPTIONS_TO_BE_DISPLAYED,
24
+ defaultOptionsList: [DEFAULT_OPTIONS, DEFAULT_OPTIONS_TO_BE_DISPLAYED]
25
+ };
26
+ }
27
+ const shared = getGlobalSingleton('constants', createSharedConstants);
28
+ export const DEFAULT_FEATURE_FLAGS = shared.DEFAULT_FEATURE_FLAGS;
29
+ export const DEFAULT_OPTIONS = shared.DEFAULT_OPTIONS;
30
+ export const DEFAULT_OPTIONS_TO_BE_DISPLAYED = shared.DEFAULT_OPTIONS_TO_BE_DISPLAYED;
31
+ export const defaultOptionsList = shared.defaultOptionsList;
package/lib/index.js CHANGED
@@ -4,41 +4,52 @@ 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
- export const wdioCustomMatchers = {};
8
- export const matchers = new Map();
9
- const extend = expectLib.extend;
10
- expectLib.extend = (extendedMatchers) => {
11
- if (!extendedMatchers || typeof extendedMatchers !== 'object') {
12
- return;
13
- }
14
- Object.entries(extendedMatchers).forEach(([name, matcher]) => {
15
- wdioCustomMatchers[name] = matcher;
16
- matchers.set(name, matcher);
7
+ import { getGlobalSingleton } from './util/globalSingleton.js';
8
+ function createSharedExpectSetup() {
9
+ const wdioCustomMatchers = {};
10
+ const matchers = new Map();
11
+ const extend = expectLib.extend;
12
+ expectLib.extend = (extendedMatchers) => {
13
+ if (!extendedMatchers || typeof extendedMatchers !== 'object') {
14
+ return;
15
+ }
16
+ Object.entries(extendedMatchers).forEach(([name, matcher]) => {
17
+ wdioCustomMatchers[name] = matcher;
18
+ matchers.set(name, matcher);
19
+ });
20
+ return extend(extendedMatchers);
21
+ };
22
+ const filteredWdioMatchers = {};
23
+ Object.entries(wdioMatchers).forEach(([matcher, value]) => {
24
+ if (typeof value === 'function') {
25
+ filteredWdioMatchers[matcher] = value;
26
+ }
17
27
  });
18
- return extend(extendedMatchers);
19
- };
20
- const filteredWdioMatchers = {};
21
- Object.entries(wdioMatchers).forEach(([matcher, value]) => {
22
- if (typeof value === 'function') {
23
- filteredWdioMatchers[matcher] = value;
24
- }
25
- });
26
- const wdioExpect = expectLib;
27
- wdioExpect.extend(filteredWdioMatchers);
28
- wdioExpect.oneOf = oneOf;
29
- Object.defineProperty(wdioExpect, 'soft', {
30
- value: (actual) => createSoftExpect(actual)
31
- });
32
- Object.defineProperty(wdioExpect, 'getSoftFailures', {
33
- value: (testId) => SoftAssertService.getInstance().getFailures(testId)
34
- });
35
- Object.defineProperty(wdioExpect, 'assertSoftFailures', {
36
- value: (testId) => SoftAssertService.getInstance().assertNoFailures(testId)
37
- });
38
- Object.defineProperty(wdioExpect, 'clearSoftFailures', {
39
- value: (testId) => SoftAssertService.getInstance().clearFailures(testId)
40
- });
41
- export const expect = wdioExpect;
28
+ const wdioExpect = expectLib;
29
+ wdioExpect.extend(filteredWdioMatchers);
30
+ wdioExpect.oneOf = oneOf;
31
+ Object.defineProperty(wdioExpect, 'soft', {
32
+ configurable: true,
33
+ value: (actual) => createSoftExpect(actual)
34
+ });
35
+ Object.defineProperty(wdioExpect, 'getSoftFailures', {
36
+ configurable: true,
37
+ value: (testId) => SoftAssertService.getInstance().getFailures(testId)
38
+ });
39
+ Object.defineProperty(wdioExpect, 'assertSoftFailures', {
40
+ configurable: true,
41
+ value: (testId) => SoftAssertService.getInstance().assertNoFailures(testId)
42
+ });
43
+ Object.defineProperty(wdioExpect, 'clearSoftFailures', {
44
+ configurable: true,
45
+ value: (testId) => SoftAssertService.getInstance().clearFailures(testId)
46
+ });
47
+ return { wdioExpect, wdioCustomMatchers, matchers };
48
+ }
49
+ const sharedExpectSetup = getGlobalSingleton('expect', createSharedExpectSetup);
50
+ export const wdioCustomMatchers = sharedExpectSetup.wdioCustomMatchers;
51
+ export const matchers = sharedExpectSetup.matchers;
52
+ export const expect = sharedExpectSetup.wdioExpect;
42
53
  export const getDefaultOptions = () => DEFAULT_OPTIONS;
43
54
  export const setDefaultOptions = (options) => {
44
55
  Object.entries(options).forEach(([key, value]) => {
@@ -11,7 +11,10 @@ function asymmetricMatch(a, b) {
11
11
  if (asymmetricA && asymmetricB) {
12
12
  return undefined;
13
13
  }
14
- const matchersUtil = { equals, contains: equals };
14
+ const matchersUtil = {
15
+ equals,
16
+ contains: (actual, expected) => actual.some(value => equals(value, expected)),
17
+ };
15
18
  if (asymmetricA) {
16
19
  return a.asymmetricMatch(b, matchersUtil);
17
20
  }
@@ -1,6 +1,6 @@
1
1
  import { waitUntil, enhanceError, compareText } from '../../utils.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
- import { expect } from '../../index.js';
3
+ import { expect } from 'expect';
4
4
  export async function toHaveLocalStorageItem(browser, key, expectedValue, options = DEFAULT_OPTIONS) {
5
5
  const { expectation = 'localStorage item', verb = 'have', isNot, matcherName = 'toHaveLocalStorageItem' } = this;
6
6
  await options.beforeAssertion?.({
@@ -1,7 +1,7 @@
1
1
  import { DEFAULT_OPTIONS } from '../../constants.js';
2
2
  import { executeCommandWithStrategy } from '../../util/executeCommand.js';
3
3
  import { compareText, enhanceError, isAsymmetricMatcher, waitUntil, wrapExpectedWithArray } from '../../utils.js';
4
- import { expect as wdioExpect } from '../../index.js';
4
+ import { expect } from 'expect';
5
5
  import { buildWdioAsymmetricMatchersWithOptions } from '../asymmetrics/asymmetricsUtils.js';
6
6
  import { isOneOfMatcher } from '../asymmetrics/oneOf.js';
7
7
  async function conditionAttributeValueMatchWithExpected(el, attribute, expectedValue, options) {
@@ -23,6 +23,7 @@ export async function toHaveAttributeAndValue(received, attribute, expectedValue
23
23
  const { success: pass, actual: attr, subject: el, context: { isSome } = {} } = await waitUntil(async (iteration) => {
24
24
  return await executeCommandWithStrategy({
25
25
  unresolvedElements: received,
26
+ supportsArrayContaining: true,
26
27
  expectedValues: expectedValue,
27
28
  singleElementCompare: (element, values) => {
28
29
  return conditionAttributeValueMatchWithExpected(element, attribute, values, options);
@@ -51,7 +52,7 @@ export async function toHaveAttribute(received, attribute, value, options = DEFA
51
52
  if (paramsCount > 2) {
52
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).');
53
54
  }
54
- expectedValue = wdioExpect.anything();
55
+ expectedValue = expect.anything();
55
56
  }
56
57
  else {
57
58
  expectedValue = value;
@@ -17,6 +17,7 @@ export async function toHaveComputedLabel(received, expectedValue, options = DEF
17
17
  const { success: pass, actual: actualLabel, subject: el, context: { isSome } = {} } = await waitUntil(async (iteration) => {
18
18
  return await executeCommandWithStrategy({
19
19
  unresolvedElements: received,
20
+ supportsArrayContaining: true,
20
21
  expectedValues: expectedValue,
21
22
  singleElementCompare: (element, expectedValue) => singleElementCompare(element, expectedValue, options),
22
23
  context: { isNot, iteration },
@@ -17,6 +17,7 @@ export async function toHaveComputedRole(received, expectedValue, options = DEFA
17
17
  const { success: pass, actual: actualRole, subject: el, context: { isSome } = {} } = await waitUntil(async (iteration) => {
18
18
  return await executeCommandWithStrategy({
19
19
  unresolvedElements: received,
20
+ supportsArrayContaining: true,
20
21
  expectedValues: expectedValue,
21
22
  singleElementCompare: (element, expectedValue) => singleElementCompare(element, expectedValue, options),
22
23
  context: { isNot, iteration },
@@ -35,6 +35,7 @@ export async function toHaveElementClass(received, expectedValue, options = DEFA
35
35
  const { success: pass, actual: attr, subject: el, context: { isSome } = {} } = await waitUntil(async (iteration) => {
36
36
  return await executeCommandWithStrategy({
37
37
  unresolvedElements: received,
38
+ supportsArrayContaining: true,
38
39
  expectedValues: expectedValue,
39
40
  singleElementCompare: (element, expectedValue) => singleElementCompare(element, attribute, expectedValue, options),
40
41
  context: { isNot, iteration },
@@ -1,6 +1,7 @@
1
+ import { equals } from '../../jasmineUtils.js';
1
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
2
3
  import { executeCommandWithStrategy } from '../../util/executeCommand.js';
3
- import { expect as wdioExpect } from '../../index.js';
4
+ import { expect } from 'expect';
4
5
  import { compareText, enhanceError, isAsymmetricMatcher, waitUntil, wrapExpectedWithArray } from '../../utils.js';
5
6
  import { buildWdioAsymmetricMatchersWithOptions } from '../asymmetrics/asymmetricsUtils.js';
6
7
  import { isOneOfMatcher } from '../asymmetrics/oneOf.js';
@@ -9,7 +10,7 @@ async function condition(el, property, expectedValue, options = DEFAULT_OPTIONS)
9
10
  const propertyValue = await el.getProperty(property);
10
11
  if (propertyValue === null || propertyValue === undefined || (!(expectedValue instanceof RegExp) && typeof propertyValue !== 'string' && !asString)) {
11
12
  if (isAsymmetricMatcher(expectedValue)) {
12
- return { success: expectedValue.asymmetricMatch(propertyValue), actual: propertyValue };
13
+ return { success: equals(propertyValue, expectedValue), actual: propertyValue };
13
14
  }
14
15
  return { success: propertyValue === expectedValue, actual: propertyValue };
15
16
  }
@@ -25,7 +26,7 @@ export async function toHaveElementProperty(received, property, value, options =
25
26
  if (paramsCount > 2) {
26
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).');
27
28
  }
28
- value = wdioExpect.anything();
29
+ value = expect.anything();
29
30
  }
30
31
  await options.beforeAssertion?.({
31
32
  matcherName,
@@ -36,6 +37,7 @@ export async function toHaveElementProperty(received, property, value, options =
36
37
  const { success: pass, actual: actualProppertyValue, subject: elements, context: { isSome } = {} } = await waitUntil(async (iteration) => {
37
38
  return await executeCommandWithStrategy({
38
39
  unresolvedElements: received,
40
+ supportsArrayContaining: true,
39
41
  expectedValues: value,
40
42
  singleElementCompare: (element, expectedValue) => {
41
43
  return condition(element, property, expectedValue, options);
@@ -17,6 +17,7 @@ export async function toHaveHTML(received, expectedValue, options = DEFAULT_OPTI
17
17
  const { success: pass, actual: actualHTML, subject: elements, context: { isSome } = {} } = await waitUntil(async (iteration) => {
18
18
  const result = await executeCommandWithStrategy({
19
19
  unresolvedElements: received,
20
+ supportsArrayContaining: true,
20
21
  expectedValues: expectedValue,
21
22
  singleElementCompare: (element, expectedValue) => singleElementCompare(element, expectedValue, options),
22
23
  context: { isNot, iteration },
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_OPTIONS } from '../../constants.js';
2
- import { compareTextOrArray, enhanceError, getFeatureFlagValue, waitUntil, } from '../../utils.js';
2
+ import { compareTextOrArray, enhanceError, getFeatureFlagValue, 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';
@@ -20,6 +20,7 @@ export async function toHaveText(received, expectedValue, options = DEFAULT_OPTI
20
20
  return await executeCommandWithStrategy({
21
21
  unresolvedElements: received,
22
22
  expectedValues: expectedValue,
23
+ supportsArrayContaining: 'arrayOnly',
23
24
  singleElementCompare: (element, values) => {
24
25
  return compareElement(element, values, options);
25
26
  },
@@ -28,6 +29,9 @@ export async function toHaveText(received, expectedValue, options = DEFAULT_OPTI
28
29
  strictConfiguration: { allowArrayWithSingleElement: true }
29
30
  });
30
31
  }, isNot, { wait: options.wait, interval: options.interval });
32
+ if (isArrayContainingMatcher(expectedValue) && actualText === undefined) {
33
+ throw new Error('toHaveText with arrayContaining requires an array of elements');
34
+ }
31
35
  const expected = fillSingleExpectedForElementArray(subject, expectedValue);
32
36
  const message = enhanceError(subject, expected, actualText, { isNot, isSome }, verb, expectation, '', options);
33
37
  const result = {
package/lib/snapshot.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { expect } from '@wdio/globals';
2
2
  import { SnapshotClient } from '@vitest/snapshot';
3
3
  import { NodeSnapshotEnvironment } from '@vitest/snapshot/environment';
4
- let service;
4
+ import { getGlobalSingleton } from './util/globalSingleton.js';
5
5
  class WebdriverIOSnapshotEnvironment extends NodeSnapshotEnvironment {
6
6
  #resolveSnapshotPath;
7
7
  constructor(resolveSnapshotPath) {
@@ -84,9 +84,6 @@ export class SnapshotService {
84
84
  }
85
85
  }
86
86
  static initiate(options) {
87
- if (!service) {
88
- service = new SnapshotService(options);
89
- }
90
- return service;
87
+ return getGlobalSingleton('snapshotService', () => new SnapshotService(options));
91
88
  }
92
89
  }
package/lib/softAssert.js CHANGED
@@ -1,14 +1,11 @@
1
+ import { getGlobalSingleton } from './util/globalSingleton.js';
1
2
  export class SoftAssertService {
2
- static instance;
3
3
  static GLOBAL_TEST_ID = '__global_soft_assert_context__';
4
4
  failureMap = new Map();
5
5
  currentTest = null;
6
6
  constructor() { }
7
7
  static getInstance() {
8
- if (!SoftAssertService.instance) {
9
- SoftAssertService.instance = new SoftAssertService();
10
- }
11
- return SoftAssertService.instance;
8
+ return getGlobalSingleton('softAssertService', () => new SoftAssertService());
12
9
  }
13
10
  setCurrentTest(testId, testName, testFile) {
14
11
  this.currentTest = { id: testId, name: testName, file: testFile };
@@ -1 +1 @@
1
- {"root":["../src/constants.ts","../src/index.ts","../src/jasmineUtils.ts","../src/matchers.ts","../src/snapshot.ts","../src/softAssert.ts","../src/softAssertService.ts","../src/softExpect.ts","../src/types.ts","../src/utils.ts","../src/api/index.ts","../src/matchers/snapshot.ts","../src/matchers/asymmetrics/asymmetricsUtils.ts","../src/matchers/asymmetrics/oneOf.ts","../src/matchers/browser/toHaveClipboardText.ts","../src/matchers/browser/toHaveLocalStorageItem.ts","../src/matchers/browser/toHaveTitle.ts","../src/matchers/browser/toHaveUrl.ts","../src/matchers/element/toBeClickable.ts","../src/matchers/element/toBeDisabled.ts","../src/matchers/element/toBeDisplayed.ts","../src/matchers/element/toBeDisplayedInViewport.ts","../src/matchers/element/toBeEnabled.ts","../src/matchers/element/toBeExisting.ts","../src/matchers/element/toBeFocused.ts","../src/matchers/element/toBeSelected.ts","../src/matchers/element/toHaveAttribute.ts","../src/matchers/element/toHaveChildren.ts","../src/matchers/element/toHaveComputedLabel.ts","../src/matchers/element/toHaveComputedRole.ts","../src/matchers/element/toHaveElementClass.ts","../src/matchers/element/toHaveElementProperty.ts","../src/matchers/element/toHaveHTML.ts","../src/matchers/element/toHaveHeight.ts","../src/matchers/element/toHaveHref.ts","../src/matchers/element/toHaveId.ts","../src/matchers/element/toHaveSize.ts","../src/matchers/element/toHaveStyle.ts","../src/matchers/element/toHaveText.ts","../src/matchers/element/toHaveValue.ts","../src/matchers/element/toHaveWidth.ts","../src/matchers/elements/toBeElementsArrayOfSize.ts","../src/matchers/mock/toBeRequested.ts","../src/matchers/mock/toBeRequestedTimes.ts","../src/matchers/mock/toBeRequestedWith.ts","../src/matchers/modifiers/some.ts","../src/util/commandOptionsUtils.ts","../src/util/elementsUtil.ts","../src/util/executeCommand.ts","../src/util/formatMessage.ts","../src/util/numberOptionsUtil.ts","../src/util/refetchElements.ts","../src/util/stringUtil.ts","../src/util/waitUntil.ts"],"version":"5.9.3"}
1
+ {"root":["../src/constants.ts","../src/index.ts","../src/jasmineUtils.ts","../src/matchers.ts","../src/snapshot.ts","../src/softAssert.ts","../src/softAssertService.ts","../src/softExpect.ts","../src/types.ts","../src/utils.ts","../src/api/index.ts","../src/matchers/snapshot.ts","../src/matchers/asymmetrics/asymmetricsUtils.ts","../src/matchers/asymmetrics/oneOf.ts","../src/matchers/browser/toHaveClipboardText.ts","../src/matchers/browser/toHaveLocalStorageItem.ts","../src/matchers/browser/toHaveTitle.ts","../src/matchers/browser/toHaveUrl.ts","../src/matchers/element/toBeClickable.ts","../src/matchers/element/toBeDisabled.ts","../src/matchers/element/toBeDisplayed.ts","../src/matchers/element/toBeDisplayedInViewport.ts","../src/matchers/element/toBeEnabled.ts","../src/matchers/element/toBeExisting.ts","../src/matchers/element/toBeFocused.ts","../src/matchers/element/toBeSelected.ts","../src/matchers/element/toHaveAttribute.ts","../src/matchers/element/toHaveChildren.ts","../src/matchers/element/toHaveComputedLabel.ts","../src/matchers/element/toHaveComputedRole.ts","../src/matchers/element/toHaveElementClass.ts","../src/matchers/element/toHaveElementProperty.ts","../src/matchers/element/toHaveHTML.ts","../src/matchers/element/toHaveHeight.ts","../src/matchers/element/toHaveHref.ts","../src/matchers/element/toHaveId.ts","../src/matchers/element/toHaveSize.ts","../src/matchers/element/toHaveStyle.ts","../src/matchers/element/toHaveText.ts","../src/matchers/element/toHaveValue.ts","../src/matchers/element/toHaveWidth.ts","../src/matchers/elements/toBeElementsArrayOfSize.ts","../src/matchers/mock/toBeRequested.ts","../src/matchers/mock/toBeRequestedTimes.ts","../src/matchers/mock/toBeRequestedWith.ts","../src/matchers/modifiers/some.ts","../src/util/commandOptionsUtils.ts","../src/util/elementsUtil.ts","../src/util/executeCommand.ts","../src/util/formatMessage.ts","../src/util/globalSingleton.ts","../src/util/numberOptionsUtil.ts","../src/util/refetchElements.ts","../src/util/stringUtil.ts","../src/util/waitUntil.ts"],"version":"5.9.3"}
@@ -1,11 +1,12 @@
1
+ import { isArrayContainingMatcher } from '../utils.js';
1
2
  export const wrapExpectedWithArray = (elements, actual, expected) => {
2
- if (Array.isArray(elements) && Array.isArray(actual) && !Array.isArray(expected)) {
3
+ if (Array.isArray(elements) && Array.isArray(actual) && !Array.isArray(expected) && !isArrayContainingMatcher(expected)) {
3
4
  expected = Array(actual.length).fill(expected);
4
5
  }
5
6
  return expected;
6
7
  };
7
8
  export const fillSingleExpectedForElementArray = (subject, value) => {
8
- if (isElementArrayLike(subject) && !Array.isArray(value)) {
9
+ if (isElementArrayLike(subject) && !Array.isArray(value) && !isArrayContainingMatcher(value)) {
9
10
  const fillerlength = subject.length > 0 ? subject.length : 1;
10
11
  return Array(fillerlength).fill(value);
11
12
  }
@@ -1,15 +1,44 @@
1
+ import { equals } from '../jasmineUtils.js';
2
+ import { isArrayContainingMatcher } from '../utils.js';
1
3
  import { isSomeWrapper } from '../matchers/modifiers/some.js';
2
4
  import { awaitElementOrArray, isElement, isStrictlyElementArray } from './elementsUtil.js';
3
5
  import { refreshElementArray } from './refetchElements.js';
4
- export async function executeCommandWithStrategy({ unresolvedElements, expectedValues, singleElementCompare, context: { isNot, iteration }, strategy = 'NewStrictMultipleElements', strictConfiguration = { allowEmptyElements: false, allowArrayWithSingleElement: false } }) {
6
+ export async function executeCommandWithStrategy({ unresolvedElements, expectedValues, singleElementCompare, context: { isNot, iteration }, strategy = 'NewStrictMultipleElements', supportsArrayContaining = false, strictConfiguration = { allowEmptyElements: false, allowArrayWithSingleElement: false } }) {
5
7
  const isSome = isSomeWrapper(unresolvedElements);
8
+ const actualReceived = isSome ? unresolvedElements.elements : unresolvedElements;
9
+ if (supportsArrayContaining && !isSome && isArrayContainingMatcher(expectedValues)) {
10
+ const { selector, elements, other } = await awaitElementOrArray(unresolvedElements);
11
+ if (elements) {
12
+ if (iteration > 0 && isStrictlyElementArray(elements)) {
13
+ await refreshElementArray(elements);
14
+ }
15
+ const settled = await Promise.allSettled(Array.from(elements).map(async (element, index) => {
16
+ return singleElementCompare(element, undefined, index);
17
+ }));
18
+ const actual = settled.map((result) => {
19
+ if (result.status === 'rejected') {
20
+ throw result.reason;
21
+ }
22
+ return result.value.actual;
23
+ });
24
+ return {
25
+ subject: elements,
26
+ actual,
27
+ success: equals(actual, expectedValues),
28
+ abort: elements.length === 0 && !isStrictlyElementArray(elements),
29
+ };
30
+ }
31
+ if (!isElement(selector) || supportsArrayContaining === 'arrayOnly') {
32
+ return { subject: selector ?? other, actual: undefined, success: !!isNot, abort: true };
33
+ }
34
+ return { subject: selector, ...await singleElementCompare(selector, expectedValues) };
35
+ }
6
36
  if (strategy === 'LegacyLooseMultipleElements') {
7
37
  if (isSome) {
8
38
  throw new Error('some(elements) works only when enabling `useToHaveTextStrictMultiElementsCompareStrategy`');
9
39
  }
10
- return legacyMultipleElementResultsStrategy(unresolvedElements, expectedValues, singleElementCompare, isNot);
40
+ return legacyMultipleElementResultsStrategy(actualReceived, expectedValues, singleElementCompare, isNot);
11
41
  }
12
- const actualReceived = isSome ? unresolvedElements.elements : unresolvedElements;
13
42
  return multipleElementResultsStrategy(actualReceived, expectedValues, singleElementCompare, { isNot, isSome, iteration }, strictConfiguration);
14
43
  }
15
44
  export const legacyMultipleElementResultsStrategy = async (unresolvedElements, expectedValues, singleElementCompare, _isNot) => {
@@ -56,10 +56,10 @@ export const enhanceError = (subject, expected, actual, context, verb, expectati
56
56
  };
57
57
  let diffString = '';
58
58
  if (isJasmineStringAsymmetricMatcher(expected)) {
59
- expected = expected.jasmineToString();
59
+ expected = expected.jasmineToString(stringify);
60
60
  }
61
61
  else if (isElementOrArrayLike(subject) && Array.isArray(expected)) {
62
- expected = expected.map(item => isJasmineStringAsymmetricMatcher(item) ? item.jasmineToString() : item);
62
+ expected = expected.map(item => isJasmineStringAsymmetricMatcher(item) ? item.jasmineToString(stringify) : item);
63
63
  }
64
64
  if (isNotInLabel && isElementOrArrayLike(subject) && Array.isArray(expected) && Array.isArray(actual) && expected.length === actual.length) {
65
65
  const { expectedFormatted, receivedFormatted } = printArrayWithMatchingItemInRed(expected, actual);
@@ -0,0 +1,8 @@
1
+ import { createRequire } from 'node:module';
2
+ const { version } = createRequire(import.meta.url)('../../package.json');
3
+ const packageMajorVersion = version.split('.')[0];
4
+ export function getGlobalSingleton(key, create) {
5
+ const globalKey = Symbol.for(`expect-webdriverio.${key}@${packageMajorVersion}`);
6
+ const globalScope = globalThis;
7
+ return (globalScope[globalKey] ??= create());
8
+ }
package/lib/utils.js CHANGED
@@ -15,6 +15,19 @@ export function isAsymmetricMatcher(expected) {
15
15
  'asymmetricMatch' in expected &&
16
16
  !!expected.asymmetricMatch);
17
17
  }
18
+ export function isArrayContainingMatcher(expected) {
19
+ if (!isAsymmetricMatcher(expected) || typeof expected.asymmetricMatch !== 'function') {
20
+ return false;
21
+ }
22
+ if (typeof expected.toString === 'function' && /^Array(Not)?Containing$/.test(expected.toString())) {
23
+ return true;
24
+ }
25
+ if ('jasmineToString' in expected && typeof expected.jasmineToString === 'function') {
26
+ const description = expected.jasmineToString(() => '');
27
+ return description === '<jasmine.arrayContaining()>';
28
+ }
29
+ return false;
30
+ }
18
31
  export function isStringContainingMatcherLike(expected) {
19
32
  return !!expected && expected.constructor.name === 'StringContaining';
20
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expect-webdriverio",
3
- "version": "6.0.10",
3
+ "version": "6.1.0",
4
4
  "author": "Mykola Grybyk <mykola.grybyk@gmail.com>",
5
5
  "description": "WebdriverIO Assertion Library",
6
6
  "license": "MIT",
@@ -86,7 +86,7 @@
86
86
  "playgrounds:snapshots:update": "cd playgrounds && npm run snapshots:update"
87
87
  },
88
88
  "dependencies": {
89
- "@vitest/snapshot": "^4.1.10",
89
+ "@vitest/snapshot": "^4.1.11",
90
90
  "deep-eql": "^5.0.2",
91
91
  "expect": "^30.4.1",
92
92
  "jest-matcher-utils": "^30.4.1"
@@ -102,7 +102,7 @@
102
102
  "@vitest/coverage-v8": "^4.1.11",
103
103
  "@vitest/eslint-plugin": "^1.6.27",
104
104
  "@wdio/eslint": "^0.1.3",
105
- "@wdio/types": "^9.31.2",
105
+ "@wdio/types": "^9.32.0",
106
106
  "eslint": "^9.39.5",
107
107
  "husky": "^9.1.7",
108
108
  "npm-run-all2": "^9.0.3",
@@ -111,7 +111,7 @@
111
111
  "strip-ansi": "^7.2.0",
112
112
  "typescript": "^5.9.3",
113
113
  "vitest": "^4.1.11",
114
- "webdriverio": "^9.31.5"
114
+ "webdriverio": "^9.32.0"
115
115
  },
116
116
  "peerDependencies": {
117
117
  "@wdio/globals": "^9.0.0",
@@ -550,6 +550,7 @@ interface WdioElementOrArrayMatchers<_R, ActualT = unknown> {
550
550
  * // Check if an element array contains the specified text
551
551
  * const elem = await $$('ul > li')
552
552
  * await expect(elem).toHaveText(['Coffee', 'Tea', 'Milk'])
553
+ * await expect(elem).toHaveText(expect.arrayContaining(['Tea', 'Coffee']))
553
554
  * ```
554
555
  */
555
556
  toHaveText: FnWhenElementOrArrayLike<ActualT, {
@@ -853,7 +854,7 @@ type WdioOneOfAsymmetricMatcher<R> = ExpectWebdriverIO.PartialMatcher<R> & {
853
854
  }
854
855
 
855
856
  type JasmineBaseAsymmetricMatcher = {
856
- jasmineToString(): string;
857
+ jasmineToString(pp?: (value: unknown) => string): string;
857
858
  asymmetricMatch(other: unknown): boolean;
858
859
  }
859
860