expect-webdriverio 5.6.9 → 6.0.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/README.md +16 -14
- package/jasmine-wdio-expect-async.d.ts +58 -18
- package/jasmine.d.ts +9 -16
- package/lib/constants.js +4 -0
- package/lib/index.js +30 -18
- package/lib/jasmineUtils.js +3 -2
- package/lib/matchers/asymmetrics/asymmetricsUtils.js +24 -0
- package/lib/matchers/asymmetrics/oneOf.js +51 -0
- package/lib/matchers/browser/toHaveClipboardText.js +8 -9
- package/lib/matchers/browser/toHaveLocalStorageItem.js +22 -14
- package/lib/matchers/browser/toHaveTitle.js +9 -10
- package/lib/matchers/browser/toHaveUrl.js +8 -9
- package/lib/matchers/element/toBeExisting.js +8 -3
- package/lib/matchers/element/toBeSelected.js +6 -13
- package/lib/matchers/element/toHaveAttribute.js +46 -45
- package/lib/matchers/element/toHaveChildren.js +30 -32
- package/lib/matchers/element/toHaveComputedLabel.js +21 -20
- package/lib/matchers/element/toHaveComputedRole.js +21 -20
- package/lib/matchers/element/toHaveElementClass.js +57 -0
- package/lib/matchers/element/toHaveElementProperty.js +40 -32
- package/lib/matchers/element/toHaveHTML.js +22 -23
- package/lib/matchers/element/toHaveHeight.js +23 -28
- package/lib/matchers/element/toHaveSize.js +16 -14
- package/lib/matchers/element/toHaveStyle.js +20 -14
- package/lib/matchers/element/toHaveText.js +26 -44
- package/lib/matchers/element/toHaveWidth.js +23 -28
- package/lib/matchers/elements/toBeElementsArrayOfSize.js +25 -30
- package/lib/matchers/mock/toBeRequestedTimes.js +12 -17
- package/lib/matchers/mock/toBeRequestedWith.js +13 -23
- package/lib/matchers/modifiers/some.js +15 -0
- package/lib/matchers.js +1 -1
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/lib/util/commandOptionsUtils.js +6 -0
- package/lib/util/elementsUtil.js +74 -4
- package/lib/util/executeCommand.js +94 -6
- package/lib/util/formatMessage.js +87 -32
- package/lib/util/numberOptionsUtil.js +92 -0
- package/lib/util/refetchElements.js +30 -5
- package/lib/util/stringUtil.js +12 -0
- package/lib/util/waitUntil.js +44 -0
- package/lib/utils.js +128 -105
- package/package.json +13 -11
- package/types/expect-global.d.ts +2 -1
- package/types/expect-webdriverio.d.ts +628 -136
- package/lib/matchers/element/toHaveClass.js +0 -61
package/README.md
CHANGED
|
@@ -7,15 +7,20 @@
|
|
|
7
7
|
|
|
8
8
|
## Key Features
|
|
9
9
|
|
|
10
|
-
- [
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
10
|
+
- [Waits](#default-options) for expectation to succeed
|
|
11
|
+
- Support single element & multi-elements
|
|
12
|
+
- Detailed [error messages](#error-messages)
|
|
13
|
+
- Works in Mocha, Cucumber, Jest, Jasmine
|
|
14
|
+
- Builtin [types](docs/Types.md) for TypeScript and JS autocompletion
|
|
14
15
|
|
|
15
16
|
## Installation
|
|
16
17
|
|
|
17
|
-
1. `npm install expect`
|
|
18
|
-
2.
|
|
18
|
+
1. `npm install expect-webdriverio`
|
|
19
|
+
2. Install your test framework adapter (if not already set up via WDIO testrunner):
|
|
20
|
+
- **Mocha** (default): `npm install @wdio/mocha-framework`
|
|
21
|
+
- **Jasmine**: `npm install @wdio/jasmine-framework`
|
|
22
|
+
- **Cucumber**: `npm install @wdio/cucumber-framework`
|
|
23
|
+
- **Jest**: No adapter needed — see [Jest Framework section](docs/Framework.md#jest)
|
|
19
24
|
|
|
20
25
|
NOTE: [WebdriverIO](https://github.com/webdriverio/webdriverio) `v9.0.0` or higher is required!
|
|
21
26
|
|
|
@@ -26,15 +31,15 @@ NOTE: [WebdriverIO](https://github.com/webdriverio/webdriverio) `v9.0.0` or high
|
|
|
26
31
|
If you run your tests through the [WDIO testrunner](https://webdriver.io/docs/clioptions) no additional setup is needed. WebdriverIO initialises `expect-webdriverio` and makes `expect` available in the global scope. So you can use it directly in your tests:
|
|
27
32
|
|
|
28
33
|
```js
|
|
29
|
-
const
|
|
30
|
-
await expect(
|
|
34
|
+
const button = await $('button')
|
|
35
|
+
await expect(button).toBeDisplayed()
|
|
31
36
|
```
|
|
32
37
|
|
|
33
38
|
See more [Examples](docs/Examples.md)
|
|
34
39
|
|
|
35
40
|
### Using in a standalone script
|
|
36
41
|
|
|
37
|
-
If you embed WebdriverIO in a standalone
|
|
42
|
+
If you embed WebdriverIO in a standalone (without `@wdio/globals`), make sure you import `expect-webdriverio` before you use it anywhere.
|
|
38
43
|
|
|
39
44
|
```js
|
|
40
45
|
import { remote } from 'webdriverio'
|
|
@@ -77,8 +82,5 @@ First of all, **feel free to raise an issue with your suggestions or help with P
|
|
|
77
82
|
|
|
78
83
|
### Planned
|
|
79
84
|
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
- cookie / localStorage matchers?
|
|
83
|
-
- text regex matchers
|
|
84
|
-
- multiremote support (if requested)
|
|
85
|
+
- cookie
|
|
86
|
+
- multiremote support (coming)
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
/// <reference types="./types/expect-webdriverio.d.ts"/>
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
*
|
|
17
|
-
*
|
|
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
|
-
|
|
29
|
+
toBeResolvedTo(expected: Expected<T extends PromiseLike<infer V> ? V : T>): PromiseLike<void>;
|
|
30
|
+
|
|
20
31
|
/**
|
|
21
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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):
|
|
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`
|
|
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
|
|
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
|
+
}
|
package/lib/constants.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
export const DEFAULT_FEATURE_FLAGS = {
|
|
2
|
+
useToHaveTextStrictMultiElementsCompareStrategy: false
|
|
3
|
+
};
|
|
1
4
|
export const DEFAULT_OPTIONS = {
|
|
2
5
|
wait: 2000,
|
|
3
6
|
interval: 100,
|
|
4
7
|
beforeAssertion: async () => { },
|
|
5
8
|
afterAssertion: async () => { },
|
|
9
|
+
featureFlags: DEFAULT_FEATURE_FLAGS
|
|
6
10
|
};
|
|
7
11
|
export const DEFAULT_OPTIONS_TO_BE_DISPLAYED = {
|
|
8
12
|
...DEFAULT_OPTIONS,
|
package/lib/index.js
CHANGED
|
@@ -3,41 +3,45 @@ import * as wdioMatchers from './matchers.js';
|
|
|
3
3
|
import { DEFAULT_OPTIONS, defaultOptionsList } from './constants.js';
|
|
4
4
|
import createSoftExpect from './softExpect.js';
|
|
5
5
|
import { SoftAssertService } from './softAssert.js';
|
|
6
|
+
import { oneOf } from './matchers/asymmetrics/oneOf.js';
|
|
7
|
+
import { some as wdioSome } from './matchers/modifiers/some.js';
|
|
6
8
|
export const wdioCustomMatchers = {};
|
|
7
9
|
export const matchers = new Map();
|
|
8
|
-
const filteredMatchers = {};
|
|
9
10
|
const extend = expectLib.extend;
|
|
10
|
-
|
|
11
|
-
if (typeof
|
|
12
|
-
filteredMatchers[matcher] = wdioMatchers[matcher];
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
expectLib.extend = (m) => {
|
|
16
|
-
if (!m || typeof m !== 'object') {
|
|
11
|
+
expectLib.extend = (extendedMatchers) => {
|
|
12
|
+
if (!extendedMatchers || typeof extendedMatchers !== 'object') {
|
|
17
13
|
return;
|
|
18
14
|
}
|
|
19
|
-
Object.entries(
|
|
15
|
+
Object.entries(extendedMatchers).forEach(([name, matcher]) => {
|
|
20
16
|
wdioCustomMatchers[name] = matcher;
|
|
21
17
|
matchers.set(name, matcher);
|
|
22
18
|
});
|
|
23
|
-
return extend(
|
|
19
|
+
return extend(extendedMatchers);
|
|
24
20
|
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
const filteredWdioMatchers = {};
|
|
22
|
+
Object.entries(wdioMatchers).forEach(([matcher, value]) => {
|
|
23
|
+
if (typeof value === 'function') {
|
|
24
|
+
filteredWdioMatchers[matcher] = value;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
const wdioExpect = expectLib;
|
|
28
|
+
wdioExpect.extend(filteredWdioMatchers);
|
|
29
|
+
wdioExpect.oneOf = oneOf;
|
|
30
|
+
Object.defineProperty(wdioExpect, 'soft', {
|
|
28
31
|
value: (actual) => createSoftExpect(actual)
|
|
29
32
|
});
|
|
30
|
-
Object.defineProperty(
|
|
33
|
+
Object.defineProperty(wdioExpect, 'getSoftFailures', {
|
|
31
34
|
value: (testId) => SoftAssertService.getInstance().getFailures(testId)
|
|
32
35
|
});
|
|
33
|
-
Object.defineProperty(
|
|
36
|
+
Object.defineProperty(wdioExpect, 'assertSoftFailures', {
|
|
34
37
|
value: (testId) => SoftAssertService.getInstance().assertNoFailures(testId)
|
|
35
38
|
});
|
|
36
|
-
Object.defineProperty(
|
|
39
|
+
Object.defineProperty(wdioExpect, 'clearSoftFailures', {
|
|
37
40
|
value: (testId) => SoftAssertService.getInstance().clearFailures(testId)
|
|
38
41
|
});
|
|
39
|
-
export const expect =
|
|
40
|
-
export const
|
|
42
|
+
export const expect = wdioExpect;
|
|
43
|
+
export const some = wdioSome;
|
|
44
|
+
export const getDefaultOptions = () => DEFAULT_OPTIONS;
|
|
41
45
|
export const setDefaultOptions = (options) => {
|
|
42
46
|
Object.entries(options).forEach(([key, value]) => {
|
|
43
47
|
defaultOptionsList.forEach((option) => {
|
|
@@ -47,7 +51,15 @@ export const setDefaultOptions = (options) => {
|
|
|
47
51
|
});
|
|
48
52
|
});
|
|
49
53
|
};
|
|
54
|
+
export const setFeatureFlags = (featureFlags) => {
|
|
55
|
+
Object.entries(featureFlags).forEach(([ffName, ffValue]) => {
|
|
56
|
+
defaultOptionsList.forEach((option) => {
|
|
57
|
+
option.featureFlags[ffName] = ffValue;
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
};
|
|
50
61
|
export const setOptions = setDefaultOptions;
|
|
62
|
+
export const getConfig = getDefaultOptions;
|
|
51
63
|
export { SnapshotService } from './snapshot.js';
|
|
52
64
|
export { SoftAssertService } from './softAssert.js';
|
|
53
65
|
export { SoftAssertionService } from './softAssertService.js';
|
package/lib/jasmineUtils.js
CHANGED
|
@@ -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) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AsymmetricMatcher } from 'expect';
|
|
2
|
+
import { isAsymmetricMatcher } from '../../utils.js';
|
|
3
|
+
export const buildWdioAsymmetricMatchersWithOptions = (expectedValue, options) => {
|
|
4
|
+
if (options) {
|
|
5
|
+
if (Array.isArray(expectedValue)) {
|
|
6
|
+
return expectedValue.map((value) => buildOneAsymmetricMatcherWithOptions(value, options));
|
|
7
|
+
}
|
|
8
|
+
return buildOneAsymmetricMatcherWithOptions(expectedValue, options);
|
|
9
|
+
}
|
|
10
|
+
return expectedValue;
|
|
11
|
+
};
|
|
12
|
+
const buildOneAsymmetricMatcherWithOptions = (expectedValue, options) => {
|
|
13
|
+
if (options) {
|
|
14
|
+
if (isAsymmetricMatcher(expectedValue) && 'withOptions' in expectedValue && typeof expectedValue.withOptions === 'function') {
|
|
15
|
+
return expectedValue.withOptions(options);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return expectedValue;
|
|
19
|
+
};
|
|
20
|
+
export class WdioAsymmetricMatchers extends AsymmetricMatcher {
|
|
21
|
+
toString() {
|
|
22
|
+
return this.toAsymmetricMatcher();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { compareTextWithArray } from '../../utils.js';
|
|
2
|
+
import { WdioAsymmetricMatchers } from './asymmetricsUtils.js';
|
|
3
|
+
export class OneOfMatcher extends WdioAsymmetricMatchers {
|
|
4
|
+
options = {};
|
|
5
|
+
constructor(...sample) {
|
|
6
|
+
super(sample);
|
|
7
|
+
}
|
|
8
|
+
setOptions(options) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
asymmetricMatch(actual) {
|
|
13
|
+
if (actual === null) {
|
|
14
|
+
return this.sample.includes(null);
|
|
15
|
+
}
|
|
16
|
+
else if (typeof actual !== 'string') {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return compareTextWithArray(actual, this.sample.filter(s => s !== null), this.options).success;
|
|
20
|
+
}
|
|
21
|
+
withOptions(options) {
|
|
22
|
+
return new OneOfMatcher(...this.sample).setOptions(options);
|
|
23
|
+
}
|
|
24
|
+
toAsymmetricMatcher() {
|
|
25
|
+
let prefix = '';
|
|
26
|
+
if (this.options.containing) {
|
|
27
|
+
prefix = 'containing';
|
|
28
|
+
}
|
|
29
|
+
else if (this.options.atStart) {
|
|
30
|
+
prefix = 'startingWith';
|
|
31
|
+
}
|
|
32
|
+
else if (this.options.atEnd) {
|
|
33
|
+
prefix = 'endingWith';
|
|
34
|
+
}
|
|
35
|
+
else if (typeof this.options.atIndex === 'number') {
|
|
36
|
+
prefix = `matchingAtIndex<${this.options.atIndex}>`;
|
|
37
|
+
}
|
|
38
|
+
const isArray = Array.isArray(this.sample);
|
|
39
|
+
const samples = isArray ? this.sample : [this.sample];
|
|
40
|
+
const formattedSamples = samples
|
|
41
|
+
.map((s) => (s instanceof RegExp ? s.toString() : `"${s}"`))
|
|
42
|
+
.join(', ');
|
|
43
|
+
if (isArray) {
|
|
44
|
+
prefix = prefix ? prefix + 'OneOf' : 'oneOf';
|
|
45
|
+
}
|
|
46
|
+
return prefix ? `${prefix}<${formattedSamples}>` : formattedSamples;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function oneOf(...sample) {
|
|
50
|
+
return new OneOfMatcher(...sample);
|
|
51
|
+
}
|
|
@@ -3,27 +3,26 @@ import { waitUntil, enhanceError, compareText } from '../../utils.js';
|
|
|
3
3
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
4
4
|
const log = logger('expect-webdriverio');
|
|
5
5
|
export async function toHaveClipboardText(browser, expectedValue, options = DEFAULT_OPTIONS) {
|
|
6
|
-
const isNot = this
|
|
7
|
-
const { expectation = 'clipboard text', verb = 'have' } = this;
|
|
6
|
+
const { expectation = 'clipboard text', verb = 'have', isNot, matcherName = 'toHaveClipboardText' } = this;
|
|
8
7
|
await options.beforeAssertion?.({
|
|
9
|
-
matcherName
|
|
8
|
+
matcherName,
|
|
10
9
|
expectedValue,
|
|
11
10
|
options,
|
|
12
11
|
});
|
|
13
|
-
|
|
14
|
-
const pass = await waitUntil(async () => {
|
|
12
|
+
const { actual, success: pass } = await waitUntil(async () => {
|
|
15
13
|
await browser.setPermissions({ name: 'clipboard-read' }, 'granted')
|
|
16
14
|
.catch((err) => log.warn(`Couldn't set clipboard permissions: ${err}`));
|
|
17
|
-
actual = await browser.execute(() => window.navigator.clipboard.readText());
|
|
18
|
-
|
|
19
|
-
|
|
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 };
|
|
18
|
+
}, isNot, { wait: options.wait, interval: options.interval });
|
|
20
19
|
const message = enhanceError('browser', expectedValue, actual, this, verb, expectation, '', options);
|
|
21
20
|
const result = {
|
|
22
21
|
pass,
|
|
23
22
|
message: () => message
|
|
24
23
|
};
|
|
25
24
|
await options.afterAssertion?.({
|
|
26
|
-
matcherName
|
|
25
|
+
matcherName,
|
|
27
26
|
expectedValue,
|
|
28
27
|
options,
|
|
29
28
|
result
|
|
@@ -1,33 +1,41 @@
|
|
|
1
1
|
import { waitUntil, enhanceError, compareText } from '../../utils.js';
|
|
2
2
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
3
|
+
import { expect } from '../../index.js';
|
|
3
4
|
export async function toHaveLocalStorageItem(browser, key, expectedValue, options = DEFAULT_OPTIONS) {
|
|
4
|
-
const isNot = this
|
|
5
|
-
const { expectation = 'localStorage item', verb = 'have' } = this;
|
|
5
|
+
const { expectation = 'localStorage item', verb = 'have', isNot, matcherName = 'toHaveLocalStorageItem' } = this;
|
|
6
6
|
await options.beforeAssertion?.({
|
|
7
|
-
matcherName
|
|
7
|
+
matcherName,
|
|
8
8
|
expectedValue: expectedValue ? [key, expectedValue] : key,
|
|
9
9
|
options,
|
|
10
10
|
});
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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) => {
|
|
14
24
|
return localStorage.getItem(storageKey);
|
|
15
25
|
}, key);
|
|
16
|
-
if (expectedValue === undefined) {
|
|
17
|
-
return actual !== null;
|
|
18
|
-
}
|
|
19
26
|
if (actual === null) {
|
|
20
|
-
return false;
|
|
27
|
+
return { actual, success: false, subject: browser };
|
|
21
28
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
const compareResult = compareText(actual, expected, options);
|
|
30
|
+
return { actual, success: compareResult.success, subject: browser };
|
|
31
|
+
}, isNot, { wait: options.wait, interval: options.interval });
|
|
32
|
+
const message = enhanceError('browser', expected, actual, this, verb, expectation, key, options);
|
|
25
33
|
const result = {
|
|
26
34
|
pass,
|
|
27
35
|
message: () => message
|
|
28
36
|
};
|
|
29
37
|
await options.afterAssertion?.({
|
|
30
|
-
matcherName
|
|
38
|
+
matcherName,
|
|
31
39
|
expectedValue: expectedValue ? [key, expectedValue] : key,
|
|
32
40
|
options,
|
|
33
41
|
result
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import { waitUntil, enhanceError, compareText } from '../../utils.js';
|
|
2
2
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
3
3
|
export async function toHaveTitle(browser, expectedValue, options = DEFAULT_OPTIONS) {
|
|
4
|
-
const isNot = this
|
|
5
|
-
const { expectation = 'title', verb = 'have' } = this;
|
|
4
|
+
const { expectation = 'title', verb = 'have', isNot, matcherName = 'toHaveTitle' } = this;
|
|
6
5
|
await options.beforeAssertion?.({
|
|
7
|
-
matcherName
|
|
6
|
+
matcherName,
|
|
8
7
|
expectedValue,
|
|
9
8
|
options,
|
|
10
9
|
});
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return
|
|
15
|
-
}, isNot, options);
|
|
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 };
|
|
14
|
+
}, isNot, { wait: options.wait, interval: options.interval });
|
|
16
15
|
const message = enhanceError('window', expectedValue, actual, this, verb, expectation, '', options);
|
|
17
16
|
const result = {
|
|
18
|
-
pass,
|
|
17
|
+
pass: success,
|
|
19
18
|
message: () => message
|
|
20
19
|
};
|
|
21
20
|
await options.afterAssertion?.({
|
|
22
|
-
matcherName
|
|
21
|
+
matcherName,
|
|
23
22
|
expectedValue,
|
|
24
23
|
options,
|
|
25
24
|
result
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import { waitUntil, enhanceError, compareText } from '../../utils.js';
|
|
2
2
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
3
3
|
export async function toHaveUrl(browser, expectedValue, options = DEFAULT_OPTIONS) {
|
|
4
|
-
const isNot = this
|
|
5
|
-
const { expectation = 'url', verb = 'have' } = this;
|
|
4
|
+
const { expectation = 'url', verb = 'have', isNot, matcherName = 'toHaveUrl' } = this;
|
|
6
5
|
await options.beforeAssertion?.({
|
|
7
|
-
matcherName
|
|
6
|
+
matcherName,
|
|
8
7
|
expectedValue,
|
|
9
8
|
options,
|
|
10
9
|
});
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return
|
|
15
|
-
}, isNot, options);
|
|
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 };
|
|
14
|
+
}, isNot, { wait: options.wait, interval: options.interval });
|
|
16
15
|
const message = enhanceError('window', expectedValue, actual, this, verb, expectation, '', options);
|
|
17
16
|
const result = {
|
|
18
17
|
pass,
|
|
19
18
|
message: () => message
|
|
20
19
|
};
|
|
21
20
|
await options.afterAssertion?.({
|
|
22
|
-
matcherName
|
|
21
|
+
matcherName,
|
|
23
22
|
expectedValue,
|
|
24
23
|
options,
|
|
25
24
|
result
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { executeCommandBe
|
|
1
|
+
import { executeCommandBe } from '../../utils.js';
|
|
2
2
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
3
3
|
export async function toExist(received, options = DEFAULT_OPTIONS) {
|
|
4
4
|
this.expectation = this.expectation || 'exist';
|
|
5
5
|
this.verb = this.verb || '';
|
|
6
|
+
this.allowEmptyElements = true;
|
|
6
7
|
await options.beforeAssertion?.({
|
|
7
8
|
matcherName: 'toExist',
|
|
8
9
|
options,
|
|
@@ -16,8 +17,12 @@ export async function toExist(received, options = DEFAULT_OPTIONS) {
|
|
|
16
17
|
return result;
|
|
17
18
|
}
|
|
18
19
|
export function toBeExisting(el, options) {
|
|
19
|
-
|
|
20
|
+
this.expectation = 'existing';
|
|
21
|
+
this.verb = 'be';
|
|
22
|
+
return toExist.call(this, el, options);
|
|
20
23
|
}
|
|
21
24
|
export function toBePresent(el, options) {
|
|
22
|
-
|
|
25
|
+
this.expectation = 'present';
|
|
26
|
+
this.verb = 'be';
|
|
27
|
+
return toExist.call(this, el, options);
|
|
23
28
|
}
|
|
@@ -2,29 +2,22 @@ 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
6
|
await options.beforeAssertion?.({
|
|
6
|
-
matcherName:
|
|
7
|
+
matcherName: this.matcherName,
|
|
7
8
|
options,
|
|
8
9
|
});
|
|
9
10
|
const result = await executeCommandBe.call(this, received, el => el?.isSelected(), options);
|
|
10
11
|
await options.afterAssertion?.({
|
|
11
|
-
matcherName:
|
|
12
|
+
matcherName: this.matcherName,
|
|
12
13
|
options,
|
|
13
14
|
result
|
|
14
15
|
});
|
|
15
16
|
return result;
|
|
16
17
|
}
|
|
17
|
-
export async function toBeChecked(
|
|
18
|
+
export async function toBeChecked(received, options = DEFAULT_OPTIONS) {
|
|
18
19
|
this.expectation = 'checked';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
options,
|
|
22
|
-
});
|
|
23
|
-
const result = await toBeSelected.call(this, el, options);
|
|
24
|
-
await options.afterAssertion?.({
|
|
25
|
-
matcherName: 'toBeChecked',
|
|
26
|
-
options,
|
|
27
|
-
result
|
|
28
|
-
});
|
|
20
|
+
this.matcherName = 'toBeChecked';
|
|
21
|
+
const result = await toBeSelected.call(this, received, options);
|
|
29
22
|
return result;
|
|
30
23
|
}
|