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
package/README.md CHANGED
@@ -1,13 +1,18 @@
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
+ > [!IMPORTANT]
4
+ > This branch is v8, in development for WebdriverIO v10. Its docs are not released yet.
5
+ > For the latest release (v7, WebdriverIO v9), see the [v7 README](https://github.com/webdriverio/expect-webdriverio/blob/v7/README.md).
6
+
7
+ ###### [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
8
 
5
9
  > [WebdriverIO](https://webdriver.io/) assertion library inspired by [expect](https://www.npmjs.com/package/expect)
6
10
 
7
11
  ## Key Features
8
12
 
9
- - [Waits](#default-options) for expectations to succeed
10
- - Supports single element `$()` & multiple elements `$$()`
13
+ - [Waits](docs/API.md#default-options) for expectations to succeed
14
+ - Supports single element `$()` & [multiple elements](docs/MultipleElements.md) `$$()`
15
+ - Supports [multi-remote](docs/MultiRemote.md) browsers & elements, with one expected value per browser instance
11
16
  - Detailed [error messages](#error-messages)
12
17
  - Works in Mocha, Cucumber, Jest, and Jasmine
13
18
  - Built-in [types](docs/Types.md) for TypeScript and JS autocompletion
@@ -21,7 +26,7 @@
21
26
  - **Cucumber**: `npm install @wdio/cucumber-framework`
22
27
  - **Jest**: No adapter needed — see [Jest Framework section](docs/Framework.md#jest)
23
28
 
24
- **Note:** [WebdriverIO](https://github.com/webdriverio/webdriverio) `v9.0.0` or higher is required!
29
+ **Note:** [WebdriverIO](https://github.com/webdriverio/webdriverio) `v9.31.5` or higher is required!
25
30
 
26
31
  ## Usage
27
32
 
@@ -30,12 +35,23 @@
30
35
  If you run your tests through the [WDIO testrunner](https://webdriver.io/docs/clioptions), no additional setup is needed. WebdriverIO initializes `expect-webdriverio` and makes `expect` available in the global scope so you can use it directly in your tests:
31
36
 
32
37
  ```js
33
- await expect($('button')).toBeDisplayed()
38
+ await expect($('#submit')).toBeDisplayed()
34
39
  await expect($$('buttons')).toBeDisplayed()
35
40
  ```
36
41
 
37
42
  See more [Examples](docs/Examples.md).
38
43
 
44
+ #### Multi-remote
45
+
46
+ With [multi-remote](https://webdriver.io/docs/multiremote), assertions check every browser instance, with a single expected value or one per instance:
47
+
48
+ ```js
49
+ await expect(multiRemoteBrowser).toHaveTitle('WebdriverIO')
50
+ await expect(multiRemoteBrowser.$('h1')).toHaveText(expect.multiRemote({ chrome: 'Welcome', firefox: 'Bienvenue' }))
51
+ ```
52
+
53
+ Some WebdriverIO environment variables are recommended, see [Multi-remote Support](docs/MultiRemote.md#requirements--configuration).
54
+
39
55
  #### Local & Browser Runners
40
56
  - **Local Runner**: Fully compatible. You can leverage your test framework adapter as mentioned above.
41
57
  - **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).
@@ -57,7 +73,7 @@ import { expect } from 'expect-webdriverio'
57
73
 
58
74
  await browser.url('https://webdriver.io')
59
75
 
60
- const $button = await browser.$('button')
76
+ const $button = await browser.$('.DocSearch-Button')
61
77
  await expect($button).toBeDisplayed()
62
78
 
63
79
  await browser.deleteSession()
@@ -86,4 +102,3 @@ First of all, **feel free to raise an issue with your suggestions or help with P
86
102
  ### Planned
87
103
 
88
104
  - Cookie matchers
89
- - Multiremote support (in progress)
@@ -80,7 +80,7 @@ declare namespace ExpectWebdriverIO {
80
80
  * Under `@wdio/jasmine-framework`, the global `expect` is overridden to use Jasmine's `expectAsync`.
81
81
  * It contains custom WebdriverIO matchers as well as Jasmine's built-in async & sync matchers but not the basic Jest's expect library matchers.
82
82
  */
83
- // @ts-expect-error: IDE might flag this, but ignore it. This way the `tsc:root-types` can pass!
83
+ // @ts-expect-error: the Jasmine `expect` is always loaded with this entry, so the two declarations conflict
84
84
  declare const expect: ExpectWebdriverIO.JasmineExpect
85
85
  declare namespace NodeJS {
86
86
  interface Global {
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/constants.js CHANGED
@@ -1,14 +1,10 @@
1
1
  import { getGlobalSingleton } from './util/globalSingleton.js';
2
2
  function createSharedConstants() {
3
- const DEFAULT_FEATURE_FLAGS = {
4
- useToHaveTextStrictMultiElementsCompareStrategy: false
5
- };
6
3
  const DEFAULT_OPTIONS = {
7
4
  wait: 2000,
8
5
  interval: 100,
9
6
  beforeAssertion: async () => { },
10
7
  afterAssertion: async () => { },
11
- featureFlags: DEFAULT_FEATURE_FLAGS
12
8
  };
13
9
  const DEFAULT_OPTIONS_TO_BE_DISPLAYED = {
14
10
  ...DEFAULT_OPTIONS,
@@ -18,14 +14,12 @@ function createSharedConstants() {
18
14
  visibilityProperty: true
19
15
  };
20
16
  return {
21
- DEFAULT_FEATURE_FLAGS,
22
17
  DEFAULT_OPTIONS,
23
18
  DEFAULT_OPTIONS_TO_BE_DISPLAYED,
24
19
  defaultOptionsList: [DEFAULT_OPTIONS, DEFAULT_OPTIONS_TO_BE_DISPLAYED]
25
20
  };
26
21
  }
27
22
  const shared = getGlobalSingleton('constants', createSharedConstants);
28
- export const DEFAULT_FEATURE_FLAGS = shared.DEFAULT_FEATURE_FLAGS;
29
23
  export const DEFAULT_OPTIONS = shared.DEFAULT_OPTIONS;
30
24
  export const DEFAULT_OPTIONS_TO_BE_DISPLAYED = shared.DEFAULT_OPTIONS_TO_BE_DISPLAYED;
31
25
  export const defaultOptionsList = shared.defaultOptionsList;
package/lib/index.js CHANGED
@@ -4,10 +4,10 @@ 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 = {};
10
- const matchers = new Map();
11
11
  const extend = expectLib.extend;
12
12
  expectLib.extend = (extendedMatchers) => {
13
13
  if (!extendedMatchers || typeof extendedMatchers !== 'object') {
@@ -15,7 +15,6 @@ function createSharedExpectSetup() {
15
15
  }
16
16
  Object.entries(extendedMatchers).forEach(([name, matcher]) => {
17
17
  wdioCustomMatchers[name] = matcher;
18
- matchers.set(name, matcher);
19
18
  });
20
19
  return extend(extendedMatchers);
21
20
  };
@@ -28,6 +27,7 @@ function createSharedExpectSetup() {
28
27
  const wdioExpect = expectLib;
29
28
  wdioExpect.extend(filteredWdioMatchers);
30
29
  wdioExpect.oneOf = oneOf;
30
+ wdioExpect.multiRemote = multiRemote;
31
31
  Object.defineProperty(wdioExpect, 'soft', {
32
32
  configurable: true,
33
33
  value: (actual) => createSoftExpect(actual)
@@ -44,11 +44,10 @@ function createSharedExpectSetup() {
44
44
  configurable: true,
45
45
  value: (testId) => SoftAssertService.getInstance().clearFailures(testId)
46
46
  });
47
- return { wdioExpect, wdioCustomMatchers, matchers };
47
+ return { wdioExpect, wdioCustomMatchers };
48
48
  }
49
49
  const sharedExpectSetup = getGlobalSingleton('expect', createSharedExpectSetup);
50
50
  export const wdioCustomMatchers = sharedExpectSetup.wdioCustomMatchers;
51
- export const matchers = sharedExpectSetup.matchers;
52
51
  export const expect = sharedExpectSetup.wdioExpect;
53
52
  export const getDefaultOptions = () => DEFAULT_OPTIONS;
54
53
  export const setDefaultOptions = (options) => {
@@ -60,15 +59,6 @@ export const setDefaultOptions = (options) => {
60
59
  });
61
60
  });
62
61
  };
63
- export const setFeatureFlags = (featureFlags) => {
64
- Object.entries(featureFlags).forEach(([ffName, ffValue]) => {
65
- defaultOptionsList.forEach((option) => {
66
- option.featureFlags[ffName] = ffValue;
67
- });
68
- });
69
- };
70
- export const setOptions = setDefaultOptions;
71
- export const getConfig = getDefaultOptions;
72
62
  export { SnapshotService } from './snapshot.js';
73
63
  export { SoftAssertService } from './softAssert.js';
74
64
  export { SoftAssertionService } from './softAssertService.js';
@@ -11,10 +11,10 @@ function asymmetricMatch(a, b) {
11
11
  if (asymmetricA && asymmetricB) {
12
12
  return undefined;
13
13
  }
14
- const matchersUtil = {
14
+ const matchersUtil = Object.assign([], {
15
15
  equals,
16
16
  contains: (actual, expected) => actual.some(value => equals(value, expected)),
17
- };
17
+ });
18
18
  if (asymmetricA) {
19
19
  return a.asymmetricMatch(b, matchersUtil);
20
20
  }
@@ -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,4 +1,4 @@
1
- import { compareTextWithArray } from '../../utils.js';
1
+ import { compareText } from '../../utils.js';
2
2
  import { WdioAsymmetricMatchers } from './asymmetricsUtils.js';
3
3
  const ONE_OF_TAG = 'expect-webdriverio.oneOf';
4
4
  const ONE_OF_SYMBOL = Symbol.for(ONE_OF_TAG);
@@ -19,7 +19,7 @@ export class OneOfMatcher extends WdioAsymmetricMatchers {
19
19
  else if (typeof actual !== 'string') {
20
20
  return false;
21
21
  }
22
- return compareTextWithArray(actual, this.sample.filter(s => s !== null), this.options).success;
22
+ return this.sample.some((expected) => expected !== null && compareText(actual, expected, this.options).success);
23
23
  }
24
24
  withOptions(options) {
25
25
  return new OneOfMatcher(...this.sample).setOptions(options);
@@ -1,7 +1,8 @@
1
- import logger from '@wdio/logger';
2
- import { waitUntil, enhanceError, compareText } from '../../utils.js';
1
+ import { waitUntil, enhanceError, compareTextOrOneOf } from '../../utils.js';
3
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
4
- const log = logger('expect-webdriverio');
3
+ import { executeBrowserCommand } from '../../util/executeBrowserCommand.js';
4
+ import { buildWdioAsymmetricMatchersWithOptions } from '../asymmetrics/asymmetricsUtils.js';
5
+ let permissionWarningLogged = false;
5
6
  export async function toHaveClipboardText(browser, expectedValue, options = DEFAULT_OPTIONS) {
6
7
  const { expectation = 'clipboard text', verb = 'have', isNot, matcherName = 'toHaveClipboardText' } = this;
7
8
  await options.beforeAssertion?.({
@@ -9,14 +10,16 @@ export async function toHaveClipboardText(browser, expectedValue, options = DEFA
9
10
  expectedValue,
10
11
  options,
11
12
  });
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 };
13
+ const expectedWithOptions = buildWdioAsymmetricMatchersWithOptions(expectedValue, options);
14
+ const { actual, success: pass, subject, expected } = await waitUntil(async () => {
15
+ return await executeBrowserCommand({
16
+ browser,
17
+ isNot,
18
+ expectedValue: expectedWithOptions,
19
+ compare: (browser, expectedValue) => compareClipboardText(browser, expectedValue, options),
20
+ });
18
21
  }, isNot, { wait: options.wait, interval: options.interval });
19
- const message = enhanceError('browser', expectedValue, actual, this, verb, expectation, '', options);
22
+ const message = enhanceError(subject, expected, actual, this, verb, expectation, '', options);
20
23
  const result = {
21
24
  pass,
22
25
  message: () => message
@@ -29,3 +32,14 @@ export async function toHaveClipboardText(browser, expectedValue, options = DEFA
29
32
  });
30
33
  return result;
31
34
  }
35
+ const compareClipboardText = async (browser, expectedValue, options) => {
36
+ await browser.setPermissions({ name: 'clipboard-read' }, 'granted')
37
+ .catch((err) => {
38
+ if (!permissionWarningLogged) {
39
+ permissionWarningLogged = true;
40
+ console.warn(`expect-webdriverio: Couldn't set clipboard permissions: ${err}`);
41
+ }
42
+ });
43
+ const actual = await browser.execute(() => window.navigator.clipboard.readText());
44
+ return compareTextOrOneOf(actual, expectedValue, options);
45
+ };
@@ -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?.({
@@ -8,28 +10,17 @@ export async function toHaveLocalStorageItem(browser, key, expectedValue, option
8
10
  expectedValue: expectedValue ? [key, expectedValue] : key,
9
11
  options,
10
12
  });
11
- const paramsCount = arguments.length;
12
- let expected;
13
- if (expectedValue === undefined) {
14
- if (paramsCount > 2) {
15
- console.warn('[DEPRECATION] Using toHaveLocalStorageItem with undefined is deprecated in favor of expect.anything().');
16
- }
17
- expected = expect.anything();
18
- }
19
- else {
20
- expected = expectedValue;
21
- }
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 };
13
+ const expected = expectedValue ?? expect.anything();
14
+ const expectedWithOptions = buildWdioAsymmetricMatchersWithOptions(expected, options);
15
+ const { actual, success: pass, subject, expected: expectedValues } = await waitUntil(async () => {
16
+ return await executeBrowserCommand({
17
+ browser,
18
+ isNot,
19
+ expectedValue: expectedWithOptions,
20
+ compare: (browser, expectedValue) => compareStorageItem(browser, key, expectedValue, options),
21
+ });
31
22
  }, isNot, { wait: options.wait, interval: options.interval });
32
- const message = enhanceError('browser', expected, actual, this, verb, expectation, key, options);
23
+ const message = enhanceError(subject, expectedValues, actual, this, verb, expectation, key, options);
33
24
  const result = {
34
25
  pass,
35
26
  message: () => message
@@ -42,3 +33,13 @@ export async function toHaveLocalStorageItem(browser, key, expectedValue, option
42
33
  });
43
34
  return result;
44
35
  }
36
+ const compareStorageItem = async (browser, key, expected, options) => {
37
+ const actual = await browser.execute((storageKey) => {
38
+ return localStorage.getItem(storageKey);
39
+ }, key);
40
+ if (actual === null) {
41
+ return { actual, success: false };
42
+ }
43
+ const compareResult = compareTextOrOneOf(actual, expected, options);
44
+ return { actual, success: compareResult.success };
45
+ };
@@ -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
+ };
@@ -2,13 +2,14 @@ import { executeCommandBe } from '../../utils.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
3
  export async function toBeClickable(received, options = DEFAULT_OPTIONS) {
4
4
  this.expectation = this.expectation || 'clickable';
5
+ const { matcherName = 'toBeClickable' } = this;
5
6
  await options.beforeAssertion?.({
6
- matcherName: 'toBeClickable',
7
+ matcherName,
7
8
  options,
8
9
  });
9
10
  const result = await executeCommandBe.call(this, received, el => el?.isClickable(), options);
10
11
  await options.afterAssertion?.({
11
- matcherName: 'toBeClickable',
12
+ matcherName,
12
13
  options,
13
14
  result
14
15
  });
@@ -2,13 +2,14 @@ import { executeCommandBe } from '../../utils.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
3
  export async function toBeDisabled(received, options = DEFAULT_OPTIONS) {
4
4
  this.expectation = this.expectation || 'disabled';
5
+ const { matcherName = 'toBeDisabled' } = this;
5
6
  await options.beforeAssertion?.({
6
- matcherName: 'toBeDisabled',
7
+ matcherName,
7
8
  options,
8
9
  });
9
10
  const result = await executeCommandBe.call(this, received, async (el) => !await el.isEnabled(), options);
10
11
  await options.afterAssertion?.({
11
- matcherName: 'toBeDisabled',
12
+ matcherName,
12
13
  options,
13
14
  result
14
15
  });
@@ -2,8 +2,9 @@ import { executeCommandBe } from '../../utils.js';
2
2
  import { DEFAULT_OPTIONS_TO_BE_DISPLAYED } from '../../constants.js';
3
3
  export async function toBeDisplayed(received, options = DEFAULT_OPTIONS_TO_BE_DISPLAYED) {
4
4
  this.expectation = this.expectation || 'displayed';
5
+ const { matcherName = 'toBeDisplayed' } = this;
5
6
  await options.beforeAssertion?.({
6
- matcherName: 'toBeDisplayed',
7
+ matcherName,
7
8
  options,
8
9
  });
9
10
  const { withinViewport, contentVisibilityAuto, opacityProperty, visibilityProperty, ...commandOptions } = { ...DEFAULT_OPTIONS_TO_BE_DISPLAYED, ...options };
@@ -14,7 +15,7 @@ export async function toBeDisplayed(received, options = DEFAULT_OPTIONS_TO_BE_DI
14
15
  visibilityProperty
15
16
  }), commandOptions);
16
17
  await options.afterAssertion?.({
17
- matcherName: 'toBeDisplayed',
18
+ matcherName,
18
19
  options,
19
20
  result
20
21
  });
@@ -2,13 +2,14 @@ import { executeCommandBe } from '../../utils.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
3
  export async function toBeDisplayedInViewport(received, options = DEFAULT_OPTIONS) {
4
4
  this.expectation = this.expectation || 'displayed in viewport';
5
+ const { matcherName = 'toBeDisplayedInViewport' } = this;
5
6
  await options.beforeAssertion?.({
6
- matcherName: 'toBeDisplayedInViewport',
7
+ matcherName,
7
8
  options,
8
9
  });
9
10
  const result = await executeCommandBe.call(this, received, el => el?.isDisplayed({ withinViewport: true }), options);
10
11
  await options.afterAssertion?.({
11
- matcherName: 'toBeDisplayedInViewport',
12
+ matcherName,
12
13
  options,
13
14
  result
14
15
  });
@@ -2,13 +2,14 @@ import { executeCommandBe } from '../../utils.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
3
  export async function toBeEnabled(received, options = DEFAULT_OPTIONS) {
4
4
  this.expectation = this.expectation || 'enabled';
5
+ const { matcherName = 'toBeEnabled' } = this;
5
6
  await options.beforeAssertion?.({
6
- matcherName: 'toBeEnabled',
7
+ matcherName,
7
8
  options,
8
9
  });
9
10
  const result = await executeCommandBe.call(this, received, el => el?.isEnabled(), options);
10
11
  await options.afterAssertion?.({
11
- matcherName: 'toBeEnabled',
12
+ matcherName,
12
13
  options,
13
14
  result
14
15
  });
@@ -4,13 +4,14 @@ export async function toExist(received, options = DEFAULT_OPTIONS) {
4
4
  this.expectation = this.expectation || 'exist';
5
5
  this.verb = this.verb || '';
6
6
  this.allowEmptyElements = true;
7
+ const { matcherName = 'toExist' } = this;
7
8
  await options.beforeAssertion?.({
8
- matcherName: 'toExist',
9
+ matcherName,
9
10
  options,
10
11
  });
11
12
  const result = await executeCommandBe.call(this, received, el => el?.isExisting(), options);
12
13
  await options.afterAssertion?.({
13
- matcherName: 'toExist',
14
+ matcherName,
14
15
  options,
15
16
  result
16
17
  });
@@ -19,10 +20,12 @@ export async function toExist(received, options = DEFAULT_OPTIONS) {
19
20
  export function toBeExisting(el, options) {
20
21
  this.expectation = 'existing';
21
22
  this.verb = 'be';
23
+ this.matcherName ??= 'toBeExisting';
22
24
  return toExist.call(this, el, options);
23
25
  }
24
26
  export function toBePresent(el, options) {
25
27
  this.expectation = 'present';
26
28
  this.verb = 'be';
29
+ this.matcherName ??= 'toBePresent';
27
30
  return toExist.call(this, el, options);
28
31
  }
@@ -2,13 +2,14 @@ import { executeCommandBe } from '../../utils.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
3
  export async function toBeFocused(received, options = DEFAULT_OPTIONS) {
4
4
  this.expectation = this.expectation || 'focused';
5
+ const { matcherName = 'toBeFocused' } = this;
5
6
  await options.beforeAssertion?.({
6
- matcherName: 'toBeFocused',
7
+ matcherName,
7
8
  options,
8
9
  });
9
10
  const result = await executeCommandBe.call(this, received, el => el?.isFocused(), options);
10
11
  await options.afterAssertion?.({
11
- matcherName: 'toBeFocused',
12
+ matcherName,
12
13
  options,
13
14
  result
14
15
  });
@@ -2,14 +2,14 @@ import { executeCommandBe } from '../../utils.js';
2
2
  import { DEFAULT_OPTIONS } from '../../constants.js';
3
3
  export async function toBeSelected(received, options = DEFAULT_OPTIONS) {
4
4
  this.expectation = this.expectation || 'selected';
5
- this.matcherName = this.matcherName || 'toBeSelected';
5
+ const { matcherName = 'toBeSelected' } = this;
6
6
  await options.beforeAssertion?.({
7
- matcherName: this.matcherName,
7
+ matcherName,
8
8
  options,
9
9
  });
10
10
  const result = await executeCommandBe.call(this, received, el => el?.isSelected(), options);
11
11
  await options.afterAssertion?.({
12
- matcherName: this.matcherName,
12
+ matcherName,
13
13
  options,
14
14
  result
15
15
  });
@@ -17,7 +17,7 @@ export async function toBeSelected(received, options = DEFAULT_OPTIONS) {
17
17
  }
18
18
  export async function toBeChecked(received, options = DEFAULT_OPTIONS) {
19
19
  this.expectation = 'checked';
20
- this.matcherName = 'toBeChecked';
20
+ this.matcherName ??= 'toBeChecked';
21
21
  const result = await toBeSelected.call(this, received, options);
22
22
  return result;
23
23
  }