expect-webdriverio 5.6.9 → 5.7.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.
@@ -1,7 +1,16 @@
1
1
  /// <reference types="./types/expect-webdriverio.d.ts"/>
2
2
 
3
- declare namespace jasmine {
3
+ /**
4
+ * Note: Under development, until proven, expect breaking changes!
5
+ * Augment the Jasmine namespace to match the behavior of `@wdio/jasmine-framework`.
6
+ * Only custom WDIO matchers are available under `expectAsync`, as well as Jasmine's built-in matchers.
7
+ * `expectAsync` is forced into the `expect` global ambient, making all Jasmine sync-matchers asynchronous.
8
+ *
9
+ * When using `@wdio/jasmine-framework`, specify `expect-webdriverio/jasmine-wdio-expect-async` in the tsconfig.json's types.
10
+ * TODO move into `@wdio/jasmine-framework` and deprecated it from `expect-webdriverio`.
11
+ */
4
12
 
13
+ declare namespace jasmine {
5
14
  /**
6
15
  * Async matchers for Jasmine to allow the typing of `expectAsync` with WebDriverIO matchers.
7
16
  * T is the type of the actual value
@@ -10,28 +19,47 @@ declare namespace jasmine {
10
19
  *
11
20
  * We force Matchers to return a `Promise<void>` since Jasmine's `expectAsync` expects a promise in all cases (different from Jest)
12
21
  */
13
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
14
- interface AsyncMatchers<T, U> extends Omit<ExpectWebdriverIO.Matchers<Promise<void>, T>, 'toMatchSnapshot' | 'toMatchInlineSnapshot'> {
22
+
23
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- U is required to properly override Jasmine's AsyncMatchers
24
+ interface AsyncMatchers<T, U> extends ExpectWebdriverIO.Matchers<Promise<void>, T> {
15
25
  /**
16
- * snapshot matcher
17
- * @param label optional snapshot label
26
+ * Override to unwrap Promise<T> since `expect` replaces `expectAsync` under @wdio/jasmine-framework.
27
+ * Without this, toBeResolvedTo expects Expected<Promise<X>> instead of Expected<X>.
18
28
  */
19
- toMatchSnapshot(label?: string): Promise<void>;
29
+ toBeResolvedTo(expected: Expected<T extends PromiseLike<infer V> ? V : T>): PromiseLike<void>;
30
+
20
31
  /**
21
- * inline snapshot matcher
22
- * @param snapshot snapshot string (autogenerated if not specified)
23
- * @param label optional snapshot label
32
+ * Rejection type is unknown since promises can reject with any value.
24
33
  */
25
- toMatchInlineSnapshot(snapshot?: string, label?: string): Promise<void>;
34
+ toBeRejectedWith(expected: unknown): PromiseLike<void>;
26
35
  }
36
+
37
+ // Needed to reference it below for the withContext method
38
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
39
+ interface Matchers<T> {}
27
40
  }
28
41
 
29
- /**
30
- * Overrides the default WDIO expect specifically for Jasmine, since `expectAsync` is forced into `expect`, making all matchers fully asynchronous. This is not the case under Jest or Mocha.
31
- * Using `jasmine.AsyncMatchers` pull on WdioMatchers above but also allow to using Jasmine's built-in matchers and also `withContext` matcher.
32
- */
33
42
  declare namespace ExpectWebdriverIO {
34
- interface Expect {
43
+
44
+ // Should be the same as https://github.com/webdriverio/webdriverio/blob/ea0e3e00288abced4c739ff9e46c46977b7cdbd2/packages/wdio-jasmine-framework/src/index.ts#L21-L29
45
+ interface JasmineAsymmetricMatchers extends Pick<ExpectWebdriverIO.AsymmetricMatchers, 'any' | 'anything' | 'arrayContaining' | 'objectContaining' | 'stringContaining' | 'stringMatching'> {}
46
+
47
+ // Hack to convert all sync matchers to return Promise<void> since `wdio/jasmine-framework` forces `expect` to be async
48
+ type JasmineSyncMatchers<T> = {
49
+ [K in keyof jasmine.Matchers<T>]: K extends 'not'
50
+ ? JasmineSyncMatchers<T>
51
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
+ : jasmine.Matchers<T>[K] extends (...args: any) => any
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ ? (...args: any[]) => Promise<void>
55
+ : jasmine.Matchers<T>[K]
56
+ }
57
+
58
+ /**
59
+ * Overrides the default WDIO expect specifically for Jasmine, since `expectAsync` is forced into `expect`, making all matchers fully asynchronous. This is not the case under Jest or Mocha.
60
+ * Using `jasmine.AsyncMatchers` includes the WdioMatchers from above, but also allows using Jasmine's built-in matchers and the `withContext` matcher.
61
+ */
62
+ interface JasmineExpect extends ExpectWebdriverIO.JasmineAsymmetricMatchers, ExpectLibInverse<ExpectWebdriverIO.JasmineAsymmetricMatchers> {
35
63
  /**
36
64
  * The `expect` function is used every time you want to test a value.
37
65
  * You will rarely call `expect` by itself.
@@ -40,10 +68,22 @@ declare namespace ExpectWebdriverIO {
40
68
  * - T: the type of the actual value, e.g. any type, not just WebdriverIO.Browser or WebdriverIO.Element
41
69
  * - R: the type of the return value, e.g. Promise<void> or void
42
70
  *
43
- * Note: The function must stay here in the namespace to overwrite correctly the expect function from the expect library.
44
- *
45
71
  * @param actual The value to apply matchers against.
46
72
  */
47
- <T = unknown>(actual: T): jasmine.AsyncMatchers<T, void>
73
+ <T = unknown>(actual: T): {
74
+ withContext(message: string): jasmine.AsyncMatchers<T, Promise<void>> & JasmineSyncMatchers<T>;
75
+ } & jasmine.AsyncMatchers<T, Promise<void>> & JasmineSyncMatchers<T>
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Under `@wdio/jasmine-framework`, the global `expect` is overridden to use Jasmine's `expectAsync`.
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
+ */
83
+ // @ts-expect-error: IDE might flag this, but ignore it. This way the `tsc:root-types` can pass!
84
+ declare const expect: ExpectWebdriverIO.JasmineExpect
85
+ declare namespace NodeJS {
86
+ interface Global {
87
+ expect: ExpectWebdriverIO.JasmineExpect
48
88
  }
49
89
  }
package/jasmine.d.ts CHANGED
@@ -1,27 +1,20 @@
1
1
  /// <reference types="./types/expect-webdriverio.d.ts"/>
2
2
 
3
+ /**
4
+ * Augment the Jasmine namespace to include the WDIO custom async matchers only.
5
+ * When using the vanilla Jasmine Library, use `jasmine.addAsyncMatchers(wdioCustomMatchers)` and specify `expect-webdriverio/jasmine` in the tsconfig.json's types.
6
+ */
7
+
3
8
  declare namespace jasmine {
4
9
 
5
10
  /**
6
- * Async matchers for Jasmine to allow the typing of `expectAsync` with WebDriverIO matchers.
11
+ * Async matchers for Jasmine to allow the typing of `expectAsync` with WebDriverIO custom matchers.
7
12
  * T is the type of the actual value
8
13
  * U is the type of the expected value
9
14
  * Both T,U must stay named as they are to override the default `AsyncMatchers` type from Jasmine.
10
15
  *
11
- * We force Matchers to return a `Promise<void>` since Jasmine's `expectAsync` expects a promise in all cases (different from Jest)
16
+ * We force Matchers to return a `Promise<void>` since under Jasmine's `expectAsync` everything is a promise.
12
17
  */
13
18
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
14
- interface AsyncMatchers<T, U> extends Omit<ExpectWebdriverIO.Matchers<Promise<void>, T>, 'toMatchSnapshot' | 'toMatchInlineSnapshot'> {
15
- /**
16
- * snapshot matcher
17
- * @param label optional snapshot label
18
- */
19
- toMatchSnapshot(label?: string): Promise<void>;
20
- /**
21
- * inline snapshot matcher
22
- * @param snapshot snapshot string (autogenerated if not specified)
23
- * @param label optional snapshot label
24
- */
25
- toMatchInlineSnapshot(snapshot?: string, label?: string): Promise<void>;
26
- }
27
- }
19
+ interface AsyncMatchers<T, U> extends ExpectWebdriverIO.Matchers<Promise<void>, T> {}
20
+ }
@@ -11,11 +11,12 @@ function asymmetricMatch(a, b) {
11
11
  if (asymmetricA && asymmetricB) {
12
12
  return undefined;
13
13
  }
14
+ const matchersUtil = { equals, contains: equals };
14
15
  if (asymmetricA) {
15
- return a.asymmetricMatch(b);
16
+ return a.asymmetricMatch(b, matchersUtil);
16
17
  }
17
18
  if (asymmetricB) {
18
- return b.asymmetricMatch(a);
19
+ return b.asymmetricMatch(a, matchersUtil);
19
20
  }
20
21
  }
21
22
  function eq(a, b, aStack, bStack, customTesters, hasKey) {
@@ -1,4 +1,4 @@
1
- import { waitUntil, enhanceError } from '../../utils.js';
1
+ import { waitUntil, enhanceError, isAsymmetricMatcher, getAsymmetricMatcherValue } from '../../utils.js';
2
2
  import { equals } from '../../jasmineUtils.js';
3
3
  import { DEFAULT_OPTIONS } from '../../constants.js';
4
4
  const STR_LIMIT = 80;
@@ -88,15 +88,6 @@ const headersMatcher = (headers, expected) => {
88
88
  }
89
89
  return equals(headers, expected);
90
90
  };
91
- const isMatcher = (filter) => {
92
- const proto = Object.getPrototypeOf(filter);
93
- return (typeof filter === 'object' &&
94
- filter !== null &&
95
- typeof proto === 'object' &&
96
- proto &&
97
- 'asymmetricMatch' in proto &&
98
- typeof proto.asymmetricMatch === 'function');
99
- };
100
91
  const minifyRequestMock = (requestMock, requestedWith) => {
101
92
  if (requestMock === undefined) {
102
93
  return requestMock;
@@ -129,10 +120,10 @@ const requestedWithParamToString = (param, transformFn) => {
129
120
  if (typeof param === 'function') {
130
121
  param = param.toString();
131
122
  }
132
- else if (isMatcher(param)) {
133
- return (param.constructor.name +
134
- ' ' +
135
- (JSON.stringify(param.sample) || ''));
123
+ else if (isAsymmetricMatcher(param)) {
124
+ const sample = getAsymmetricMatcherValue(param);
125
+ const sampleString = typeof sample === 'string' ? sample : sample instanceof RegExp ? sample.toString() : JSON.stringify(sample) || sample?.toString();
126
+ return `${param.constructor.name} ${sampleString || ''}`;
136
127
  }
137
128
  else if (transformFn && typeof param === 'object' && param !== null) {
138
129
  param = transformFn(param);
package/lib/utils.js CHANGED
@@ -5,19 +5,47 @@ import { wrapExpectedWithArray } from './util/elementsUtil.js';
5
5
  import { executeCommand } from './util/executeCommand.js';
6
6
  import { enhanceError, enhanceErrorBe, numberError } from './util/formatMessage.js';
7
7
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
8
- const asymmetricMatcher = typeof Symbol === 'function' && Symbol.for
9
- ? Symbol.for('jest.asymmetricMatcher')
10
- : 0x13_57_a5;
8
+ export function isJasmineStringAsymmetricMatcher(expected) {
9
+ return isAsymmetricMatcher(expected) && 'expected' in expected;
10
+ }
11
11
  export function isAsymmetricMatcher(expected) {
12
12
  return (typeof expected === 'object' &&
13
- expected &&
14
- '$$typeof' in expected &&
13
+ !!expected &&
15
14
  'asymmetricMatch' in expected &&
16
- expected.$$typeof === asymmetricMatcher &&
17
- Boolean(expected.asymmetricMatch));
15
+ !!expected.asymmetricMatch);
16
+ }
17
+ export function isStringContainingMatcherLike(expected) {
18
+ return !!expected && expected.constructor.name === 'StringContaining';
19
+ }
20
+ export function isInversedStringContainingMatcher(expected) {
21
+ return isStringContainingMatcherLike(expected) && expected.inverse === true;
18
22
  }
19
- function isStringContainingMatcher(expected) {
20
- return isAsymmetricMatcher(expected) && ['StringContaining', 'StringNotContaining'].includes(expected.toString());
23
+ export function getStringAsymmetricMatcherValue(expected) {
24
+ if ('expected' in expected) {
25
+ return expected.expected;
26
+ }
27
+ else if ('regexp' in expected) {
28
+ return expected.regexp;
29
+ }
30
+ else if ('sample' in expected) {
31
+ return expected.sample;
32
+ }
33
+ throw new Error(`Could not extract value from asymmetric matcher: ${expected}. Please report this issue to the expect-webdriverio maintainers.`);
34
+ }
35
+ export function getAsymmetricMatcherValue(expected) {
36
+ if ('expected' in expected) {
37
+ return expected.expected;
38
+ }
39
+ else if ('expectedObject' in expected) {
40
+ return expected.expectedObject;
41
+ }
42
+ else if ('regexp' in expected) {
43
+ return expected.regexp;
44
+ }
45
+ else if ('sample' in expected) {
46
+ return expected.sample;
47
+ }
48
+ return undefined;
21
49
  }
22
50
  const waitUntil = async (condition, isNot = false, { wait = DEFAULT_OPTIONS.wait, interval = DEFAULT_OPTIONS.interval } = {}) => {
23
51
  if (wait === 0) {
@@ -102,10 +130,11 @@ export const compareText = (actual, expected, { ignoreCase = false, trim = true,
102
130
  if (typeof expected === 'string') {
103
131
  expected = expected.toLowerCase();
104
132
  }
105
- else if (isStringContainingMatcher(expected)) {
106
- expected = (expected.toString() === 'StringContaining'
107
- ? expect.stringContaining(expected.sample?.toString().toLowerCase())
108
- : expect.not.stringContaining(expected.sample?.toString().toLowerCase()));
133
+ else if (isStringContainingMatcherLike(expected)) {
134
+ const sample = getStringAsymmetricMatcherValue(expected).toString().toLocaleLowerCase();
135
+ expected = (isInversedStringContainingMatcher(expected)
136
+ ? expect.not.stringContaining(sample)
137
+ : expect.stringContaining(sample));
109
138
  }
110
139
  }
111
140
  if (isAsymmetricMatcher(expected)) {
@@ -169,10 +198,11 @@ export const compareTextWithArray = (actual, expectedArray, { ignoreCase = false
169
198
  if (typeof item === 'string') {
170
199
  return item.toLowerCase();
171
200
  }
172
- if (isStringContainingMatcher(item)) {
173
- return (item.toString() === 'StringContaining'
174
- ? expect.stringContaining(item.sample?.toString().toLowerCase())
175
- : expect.not.stringContaining(item.sample?.toString().toLowerCase()));
201
+ if (isStringContainingMatcherLike(item)) {
202
+ const sample = getStringAsymmetricMatcherValue(item).toString().toLocaleLowerCase();
203
+ return (isInversedStringContainingMatcher(item)
204
+ ? expect.not.stringContaining(sample)
205
+ : expect.stringContaining(sample));
176
206
  }
177
207
  return item;
178
208
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expect-webdriverio",
3
- "version": "5.6.9",
3
+ "version": "5.7.0",
4
4
  "author": "Mykola Grybyk <mykola.grybyk@gmail.com>",
5
5
  "description": "WebdriverIO Assertion Library",
6
6
  "license": "MIT",
@@ -83,12 +83,12 @@
83
83
  "jest-matcher-utils": "^30.4.1"
84
84
  },
85
85
  "devDependencies": {
86
+ "@jest/globals": "^30.4.1",
86
87
  "@types/debug": "^4.1.13",
87
88
  "@types/jasmine": "^5.1.15",
88
89
  "@types/jest": "^30.0.0",
89
- "@types/node": "^24.12.4",
90
90
  "@types/mocha": "^10.0.10",
91
- "@jest/globals": "^30.4.1",
91
+ "@types/node": "^24.12.4",
92
92
  "@vitest/coverage-v8": "^4.1.7",
93
93
  "@wdio/eslint": "^0.1.3",
94
94
  "@wdio/types": "^9.27.2",
@@ -97,6 +97,7 @@
97
97
  "npm-run-all2": "^9.0.1",
98
98
  "release-it": "^20.2.0",
99
99
  "rimraf": "^6.1.3",
100
+ "strip-ansi": "^7.2.0",
100
101
  "typescript": "^5.9.3",
101
102
  "vitest": "^4.1.7",
102
103
  "webdriverio": "^9.27.2"
@@ -5,11 +5,12 @@
5
5
  * Required when used in standalone mode (mocha) or to override the one of Jasmine
6
6
  */
7
7
 
8
- //// @ts-expect-error: IDE might flags this one but just does be concerned by it. This way the `tsc:root-types` can pass!
8
+ // @ts-expect-error: IDE might flags this one but just does be concerned by it. This way the `tsc:root-types` can pass!
9
9
  declare const expect: ExpectWebdriverIO.Expect
10
10
 
11
11
  declare namespace NodeJS {
12
12
  interface Global {
13
+ // @ts-expect-error: Both wdio & Jasmine augmentation declare different global expect explaining why TS is complaining here
13
14
  expect: ExpectWebdriverIO.Expect
14
15
  }
15
16
  }
@@ -468,8 +468,41 @@ type WdioAsymmetricMatchers = ExpectLibAsymmetricMatchers
468
468
  type WdioAsymmetricMatcher<R> = ExpectWebdriverIO.PartialMatcher<R> & {
469
469
  // Overwrite protected properties of expect.AsymmetricMatcher to access them
470
470
  sample: R;
471
+ inverse?: boolean;
471
472
  }
472
473
 
474
+ type JasmineBaseAsymmetricMatcher = {
475
+ jasmineToString(): string;
476
+ asymmetricMatch(other: unknown): boolean;
477
+ }
478
+
479
+ /**
480
+ * Jasmine asymetric matcher does not always use sample, some use expected. `any` uses even `expectedObject`
481
+ * @see https://github.com/jasmine/jasmine/tree/v5.13.0/src/core/asymmetric_equality
482
+ */
483
+ type JasmineAsymmetricMatcher<R> = JasmineBaseAsymmetricMatcher & ({
484
+ expected: R;
485
+ } | {
486
+ sample: R;
487
+ } | {
488
+ regexp: string | RegExp;
489
+ } | {
490
+ expectedObject: R;
491
+ } | {} // jasmine.anything()
492
+ )
493
+
494
+ type JasmineStringContainingAsymmetricMatcher<R> = JasmineBaseAsymmetricMatcher & {
495
+ expected: R;
496
+ }
497
+
498
+ type JasmineStringMatchingAsymmetricMatcher<R extends string | RegExp> = JasmineBaseAsymmetricMatcher & {
499
+ regexp: R;
500
+ }
501
+
502
+ type JasmineStringAsymmetricMatcher<R extends string | RegExp> = JasmineStringContainingAsymmetricMatcher<R> | JasmineStringMatchingAsymmetricMatcher<R>
503
+
504
+ type AsymmetricMatcher<R> = WdioAsymmetricMatcher<R> | JasmineStringContainingAsymmetricMatcher<R> | (R extends string | RegExp ? JasmineStringMatchingAsymmetricMatcher<R> : never) | JasmineAsymmetricMatcher<R>
505
+
473
506
  declare namespace ExpectWebdriverIO {
474
507
  /**
475
508
  * When importing expect from 'expect-webdriverio', instead of using globals this is the one used.
@@ -512,7 +545,13 @@ declare namespace ExpectWebdriverIO {
512
545
  <T = unknown>(actual: T): T extends PromiseLike<unknown> ? ExpectWebdriverIO.MatchersAndInverse<void, T> & ExpectWebdriverIO.PromiseMatchers<T> : ExpectWebdriverIO.MatchersAndInverse<void, T>;
513
546
  }
514
547
 
515
- interface Matchers<R extends void | Promise<void>, T> extends WdioMatchers<R, T> {}
548
+ /**
549
+ * Matchers defining the custom wdio matchers.
550
+ * Matchers is documented as the interface to augment to add other custom matchers.
551
+ *
552
+ * We MUST NOT have Jest's expect Lib matchers here, so that we can use it with Jasmine augmentation as well.
553
+ */
554
+ interface Matchers<R extends void | Promise<void>, T> extends WdioCustomMatchers<R, T> {}
516
555
 
517
556
  interface AsymmetricMatchers extends WdioAsymmetricMatchers {}
518
557
 
@@ -522,7 +561,7 @@ declare namespace ExpectWebdriverIO {
522
561
  * End of block overloading types from the expect library.
523
562
  */
524
563
 
525
- type MatchersAndInverse<R extends void | Promise<void>, ActualT> = ExpectWebdriverIO.Matchers<R, ActualT> & ExpectLibInverse<ExpectWebdriverIO.Matchers<R, ActualT>>
564
+ type MatchersAndInverse<R extends void | Promise<void>, ActualT> = (ExpectWebdriverIO.Matchers<R, ActualT> & ExpectLibMatchers<R, ActualT>) & ExpectLibInverse<ExpectWebdriverIO.Matchers<R, ActualT> & ExpectLibMatchers<R, ActualT>>
526
565
 
527
566
  /**
528
567
  * Take from expect library