diff --git a/.npmignore b/.npmignore
index e4d6a736f..3e5c84af7 100644
--- a/.npmignore
+++ b/.npmignore
@@ -3,6 +3,7 @@
!lib/**/*.js
!types/**/*.d.ts
!jasmine.d.ts
+!jasmine-wdio-expect-async.d.ts
!jest.d.ts
!LICENSE
!package.json
diff --git a/README.md b/README.md
index d686b272d..d9ee97d35 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# expect-webdriverio [](https://github.com/webdriverio/expect-webdriverio/actions/workflows/test.yml)
-###### [API](docs/API.md) | [TypeScript / JS Autocomplete](/docs/Types.md) | [Examples](docs/Examples.md) | [Extending Matchers](/docs/Extend.md)
+###### [API](docs/API.md) | [TypeScript / JS Autocomplete](docs/Types.md) | [Examples](docs/Examples.md) | [Extending Matchers](docs/CustomMatchers.md)
> [WebdriverIO](https://webdriver.io/) Assertion library inspired by [expect](https://www.npmjs.com/package/expect)
diff --git a/docs/API.md b/docs/API.md
index 1df796e15..76309a988 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -111,6 +111,11 @@ When set to `true` (default), the service will automatically assert all soft ass
This is useful if you want full control over when soft assertions are verified or if you want to handle soft assertion failures in a custom way.
+### Known limitations
+
+For Jasmine, using `wdio-jasmine-framework` will give a better plug-and-play experiences, else without it, the soft assertion service and custom matchers might not work/be registered correctly.
+Moreover, if Jasmine augmentation is used, the soft assertion function are not exposed in the typing, but could still work depending of your configuration. See [this issue](https://github.com/webdriverio/expect-webdriverio/issues/1893) for more details.
+
## Default Options
These default options below are connected to the [`waitforTimeout`](https://webdriver.io/docs/options#waitfortimeout) and [`waitforInterval`](https://webdriver.io/docs/options#waitforinterval) options set in the config.
@@ -667,7 +672,7 @@ await expect(mock).toBeRequestedTimes({ gte: 5, lte: 10 }) // request called at
Checks that mock was called according to the expected options.
-Most of the options supports expect/jasmine partial matchers like [expect.objectContaining](https://jestjs.io/docs/en/expect#expectobjectcontainingobject)
+Most of the options supports expect/jasmine partial matchers like [expect.objectContaining](https://jestjs.io/docs/expect#expectobjectcontainingobject)
##### Usage
@@ -858,7 +863,7 @@ await expect(elem).toHaveElementClass(/Container/i)
## Default Matchers
-In addition to the `expect-webdriverio` matchers you can use builtin Jest's [expect](https://jestjs.io/docs/en/expect) assertions or [expect/expectAsync](https://jasmine.github.io/api/3.5/global.html#expect) for Jasmine.
+In addition to the `expect-webdriverio` matchers you can use builtin Jest's [expect](https://jestjs.io/docs/expect) assertions or [expect/expectAsync](https://jasmine.github.io/api/edge/global.html#expect) for Jasmine.
## Asymmetric Matchers
diff --git a/docs/Extend.md b/docs/CustomMatchers.md
similarity index 78%
rename from docs/Extend.md
rename to docs/CustomMatchers.md
index 6975ed5a5..8fa9b00ef 100644
--- a/docs/Extend.md
+++ b/docs/CustomMatchers.md
@@ -2,8 +2,8 @@
Similar to how `expect-webdriverio` extends Jasmine/Jest matchers it's possible to add custom matchers.
-- Jasmine see [custom matchers](https://jasmine.github.io/2.5/custom_matcher.html) doc
-- Everyone else see Jest's [expect.extend](https://jestjs.io/docs/en/expect#expectextendmatchers)
+- [Jasmine](https://jasmine.github.io/) see [custom matchers](https://jasmine.github.io/tutorials/custom_matchers) doc
+- Everyone else see [Jest's expect.extend](https://jestjs.io/docs/expect#expectextendmatchers)
Custom matchers should be added in wdio `before` hook
diff --git a/docs/Examples.md b/docs/Examples.md
index a5afc7d0b..0febe90d8 100644
--- a/docs/Examples.md
+++ b/docs/Examples.md
@@ -50,7 +50,7 @@ describe('suite', () => {
WebdriverIO test runner
- Mocha https://github.com/mgrybyk/webdriverio-devtools
- Cucumber https://gitlab.com/bar_foo/wdio-cucumber-typescript
-- Jasmine https://github.com/mgrybyk/wdio-jasmine-boilerplate
+- Jasmine https://github.com/webdriverio/jasmine-boilerplate
Standalone
- Jest https://github.com/erwinheitzman/jest-webdriverio-standalone-boilerplate
diff --git a/docs/Framework.md b/docs/Framework.md
new file mode 100644
index 000000000..17f075826
--- /dev/null
+++ b/docs/Framework.md
@@ -0,0 +1,252 @@
+# Expect-WebDriverIO Framework
+
+Expect-WebDriverIO is inspired by [`expect`](https://www.npmjs.com/package/expect) but also extends it. Therefore, we can use everything provided by the expect API with some WebDriverIO enhancements. Yes, this is a package of Jest but it is usable without Jest.
+
+## Compatibility
+
+We can pair `expect-webdriverio` with [Jest](https://jestjs.io/), [Mocha](https://mochajs.org/), and [Jasmine](https://jasmine.github.io/) and even [Cucumber](https://www.npmjs.com/package/@cucumber/cucumber)
+
+It is highly recommended to use it with a [WDIO Testrunner](https://webdriver.io/docs/clioptions) which provides additional auto-configuration for a plug-and-play experience.
+
+When used **outside of [WDIO Testrunner](https://webdriver.io/docs/clioptions)**, types need to be added to your [`tsconfig.json`](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html), and some additional configuration for WDIO matchers, soft assertions, and snapshot service is required.
+
+### Jest
+We can use `expect-webdriverio` with [Jest](https://jestjs.io/) using [`@jest/globals`](https://www.npmjs.com/package/@jest/globals) alone (preferred) and optionally [`@types/jest`](https://www.npmjs.com/package/@types/jest) (which has global ambient support).
+ - Note: Jest maintainers do not support [`@types/jest`](https://www.npmjs.com/package/@types/jest). If this library gets out of date or has problems, support might be dropped.
+ - Note: With Jest, the matchers `toMatchSnapshot` and `toMatchInlineSnapshot` are overloaded. To resolve the types correctly, `expect-webdriverio/jest` must be last.
+
+#### With `@jest/globals`
+When paired only with [`@jest/globals`](https://www.npmjs.com/package/@jest/globals), we should `import` the `expect` function from `expect-webdriverio`.
+
+```ts
+import { expect } from 'expect-webdriverio'
+import { describe, it, expect as jestExpect } from '@jest/globals'
+
+describe('My tests', async () => {
+ it('should verify my browser to have the expected url', async () => {
+ await expect(browser).toHaveUrl('https://example.com')
+ })
+})
+```
+
+No `types` are expected in `tsconfig.json`.
+Optionally, to avoid needing `import { expect } from 'expect-webdriverio'`, you can use the following:
+
+
+```json
+{
+ "compilerOptions": {
+ "types": ["expect-webdriverio/expect-global"]
+ }
+}
+```
+##### Augmenting `@jest/globals` JestMatchers
+Multiple attempts were made to augment `@jest/globals` to support `expect-webdriverio` matchers directly on JestMatchers. However, no namespace is available to augment it; therefore, only module augmentation can be used. This method does not allow adding matchers with the `extends` keyword; instead, they need to be added directly in the interface of the module declaration augmentation, which would create a lot of code duplication.
+
+This [Jest issue](https://github.com/jestjs/jest/issues/12424) seems to target this problem, but it is still in progress.
+
+#### With `@types/jest`
+When also paired with [`@types/jest`](https://www.npmjs.com/package/@types/jest), no imports are required. Global ambient types are already defined correctly and you can simply use Jest's `expect` directly.
+
+If you are NOT using WDIO Testrunner, some prerequisite configuration is required.
+
+Option 1: Replace the expect globally with the `expect-webdriverio` one:
+```ts
+import { expect } from "expect-webdriverio";
+(globalThis as any).expect = expect;
+```
+
+Option 2: Reconfigure Jest's expect with the custom matchers and the soft assertion:
+```ts
+// Configure the custom matchers:
+import { expect } from "@jest/globals";
+import { matchers } from "expect-webdriverio";
+
+beforeAll(async () => {
+ expect.extend(matchers as Record);
+});
+```
+
+[Optional] For the soft assertion, the `createSoftExpect` is currently not correctly exposed but the below works:
+```ts
+// @ts-ignore
+import * as createSoftExpect from "expect-webdriverio/lib/softExpect";
+
+beforeAll(async () => {
+ Object.defineProperty(expect, "soft", {
+ value: (actual: T) => createSoftExpect.default(actual),
+ });
+
+ // Add soft assertions utility methods
+ Object.defineProperty(expect, "getSoftFailures", {
+ value: (testId?: string) => SoftAssertService.getInstance().getFailures(testId),
+ });
+
+ Object.defineProperty(expect, "assertSoftFailures", {
+ value: (testId?: string) => SoftAssertService.getInstance().assertNoFailures(testId),
+ });
+
+ Object.defineProperty(expect, "clearSoftFailures", {
+ value: (testId?: string) => SoftAssertService.getInstance().clearFailures(testId),
+ });
+});
+```
+
+Then as shown below, no imports are required and we can use WDIO matchers directly on Jest's `expect`:
+```ts
+describe('My tests', async () => {
+ it('should verify my browser to have the expected url', async () => {
+ await expect(browser).toHaveUrl('https://example.com')
+ })
+})
+```
+
+Expected in `tsconfig.json`:
+```json
+{
+ "compilerOptions": {
+ "types": [
+ "@types/jest",
+ "expect-webdriverio/jest" // Must be last for overloaded matchers `toMatchSnapshot` and `toMatchInlineSnapshot`
+ ]
+ }
+}
+```
+
+### Mocha
+When paired with [Mocha](https://mochajs.org/), it can be used without (standalone) or with [`chai`](https://www.chaijs.com/) (or any other assertion library).
+
+#### Standalone
+No import is required; everything is set globally.
+
+```ts
+describe('My tests', async () => {
+ it('should verify my browser to have the expected url', async () => {
+ await expect(browser).toHaveUrl('https://example.com')
+ })
+})
+```
+
+Expected in `tsconfig.json`:
+```json
+{
+ "compilerOptions": {
+ "types": [
+ "@types/mocha",
+ "expect-webdriverio/expect-global"
+ ]
+ }
+}
+```
+
+#### Chai
+`expect-webdriverio` can coexist with the [Chai](https://www.chaijs.com/) assertion library by importing both libraries explicitly.
+See also this [documentation](https://webdriver.io/docs/assertion/#migrating-from-chai).
+
+### Jasmine
+When paired with [Jasmine](https://jasmine.github.io/), [`@wdio/jasmine-framework`](https://www.npmjs.com/package/@wdio/jasmine-framework) is also required to configure it correctly, as it needs to force `expect` to be `expectAsync` and also register the WDIO matchers with `addAsyncMatcher` since `expect-webdriverio` only supports the Jest-style `expect.extend` version.
+
+The types `expect-webdriverio/jasmine` are still offered but are subject to removal or being moved into `@wdio/jasmine-framework`. The usage of `expectAsync` is also subject to future removal.
+
+#### Jasmine `expectAsync`
+When not using `@wdio/globals/types` or having `@types/jasmine` before it, the Jasmine expect is shown as the global ambient type. Therefore, when also defining `expect-webdriverio/jasmine`, we can use WDIO custom matchers on the `expectAsync`. Without `@wdio/jasmine-framework`, matchers will need to be registered manually.
+
+```ts
+describe('My tests', async () => {
+ it('should verify my browser to have the expected url', async () => {
+ await expectAsync(browser).toHaveUrl('https://example.com')
+ await expectAsync(true).toBe(true)
+ })
+})
+```
+
+Expected in `tsconfig.json`:
+```json
+{
+ "compilerOptions": {
+ "types": [
+ "@types/jasmine",
+ "expect-webdriverio/jasmine"
+ ]
+ }
+}
+```
+
+#### Global `expectAsync` force as `expect`
+When the global ambiant is the `expect` of wdio but forced to be `expectAsync` under the hood, like when using `@wdio/jasmine-framework`, then even the basic matchers need to be awaited
+
+```ts
+describe('My tests', async () => {
+ it('should verify my browser to have the expected url', async () => {
+ await expect(browser).toHaveUrl('https://example.com')
+
+ // Even basic matchers requires expect since they are promises underneath
+ await expect(true).toBe(true)
+ })
+})
+```
+
+Expected in `tsconfig.json`:
+```json
+{
+ "compilerOptions": {
+ "types": [
+ "@wdio/globals/types",
+ "@wdio/jasmine-framework",
+ "@types/jasmine",
+ "expect-webdriverio/jasmine-wdio-expect-async", // Force expect to return Promises
+ ]
+ }
+}
+```
+
+#### `expect` of `expect-webdriverio`
+It is preferable to use the `expect` from `expect-webdriverio` to guarantee future compatibility.
+
+```ts
+// Required if we do not force the 'expect-webdriverio' expect globally with `"expect-webdriverio/expect-global"`
+import { expect as wdioExpect } from 'expect-webdriverio'
+
+describe('My tests', async () => {
+ it('should verify my browser to have the expected url', async () => {
+ await wdioExpect(browser).toHaveUrl('https://example.com')
+
+ // No required await
+ wdioExpect(true).toBe(true)
+ })
+})
+
+
+Expected in `tsconfig.json`:
+```json
+{
+ "compilerOptions": {
+ "types": [
+ "@types/jasmine",
+ // "expect-webdriverio/expect-global", // Optional to have the global ambient expect the one of wdio
+ ]
+ }
+}
+```
+
+
+#### Asymmetric matchers
+Asymmetric matchers have limited support. Even though `jasmine.stringContaining` does not produce a typing error, it may not work even with `@wdio/jasmine-framework`. However, the example below should work:
+
+```ts
+describe('My tests', async () => {
+ it('should verify my browser to have the expected url', async () => {
+ await expectAsync(browser).toHaveUrl(wdioExpect.stringContaining('WebdriverIO'))
+ })
+})
+```
+
+
+### Jest & Jasmine Augmentation Notes
+
+If you are already using Jest or Jasmine globally, using `import { expect } from 'expect-webdriverio'` is the most compatible approach, even though augmentation exists.
+It is recommended to build your project using this approach instead of relying on augmentation, to ensure future compatibility and avoid augmentation limitations. See [this issue](https://github.com/webdriverio/expect-webdriverio/issues/1893) for more information.
+
+### Cucumber
+
+More details to come. In short, when paired with `@wdio/cucumber-framework`, you can use WDIO's expect with Cucumber and even [Gherkin](https://www.npmjs.com/package/@cucumber/gherkin).
\ No newline at end of file
diff --git a/docs/Types.md b/docs/Types.md
index ffc12287f..de1f486ab 100644
--- a/docs/Types.md
+++ b/docs/Types.md
@@ -1,14 +1,17 @@
+# Types Definition
## TypeScript
If you are using the [WDIO Testrunner](https://webdriver.io/docs/clioptions) everything will be automatically setup. Just follow the [setup guide](https://webdriver.io/docs/typescript#framework-setup) from the docs. However if you run WebdriverIO with a different testrunner or in a simple Node.js script you will need to add `expect-webdriverio` to `types` in the `tsconfig.json`.
-- `"expect-webdriverio"` for everyone except of Jasmine/Jest users.
-- `"expect-webdriverio/jasmine"` Jasmine
-- `"expect-webdriverio/jest"` Jest
+- `"expect-webdriverio"` for everyone except Jasmine/Jest users.
+- `"expect-webdriverio/jasmine"` for [Jasmine](https://jasmine.github.io/)
+- `"expect-webdriverio/jest"` for [Jest](https://jestjs.io/)
+- `"expect-webdriverio/expect-global"` // Optional, if you wish to use expect of `expect-webdriverio` globally without explicit import
+ - Note: Same as the former `"expect-webdriverio/types"`, now deprecated!
## JavaScript (VSCode)
-It's required to create `jsconfig.json` in project root and refer to the type definitions to make autocompletion work in vanilla js.
+It's required to create [`jsconfig.json`](https://code.visualstudio.com/docs/languages/jsconfig) in project root and refer to the type definitions to make autocompletion work in vanilla js.
```json
{
@@ -19,3 +22,15 @@ It's required to create `jsconfig.json` in project root and refer to the type de
]
}
```
+
+## Jasmine special case
+[Jasmine](https://jasmine.github.io/) is different from [Jest](https://jestjs.io/) or the standard `expect` definition since it supports promises using `expectAsync` which makes it quite challenging.
+
+Even though this library by itself is not fully Jasmine-ready, it offers the types of the matcher only on the `AsyncMatcher` since using `jasmine.expect` does not work out-of-the-box. However, if you are pulling on the `expect` of `expect-webdriverio`, you will be able to have the WebDriverIO matcher types on `expect`.
+
+Support of `expectAsync` keyword is subject to change and may be dropped in the future!
+
+### Dependency on `@wdio/jasmine-framework`
+As mentioned above, this library alone is not working with Jasmine. It is required to manually do some tweaks, or it is strongly recommended to also pair it with `@wdio/jasmine-framework`. See [Framework.md](Framework.md) for more information.
+
+When using [`@wdio/jasmine-framework`](https://www.npmjs.com/package/@wdio/jasmine-framework), since it replaces `jasmine.expect` with `jasmine.expectAsync`, then matchers are usable on the keyword `expect`, but still typing on `expect` directly from [Jasmine](https://jasmine.github.io/) namespace is not supported as of today!
\ No newline at end of file
diff --git a/jasmine-wdio-expect-async.d.ts b/jasmine-wdio-expect-async.d.ts
new file mode 100644
index 000000000..b9caa3d58
--- /dev/null
+++ b/jasmine-wdio-expect-async.d.ts
@@ -0,0 +1,22 @@
+///
+
+/**
+ * Overrides the default wdio `expect` for Jasmine case specifically since the `expect` is now completely asynchronous which is not the case under Jest or standalone.
+ */
+declare namespace ExpectWebdriverIO {
+ interface Expect extends ExpectWebdriverIO.AsymmetricMatchers, ExpectLibInverse, WdioExpect {
+ /**
+ * The `expect` function is used every time you want to test a value.
+ * You will rarely call `expect` by itself.
+ *
+ * expect function declaration contains two generics:
+ * - T: the type of the actual value, e.g. any type, not just WebdriverIO.Browser or WebdriverIO.Element
+ * - R: the type of the return value, e.g. Promise or void
+ *
+ * Note: The function must stay here in the namespace to overwrite correctly the expect function from the expect library.
+ *
+ * @param actual The value to apply matchers against.
+ */
+ (actual: T): ExpectWebdriverIO.MatchersAndInverse, T>
+ }
+}
\ No newline at end of file
diff --git a/jasmine.d.ts b/jasmine.d.ts
index 0d9ddcddd..d13a1f2f2 100644
--- a/jasmine.d.ts
+++ b/jasmine.d.ts
@@ -1,6 +1,27 @@
///
declare namespace jasmine {
+
+ /**
+ * Async matchers for Jasmine to allow the typing of `expectAsync` with WebDriverIO matchers.
+ * T is the type of the actual value
+ * U is the type of the expected value
+ * Both T,U must stay named as they are to override the default `AsyncMatchers` type from Jasmine.
+ *
+ * We force Matchers to return a `Promise` since Jasmine's `expectAsync` expects a promise in all cases (different from Jest)
+ */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
- interface AsyncMatchers extends ExpectWebdriverIO.Matchers, T> {}
-}
+ interface AsyncMatchers extends Omit, T>, 'toMatchSnapshot' | 'toMatchInlineSnapshot'> {
+ /**
+ * snapshot matcher
+ * @param label optional snapshot label
+ */
+ toMatchSnapshot(label?: string): Promise;
+ /**
+ * inline snapshot matcher
+ * @param snapshot snapshot string (autogenerated if not specified)
+ * @param label optional snapshot label
+ */
+ toMatchInlineSnapshot(snapshot?: string, label?: string): Promise;
+ }
+}
\ No newline at end of file
diff --git a/jest.d.ts b/jest.d.ts
index 3d0182db7..cf96fe737 100644
--- a/jest.d.ts
+++ b/jest.d.ts
@@ -1,5 +1,37 @@
///
+/**
+ * Augment the Jest namespace to include the matchers from expect-webdriverio.
+ * When Jest Library is used, it specifies `expect-webdriverio/jest` for this file in the tsconfig.json's types.
+ */
+
declare namespace jest {
- interface Matchers extends ExpectWebdriverIO.Matchers { }
-}
+
+ interface Matchers, T> extends ExpectWebdriverIO.Matchers {
+
+ /**
+ * Below are overloaded Jest's matchers not part of `expect` but of `jest-snapshot`.
+ * @see https://github.com/jestjs/jest/blob/73dbef5d2d3195a1e55fb254c54cce70d3036252/packages/jest-snapshot/src/types.ts#L37
+ *
+ * Note: We need to define them below so that they are correctly typed overloaded.
+ * Else even when extending `WdioJestOverloadedMatchers` we have typing errors.
+ */
+
+ /**
+ * snapshot matcher
+ * @param label optional snapshot label
+ */
+ toMatchSnapshot(label?: string): T extends WdioPromiseLike ? Promise : void;
+
+ /**
+ * inline snapshot matcher
+ * @param snapshot snapshot string (autogenerated if not specified)
+ * @param label optional snapshot label
+ */
+ toMatchInlineSnapshot(snapshot?: string, label?: string): T extends WdioPromiseLike ? Promise : void;
+ }
+
+ interface Expect extends ExpectWebdriverIO.Expect {}
+
+ interface InverseAsymmetricMatchers extends ExpectWebdriverIO.AsymmetricMatchers {}
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index fa6a01d08..251d73a5d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,12 +11,15 @@
"dependencies": {
"@vitest/snapshot": "^3.2.4",
"deep-eql": "^5.0.2",
- "expect": "^30.0.0",
- "jest-matcher-utils": "^30.0.0"
+ "expect": "^30.0.4",
+ "jest-matcher-utils": "^30.0.4"
},
"devDependencies": {
+ "@jest/globals": "^30.0.4",
"@types/debug": "^4.1.12",
+ "@types/jasmine": "^5.1.8",
"@types/jest": "^30.0.0",
+ "@types/mocha": "^10.0.10",
"@types/node": "^24.0.3",
"@vitest/coverage-v8": "^3.2.4",
"@wdio/eslint": "^0.1.1",
@@ -33,7 +36,7 @@
"webdriverio": "^9.15.0"
},
"engines": {
- "node": ">=18 || >=20 || >=22"
+ "node": ">=20"
},
"peerDependencies": {
"@wdio/globals": "^9.0.0",
@@ -80,16 +83,172 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-string-parser": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
- "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
+ "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
+ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.0",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.27.3",
+ "@babel/helpers": "^7.27.6",
+ "@babel/parser": "^7.28.0",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.0",
+ "@babel/types": "^7.28.0",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
+ "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.0",
+ "@babel/types": "^7.28.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
+ "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-validator-identifier": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
@@ -99,14 +258,38 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.27.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
+ "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.27.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/parser": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
- "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
+ "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.26.0"
+ "@babel/types": "^7.28.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -115,15 +298,288 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz",
+ "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
+ "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
+ "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
+ "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.0",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/types": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
- "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz",
+ "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -1195,6 +1651,120 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
@@ -1214,10 +1784,40 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/@jest/environment": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.4.tgz",
+ "integrity": "sha512-5NT+sr7ZOb8wW7C4r7wOKnRQ8zmRWQT2gW4j73IXAKp5/PX1Z8MCStBLQDYfIG3n1Sw0NRfYGdp0iIPVooBAFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/fake-timers": "30.0.4",
+ "@jest/types": "30.0.1",
+ "@types/node": "*",
+ "jest-mock": "30.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.4.tgz",
+ "integrity": "sha512-Z/DL7t67LBHSX4UzDyeYKqOxE/n7lbrrgEwWM3dGiH5Dgn35nk+YtgzKudmfIrBI8DRRrKYY5BCo3317HZV1Fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "30.0.4",
+ "jest-snapshot": "30.0.4"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
"node_modules/@jest/expect-utils": {
- "version": "30.0.2",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.2.tgz",
- "integrity": "sha512-FHF2YdtFBUQOo0/qdgt+6UdBFcNPF/TkVzcc+4vvf8uaBzUlONytGBeeudufIHHW1khRfM1sBbRT1VCK7n/0dQ==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.4.tgz",
+ "integrity": "sha512-EgXecHDNfANeqOkcak0DxsoVI4qkDUsR7n/Lr2vtmTBjwLPBnnPOF71S11Q8IObWzxm2QgQoY6f9hzrRD3gHRA==",
"license": "MIT",
"dependencies": {
"@jest/get-type": "30.0.1"
@@ -1226,6 +1826,24 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/@jest/fake-timers": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.4.tgz",
+ "integrity": "sha512-qZ7nxOcL5+gwBO6LErvwVy5k06VsX/deqo2XnVUSTV0TNC9lrg8FC3dARbi+5lmrr5VyX5drragK+xLcOjvjYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.0.1",
+ "@sinonjs/fake-timers": "^13.0.0",
+ "@types/node": "*",
+ "jest-message-util": "30.0.2",
+ "jest-mock": "30.0.2",
+ "jest-util": "30.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
"node_modules/@jest/get-type": {
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz",
@@ -1235,11 +1853,26 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/@jest/globals": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.4.tgz",
+ "integrity": "sha512-avyZuxEHF2EUhFF6NEWVdxkRRV6iXXcIES66DLhuLlU7lXhtFG/ySq/a8SRZmEJSsLkNAFX6z6mm8KWyXe9OEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "30.0.4",
+ "@jest/expect": "30.0.4",
+ "@jest/types": "30.0.1",
+ "jest-mock": "30.0.2"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
"node_modules/@jest/pattern": {
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz",
"integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==",
- "license": "MIT",
"dependencies": {
"@types/node": "*",
"jest-regex-util": "30.0.1"
@@ -1262,11 +1895,119 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
+ "node_modules/@jest/snapshot-utils": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.4.tgz",
+ "integrity": "sha512-BEpX8M/Y5lG7MI3fmiO+xCnacOrVsnbqVrcDZIT8aSGkKV1w2WwvRQxSWw5SIS8ozg7+h8tSj5EO1Riqqxcdag==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.0.1",
+ "chalk": "^4.1.2",
+ "graceful-fs": "^4.2.11",
+ "natural-compare": "^1.4.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/snapshot-utils/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@jest/snapshot-utils/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.4.tgz",
+ "integrity": "sha512-atvy4hRph/UxdCIBp+UB2jhEA/jJiUeGZ7QPgBi9jUUKNgi3WEoMXGNG7zbbELG2+88PMabUNCDchmqgJy3ELg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@jest/types": "30.0.1",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "babel-plugin-istanbul": "^7.0.0",
+ "chalk": "^4.1.2",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.11",
+ "jest-haste-map": "30.0.2",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.0.2",
+ "micromatch": "^4.0.8",
+ "pirates": "^4.0.7",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^5.0.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@jest/transform/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@jest/transform/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
"node_modules/@jest/types": {
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz",
"integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==",
- "license": "MIT",
"dependencies": {
"@jest/pattern": "30.0.1",
"@jest/schemas": "30.0.1",
@@ -1284,7 +2025,6 @@
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz",
"integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==",
- "license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
},
@@ -1293,16 +2033,14 @@
}
},
"node_modules/@jest/types/node_modules/@sinclair/typebox": {
- "version": "0.34.36",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.36.tgz",
- "integrity": "sha512-JFHFhF6MqqRE49JDAGX/EPlHwxIukrKMhNwlMoB/wIJBkvu3+ciO335yDYPP3soI01FkhVXWnyNPKEl+EsC4Zw==",
- "license": "MIT"
+ "version": "0.34.35",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz",
+ "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A=="
},
"node_modules/@jest/types/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -1317,7 +2055,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -1330,18 +2067,14 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
- "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
@@ -1354,16 +2087,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
@@ -1371,9 +2094,9 @@
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "version": "0.3.29",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
+ "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1653,6 +2376,19 @@
"node": ">=14"
}
},
+ "node_modules/@pkgr/core": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz",
+ "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
"node_modules/@promptbook/utils": {
"version": "0.69.5",
"resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.69.5.tgz",
@@ -1982,6 +2718,26 @@
"optional": true,
"peer": true
},
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "13.0.5",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz",
+ "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.1"
+ }
+ },
"node_modules/@stylistic/eslint-plugin": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.2.0.tgz",
@@ -2092,6 +2848,12 @@
"@types/istanbul-lib-report": "*"
}
},
+ "node_modules/@types/jasmine": {
+ "version": "5.1.8",
+ "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.8.tgz",
+ "integrity": "sha512-u7/CnvRdh6AaaIzYjCgUuVbREFgulhX05Qtf6ZtW+aOcjCKKVvKgpkPYJBFTZSHtFBYimzU4zP0V2vrEsq9Wcg==",
+ "dev": true
+ },
"node_modules/@types/jest": {
"version": "30.0.0",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz",
@@ -2158,6 +2920,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/mocha": {
+ "version": "10.0.10",
+ "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz",
+ "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/ms": {
"version": "0.7.34",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
@@ -2469,6 +3238,13 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/@vitest/coverage-v8": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz",
@@ -3275,6 +4051,20 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/archiver": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz",
@@ -3456,6 +4246,111 @@
"devOptional": true,
"license": "Apache-2.0"
},
+ "node_modules/babel-plugin-istanbul": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz",
+ "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-instrument": "^6.0.2",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
+ "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -3639,6 +4534,16 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@@ -3785,6 +4690,16 @@
"node": ">=6"
}
},
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001707",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz",
@@ -5107,14 +6022,14 @@
"license": "ISC"
},
"node_modules/expect": {
- "version": "30.0.2",
- "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.2.tgz",
- "integrity": "sha512-YN9Mgv2mtTWXVmifQq3QT+ixCL/uLuLJw+fdp8MOjKqu8K3XQh3o5aulMM1tn+O2DdrWNxLZTeJsCY/VofUA0A==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.4.tgz",
+ "integrity": "sha512-dDLGjnP2cKbEppxVICxI/Uf4YemmGMPNy0QytCbfafbpYk9AFQsxb8Uyrxii0RPK7FWgLGlSem+07WirwS3cFQ==",
"license": "MIT",
"dependencies": {
- "@jest/expect-utils": "30.0.2",
+ "@jest/expect-utils": "30.0.4",
"@jest/get-type": "30.0.1",
- "jest-matcher-utils": "30.0.2",
+ "jest-matcher-utils": "30.0.4",
"jest-message-util": "30.0.2",
"jest-mock": "30.0.2",
"jest-util": "30.0.2"
@@ -5557,6 +6472,16 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -5691,6 +6616,13 @@
"node": ">=12.20.0"
}
},
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -5756,6 +6688,16 @@
"node": "^16.13.0 || >=18.0.0"
}
},
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -5779,6 +6721,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/get-port": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz",
@@ -6174,6 +7126,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -6433,6 +7397,23 @@
"node": ">=8"
}
},
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
@@ -6494,9 +7475,9 @@
}
},
"node_modules/jest-diff": {
- "version": "30.0.2",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.2.tgz",
- "integrity": "sha512-2UjrNvDJDn/oHFpPrUTVmvYYDNeNtw2DlY3er8bI6vJJb9Fb35ycp/jFLd5RdV59tJ8ekVXX3o/nwPcscgXZJQ==",
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.4.tgz",
+ "integrity": "sha512-TSjceIf6797jyd+R64NXqicttROD+Qf98fex7CowmlSn7f8+En0da1Dglwr1AXxDtVizoxXYZBlUQwNhoOXkNw==",
"license": "MIT",
"dependencies": {
"@jest/diff-sequences": "30.0.1",
@@ -6521,9 +7502,9 @@
}
},
"node_modules/jest-diff/node_modules/@sinclair/typebox": {
- "version": "0.34.36",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.36.tgz",
- "integrity": "sha512-JFHFhF6MqqRE49JDAGX/EPlHwxIukrKMhNwlMoB/wIJBkvu3+ciO335yDYPP3soI01FkhVXWnyNPKEl+EsC4Zw==",
+ "version": "0.34.37",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz",
+ "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==",
"license": "MIT"
},
"node_modules/jest-diff/node_modules/ansi-styles": {
@@ -6594,15 +7575,40 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
- "node_modules/jest-matcher-utils": {
+ "node_modules/jest-haste-map": {
"version": "30.0.2",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.2.tgz",
- "integrity": "sha512-1FKwgJYECR8IT93KMKmjKHSLyru0DqguThov/aWpFccC0wbiXGOxYEu7SScderBD7ruDOpl7lc5NG6w3oxKfaA==",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.2.tgz",
+ "integrity": "sha512-telJBKpNLeCb4MaX+I5k496556Y2FiKR/QLZc0+MGBYl4k3OO0472drlV2LUe7c1Glng5HuAu+5GLYp//GpdOQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "30.0.1",
+ "@types/node": "*",
+ "anymatch": "^3.1.3",
+ "fb-watchman": "^2.0.2",
+ "graceful-fs": "^4.2.11",
+ "jest-regex-util": "30.0.1",
+ "jest-util": "30.0.2",
+ "jest-worker": "30.0.2",
+ "micromatch": "^4.0.8",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.3"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.4.tgz",
+ "integrity": "sha512-ubCewJ54YzeAZ2JeHHGVoU+eDIpQFsfPQs0xURPWoNiO42LGJ+QGgfSf+hFIRplkZDkhH5MOvuxHKXRTUU3dUQ==",
"license": "MIT",
"dependencies": {
"@jest/get-type": "30.0.1",
"chalk": "^4.1.2",
- "jest-diff": "30.0.2",
+ "jest-diff": "30.0.4",
"pretty-format": "30.0.2"
},
"engines": {
@@ -6622,9 +7628,9 @@
}
},
"node_modules/jest-matcher-utils/node_modules/@sinclair/typebox": {
- "version": "0.34.36",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.36.tgz",
- "integrity": "sha512-JFHFhF6MqqRE49JDAGX/EPlHwxIukrKMhNwlMoB/wIJBkvu3+ciO335yDYPP3soI01FkhVXWnyNPKEl+EsC4Zw==",
+ "version": "0.34.37",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz",
+ "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==",
"license": "MIT"
},
"node_modules/jest-matcher-utils/node_modules/ansi-styles": {
@@ -6688,7 +7694,6 @@
"version": "30.0.2",
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.2.tgz",
"integrity": "sha512-vXywcxmr0SsKXF/bAD7t7nMamRvPuJkras00gqYeB1V0WllxZrbZ0paRr3XqpFU2sYYjD0qAaG2fRyn/CGZ0aw==",
- "license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@jest/types": "30.0.1",
@@ -6708,7 +7713,6 @@
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz",
"integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==",
- "license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.34.0"
},
@@ -6717,16 +7721,14 @@
}
},
"node_modules/jest-message-util/node_modules/@sinclair/typebox": {
- "version": "0.34.36",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.36.tgz",
- "integrity": "sha512-JFHFhF6MqqRE49JDAGX/EPlHwxIukrKMhNwlMoB/wIJBkvu3+ciO335yDYPP3soI01FkhVXWnyNPKEl+EsC4Zw==",
- "license": "MIT"
+ "version": "0.34.35",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz",
+ "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A=="
},
"node_modules/jest-message-util/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -6741,7 +7743,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -6757,7 +7758,6 @@
"version": "30.0.2",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
"integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
- "license": "MIT",
"dependencies": {
"@jest/schemas": "30.0.1",
"ansi-styles": "^5.2.0",
@@ -6771,7 +7771,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "license": "MIT",
"engines": {
"node": ">=10"
},
@@ -6783,7 +7782,6 @@
"version": "30.0.2",
"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.2.tgz",
"integrity": "sha512-PnZOHmqup/9cT/y+pXIVbbi8ID6U1XHRmbvR7MvUy4SLqhCbwpkmXhLbsWbGewHrV5x/1bF7YDjs+x24/QSvFA==",
- "license": "MIT",
"dependencies": {
"@jest/types": "30.0.1",
"@types/node": "*",
@@ -6797,16 +7795,128 @@
"version": "30.0.1",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz",
"integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==",
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "30.0.4",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.4.tgz",
+ "integrity": "sha512-S/8hmSkeUib8WRUq9pWEb5zMfsOjiYWDWzFzKnjX7eDyKKgimsu9hcmsUEg8a7dPAw8s/FacxsXquq71pDgPjQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.27.4",
+ "@babel/generator": "^7.27.5",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-syntax-typescript": "^7.27.1",
+ "@babel/types": "^7.27.3",
+ "@jest/expect-utils": "30.0.4",
+ "@jest/get-type": "30.0.1",
+ "@jest/snapshot-utils": "30.0.4",
+ "@jest/transform": "30.0.4",
+ "@jest/types": "30.0.1",
+ "babel-preset-current-node-syntax": "^1.1.0",
+ "chalk": "^4.1.2",
+ "expect": "30.0.4",
+ "graceful-fs": "^4.2.11",
+ "jest-diff": "30.0.4",
+ "jest-matcher-utils": "30.0.4",
+ "jest-message-util": "30.0.2",
+ "jest-util": "30.0.2",
+ "pretty-format": "30.0.2",
+ "semver": "^7.7.2",
+ "synckit": "^0.11.8"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/@jest/schemas": {
+ "version": "30.0.1",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz",
+ "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.34.0"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/@sinclair/typebox": {
+ "version": "0.34.37",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz",
+ "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jest-snapshot/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/pretty-format": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz",
+ "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "30.0.1",
+ "ansi-styles": "^5.2.0",
+ "react-is": "^18.3.1"
+ },
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
+ "node_modules/jest-snapshot/node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
"node_modules/jest-util": {
"version": "30.0.2",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.2.tgz",
"integrity": "sha512-8IyqfKS4MqprBuUpZNlFB5l+WFehc8bfCe1HSZFHzft2mOuND8Cvi9r1musli+u6F3TqanCZ/Ik4H4pXUolZIg==",
- "license": "MIT",
"dependencies": {
"@jest/types": "30.0.1",
"@types/node": "*",
@@ -6823,7 +7933,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -6838,7 +7947,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -6854,7 +7962,6 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "license": "MIT",
"engines": {
"node": ">=12"
},
@@ -6862,6 +7969,39 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/jest-worker": {
+ "version": "30.0.2",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.2.tgz",
+ "integrity": "sha512-RN1eQmx7qSLFA+o9pfJKlqViwL5wt+OL3Vff/A+/cPsmuw7NPwfgl33AP+/agRmHzPOFgXviRycR9kYwlcRQXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@ungap/structured-clone": "^1.3.0",
+ "jest-util": "30.0.2",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.1.1"
+ },
+ "engines": {
+ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
"node_modules/jiti": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
@@ -6940,6 +8080,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@@ -7309,6 +8462,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
"node_modules/memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
@@ -7564,6 +8727,13 @@
"node": ">= 12"
}
},
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@@ -7896,6 +9066,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/pac-proxy-agent": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.1.0.tgz",
@@ -8029,6 +9209,16 @@
"node": ">=8"
}
},
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -8126,6 +9316,16 @@
"node": ">=0.10"
}
},
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/pkg-types": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz",
@@ -9315,6 +10515,22 @@
"node": ">=8"
}
},
+ "node_modules/synckit": {
+ "version": "0.11.8",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz",
+ "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.2.4"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
"node_modules/tar-fs": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz",
@@ -9532,6 +10748,13 @@
"node": ">=0.6.0"
}
},
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -9577,6 +10800,16 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/type-fest": {
"version": "4.38.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.38.0.tgz",
@@ -10035,6 +11268,16 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
@@ -10924,6 +12167,20 @@
"devOptional": true,
"license": "ISC"
},
+ "node_modules/write-file-atomic": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz",
+ "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
"node_modules/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
@@ -10955,6 +12212,13 @@
"node": ">=10"
}
},
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
diff --git a/package.json b/package.json
index 41c8ffd53..474969087 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"exports": {
".": [
{
- "types": "./types/standalone.d.ts",
+ "types": "./types/expect-webdriverio.d.ts",
"import": "./lib/index.js"
},
"./lib/index.js"
@@ -33,42 +33,52 @@
"types": "./jest.d.ts"
}
],
- "./types": "./types/jest-global.d.ts"
+ "./jasmine": [
+ {
+ "types": "./jasmine.d.ts"
+ }
+ ],
+ "./types": "./types/expect-global.d.ts",
+ "./expect-global": "./types/expect-global.d.ts"
},
- "types": "./types/standalone.d.ts",
+ "types": "./types/expect-webdriverio.d.ts",
"typeScriptVersion": "3.8.3",
"engines": {
- "node": ">=18 || >=20 || >=22"
+ "node": ">=20"
},
"scripts": {
"build": "run-s clean compile",
"clean": "run-p clean:*",
"clean:build": "rimraf ./lib",
- "clean:tests": "rimraf test-types/**/node_modules && rimraf test-types/**/dist",
"compile": "tsc --build tsconfig.build.json",
- "tsc:root-types": "tsc jasmine.d.ts jest.d.ts",
+ "tsc:root-types": "node types-checks-filter-out-node_modules.js",
"test": "run-s test:*",
"test:tsc": "tsc --project tsconfig.json --noEmit",
"test:lint": "eslint .",
"test:unit": "vitest --run",
- "test:types": "node test-types/copy && npm run ts && npm run clean:tests && npm run tsc:root-types",
- "ts": "run-s ts:*",
- "ts:default": "cd test-types/default && tsc -p ./tsconfig.json --incremental",
- "ts:jest": "cd test-types/jest && tsc -p ./tsconfig.json --incremental",
- "ts:jasmine": "cd test-types/jasmine && tsc -p ./tsconfig.json --incremental",
+ "test:types": "npm run ts && npm run tsc:root-types",
+ "ts": "run-s ts:* ts:*:*",
+ "ts:jest:@jest/global": "cd test-types/jest-@jest_global && tsc --project ./tsconfig.json",
+ "ts:jest:@types-jest": "cd test-types/jest-@types_jest && tsc --project ./tsconfig.json",
+ "ts:mocha": "cd test-types/mocha && tsc --project ./tsconfig.json",
+ "ts:jasmine": "cd test-types/jasmine && tsc --project ./tsconfig.json",
+ "ts:jasmine-async": "cd test-types/jasmine-async && tsc --project ./tsconfig.json",
"watch": "npm run compile -- --watch",
"prepare": "husky install"
},
"dependencies": {
"@vitest/snapshot": "^3.2.4",
"deep-eql": "^5.0.2",
- "expect": "^30.0.0",
- "jest-matcher-utils": "^30.0.0"
+ "expect": "^30.0.4",
+ "jest-matcher-utils": "^30.0.4"
},
"devDependencies": {
"@types/debug": "^4.1.12",
+ "@types/jasmine": "^5.1.8",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.3",
+ "@types/mocha": "^10.0.10",
+ "@jest/globals": "^30.0.4",
"@vitest/coverage-v8": "^3.2.4",
"@wdio/eslint": "^0.1.1",
"@wdio/types": "^9.15.0",
diff --git a/src/index.ts b/src/index.ts
index b79759e75..c88bd5c10 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,13 +1,12 @@
-///
+///
import { expect as expectLib } from 'expect'
-import type { RawMatcherFn } from './types.js'
-
+import type { WdioMatchersObject } from './types.js'
import * as wdioMatchers from './matchers.js'
import { DEFAULT_OPTIONS } from './constants.js'
import createSoftExpect from './softExpect.js'
import { SoftAssertService } from './softAssert.js'
-export const matchers = new Map()
+export const matchers: WdioMatchersObject = new Map()
const filteredMatchers = {}
const extend = expectLib.extend
diff --git a/src/jasmineUtils.ts b/src/jasmineUtils.ts
index 82d315dae..346fb2698 100644
--- a/src/jasmineUtils.ts
+++ b/src/jasmineUtils.ts
@@ -59,7 +59,7 @@ function asymmetricMatch(a: any, b: any) {
}
// Equality function lovingly adapted from isEqual in
-// [Underscore](http://underscorejs.org)
+// [Underscore](https://underscorejs.org)
function eq(
a: any,
b: any,
diff --git a/src/matchers/browser/toHaveClipboardText.ts b/src/matchers/browser/toHaveClipboardText.ts
index 98e52424d..00b023408 100644
--- a/src/matchers/browser/toHaveClipboardText.ts
+++ b/src/matchers/browser/toHaveClipboardText.ts
@@ -7,7 +7,7 @@ const log = logger('expect-webdriverio')
export async function toHaveClipboardText(
browser: WebdriverIO.Browser,
- expectedValue: string | RegExp | ExpectWebdriverIO.PartialMatcher,
+ expectedValue: string | RegExp | WdioAsymmetricMatcher,
options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS
) {
const isNot = this.isNot
diff --git a/src/matchers/browser/toHaveTitle.ts b/src/matchers/browser/toHaveTitle.ts
index b7287d9f8..4c18dd7f8 100644
--- a/src/matchers/browser/toHaveTitle.ts
+++ b/src/matchers/browser/toHaveTitle.ts
@@ -3,7 +3,7 @@ import { DEFAULT_OPTIONS } from '../../constants.js'
export async function toHaveTitle(
browser: WebdriverIO.Browser,
- expectedValue: string | RegExp | ExpectWebdriverIO.PartialMatcher,
+ expectedValue: string | RegExp | WdioAsymmetricMatcher,
options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS
) {
const isNot = this.isNot
diff --git a/src/matchers/browser/toHaveUrl.ts b/src/matchers/browser/toHaveUrl.ts
index a88d25c17..06719ac5d 100644
--- a/src/matchers/browser/toHaveUrl.ts
+++ b/src/matchers/browser/toHaveUrl.ts
@@ -3,7 +3,7 @@ import { DEFAULT_OPTIONS } from '../../constants.js'
export async function toHaveUrl(
browser: WebdriverIO.Browser,
- expectedValue: string | RegExp | ExpectWebdriverIO.PartialMatcher,
+ expectedValue: string | RegExp | WdioAsymmetricMatcher,
options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS
) {
const isNot = this.isNot
diff --git a/src/matchers/element/toHaveAttribute.ts b/src/matchers/element/toHaveAttribute.ts
index cd0a2b141..615dab685 100644
--- a/src/matchers/element/toHaveAttribute.ts
+++ b/src/matchers/element/toHaveAttribute.ts
@@ -17,7 +17,7 @@ async function conditionAttr(el: WebdriverIO.Element, attribute: string) {
}
-async function conditionAttrAndValue(el: WebdriverIO.Element, attribute: string, value: string | RegExp | ExpectWebdriverIO.PartialMatcher, options: ExpectWebdriverIO.StringOptions) {
+async function conditionAttrAndValue(el: WebdriverIO.Element, attribute: string, value: string | RegExp | WdioAsymmetricMatcher, options: ExpectWebdriverIO.StringOptions) {
const attr = await el.getAttribute(attribute)
if (typeof attr !== 'string') {
return { result: false, value: attr }
@@ -26,7 +26,7 @@ async function conditionAttrAndValue(el: WebdriverIO.Element, attribute: string,
return compareText(attr, value, options)
}
-export async function toHaveAttributeAndValue(received: WdioElementMaybePromise, attribute: string, value: string | RegExp | ExpectWebdriverIO.PartialMatcher, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS) {
+export async function toHaveAttributeAndValue(received: WdioElementMaybePromise, attribute: string, value: string | RegExp | WdioAsymmetricMatcher, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS) {
const isNot = this.isNot
const { expectation = 'attribute', verb = 'have' } = this
@@ -73,7 +73,7 @@ async function toHaveAttributeFn(received: WdioElementMaybePromise, attribute: s
export async function toHaveAttribute(
received: WdioElementMaybePromise,
attribute: string,
- value?: string | RegExp | ExpectWebdriverIO.PartialMatcher,
+ value?: string | RegExp | WdioAsymmetricMatcher,
options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS
) {
await options.beforeAssertion?.({
diff --git a/src/matchers/element/toHaveClass.ts b/src/matchers/element/toHaveClass.ts
index da20e3110..864d4ad04 100644
--- a/src/matchers/element/toHaveClass.ts
+++ b/src/matchers/element/toHaveClass.ts
@@ -1,9 +1,9 @@
import { DEFAULT_OPTIONS } from '../../constants.js'
import type { WdioElementMaybePromise } from '../../types.js'
-import { compareText, compareTextWithArray, enhanceError, executeCommand, isAsymmeyricMatcher, waitUntil, wrapExpectedWithArray } from '../../utils.js'
+import { compareText, compareTextWithArray, enhanceError, executeCommand, isAsymmetricMatcher, waitUntil, wrapExpectedWithArray } from '../../utils.js'
import { toHaveAttributeAndValue } from './toHaveAttribute.js'
-async function condition(el: WebdriverIO.Element, attribute: string, value: string | RegExp | Array | ExpectWebdriverIO.PartialMatcher, options: ExpectWebdriverIO.StringOptions) {
+async function condition(el: WebdriverIO.Element, attribute: string, value: string | RegExp | Array | WdioAsymmetricMatcher, options: ExpectWebdriverIO.StringOptions) {
const actualClass = await el.getAttribute(attribute)
if (typeof actualClass !== 'string') {
return { result: false }
@@ -13,7 +13,7 @@ async function condition(el: WebdriverIO.Element, attribute: string, value: stri
* if value is an asymmetric matcher, no need to split class names
* into an array and compare each of them
*/
- if (isAsymmeyricMatcher(value)) {
+ if (isAsymmetricMatcher(value)) {
return compareText(actualClass, value, options)
}
@@ -39,7 +39,7 @@ export function toHaveClass(...args: unknown[]) {
export async function toHaveElementClass(
received: WdioElementMaybePromise,
- expectedValue: string | RegExp | Array | ExpectWebdriverIO.PartialMatcher,
+ expectedValue: string | RegExp | Array | WdioAsymmetricMatcher,
options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS
) {
const isNot = this.isNot
@@ -83,7 +83,7 @@ export async function toHaveElementClass(
/**
* @deprecated
*/
-export function toHaveClassContaining(el: WebdriverIO.Element, className: string | RegExp | ExpectWebdriverIO.PartialMatcher, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS) {
+export function toHaveClassContaining(el: WebdriverIO.Element, className: string | RegExp | WdioAsymmetricMatcher, options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS) {
return toHaveAttributeAndValue.call(this, el, 'class', className, {
...options,
containing: true
diff --git a/src/matchers/element/toHaveComputedLabel.ts b/src/matchers/element/toHaveComputedLabel.ts
index 0e316d061..50e2a9324 100644
--- a/src/matchers/element/toHaveComputedLabel.ts
+++ b/src/matchers/element/toHaveComputedLabel.ts
@@ -11,7 +11,7 @@ import {
async function condition(
el: WebdriverIO.Element,
- label: string | RegExp | ExpectWebdriverIO.PartialMatcher | Array,
+ label: string | RegExp | WdioAsymmetricMatcher | Array,
options: ExpectWebdriverIO.HTMLOptions
) {
const actualLabel = await el.getComputedLabel()
@@ -23,7 +23,7 @@ async function condition(
export async function toHaveComputedLabel(
received: WdioElementMaybePromise,
- expectedValue: string | RegExp | ExpectWebdriverIO.PartialMatcher | Array,
+ expectedValue: string | RegExp | WdioAsymmetricMatcher | Array,
options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS
) {
const isNot = this.isNot
diff --git a/src/matchers/element/toHaveComputedRole.ts b/src/matchers/element/toHaveComputedRole.ts
index 22e139ee5..916506b97 100644
--- a/src/matchers/element/toHaveComputedRole.ts
+++ b/src/matchers/element/toHaveComputedRole.ts
@@ -11,7 +11,7 @@ import {
async function condition(
el: WebdriverIO.Element,
- role: string | RegExp | ExpectWebdriverIO.PartialMatcher | Array,
+ role: string | RegExp | WdioAsymmetricMatcher | Array,
options: ExpectWebdriverIO.HTMLOptions
) {
const actualRole = await el.getComputedRole()
@@ -23,7 +23,7 @@ async function condition(
export async function toHaveComputedRole(
received: WdioElementMaybePromise,
- expectedValue: string | RegExp | ExpectWebdriverIO.PartialMatcher | Array,
+ expectedValue: string | RegExp | WdioAsymmetricMatcher | Array,
options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS
) {
const isNot = this.isNot
diff --git a/src/matchers/element/toHaveElementProperty.ts b/src/matchers/element/toHaveElementProperty.ts
index 0c7bdfb79..cdf4146b0 100644
--- a/src/matchers/element/toHaveElementProperty.ts
+++ b/src/matchers/element/toHaveElementProperty.ts
@@ -30,13 +30,13 @@ async function condition(
}
prop = prop.toString()
- return compareText(prop as string, value as string | RegExp | ExpectWebdriverIO.PartialMatcher, options)
+ return compareText(prop as string, value as string | RegExp | WdioAsymmetricMatcher, options)
}
export async function toHaveElementProperty(
received: WdioElementMaybePromise,
property: string,
- value?: string | RegExp | ExpectWebdriverIO.PartialMatcher,
+ value?: string | RegExp | WdioAsymmetricMatcher,
options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS
) {
const isNot = this.isNot
diff --git a/src/matchers/element/toHaveHTML.ts b/src/matchers/element/toHaveHTML.ts
index ac1e45e19..1f7b976e2 100644
--- a/src/matchers/element/toHaveHTML.ts
+++ b/src/matchers/element/toHaveHTML.ts
@@ -9,7 +9,7 @@ import {
wrapExpectedWithArray
} from '../../utils.js'
-async function condition(el: WebdriverIO.Element, html: string | RegExp | ExpectWebdriverIO.PartialMatcher | Array, options: ExpectWebdriverIO.HTMLOptions) {
+async function condition(el: WebdriverIO.Element, html: string | RegExp | WdioAsymmetricMatcher | Array, options: ExpectWebdriverIO.HTMLOptions) {
const actualHTML = await el.getHTML(options)
if (Array.isArray(html)) {
return compareTextWithArray(actualHTML, html, options)
@@ -19,7 +19,7 @@ async function condition(el: WebdriverIO.Element, html: string | RegExp | Expect
export async function toHaveHTML(
received: ChainablePromiseArray | ChainablePromiseElement,
- expectedValue: string | RegExp | ExpectWebdriverIO.PartialMatcher | Array,
+ expectedValue: string | RegExp | WdioAsymmetricMatcher | Array,
options: ExpectWebdriverIO.HTMLOptions = DEFAULT_OPTIONS
) {
const isNot = this.isNot
diff --git a/src/matchers/element/toHaveId.ts b/src/matchers/element/toHaveId.ts
index 99be37be3..6bc3d4ae2 100644
--- a/src/matchers/element/toHaveId.ts
+++ b/src/matchers/element/toHaveId.ts
@@ -4,7 +4,7 @@ import type { WdioElementMaybePromise } from '../../types.js'
export async function toHaveId(
el: WdioElementMaybePromise,
- expectedValue: string | RegExp | ExpectWebdriverIO.PartialMatcher,
+ expectedValue: string | RegExp | WdioAsymmetricMatcher,
options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS
) {
await options.beforeAssertion?.({
diff --git a/src/matchers/element/toHaveText.ts b/src/matchers/element/toHaveText.ts
index 1233733e6..37dbc4d21 100644
--- a/src/matchers/element/toHaveText.ts
+++ b/src/matchers/element/toHaveText.ts
@@ -8,7 +8,7 @@ import {
wrapExpectedWithArray
} from '../../utils.js'
-async function condition(el: WebdriverIO.Element | WebdriverIO.ElementArray, text: string | RegExp | Array | ExpectWebdriverIO.PartialMatcher | Array, options: ExpectWebdriverIO.StringOptions) {
+async function condition(el: WebdriverIO.Element | WebdriverIO.ElementArray, text: string | RegExp | Array | WdioAsymmetricMatcher | Array, options: ExpectWebdriverIO.StringOptions) {
const actualTextArray: string[] = []
const resultArray: boolean[] = []
let checkAllValuesMatchCondition: boolean
@@ -39,7 +39,7 @@ async function condition(el: WebdriverIO.Element | WebdriverIO.ElementArray, tex
export async function toHaveText(
received: ChainablePromiseElement | ChainablePromiseArray,
- expectedValue: string | RegExp | ExpectWebdriverIO.PartialMatcher | Array,
+ expectedValue: string | RegExp | WdioAsymmetricMatcher | Array,
options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS
) {
const isNot = this.isNot
diff --git a/src/matchers/element/toHaveValue.ts b/src/matchers/element/toHaveValue.ts
index e11664bdd..6c032b2c8 100644
--- a/src/matchers/element/toHaveValue.ts
+++ b/src/matchers/element/toHaveValue.ts
@@ -4,7 +4,7 @@ import type { WdioElementMaybePromise } from '../../types.js'
export function toHaveValue(
el: WdioElementMaybePromise,
- value: string | RegExp | ExpectWebdriverIO.PartialMatcher,
+ value: string | RegExp | WdioAsymmetricMatcher,
options: ExpectWebdriverIO.StringOptions = DEFAULT_OPTIONS
) {
return toHaveElementProperty.call(this, el, 'value', value, options)
diff --git a/src/matchers/mock/toBeRequestedWith.ts b/src/matchers/mock/toBeRequestedWith.ts
index ad55a5eb4..98f2b3267 100644
--- a/src/matchers/mock/toBeRequestedWith.ts
+++ b/src/matchers/mock/toBeRequestedWith.ts
@@ -122,7 +122,7 @@ const statusCodeMatcher = (statusCode: number, expected?: number | Array
*/
const urlMatcher = (
url: string,
- expected?: string | ExpectWebdriverIO.PartialMatcher | ((url: string) => boolean)
+ expected?: string | ExpectWebdriverIO.PartialMatcher | ((url: string) => boolean)
) => {
if (typeof expected === 'undefined') {
return true
@@ -140,7 +140,7 @@ const headersMatcher = (
headers: Record,
expected?:
| Record
- | ExpectWebdriverIO.PartialMatcher
+ | ExpectWebdriverIO.PartialMatcher>
| ((headers: Record) => boolean)
) => {
/**
@@ -215,7 +215,7 @@ const headersMatcher = (
// // get matcher sample if expected value is a special matcher like `expect.objectContaining({ foo: 'bar })`
// const actualSample = isMatcher(expected)
-// ? (expected as ExpectWebdriverIO.PartialMatcher).sample
+// ? (expected as WdioAsymmetricMatcher).sample
// : expected
// return (
@@ -315,7 +315,7 @@ const requestedWithParamToString = (
param:
| string
| ExpectWebdriverIO.JsonCompatible
- | ExpectWebdriverIO.PartialMatcher
+ | ExpectWebdriverIO.PartialMatcher
| Function
| undefined,
transformFn?: (param: ExpectWebdriverIO.JsonCompatible) => ExpectWebdriverIO.JsonCompatible | string
@@ -330,7 +330,7 @@ const requestedWithParamToString = (
return (
param.constructor.name +
' ' +
- (JSON.stringify((param as ExpectWebdriverIO.PartialMatcher).sample) || '')
+ (JSON.stringify((param as WdioAsymmetricMatcher).sample) || '')
)
} else if (transformFn && typeof param === 'object' && param !== null) {
param = transformFn(param as ExpectWebdriverIO.JsonCompatible)
diff --git a/src/matchers/snapshot.ts b/src/matchers/snapshot.ts
index 5a1e6273f..daf36868a 100644
--- a/src/matchers/snapshot.ts
+++ b/src/matchers/snapshot.ts
@@ -3,7 +3,7 @@ import type { AssertionError } from 'node:assert'
import { expect } from 'expect'
import { stripSnapshotIndentation } from '@vitest/snapshot'
-import { SnapshotService } from '../snapshot.js'
+import { SnapshotService } from '../snapshot'
interface InlineSnapshotOptions {
inlineSnapshot: string
diff --git a/src/softExpect.ts b/src/softExpect.ts
index 4aa2dfbb9..31edd5402 100644
--- a/src/softExpect.ts
+++ b/src/softExpect.ts
@@ -76,7 +76,8 @@ const createSoftMatcher = (
return async (...args: unknown[]) => {
try {
// Build the expectation chain
- let expectChain = expect(actual)
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let expectChain: any = expect(actual)
if (prefix === 'not') {
expectChain = expectChain.not
@@ -86,7 +87,7 @@ const createSoftMatcher = (
expectChain = expectChain.rejects
}
- return await ((expectChain as unknown) as Record Promise>)[matcherName](...args)
+ return await ((expectChain as unknown) as Record ExpectWebdriverIO.AsyncAssertionResult>)[matcherName](...args)
} catch (error) {
// Record the failure
diff --git a/src/types.ts b/src/types.ts
index ecda2c93c..d74569d50 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -12,3 +12,5 @@ export type WdioElementsMaybePromise =
export type RawMatcherFn = {
(this: Context, actual: unknown, ...expected: unknown[]): ExpectationResult;
}
+
+export type WdioMatchersObject = Map
\ No newline at end of file
diff --git a/src/utils.ts b/src/utils.ts
index ad2d8bb19..66c946d31 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -16,7 +16,7 @@ const asymmetricMatcher =
? Symbol.for('jest.asymmetricMatcher')
: 0x13_57_a5
-export function isAsymmeyricMatcher(expected: unknown): expected is ExpectWebdriverIO.PartialMatcher {
+export function isAsymmetricMatcher(expected: unknown): expected is WdioAsymmetricMatcher {
return (
typeof expected === 'object' &&
typeof expected === 'object' &&
@@ -28,14 +28,14 @@ export function isAsymmeyricMatcher(expected: unknown): expected is ExpectWebdri
) as boolean
}
-function isStringContainingMatcher(expected: unknown): expected is ExpectWebdriverIO.PartialMatcher {
- return isAsymmeyricMatcher(expected) && ['StringContaining', 'StringNotContaining'].includes(expected.toString())
+function isStringContainingMatcher(expected: unknown): expected is WdioAsymmetricMatcher {
+ return isAsymmetricMatcher(expected) && ['StringContaining', 'StringNotContaining'].includes(expected.toString())
}
/**
* wait for expectation to succeed
* @param condition function
- * @param isNot https://jestjs.io/docs/en/expect#thisisnot
+ * @param isNot https://jestjs.io/docs/expect#thisisnot
* @param options wait, interval, etc
*/
const waitUntil = async (
@@ -90,7 +90,7 @@ async function executeCommandBe(
received: WdioElementMaybePromise,
command: (el: WebdriverIO.Element) => Promise,
options: ExpectWebdriverIO.CommandOptions
-): Promise {
+): ExpectWebdriverIO.AsyncAssertionResult {
const { isNot, expectation, verb = 'be' } = this
let el = await received?.getElement()
@@ -143,7 +143,7 @@ const compareNumbers = (actual: number, options: ExpectWebdriverIO.NumberOptions
export const compareText = (
actual: string,
- expected: string | RegExp | ExpectWebdriverIO.PartialMatcher,
+ expected: string | RegExp | WdioAsymmetricMatcher,
{
ignoreCase = false,
trim = true,
@@ -174,11 +174,11 @@ export const compareText = (
} else if (isStringContainingMatcher(expected)) {
expected = (expected.toString() === 'StringContaining'
? expect.stringContaining(expected.sample?.toString().toLowerCase())
- : expect.not.stringContaining(expected.sample?.toString().toLowerCase())) as ExpectWebdriverIO.PartialMatcher
+ : expect.not.stringContaining(expected.sample?.toString().toLowerCase())) as WdioAsymmetricMatcher
}
}
- if (isAsymmeyricMatcher(expected)) {
+ if (isAsymmetricMatcher(expected)) {
const result = expected.asymmetricMatch(actual)
return {
value: actual,
@@ -229,7 +229,7 @@ export const compareText = (
export const compareTextWithArray = (
actual: string,
- expectedArray: Array,
+ expectedArray: Array>,
{
ignoreCase = false,
trim = false,
@@ -262,7 +262,7 @@ export const compareTextWithArray = (
if (isStringContainingMatcher(item)) {
return (item.toString() === 'StringContaining'
? expect.stringContaining(item.sample?.toString().toLowerCase())
- : expect.not.stringContaining(item.sample?.toString().toLowerCase())) as ExpectWebdriverIO.PartialMatcher
+ : expect.not.stringContaining(item.sample?.toString().toLowerCase())) as WdioAsymmetricMatcher
}
return item
})
@@ -272,7 +272,7 @@ export const compareTextWithArray = (
if (expected instanceof RegExp) {
return !!actual.match(expected)
}
- if (isAsymmeyricMatcher(expected)) {
+ if (isAsymmetricMatcher(expected)) {
return expected.asymmetricMatch(actual)
}
if (containing) {
diff --git a/test-types/copy.js b/test-types/copy.js
deleted file mode 100644
index 39a9dcc93..000000000
--- a/test-types/copy.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import path from 'node:path'
-import url from 'node:url'
-
-import { rimraf } from 'rimraf'
-
-import shelljs from 'shelljs'
-
-const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
-const ROOT = path.resolve(__dirname, '..')
-
-// TypeScript project root for testing particular typings
-const outDirs = ['default', 'jest', 'jasmine']
-
-const defaultPackages = ['expect', 'jest-matcher-utils']
-const jestPackages = ['@types/jest']
-const jasminePackages = ['@types/jasmine']
-
-const testFile = 'types.ts'
-
-const artifactDirs = ['node_modules', 'dist', testFile]
-
-/**
- * copy package.json and typings from package to type-generation/test/.../node_modules
- */
-async function copy() {
- for (const outDir of outDirs) {
- const packages = [...defaultPackages]
- if (outDir === 'jest') {
- packages.push(...jestPackages)
- }
- if (outDir === 'jasmine') {
- packages.push(...jasminePackages)
- }
-
- // link node_modules
- for (const packageName of packages) {
- const destination = path.join(__dirname, outDir, 'node_modules', packageName)
-
- const destDir = destination.split(path.sep).slice(0, -1).join(path.sep)
- shelljs.mkdir('-p', destDir)
- shelljs.ln('-s', path.join(ROOT, 'node_modules', packageName), destination)
- }
-
- // link test file
- shelljs.ln('-s', path.join(__dirname, testFile), path.join(__dirname, outDir, testFile))
-
- // copy expect-webdriverio
- const destDir = path.join(__dirname, outDir, 'node_modules', 'expect-webdriverio')
-
- shelljs.mkdir('-p', destDir)
- shelljs.cp('*.d.ts', destDir)
- shelljs.cp('package.json', destDir)
- shelljs.cp('-r', 'types', destDir)
- }
-}
-
-/**
- * delete eventual artifacts from test folders
- */
-await Promise.all(
- artifactDirs.map((dir) =>
- Promise.all(outDirs.map((testDir) => rimraf(path.join(__dirname, testDir, dir))))
- )
-)
-
-/**
- * if successful, start test
- */
-await copy()
diff --git a/test-types/default/tsconfig.json b/test-types/default/tsconfig.json
deleted file mode 100644
index 0f5a1fc58..000000000
--- a/test-types/default/tsconfig.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "compilerOptions": {
- "outDir": "dist",
- "noImplicitAny": true,
- "target": "ES2020",
- "esModuleInterop": true,
- "module": "Node16",
- "skipLibCheck": true,
- "types": [
- "expect-webdriverio"
- ]
- }
-}
diff --git a/test-types/jasmine-async/customMatchers/customMatchers-module-expect.d.ts b/test-types/jasmine-async/customMatchers/customMatchers-module-expect.d.ts
new file mode 100644
index 000000000..1111096ec
--- /dev/null
+++ b/test-types/jasmine-async/customMatchers/customMatchers-module-expect.d.ts
@@ -0,0 +1,29 @@
+import 'expect'
+
+/**
+ * Custom matchers under the `expect` module.
+ * @see {@link https://jestjs.io/docs/expect#expectextendmatchers}
+ */
+declare module 'expect' {
+ interface AsymmetricMatchers {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ toBeWithinRange(floor: number, ceiling: number): any
+ toHaveSimpleCustomProperty(string: string): string
+ toHaveCustomProperty(element: ChainablePromiseElement | WebdriverIO.Element): Promise>
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ interface Matchers {
+ // Custom matchers in Jasmine need to return a Promise, potential breaking change to document
+ toBeWithinRange(floor: number, ceiling: number): Promise
+ toHaveSimpleCustomProperty(string: string | ExpectWebdriverIO.PartialMatcher): Promise
+ toHaveCustomProperty:
+ // Useful to typecheck the custom matcher so it is only used on elements
+ T extends ChainablePromiseElement | WebdriverIO.Element ?
+ (test: string | ExpectWebdriverIO.PartialMatcher |
+ // Needed for the custom asymmetric matcher defined above to be typed correctly
+ Promise>)
+ // Using `never` blocks the call on non-element types
+ => Promise : never;
+ }
+}
\ No newline at end of file
diff --git a/test-types/jasmine-async/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts b/test-types/jasmine-async/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts
new file mode 100644
index 000000000..00153b892
--- /dev/null
+++ b/test-types/jasmine-async/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts
@@ -0,0 +1,14 @@
+/**
+ * Custom matchers under the `ExpectWebdriverIO` namespace.
+ * @see {@link https://webdriver.io/docs/custommatchers/#typescript-support}
+ */
+declare namespace ExpectWebdriverIO {
+ interface AsymmetricMatchers {
+ toBeCustom(): ExpectWebdriverIO.PartialMatcher;
+ toBeCustomPromise(chainableElement: ChainablePromiseElement): Promise>;
+ }
+ interface Matchers {
+ toBeCustom(): R;
+ toBeCustomPromise: T extends ChainablePromiseElement ? (expected?: string | ExpectWebdriverIO.PartialMatcher | Promise>) => Promise : never;
+ }
+}
\ No newline at end of file
diff --git a/test-types/jasmine-async/tsconfig.json b/test-types/jasmine-async/tsconfig.json
new file mode 100644
index 000000000..90080327e
--- /dev/null
+++ b/test-types/jasmine-async/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "noEmit": true,
+ "noImplicitAny": true,
+ "target": "es2022",
+ "module": "node18",
+ "skipLibCheck": true,
+ "types": [
+ "@types/jasmine",
+ "../../jasmine.d.ts"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/test-types/jasmine-async/types-jasmine_async.test.ts b/test-types/jasmine-async/types-jasmine_async.test.ts
new file mode 100644
index 000000000..01f9611e4
--- /dev/null
+++ b/test-types/jasmine-async/types-jasmine_async.test.ts
@@ -0,0 +1,811 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import { expect as wdioExpect } from 'expect-webdriverio'
+describe('type assertions', () => {
+ const chainableElement = {} as unknown as ChainablePromiseElement
+ const chainableArray = {} as ChainablePromiseArray
+
+ const element: WebdriverIO.Element = {} as unknown as WebdriverIO.Element
+ const elementArray: WebdriverIO.ElementArray = [] as unknown as WebdriverIO.ElementArray
+
+ const networkMock: WebdriverIO.Mock = {} as unknown as WebdriverIO.Mock
+
+ // Type assertions
+ let expectPromiseVoid: Promise
+ let expectVoid: void
+ let expectPromiseUnknown: Promise
+
+ describe('Browser', () => {
+ const browser: WebdriverIO.Browser = {} as unknown as WebdriverIO.Browser
+
+ describe('toHaveUrl', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expectAsync(browser).toHaveUrl('https://example.com')
+ expectPromiseVoid = expectAsync(browser).not.toHaveUrl('https://example.com')
+
+ // Asymmetric matchers
+ expectPromiseVoid = expectAsync(browser).toHaveUrl(wdioExpect.stringContaining('WebdriverIO'))
+ expectPromiseVoid = expectAsync(browser).toHaveUrl(wdioExpect.not.stringContaining('WebdriverIO'))
+ expectPromiseVoid = expectAsync(browser).toHaveUrl(wdioExpect.any(String))
+ expectPromiseVoid = expectAsync(browser).toHaveUrl(wdioExpect.anything())
+
+ // @ts-expect-error
+ expectVoid = expectAsync(browser).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ expectVoid = expectAsync(browser).not.toHaveUrl('https://example.com')
+ // @ts-expect-error
+ expectVoid = expectAsync(browser).toHaveUrl(wdioExpect.stringContaining('WebdriverIO'))
+
+ // @ts-expect-error
+ await expectAsync(browser).toHaveUrl(6)
+ })
+
+ it('should have ts errors when actual is not a Browser element', async () => {
+ // @ts-expect-error
+ await expectAsync(element).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await expectAsync(element).not.toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await expectAsync(true).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await expectAsync(true).not.toHaveUrl('https://example.com')
+ })
+ })
+
+ describe('toHaveTitle', () => {
+ it('should be supported correctly', async () => {
+ const test = expectAsync('text')
+ expectPromiseVoid = expectAsync(browser).toHaveTitle('https://example.com')
+ expectPromiseVoid = expectAsync(browser).not.toHaveTitle('https://example.com')
+
+ // Asymmetric matchers
+ expectPromiseVoid = expectAsync(browser).toHaveTitle(wdioExpect.stringContaining('WebdriverIO'))
+ expectPromiseVoid = expectAsync(browser).toHaveTitle(wdioExpect.any(String))
+ expectPromiseVoid = expectAsync(browser).toHaveTitle(wdioExpect.anything())
+
+ // @ts-expect-error
+ expectVoid = expectAsync(browser).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ expectVoid = expectAsync(browser).not.toHaveTitle('https://example.com')
+ // @ts-expect-error
+ expectVoid = expectAsync(browser).toHaveTitle(wdioExpect.stringContaining('WebdriverIO'))
+ })
+
+ it('should have ts errors when actual is not a Browser element', async () => {
+ // @ts-expect-error
+ await expectAsync(element).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await expectAsync(element).not.toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await expectAsync(true).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await expectAsync(true).not.toHaveTitle('https://example.com')
+ })
+ })
+ })
+
+ describe('element', () => {
+
+ describe('toBeDisabled', () => {
+ it('should be supported correctly', async () => {
+ // Element
+ expectPromiseVoid = expectAsync(element).toBeDisabled()
+ expectPromiseVoid = expectAsync(element).not.toBeDisabled()
+
+ // Element array
+ expectPromiseVoid = expectAsync(elementArray).toBeDisabled()
+ expectPromiseVoid = expectAsync(elementArray).not.toBeDisabled()
+
+ // Chainable element
+ expectPromiseVoid = expectAsync(chainableElement).toBeDisabled()
+ expectPromiseVoid = expectAsync(chainableElement).not.toBeDisabled()
+
+ // Chainable element array
+ expectPromiseVoid = expectAsync(chainableArray).toBeDisabled()
+ expectPromiseVoid = expectAsync(chainableArray).not.toBeDisabled()
+
+ // @ts-expect-error
+ expectVoid = expectAsync(element).toBeDisabled()
+ // @ts-expect-error
+ expectVoid = expectAsync(element).not.toBeDisabled()
+ })
+
+ it('should have ts errors when actual is not an element', async () => {
+ // @ts-expect-error
+ await expectAsync(browser).toBeDisabled()
+ // @ts-expect-error
+ await expectAsync(browser).not.toBeDisabled()
+ // @ts-expect-error
+ await expectAsync(true).toBeDisabled()
+ // @ts-expect-error
+ await expectAsync(true).not.toBeDisabled()
+ })
+ })
+
+ describe('toHaveText', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expectAsync(element).toHaveText('text')
+ expectPromiseVoid = expectAsync(element).toHaveText(/text/)
+ expectPromiseVoid = expectAsync(element).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expectAsync(element).toHaveText([wdioExpect.stringContaining('text1'), wdioExpect.stringContaining('text2')])
+ expectPromiseVoid = expectAsync(element).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expectAsync(element).toHaveText(['text1', /text1/, wdioExpect.stringContaining('text3')])
+ await expectAsync(element).toHaveText(
+ 'My-Ex-Am-Ple',
+ {
+ replace: [[/-/g, ' '], [/[A-Z]+/g, (match: string) => match.toLowerCase()]]
+ }
+ )
+
+ expectPromiseVoid = expectAsync(element).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expectAsync(element).toHaveText('text')
+ // @ts-expect-error
+ await expectAsync(element).toHaveText(6)
+
+ expectPromiseVoid = expectAsync(chainableElement).toHaveText('text')
+ expectPromiseVoid = expectAsync(chainableElement).toHaveText(/text/)
+ expectPromiseVoid = expectAsync(chainableElement).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expectAsync(chainableElement).toHaveText([wdioExpect.stringContaining('text1'), wdioExpect.stringContaining('text2')])
+ expectPromiseVoid = expectAsync(chainableElement).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expectAsync(chainableElement).toHaveText(['text1', /text1/, wdioExpect.stringContaining('text3')])
+
+ expectPromiseVoid = expectAsync(chainableElement).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expectAsync(chainableElement).toHaveText('text')
+ // @ts-expect-error
+ await expectAsync(chainableElement).toHaveText(6)
+
+ expectPromiseVoid = expectAsync(elementArray).toHaveText('text')
+ expectPromiseVoid = expectAsync(elementArray).toHaveText(/text/)
+ expectPromiseVoid = expectAsync(elementArray).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expectAsync(elementArray).toHaveText([wdioExpect.stringContaining('text1'), wdioExpect.stringContaining('text2')])
+ expectPromiseVoid = expectAsync(elementArray).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expectAsync(elementArray).toHaveText(['text1', /text1/, wdioExpect.stringContaining('text3')])
+
+ expectPromiseVoid = expectAsync(elementArray).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expectAsync(elementArray).toHaveText('text')
+ // @ts-expect-error
+ await expectAsync(elementArray).toHaveText(6)
+
+ expectPromiseVoid = expectAsync(chainableArray).toHaveText('text')
+ expectPromiseVoid = expectAsync(chainableArray).toHaveText(/text/)
+ expectPromiseVoid = expectAsync(chainableArray).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expectAsync(chainableArray).toHaveText([wdioExpect.stringContaining('text1'), wdioExpect.stringContaining('text2')])
+ expectPromiseVoid = expectAsync(chainableArray).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expectAsync(chainableArray).toHaveText(['text1', /text1/, wdioExpect.stringContaining('text3')])
+
+ expectPromiseVoid = expectAsync(chainableArray).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expectAsync(chainableArray).toHaveText('text')
+ // @ts-expect-error
+ await expectAsync(chainableArray).toHaveText(6)
+
+ // @ts-expect-error
+ await expectAsync(browser).toHaveText('text')
+ })
+
+ it('should have ts errors when actual is not an element', async () => {
+ // @ts-expect-error
+ await expectAsync(browser).toHaveText('text')
+ // @ts-expect-error
+ await expectAsync(browser).not.toHaveText('text')
+ // @ts-expect-error
+ await expectAsync(true).toHaveText('text')
+ // @ts-expect-error
+ await expectAsync(true).toHaveText('text')
+ })
+
+ it('should have ts errors when actual is string or Promise', async () => {
+ // @ts-expect-error
+ await expectAsync('text').toHaveText('text')
+ // @ts-expect-error
+ await expectAsync('text').not.toHaveText('text')
+ // @ts-expect-error
+ await expectAsync(Promise.resolve('text')).toHaveText('text')
+ // @ts-expect-error
+ await expectAsync(Promise.resolve('text')).toHaveText('text')
+ })
+ })
+
+ describe('toHaveHeight', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expectAsync(element).toHaveHeight(100)
+ expectPromiseVoid = expectAsync(element).toHaveHeight(100, { message: 'Custom error message' })
+ expectPromiseVoid = expectAsync(element).not.toHaveHeight(100)
+ expectPromiseVoid = expectAsync(element).not.toHaveHeight(100, { message: 'Custom error message' })
+
+ expectPromiseVoid = expectAsync(element).toHaveHeight({ width: 100, height: 200 })
+ expectPromiseVoid = expectAsync(element).toHaveHeight({ width: 100, height: 200 }, { message: 'Custom error message' })
+ expectPromiseVoid = expectAsync(element).not.toHaveHeight({ width: 100, height: 200 })
+ expectPromiseVoid = expectAsync(element).not.toHaveHeight({ width: 100, height: 200 }, { message: 'Custom error message' })
+
+ // @ts-expect-error
+ expectVoid = expectAsync(element).toHaveHeight(100)
+ // @ts-expect-error
+ expectVoid = expectAsync(element).not.toHaveHeight(100)
+
+ // @ts-expect-error
+ expectVoid = expectAsync(element).toHaveHeight({ width: 100, height: 200 })
+ // @ts-expect-error
+ expectVoid = expectAsync(element).not.toHaveHeight({ width: 100, height: 200 })
+
+ // @ts-expect-error
+ await expectAsync(browser).toHaveHeight(100)
+ })
+
+ it('should have ts errors when actual is string or Promise', async () => {
+ // @ts-expect-error
+ await expectAsync('text').toHaveText('text')
+ // @ts-expect-error
+ await expectAsync('text').not.toHaveText('text')
+ // @ts-expect-error
+ await expectAsync(Promise.resolve('text')).toHaveText('text')
+ // @ts-expect-error
+ await expectAsync(Promise.resolve('text')).toHaveText('text')
+ })
+ })
+
+ describe('toMatchSnapshot', () => {
+
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expectAsync(element).toMatchSnapshot()
+ expectPromiseVoid = expectAsync(element).toMatchSnapshot('test label')
+ expectPromiseVoid = expectAsync(element).not.toMatchSnapshot('test label')
+
+ expectPromiseVoid = expectAsync(chainableElement).toMatchSnapshot()
+ expectPromiseVoid = expectAsync(chainableElement).toMatchSnapshot('test label')
+ expectPromiseVoid = expectAsync(chainableElement).not.toMatchSnapshot('test label')
+ })
+ })
+
+ describe('toMatchInlineSnapshot', () => {
+
+ it('should be correctly supported', async () => {
+ expectPromiseVoid = expectAsync(element).toMatchInlineSnapshot()
+ expectPromiseVoid = expectAsync(element).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expectAsync(element).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ expectPromiseVoid = expectAsync(chainableElement).toMatchInlineSnapshot()
+ expectPromiseVoid = expectAsync(chainableElement).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expectAsync(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ //@ts-expect-error
+ expectVoid = expectAsync(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+ })
+
+ it('should be correctly supported with getCSSProperty()', async () => {
+ expectPromiseVoid = expectAsync(element.getCSSProperty('test')).toMatchInlineSnapshot()
+ expectPromiseVoid = expectAsync(element.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expectAsync(element.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ expectPromiseVoid = expectAsync(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot()
+ expectPromiseVoid = expectAsync(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expectAsync(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ //@ts-expect-error
+ expectVoid = expectAsync(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+ })
+ })
+
+ describe('toBeElementsArrayOfSize', async () => {
+
+ it('should work correctly when actual is chainableArray', async () => {
+ expectPromiseVoid = expectAsync(chainableArray).toBeElementsArrayOfSize(5)
+ expectPromiseVoid = expectAsync(chainableArray).toBeElementsArrayOfSize({ lte: 10 })
+
+ // @ts-expect-error
+ expectVoid = expectAsync(chainableArray).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ expectVoid = expectAsync(chainableArray).toBeElementsArrayOfSize({ lte: 10 })
+ })
+
+ it('should not work when actual is not chainableArray', async () => {
+ // @ts-expect-error
+ await expectAsync(chainableElement).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ await expectAsync(chainableElement).toBeElementsArrayOfSize({ lte: 10 })
+ // @ts-expect-error
+ await expectAsync(true).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ await expectAsync(true).toBeElementsArrayOfSize({ lte: 10 })
+ })
+ })
+ })
+
+ describe('Custom matchers', () => {
+ describe('using `ExpectWebdriverIO` namespace augmentation', () => {
+
+ it('should supported correctly a promise custom matcher with only chainableElement as actual', async () => {
+ expectPromiseVoid = expectAsync(chainableElement).toBeCustomPromise()
+ expectPromiseVoid = expectAsync(chainableElement).toBeCustomPromise(wdioExpect.stringContaining('test'))
+ expectPromiseVoid = expectAsync(chainableElement).not.toBeCustomPromise(wdioExpect.not.stringContaining('test'))
+
+ // @ts-expect-error
+ expectAsync('test').toBeCustomPromise()
+ // @ts-expect-error
+ expectVoid = expectAsync(chainableElement).toBeCustomPromise()
+ // @ts-expect-error
+ expectVoid = expectAsync(chainableElement).toBeCustomPromise(wdioExpect.stringContaining('test'))
+ // @ts-expect-error
+ expectVoid = expectAsync(chainableElement).not.toBeCustomPromise(wdioExpect.stringContaining('test'))
+ // @ts-expect-error
+ expectAsync(chainableElement).toBeCustomPromise(wdioExpect.stringContaining(6))
+ })
+
+ it('should support custom asymmetric matcher', async () => {
+ const expectString1 : ExpectWebdriverIO.PartialMatcher = wdioExpect.toBeCustom()
+ const expectString2 : ExpectWebdriverIO.PartialMatcher = wdioExpect.not.toBeCustom()
+
+ expectPromiseVoid = expectAsync(chainableElement).toBeCustomPromise(wdioExpect.toBeCustom())
+
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.toBeCustom()
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.not.toBeCustom()
+
+ //@ts-expect-error
+ expectVoid = expectAsync(chainableElement).toBeCustomPromise(wdioExpect.toBeCustom())
+ })
+ })
+
+ describe('using `expect` module declaration', () => {
+
+ it('should support a simple matcher', async () => {
+ expectPromiseVoid = expectAsync(5).toBeWithinRange(1, 10)
+
+ // Or as an asymmetric matcher:
+ expectPromiseVoid = expectAsync({ value: 5 }).toEqual({
+ value: wdioExpect.toBeWithinRange(1, 10)
+ })
+
+ // @ts-expect-error
+ expectVoid = expectAsync(5).toBeWithinRange(1, '10')
+ // @ts-expect-error
+ expectAsync(5).toBeWithinRange('1')
+ })
+
+ it('should support a simple custom matcher with a chainable element matcher with promise', async () => {
+ expectPromiseVoid = expectAsync(chainableElement).toHaveSimpleCustomProperty('text')
+ expectPromiseVoid = expectAsync(chainableElement).toHaveSimpleCustomProperty(wdioExpect.stringContaining('text'))
+ expectPromiseVoid = expectAsync(chainableElement).not.toHaveSimpleCustomProperty(wdioExpect.not.stringContaining('text'))
+
+ // Or as a custom asymmetric matcher:
+ expectPromiseVoid = expectAsync(chainableElement).toHaveSimpleCustomProperty(
+ wdioExpect.toHaveSimpleCustomProperty('string')
+ )
+ const expectString1:string = wdioExpect.toHaveSimpleCustomProperty('string')
+ const expectString2:string = wdioExpect.not.toHaveSimpleCustomProperty('string')
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.toHaveSimpleCustomProperty(chainableElement)
+ // @ts-expect-error
+ expectVoid = wdioExpect.not.toHaveSimpleCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.toHaveSimpleCustomProperty(chainableElement)
+ })
+
+ it('should support a chainable element matcher with promise', async () => {
+ expectPromiseVoid = expectAsync(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = expectAsync(chainableElement).toHaveCustomProperty(wdioExpect.stringContaining('text'))
+ expectPromiseVoid = expectAsync(chainableElement).not.toHaveCustomProperty(wdioExpect.not.stringContaining('text'))
+
+ // Or as a custom asymmetric matcher:
+ expectPromiseVoid = expectAsync(chainableElement).toHaveCustomProperty(
+ await wdioExpect.toHaveCustomProperty(chainableElement)
+ )
+ const expectPromiseWdioElement1: Promise> = wdioExpect.toHaveCustomProperty(chainableElement)
+ const expectPromiseWdioElement2: Promise> = wdioExpect.not.toHaveCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.toHaveCustomProperty(chainableElement)
+ // @ts-expect-error
+ expectVoid = wdioExpect.not.toHaveCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.toHaveCustomProperty(chainableElement)
+ // @ts-expect-error
+ wdioExpect.toHaveCustomProperty('test')
+
+ await expectAsync(chainableElement).toHaveCustomProperty(
+ await wdioExpect.toHaveCustomProperty(chainableElement)
+ )
+ })
+ })
+ })
+
+ describe('toBe', () => {
+ it('should expect void type when actual is a boolean', async () => {
+ expectVoid = expect(true).toBe(true)
+ expectVoid = expect(true).not.toBe(true)
+
+ expectPromiseVoid = expectAsync(true).toBe(true)
+ expectPromiseVoid = expectAsync(true).not.toBe(true)
+ })
+
+ it('should expect Promise when actual is a chainable since toBe does not need to be awaited', async () => {
+ expectPromiseVoid = expectAsync(chainableElement).toBe(true)
+ expectPromiseVoid = expectAsync(chainableElement).not.toBe(true)
+ })
+
+ it('should expect Promise type when actual is a Promise since it is expectAsync', async () => {
+ const promiseBoolean = Promise.resolve(true)
+
+ expectPromiseUnknown = expectAsync(promiseBoolean).toBe(true)
+ expectPromiseUnknown = expectAsync(promiseBoolean).not.toBe(true)
+
+ expectPromiseVoid = expectAsync(promiseBoolean).toBe(true)
+ expectPromiseVoid = expectAsync(promiseBoolean).toBe(true)
+ })
+
+ it('should work with string', async () => {
+ expectPromiseUnknown = expectAsync('text').toBe(true)
+ expectPromiseUnknown = expectAsync('text').not.toBe(true)
+ expectPromiseUnknown = expectAsync('text').toBe(wdioExpect.stringContaining('text'))
+ expectPromiseUnknown = expectAsync('text').not.toBe(wdioExpect.stringContaining('text'))
+
+ expectPromiseVoid = expectAsync('text').toBe(true)
+ expectPromiseVoid = expectAsync('text').not.toBe(true)
+ expectPromiseVoid = expectAsync('text').toBe(wdioExpect.stringContaining('text'))
+ expectPromiseVoid = expectAsync('text').not.toBe(wdioExpect.stringContaining('text'))
+ })
+ })
+
+ describe('Promise type assertions', () => {
+ const booleanPromise: Promise = Promise.resolve(true)
+
+ it('should expect a Promise of type', async () => {
+ const expectPromiseBoolean1: jasmine.AsyncMatchers = expectAsync(booleanPromise)
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const expectPromiseBoolean2: jasmine.AsyncMatchers = expectAsync(booleanPromise).not
+ })
+
+ it('should work with resolves & rejects correctly', async () => {
+ //@ts-expect-error
+ expectAsync(booleanPromise).resolves.toBe(true)
+ //@ts-expect-error
+ expectAsync(booleanPromise).rejects.toBe(true)
+ })
+
+ it('should not support chainable and expect PromiseVoid with toBe', async () => {
+ expectPromiseVoid = expectAsync(chainableElement).toBe(true)
+ expectPromiseVoid = expectAsync(chainableElement).not.toBe(true)
+ })
+ })
+
+ describe('Network Matchers', () => {
+ const promiseNetworkMock = Promise.resolve(networkMock)
+
+ it('should not have ts errors when typing to Promise', async () => {
+ expectPromiseVoid = expectAsync(promiseNetworkMock).toBeRequested()
+ expectPromiseVoid = expectAsync(promiseNetworkMock).toBeRequestedTimes(2)
+ expectPromiseVoid = expectAsync(promiseNetworkMock).toBeRequestedTimes({ gte: 5, lte: 10 })
+
+ expectPromiseVoid = expectAsync(promiseNetworkMock).not.toBeRequested()
+ expectPromiseVoid = expectAsync(promiseNetworkMock).not.toBeRequestedTimes(2)
+ expectPromiseVoid = expectAsync(promiseNetworkMock).not.toBeRequestedTimes({ gte: 5, lte: 10 })
+
+ expectPromiseVoid = expectAsync(promiseNetworkMock).toBeRequestedWith({
+ url: 'http://localhost:8080/api',
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: { Authorization: 'foo' },
+ responseHeaders: { Authorization: 'bar' },
+ postData: { title: 'foo', description: 'bar' },
+ response: { success: true },
+ })
+
+ expectPromiseVoid = expectAsync(promiseNetworkMock).toBeRequestedWith({
+ url: wdioExpect.stringContaining('test'),
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: wdioExpect.objectContaining({ Authorization: 'foo' }),
+ responseHeaders: wdioExpect.objectContaining({ Authorization: 'bar' }),
+ postData: wdioExpect.objectContaining({ title: 'foo', description: 'bar' }),
+ response: wdioExpect.objectContaining({ success: true }),
+ })
+
+ expectPromiseVoid = expectAsync(promiseNetworkMock).toBeRequestedWith({
+ url: wdioExpect.stringMatching(/.*\/api\/.*/i),
+ method: ['POST', 'PUT'],
+ statusCode: [401, 403],
+ requestHeaders: headers => headers.Authorization.startsWith('Bearer '),
+ postData: wdioExpect.objectContaining({ released: true, title: wdioExpect.stringContaining('foobar') }),
+ response: (r: { data: { items: unknown[] } }) => Array.isArray(r) && r.data.items.length === 20
+ })
+ })
+
+ it('should have ts errors when typing to void', async () => {
+ // @ts-expect-error
+ expectVoid = expectAsync(promiseNetworkMock).toBeRequested()
+ // @ts-expect-error
+ expectVoid = expectAsync(promiseNetworkMock).toBeRequestedTimes(2) // await expectAsync(mock).toBeRequestedTimes({ eq: 2 })
+ // @ts-expect-error
+ expectVoid = expectAsync(promiseNetworkMock).toBeRequestedTimes({ gte: 5, lte: 10 }) // request called at least 5 times but less than 11
+
+ // @ts-expect-error
+ expectVoid = expectAsync(promiseNetworkMock).not.toBeRequested()
+ // @ts-expect-error
+ expectVoid = expectAsync(promiseNetworkMock).not.toBeRequestedTimes(2) // await expectAsync(mock).toBeRequestedTimes({ eq: 2 })
+ // @ts-expect-error
+ expectVoid = expectAsync(promiseNetworkMock).not.toBeRequestedTimes({ gte: 5, lte: 10 }) // request called at least 5 times but less than 11
+
+ // @ts-expect-error
+ expectVoid = expectAsync(promiseNetworkMock).toBeRequestedWith({
+ url: 'http://localhost:8080/api',
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: { Authorization: 'foo' },
+ responseHeaders: { Authorization: 'bar' },
+ postData: { title: 'foo', description: 'bar' },
+ response: { success: true },
+ })
+
+ // @ts-expect-error
+ expectVoid = expectAsync(promiseNetworkMock).toBeRequestedWith(wdioExpect.objectContaining({
+ response: { success: true },
+ }))
+ })
+ })
+
+ describe('Expect', () => {
+ it('should have ts errors when using a non existing wdioExpect.function', async () => {
+ // @ts-expect-error
+ wdioExpect.unimplementedFunction()
+ })
+
+ it('should support stringContaining, anything and more', async () => {
+ wdioExpect.stringContaining('WebdriverIO')
+ wdioExpect.stringMatching(/WebdriverIO/)
+ wdioExpect.arrayContaining(['WebdriverIO', 'Test'])
+ wdioExpect.objectContaining({ name: 'WebdriverIO' })
+ // Was not there but works!
+ wdioExpect.closeTo(5, 10)
+ wdioExpect.arrayContaining(['WebdriverIO', 'Test'])
+ // New from jest 30!!
+ wdioExpect.arrayOf(wdioExpect.stringContaining('WebdriverIO'))
+
+ wdioExpect.anything()
+ wdioExpect.any(Function)
+ wdioExpect.any(Number)
+ wdioExpect.any(Boolean)
+ wdioExpect.any(String)
+ wdioExpect.any(Symbol)
+ wdioExpect.any(Date)
+ wdioExpect.any(Error)
+
+ wdioExpect.not.stringContaining('WebdriverIO')
+ wdioExpect.not.stringMatching(/WebdriverIO/)
+ wdioExpect.not.arrayContaining(['WebdriverIO', 'Test'])
+ wdioExpect.not.objectContaining({ name: 'WebdriverIO' })
+ wdioExpect.not.closeTo(5, 10)
+ wdioExpect.not.arrayContaining(['WebdriverIO', 'Test'])
+ wdioExpect.not.arrayOf(wdioExpect.stringContaining('WebdriverIO'))
+ })
+
+ describe('Soft Assertions', async () => {
+ const actualString: string = 'Test Page'
+ const actualPromiseString: Promise = Promise.resolve('Test Page')
+
+ describe('wdioExpect.soft', () => {
+ it('should not need to be awaited/be a promise if actual is non-promise type', async () => {
+ const expectWdioMatcher1: WdioCustomMatchers = wdioExpect.soft(actualString)
+ expectVoid = wdioExpect.soft(actualString).toBe('Test Page')
+ expectVoid = wdioExpect.soft(actualString).not.toBe('Test Page')
+ expectVoid = wdioExpect.soft(actualString).not.toBe(wdioExpect.stringContaining('Test Page'))
+
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.soft(actualString).toBe('Test Page')
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.soft(actualString).not.toBe('Test Page')
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.soft(actualString).not.toBe(wdioExpect.stringContaining('Test Page'))
+ })
+
+ it('should need to be awaited/be a promise if actual is promise type', async () => {
+ const expectWdioMatcher1: ExpectWebdriverIO.MatchersAndInverse, Promise> = wdioExpect.soft(actualPromiseString)
+ expectPromiseVoid = wdioExpect.soft(actualPromiseString).toBe('Test Page')
+ expectPromiseVoid = wdioExpect.soft(actualPromiseString).not.toBe('Test Page')
+ expectPromiseVoid = wdioExpect.soft(actualPromiseString).not.toBe(wdioExpect.stringContaining('Test Page'))
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(actualPromiseString).toBe('Test Page')
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(actualPromiseString).not.toBe('Test Page')
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(actualPromiseString).not.toBe(wdioExpect.stringContaining('Test Page'))
+ })
+
+ it('should support chainable element', async () => {
+ const expectElement: WdioCustomMatchers = wdioExpect.soft(element)
+ const expectElementChainable: WdioCustomMatchers = wdioExpect.soft(chainableElement)
+
+ // // @ts-expect-error
+ // const expectElement2: WdioCustomMatchers, WebdriverIO.Element> = wdioExpect.soft(element)
+ // // @ts-expect-error
+ // const expectElementChainable2: WdioCustomMatchers, typeof chainableElement> = wdioExpect.soft(chainableElement)
+ })
+
+ it('should support chainable element with wdio Matchers', async () => {
+ expectPromiseVoid = wdioExpect.soft(element).toBeDisplayed()
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toBeDisplayed()
+ expectPromiseVoid = wdioExpect.soft(chainableArray).toBeDisplayed()
+ await wdioExpect.soft(element).toBeDisplayed()
+ await wdioExpect.soft(chainableElement).toBeDisplayed()
+ await wdioExpect.soft(chainableArray).toBeDisplayed()
+
+ expectPromiseVoid = wdioExpect.soft(element).not.toBeDisplayed()
+ expectPromiseVoid = wdioExpect.soft(chainableElement).not.toBeDisplayed()
+ expectPromiseVoid = wdioExpect.soft(chainableArray).not.toBeDisplayed()
+ await wdioExpect.soft(element).not.toBeDisplayed()
+ await wdioExpect.soft(chainableElement).not.toBeDisplayed()
+ await wdioExpect.soft(chainableArray).not.toBeDisplayed()
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(element).toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableArray).toBeDisplayed()
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(element).not.toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).not.toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableArray).not.toBeDisplayed()
+ })
+ it('should work with custom matcher and custom asymmetric matchers from `expect` module', async () => {
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(wdioExpect.stringContaining('text'))
+ expectPromiseVoid = wdioExpect.soft(chainableElement).not.toHaveCustomProperty(wdioExpect.not.stringContaining('text'))
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(
+ wdioExpect.toHaveCustomProperty(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty('text')
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(wdioExpect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).not.toHaveCustomProperty(wdioExpect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(
+ wdioExpect.toHaveCustomProperty(chainableElement)
+ )
+
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(wdioExpect.stringContaining('text'))
+ expectPromiseVoid = wdioExpect.soft(chainableElement).not.toHaveCustomProperty(wdioExpect.not.stringContaining('text'))
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(
+ wdioExpect.toHaveCustomProperty(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty('text')
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(wdioExpect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).not.toHaveCustomProperty(wdioExpect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(
+ wdioExpect.toHaveCustomProperty(chainableElement)
+ )
+ })
+
+ it('should work with custom matcher and custom asymmetric matchers from `ExpectWebDriverIO` namespace', async () => {
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toBeCustomPromise('text')
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(wdioExpect.stringContaining('text'))
+ expectPromiseVoid = wdioExpect.soft(chainableElement).not.toBeCustomPromise(wdioExpect.not.stringContaining('text'))
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(
+ wdioExpect.toBeCustomPromise(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toBeCustomPromise('text')
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(wdioExpect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).not.toBeCustomPromise(wdioExpect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(
+ wdioExpect.toBeCustomPromise(chainableElement)
+ )
+
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toBeCustomPromise('text')
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(wdioExpect.stringContaining('text'))
+ expectPromiseVoid = wdioExpect.soft(chainableElement).not.toBeCustomPromise(wdioExpect.not.stringContaining('text'))
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(
+ wdioExpect.toBeCustomPromise(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toBeCustomPromise('text')
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(wdioExpect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).not.toBeCustomPromise(wdioExpect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(
+ wdioExpect.toBeCustomPromise(chainableElement)
+ )
+ })
+ })
+
+ describe('wdioExpect.getSoftFailures', () => {
+ it('should be of type `SoftFailure`', async () => {
+ const expectSoftFailure1: ExpectWebdriverIO.SoftFailure[] = wdioExpect.getSoftFailures()
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.getSoftFailures()
+ })
+ })
+
+ describe('wdioExpect.assertSoftFailures', () => {
+ it('should be of type void', async () => {
+ expectVoid = wdioExpect.assertSoftFailures()
+
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.assertSoftFailures()
+ })
+ })
+
+ describe('wdioExpect.clearSoftFailures', () => {
+ it('should be of type void', async () => {
+ expectVoid = wdioExpect.clearSoftFailures()
+
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.clearSoftFailures()
+ })
+ })
+ })
+ })
+
+ describe('Asymmetric matchers', () => {
+ const string: string = 'WebdriverIO is a test framework'
+ const array: string[] = ['WebdriverIO', 'Test']
+ const object: { name: string } = { name: 'WebdriverIO' }
+ const number: number = 1
+
+ it('should have no ts error using asymmetric matchers', async () => {
+ expectAsync(string).toEqual(wdioExpect.stringContaining('WebdriverIO'))
+ expectAsync(array).toEqual(wdioExpect.arrayContaining(['WebdriverIO', 'Test']))
+ expectAsync(object).toEqual(wdioExpect.objectContaining({ name: 'WebdriverIO' }))
+ // This one is tested and is working correctly, surprisingly!
+ expectAsync(number).toEqual(wdioExpect.closeTo(1.0001, 0.0001))
+ // New from jest 30, should work!
+ expectAsync(['apple', 'banana', 'cherry']).toEqual(wdioExpect.arrayOf(wdioExpect.any(String)))
+ })
+ })
+
+ describe('Jasmine only cases', () => {
+ let expectPromiseLikeVoid: PromiseLike
+ it('should support expectAsync correctly for non wdio types', async () => {
+ expectPromiseLikeVoid = expectAsync(Promise.resolve('test')).toBeResolved()
+ expectPromiseLikeVoid = expectAsync(Promise.resolve('test')).toBeResolvedTo(wdioExpect.stringContaining('test error'))
+ expectPromiseLikeVoid = expectAsync(Promise.resolve('test')).not.toBeResolvedTo(wdioExpect.not.stringContaining('test error'))
+ expectPromiseLikeVoid = expectAsync(Promise.resolve('test')).toBeRejected()
+ expectPromiseLikeVoid = expectAsync(Promise.resolve('test')).not.toBeResolved()
+ expectPromiseLikeVoid = expectAsync(Promise.resolve('test')).not.toBeRejected()
+
+ // @ts-expect-error
+ expectVoid = expectAsync(Promise.resolve('test')).toBeResolved()
+ // @ts-expect-error
+ expectVoid = expectAsync(Promise.resolve('test')).toBeRejected()
+
+ // @ts-expect-error
+ expectVoid = expectAsync(Promise.resolve('test')).toBeResolved()
+ })
+ it('jasmine special asymmetric matcher', async () => {
+ // Note: Even though the below is valid syntax, jasmine prefix for asymmetric matchers is not supported by wdioExpect.
+ expectAsync({}).toEqual(jasmine.any(Object))
+ expectAsync(12).toEqual(jasmine.any(Number))
+ })
+
+ })
+})
diff --git a/test-types/jasmine/customMatchers/customMatchers-module-expect.d.ts b/test-types/jasmine/customMatchers/customMatchers-module-expect.d.ts
new file mode 100644
index 000000000..125fb77c7
--- /dev/null
+++ b/test-types/jasmine/customMatchers/customMatchers-module-expect.d.ts
@@ -0,0 +1,26 @@
+import 'expect'
+
+/**
+ * Custom matchers under the `expect` module.
+ * @see {@link https://jestjs.io/docs/expect#expectextendmatchers}
+ */
+declare module 'expect' {
+ interface AsymmetricMatchers {
+ toBeWithinRange(floor: number, ceiling: number): void
+ toHaveSimpleCustomProperty(string: string): string
+ toHaveCustomProperty(element: ChainablePromiseElement | WebdriverIO.Element): Promise>
+ }
+
+ interface Matchers {
+ toBeWithinRange(floor: number, ceiling: number): R
+ toHaveSimpleCustomProperty(string: string | ExpectWebdriverIO.PartialMatcher): R
+ toHaveCustomProperty:
+ // Useful to typecheck the custom matcher so it is only used on elements
+ T extends ChainablePromiseElement | WebdriverIO.Element ?
+ (test: string | ExpectWebdriverIO.PartialMatcher |
+ // Needed for the custom asymmetric matcher defined above to be typed correctly
+ Promise>)
+ // Using `never` blocks the call on non-element types
+ => R : never;
+ }
+}
\ No newline at end of file
diff --git a/test-types/jasmine/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts b/test-types/jasmine/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts
new file mode 100644
index 000000000..89423c9d3
--- /dev/null
+++ b/test-types/jasmine/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts
@@ -0,0 +1,14 @@
+/**
+ * Custom matchers under the `ExpectWebdriverIO` namespace.
+ * @see {@link https://webdriver.io/docs/custommatchers/#typescript-support}
+ */
+declare namespace ExpectWebdriverIO {
+ interface AsymmetricMatchers {
+ toBeCustom(): ExpectWebdriverIO.PartialMatcher;
+ toBeCustomPromise(chainableElement: ChainablePromiseElement): Promise>;
+ }
+ interface Matchers {
+ toBeCustom(): R;
+ toBeCustomPromise: T extends ChainablePromiseElement ? (expected?: string | ExpectWebdriverIO.PartialMatcher | Promise>) => R : never;
+ }
+}
\ No newline at end of file
diff --git a/test-types/jasmine/tsconfig.json b/test-types/jasmine/tsconfig.json
index 6407f175a..3b199e0ef 100644
--- a/test-types/jasmine/tsconfig.json
+++ b/test-types/jasmine/tsconfig.json
@@ -1,14 +1,14 @@
{
"compilerOptions": {
- "outDir": "dist",
+ "noEmit": true,
"noImplicitAny": true,
- "target": "ES2020",
- "module": "Node16",
+ "target": "es2022",
+ "module": "node18",
"skipLibCheck": true,
"types": [
- "@types/jest",
- "expect-webdriverio/jest",
- "@wdio/globals/types"
+ "@types/jasmine",
+ "../../jasmine.d.ts",
+ "../../jasmine-wdio-expect-async.d.ts"
]
}
-}
+}
\ No newline at end of file
diff --git a/test-types/jasmine/types-jasmine.test.ts b/test-types/jasmine/types-jasmine.test.ts
new file mode 100644
index 000000000..7b9749f77
--- /dev/null
+++ b/test-types/jasmine/types-jasmine.test.ts
@@ -0,0 +1,831 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+
+// Desired since we do not want to overwrite the global `expect` from Jasmine
+import { expect as wdioExpect } from 'expect-webdriverio'
+describe('type assertions', () => {
+ const chainableElement = {} as unknown as ChainablePromiseElement
+ const chainableArray = {} as ChainablePromiseArray
+
+ const element: WebdriverIO.Element = {} as unknown as WebdriverIO.Element
+ const elementArray: WebdriverIO.ElementArray = [] as unknown as WebdriverIO.ElementArray
+
+ const networkMock: WebdriverIO.Mock = {} as unknown as WebdriverIO.Mock
+
+ // Type assertions
+ let expectPromiseVoid: Promise
+ let expectVoid: void
+
+ describe('Browser', () => {
+ const browser: WebdriverIO.Browser = {} as unknown as WebdriverIO.Browser
+
+ describe('toHaveUrl', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = wdioExpect(browser).toHaveUrl('https://example.com')
+ expectPromiseVoid = wdioExpect(browser).not.toHaveUrl('https://example.com')
+
+ // Asymmetric matchers
+ expectPromiseVoid = wdioExpect(browser).toHaveUrl(wdioExpect.stringContaining('WebdriverIO'))
+ expectPromiseVoid = wdioExpect(browser).toHaveUrl(wdioExpect.not.stringContaining('WebdriverIO'))
+ expectPromiseVoid = wdioExpect(browser).toHaveUrl(wdioExpect.any(String))
+ expectPromiseVoid = wdioExpect(browser).toHaveUrl(wdioExpect.anything())
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(browser).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ expectVoid = wdioExpect(browser).not.toHaveUrl('https://example.com')
+ // @ts-expect-error
+ expectVoid = wdioExpect(browser).toHaveUrl(wdioExpect.stringContaining('WebdriverIO'))
+
+ // @ts-expect-error
+ await wdioExpect(browser).toHaveUrl(6)
+ })
+
+ it('should have ts errors when actual is not a Browser element', async () => {
+ // @ts-expect-error
+ await wdioExpect(element).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await wdioExpect(element).not.toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await wdioExpect(true).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await wdioExpect(true).not.toHaveUrl('https://example.com')
+ })
+ })
+
+ describe('toHaveTitle', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = wdioExpect(browser).toHaveTitle('https://example.com')
+ expectPromiseVoid = wdioExpect(browser).not.toHaveTitle('https://example.com')
+
+ // Asymmetric matchers
+ expectPromiseVoid = wdioExpect(browser).toHaveTitle(wdioExpect.stringContaining('WebdriverIO'))
+ expectPromiseVoid = wdioExpect(browser).toHaveTitle(wdioExpect.any(String))
+ expectPromiseVoid = wdioExpect(browser).toHaveTitle(wdioExpect.anything())
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(browser).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ expectVoid = wdioExpect(browser).not.toHaveTitle('https://example.com')
+ // @ts-expect-error
+ expectVoid = wdioExpect(browser).toHaveTitle(wdioExpect.stringContaining('WebdriverIO'))
+ })
+
+ it('should have ts errors when actual is not a Browser element', async () => {
+ // @ts-expect-error
+ await wdioExpect(element).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await wdioExpect(element).not.toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await wdioExpect(true).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await wdioExpect(true).not.toHaveTitle('https://example.com')
+ })
+ })
+ })
+
+ describe('element', () => {
+
+ describe('toBeDisabled', () => {
+ it('should be supported correctly', async () => {
+ // Element
+ expectPromiseVoid = wdioExpect(element).toBeDisabled()
+ expectPromiseVoid = wdioExpect(element).not.toBeDisabled()
+
+ // Element array
+ expectPromiseVoid = wdioExpect(elementArray).toBeDisabled()
+ expectPromiseVoid = wdioExpect(elementArray).not.toBeDisabled()
+
+ // Chainable element
+ expectPromiseVoid = wdioExpect(chainableElement).toBeDisabled()
+ expectPromiseVoid = wdioExpect(chainableElement).not.toBeDisabled()
+
+ // Chainable element array
+ expectPromiseVoid = wdioExpect(chainableArray).toBeDisabled()
+ expectPromiseVoid = wdioExpect(chainableArray).not.toBeDisabled()
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(element).toBeDisabled()
+ // @ts-expect-error
+ expectVoid = wdioExpect(element).not.toBeDisabled()
+ })
+
+ it('should have ts errors when actual is not an element', async () => {
+ // @ts-expect-error
+ await wdioExpect(browser).toBeDisabled()
+ // @ts-expect-error
+ await wdioExpect(browser).not.toBeDisabled()
+ // @ts-expect-error
+ await wdioExpect(true).toBeDisabled()
+ // @ts-expect-error
+ await wdioExpect(true).not.toBeDisabled()
+ })
+ })
+
+ describe('toHaveText', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = wdioExpect(element).toHaveText('text')
+ expectPromiseVoid = wdioExpect(element).toHaveText(/text/)
+ expectPromiseVoid = wdioExpect(element).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = wdioExpect(element).toHaveText([wdioExpect.stringContaining('text1'), wdioExpect.stringContaining('text2')])
+ expectPromiseVoid = wdioExpect(element).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = wdioExpect(element).toHaveText(['text1', /text1/, wdioExpect.stringContaining('text3')])
+ await wdioExpect(element).toHaveText(
+ 'My-Ex-Am-Ple',
+ {
+ replace: [[/-/g, ' '], [/[A-Z]+/g, (match: string) => match.toLowerCase()]]
+ }
+ )
+
+ expectPromiseVoid = wdioExpect(element).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(element).toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect(element).toHaveText(6)
+
+ expectPromiseVoid = wdioExpect(chainableElement).toHaveText('text')
+ expectPromiseVoid = wdioExpect(chainableElement).toHaveText(/text/)
+ expectPromiseVoid = wdioExpect(chainableElement).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = wdioExpect(chainableElement).toHaveText([wdioExpect.stringContaining('text1'), wdioExpect.stringContaining('text2')])
+ expectPromiseVoid = wdioExpect(chainableElement).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = wdioExpect(chainableElement).toHaveText(['text1', /text1/, wdioExpect.stringContaining('text3')])
+
+ expectPromiseVoid = wdioExpect(chainableElement).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(chainableElement).toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect(chainableElement).toHaveText(6)
+
+ expectPromiseVoid = wdioExpect(elementArray).toHaveText('text')
+ expectPromiseVoid = wdioExpect(elementArray).toHaveText(/text/)
+ expectPromiseVoid = wdioExpect(elementArray).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = wdioExpect(elementArray).toHaveText([wdioExpect.stringContaining('text1'), wdioExpect.stringContaining('text2')])
+ expectPromiseVoid = wdioExpect(elementArray).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = wdioExpect(elementArray).toHaveText(['text1', /text1/, wdioExpect.stringContaining('text3')])
+
+ expectPromiseVoid = wdioExpect(elementArray).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(elementArray).toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect(elementArray).toHaveText(6)
+
+ expectPromiseVoid = wdioExpect(chainableArray).toHaveText('text')
+ expectPromiseVoid = wdioExpect(chainableArray).toHaveText(/text/)
+ expectPromiseVoid = wdioExpect(chainableArray).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = wdioExpect(chainableArray).toHaveText([wdioExpect.stringContaining('text1'), wdioExpect.stringContaining('text2')])
+ expectPromiseVoid = wdioExpect(chainableArray).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = wdioExpect(chainableArray).toHaveText(['text1', /text1/, wdioExpect.stringContaining('text3')])
+
+ expectPromiseVoid = wdioExpect(chainableArray).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(chainableArray).toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect(chainableArray).toHaveText(6)
+
+ // @ts-expect-error
+ await wdioExpect(browser).toHaveText('text')
+ })
+
+ it('should have ts errors when actual is not an element', async () => {
+ // @ts-expect-error
+ await wdioExpect(browser).toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect(browser).not.toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect(true).toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect(true).toHaveText('text')
+ })
+
+ it('should have ts errors when actual is string or Promise', async () => {
+ // @ts-expect-error
+ await wdioExpect('text').toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect('text').not.toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect(Promise.resolve('text')).toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect(Promise.resolve('text')).toHaveText('text')
+ })
+ })
+
+ describe('toHaveHeight', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = wdioExpect(element).toHaveHeight(100)
+ expectPromiseVoid = wdioExpect(element).toHaveHeight(100, { message: 'Custom error message' })
+ expectPromiseVoid = wdioExpect(element).not.toHaveHeight(100)
+ expectPromiseVoid = wdioExpect(element).not.toHaveHeight(100, { message: 'Custom error message' })
+
+ expectPromiseVoid = wdioExpect(element).toHaveHeight({ width: 100, height: 200 })
+ expectPromiseVoid = wdioExpect(element).toHaveHeight({ width: 100, height: 200 }, { message: 'Custom error message' })
+ expectPromiseVoid = wdioExpect(element).not.toHaveHeight({ width: 100, height: 200 })
+ expectPromiseVoid = wdioExpect(element).not.toHaveHeight({ width: 100, height: 200 }, { message: 'Custom error message' })
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(element).toHaveHeight(100)
+ // @ts-expect-error
+ expectVoid = wdioExpect(element).not.toHaveHeight(100)
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(element).toHaveHeight({ width: 100, height: 200 })
+ // @ts-expect-error
+ expectVoid = wdioExpect(element).not.toHaveHeight({ width: 100, height: 200 })
+
+ // @ts-expect-error
+ await wdioExpect(browser).toHaveHeight(100)
+ })
+
+ it('should have ts errors when actual is string or Promise', async () => {
+ // @ts-expect-error
+ await wdioExpect('text').toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect('text').not.toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect(Promise.resolve('text')).toHaveText('text')
+ // @ts-expect-error
+ await wdioExpect(Promise.resolve('text')).toHaveText('text')
+ })
+ })
+
+ describe('toMatchSnapshot', () => {
+
+ it('should be supported correctly', async () => {
+ expectVoid = wdioExpect(element).toMatchSnapshot()
+ expectVoid = wdioExpect(element).toMatchSnapshot('test label')
+ expectVoid = wdioExpect(element).not.toMatchSnapshot('test label')
+
+ expectPromiseVoid = wdioExpect(chainableElement).toMatchSnapshot()
+ expectPromiseVoid = wdioExpect(chainableElement).toMatchSnapshot('test label')
+ expectPromiseVoid = wdioExpect(chainableElement).not.toMatchSnapshot('test label')
+
+ //@ts-expect-error
+ expectPromiseVoid = wdioExpect(element).toMatchSnapshot()
+ //@ts-expect-error
+ expectPromiseVoid = wdioExpect(element).not.toMatchSnapshot()
+ //@ts-expect-error
+ expectVoid = wdioExpect(chainableElement).toMatchSnapshot()
+ //@ts-expect-error
+ expectVoid = wdioExpect(chainableElement).not.toMatchSnapshot()
+ })
+ })
+
+ describe('toMatchInlineSnapshot', () => {
+
+ it('should be correctly supported', async () => {
+ expectVoid = wdioExpect(element).toMatchInlineSnapshot()
+ expectVoid = wdioExpect(element).toMatchInlineSnapshot('test snapshot')
+ expectVoid = wdioExpect(element).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ expectPromiseVoid = wdioExpect(chainableElement).toMatchInlineSnapshot()
+ expectPromiseVoid = wdioExpect(chainableElement).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = wdioExpect(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ //@ts-expect-error
+ expectPromiseVoid = wdioExpect(element).toMatchInlineSnapshot()
+ //@ts-expect-error
+ expectVoid = wdioExpect(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+ })
+
+ it('should be correctly supported with getCSSProperty()', async () => {
+ expectPromiseVoid = wdioExpect(element.getCSSProperty('test')).toMatchInlineSnapshot()
+ expectPromiseVoid = wdioExpect(element.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = wdioExpect(element.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ expectPromiseVoid = wdioExpect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot()
+ expectPromiseVoid = wdioExpect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = wdioExpect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ //@ts-expect-error
+ expectPromiseVoid = wdioExpect(element).toMatchInlineSnapshot()
+ //@ts-expect-error
+ expectVoid = wdioExpect(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+ })
+ })
+
+ describe('toBeElementsArrayOfSize', async () => {
+
+ it('should work correctly when actual is chainableArray', async () => {
+ expectPromiseVoid = wdioExpect(chainableArray).toBeElementsArrayOfSize(5)
+ expectPromiseVoid = wdioExpect(chainableArray).toBeElementsArrayOfSize({ lte: 10 })
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(chainableArray).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ expectVoid = wdioExpect(chainableArray).toBeElementsArrayOfSize({ lte: 10 })
+ })
+
+ it('should not work when actual is not chainableArray', async () => {
+ // @ts-expect-error
+ await wdioExpect(chainableElement).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ await wdioExpect(chainableElement).toBeElementsArrayOfSize({ lte: 10 })
+ // @ts-expect-error
+ await wdioExpect(true).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ await wdioExpect(true).toBeElementsArrayOfSize({ lte: 10 })
+ })
+ })
+ })
+
+ describe('Custom matchers', () => {
+ describe('using `ExpectWebdriverIO` namespace augmentation', () => {
+ it('should supported correctly a non-promise custom matcher', async () => {
+ expectPromiseVoid = wdioExpect('test').toBeCustom()
+ expectPromiseVoid = wdioExpect('test').not.toBeCustom()
+
+ // @ts-expect-error
+ expectVoid = wdioExpect('test').toBeCustom()
+ // @ts-expect-error
+ expectVoid = wdioExpect('test').not.toBeCustom()
+
+ expectPromiseVoid = wdioExpect(1).toBeWithinRange(0, 2)
+ })
+
+ it('should supported correctly a promise custom matcher with only chainableElement as actual', async () => {
+ expectPromiseVoid = wdioExpect(chainableElement).toBeCustomPromise()
+ expectPromiseVoid = wdioExpect(chainableElement).toBeCustomPromise(wdioExpect.stringContaining('test'))
+ expectPromiseVoid = wdioExpect(chainableElement).not.toBeCustomPromise(wdioExpect.not.stringContaining('test'))
+
+ // @ts-expect-error
+ wdioExpect('test').toBeCustomPromise()
+ // @ts-expect-error
+ expectVoid = wdioExpect(chainableElement).toBeCustomPromise()
+ // @ts-expect-error
+ expectVoid = wdioExpect(chainableElement).toBeCustomPromise(wdioExpect.stringContaining('test'))
+ // @ts-expect-error
+ expectVoid = wdioExpect(chainableElement).not.toBeCustomPromise(wdioExpect.stringContaining('test'))
+ // @ts-expect-error
+ wdioExpect(chainableElement).toBeCustomPromise(wdioExpect.stringContaining(6))
+ })
+
+ it('should support custom asymmetric matcher', async () => {
+ const expectString1 : ExpectWebdriverIO.PartialMatcher = wdioExpect.toBeCustom()
+ const expectString2 : ExpectWebdriverIO.PartialMatcher = wdioExpect.not.toBeCustom()
+
+ expectPromiseVoid = wdioExpect(chainableElement).toBeCustomPromise(wdioExpect.toBeCustom())
+
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.toBeCustom()
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.not.toBeCustom()
+
+ //@ts-expect-error
+ expectVoid = wdioExpect(chainableElement).toBeCustomPromise(wdioExpect.toBeCustom())
+ })
+ })
+
+ describe('using `expect` module declaration', () => {
+
+ it('should support a simple matcher', async () => {
+ expectPromiseVoid = wdioExpect(5).toBeWithinRange(1, 10)
+
+ // Or as an asymmetric matcher:
+ expectPromiseVoid = wdioExpect({ value: 5 }).toEqual({
+ value: wdioExpect.toBeWithinRange(1, 10)
+ })
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(5).toBeWithinRange(1, '10')
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect(5).toBeWithinRange('1')
+ })
+
+ it('should support a simple custom matcher with a chainable element matcher with promise', async () => {
+ wdioExpect(chainableElement)
+ expectPromiseVoid = wdioExpect(chainableElement).toHaveSimpleCustomProperty('text')
+ expectPromiseVoid = wdioExpect(chainableElement).toHaveSimpleCustomProperty(wdioExpect.stringContaining('text'))
+ expectPromiseVoid = wdioExpect(chainableElement).not.toHaveSimpleCustomProperty(wdioExpect.not.stringContaining('text'))
+
+ // Or as a custom asymmetric matcher:
+ expectPromiseVoid = wdioExpect(chainableElement).toHaveSimpleCustomProperty(
+ wdioExpect.toHaveSimpleCustomProperty('string')
+ )
+ const expectString1:string = wdioExpect.toHaveSimpleCustomProperty('string')
+ const expectString2:string = wdioExpect.not.toHaveSimpleCustomProperty('string')
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.toHaveSimpleCustomProperty(chainableElement)
+ // @ts-expect-error
+ expectVoid = wdioExpect.not.toHaveSimpleCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.toHaveSimpleCustomProperty(chainableElement)
+ })
+
+ it('should support a chainable element matcher with promise', async () => {
+ expectPromiseVoid = wdioExpect(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = wdioExpect(chainableElement).toHaveCustomProperty(wdioExpect.stringContaining('text'))
+ expectPromiseVoid = wdioExpect(chainableElement).not.toHaveCustomProperty(wdioExpect.not.stringContaining('text'))
+
+ // Or as a custom asymmetric matcher:
+ expectPromiseVoid = wdioExpect(chainableElement).toHaveCustomProperty(
+ await wdioExpect.toHaveCustomProperty(chainableElement)
+ )
+ const expectPromiseWdioElement1: Promise> = wdioExpect.toHaveCustomProperty(chainableElement)
+ const expectPromiseWdioElement2: Promise> = wdioExpect.not.toHaveCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.toHaveCustomProperty(chainableElement)
+ // @ts-expect-error
+ expectVoid = wdioExpect.not.toHaveCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.toHaveCustomProperty(chainableElement)
+ // @ts-expect-error
+ wdioExpect.toHaveCustomProperty('test')
+
+ await wdioExpect(chainableElement).toHaveCustomProperty(
+ await wdioExpect.toHaveCustomProperty(chainableElement)
+ )
+ })
+ })
+ })
+
+ describe('toBe', () => {
+
+ it('should expect void type when actual is a boolean', async () => {
+ expectPromiseVoid = wdioExpect(true).toBe(true)
+ expectPromiseVoid = wdioExpect(true).not.toBe(true)
+
+ //@ts-expect-error
+ expectVoid = wdioExpect(true).toBe(true)
+ //@ts-expect-error
+ expectVoid = wdioExpect(true).not.toBe(true)
+ })
+
+ it('should not expect Promise when actual is a chainable since toBe does not need to be awaited', async () => {
+ expectPromiseVoid = wdioExpect(chainableElement).toBe(true)
+ expectPromiseVoid = wdioExpect(chainableElement).not.toBe(true)
+
+ //@ts-expect-error
+ expectVoid = wdioExpect(chainableElement).toBe(true)
+ //@ts-expect-error
+ expectVoid = wdioExpect(chainableElement).not.toBe(true)
+ })
+
+ it('should still expect void type when actual is a Promise since we do not overload them', async () => {
+ const promiseBoolean = Promise.resolve(true)
+
+ expectPromiseVoid = wdioExpect(promiseBoolean).toBe(true)
+ expectPromiseVoid = wdioExpect(promiseBoolean).not.toBe(true)
+
+ //@ts-expect-error
+ expectVoid = wdioExpect(promiseBoolean).toBe(true)
+ //@ts-expect-error
+ expectVoid = wdioExpect(promiseBoolean).toBe(true)
+ })
+
+ it('should work with string', async () => {
+ expectPromiseVoid = wdioExpect('text').toBe(true)
+ expectPromiseVoid = wdioExpect('text').not.toBe(true)
+ expectPromiseVoid = wdioExpect('text').toBe(wdioExpect.stringContaining('text'))
+ expectPromiseVoid = wdioExpect('text').not.toBe(wdioExpect.stringContaining('text'))
+
+ //@ts-expect-error
+ expectVoid = wdioExpect('text').toBe(true)
+ //@ts-expect-error
+ expectVoid = wdioExpect('text').not.toBe(true)
+ //@ts-expect-error
+ expectVoid = wdioExpect('text').toBe(wdioExpect.stringContaining('text'))
+ //@ts-expect-error
+ expectVoid = wdioExpect('text').not.toBe(wdioExpect.stringContaining('text'))
+ })
+ })
+
+ describe('Promise type assertions', () => {
+ const booleanPromise: Promise = Promise.resolve(true)
+
+ it('should not compile', async () => {
+ //@ts-expect-error
+ wdioExpect(booleanPromise).resolves.toBe(true)
+ //@ts-expect-error
+ wdioExpect(booleanPromise).rejects.toBe(true)
+ })
+
+ })
+
+ describe('Network Matchers', () => {
+ const promiseNetworkMock = Promise.resolve(networkMock)
+
+ it('should not have ts errors when typing to Promise', async () => {
+ expectPromiseVoid = wdioExpect(promiseNetworkMock).toBeRequested()
+ expectPromiseVoid = wdioExpect(promiseNetworkMock).toBeRequestedTimes(2)
+ expectPromiseVoid = wdioExpect(promiseNetworkMock).toBeRequestedTimes({ gte: 5, lte: 10 })
+
+ expectPromiseVoid = wdioExpect(promiseNetworkMock).not.toBeRequested()
+ expectPromiseVoid = wdioExpect(promiseNetworkMock).not.toBeRequestedTimes(2)
+ expectPromiseVoid = wdioExpect(promiseNetworkMock).not.toBeRequestedTimes({ gte: 5, lte: 10 })
+
+ expectPromiseVoid = wdioExpect(promiseNetworkMock).toBeRequestedWith({
+ url: 'http://localhost:8080/api',
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: { Authorization: 'foo' },
+ responseHeaders: { Authorization: 'bar' },
+ postData: { title: 'foo', description: 'bar' },
+ response: { success: true },
+ })
+
+ expectPromiseVoid = wdioExpect(promiseNetworkMock).toBeRequestedWith({
+ url: wdioExpect.stringContaining('test'),
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: wdioExpect.objectContaining({ Authorization: 'foo' }),
+ responseHeaders: wdioExpect.objectContaining({ Authorization: 'bar' }),
+ postData: wdioExpect.objectContaining({ title: 'foo', description: 'bar' }),
+ response: wdioExpect.objectContaining({ success: true }),
+ })
+
+ expectPromiseVoid = wdioExpect(promiseNetworkMock).toBeRequestedWith({
+ url: wdioExpect.stringMatching(/.*\/api\/.*/i),
+ method: ['POST', 'PUT'],
+ statusCode: [401, 403],
+ requestHeaders: headers => headers.Authorization.startsWith('Bearer '),
+ postData: wdioExpect.objectContaining({ released: true, title: wdioExpect.stringContaining('foobar') }),
+ response: (r: { data: { items: unknown[] } }) => Array.isArray(r) && r.data.items.length === 20
+ })
+ })
+
+ it('should have ts errors when typing to void', async () => {
+ // @ts-expect-error
+ expectVoid = wdioExpect(promiseNetworkMock).toBeRequested()
+ // @ts-expect-error
+ expectVoid = wdioExpect(promiseNetworkMock).toBeRequestedTimes(2) // await wdioExpect(mock).toBeRequestedTimes({ eq: 2 })
+ // @ts-expect-error
+ expectVoid = wdioExpect(promiseNetworkMock).toBeRequestedTimes({ gte: 5, lte: 10 }) // request called at least 5 times but less than 11
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(promiseNetworkMock).not.toBeRequested()
+ // @ts-expect-error
+ expectVoid = wdioExpect(promiseNetworkMock).not.toBeRequestedTimes(2) // await wdioExpect(mock).toBeRequestedTimes({ eq: 2 })
+ // @ts-expect-error
+ expectVoid = wdioExpect(promiseNetworkMock).not.toBeRequestedTimes({ gte: 5, lte: 10 }) // request called at least 5 times but less than 11
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(promiseNetworkMock).toBeRequestedWith({
+ url: 'http://localhost:8080/api',
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: { Authorization: 'foo' },
+ responseHeaders: { Authorization: 'bar' },
+ postData: { title: 'foo', description: 'bar' },
+ response: { success: true },
+ })
+
+ // @ts-expect-error
+ expectVoid = wdioExpect(promiseNetworkMock).toBeRequestedWith(wdioExpect.objectContaining({
+ response: { success: true },
+ }))
+ })
+ })
+
+ describe('Expect', () => {
+ it('should have ts errors when using a non existing wdioExpect.function', async () => {
+ // @ts-expect-error
+ wdioExpect.unimplementedFunction()
+ })
+
+ it('should support stringContaining, anything and more', async () => {
+ wdioExpect.stringContaining('WebdriverIO')
+ wdioExpect.stringMatching(/WebdriverIO/)
+ wdioExpect.arrayContaining(['WebdriverIO', 'Test'])
+ wdioExpect.objectContaining({ name: 'WebdriverIO' })
+ // Was not there but works!
+ wdioExpect.closeTo(5, 10)
+ wdioExpect.arrayContaining(['WebdriverIO', 'Test'])
+ // New from jest 30!!
+ wdioExpect.arrayOf(wdioExpect.stringContaining('WebdriverIO'))
+
+ wdioExpect.anything()
+ wdioExpect.any(Function)
+ wdioExpect.any(Number)
+ wdioExpect.any(Boolean)
+ wdioExpect.any(String)
+ wdioExpect.any(Symbol)
+ wdioExpect.any(Date)
+ wdioExpect.any(Error)
+
+ wdioExpect.not.stringContaining('WebdriverIO')
+ wdioExpect.not.stringMatching(/WebdriverIO/)
+ wdioExpect.not.arrayContaining(['WebdriverIO', 'Test'])
+ wdioExpect.not.objectContaining({ name: 'WebdriverIO' })
+ wdioExpect.not.closeTo(5, 10)
+ wdioExpect.not.arrayContaining(['WebdriverIO', 'Test'])
+ wdioExpect.not.arrayOf(wdioExpect.stringContaining('WebdriverIO'))
+ })
+
+ describe('Soft Assertions', async () => {
+ const actualString: string = 'Test Page'
+ const actualPromiseString: Promise = Promise.resolve('Test Page')
+
+ describe('wdioExpect.soft', () => {
+ it('should not need to be awaited/be a promise if actual is non-promise type', async () => {
+ const expectWdioMatcher1: WdioCustomMatchers = wdioExpect.soft(actualString)
+ expectVoid = wdioExpect.soft(actualString).toBe('Test Page')
+ expectVoid = wdioExpect.soft(actualString).not.toBe('Test Page')
+ expectVoid = wdioExpect.soft(actualString).not.toBe(wdioExpect.stringContaining('Test Page'))
+
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.soft(actualString).toBe('Test Page')
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.soft(actualString).not.toBe('Test Page')
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.soft(actualString).not.toBe(wdioExpect.stringContaining('Test Page'))
+ })
+
+ it('should need to be awaited/be a promise if actual is promise type', async () => {
+ const expectWdioMatcher1: ExpectWebdriverIO.MatchersAndInverse, Promise> = wdioExpect.soft(actualPromiseString)
+ expectPromiseVoid = wdioExpect.soft(actualPromiseString).toBe('Test Page')
+ expectPromiseVoid = wdioExpect.soft(actualPromiseString).not.toBe('Test Page')
+ expectPromiseVoid = wdioExpect.soft(actualPromiseString).not.toBe(wdioExpect.stringContaining('Test Page'))
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(actualPromiseString).toBe('Test Page')
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(actualPromiseString).not.toBe('Test Page')
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(actualPromiseString).not.toBe(wdioExpect.stringContaining('Test Page'))
+ })
+
+ it('should support chainable element', async () => {
+ const expectElement: ExpectWebdriverIO.MatchersAndInverse = wdioExpect.soft(element)
+ const expectElementChainable: ExpectWebdriverIO.MatchersAndInverse = wdioExpect.soft(chainableElement)
+
+ // @ts-expect-error
+ const expectElement2: ExpectWebdriverIO.MatchersAndInverse, WebdriverIO.Element> = wdioExpect.soft(element)
+ // @ts-expect-error
+ const expectElementChainable2: ExpectWebdriverIO.MatchersAndInverse, typeof chainableElement> = wdioExpect.soft(chainableElement)
+ })
+
+ it('should support chainable element with wdio Matchers', async () => {
+ expectPromiseVoid = wdioExpect.soft(element).toBeDisplayed()
+ expectPromiseVoid = wdioExpect.soft(chainableElement).toBeDisplayed()
+ expectPromiseVoid = wdioExpect.soft(chainableArray).toBeDisplayed()
+ await wdioExpect.soft(element).toBeDisplayed()
+ await wdioExpect.soft(chainableElement).toBeDisplayed()
+ await wdioExpect.soft(chainableArray).toBeDisplayed()
+
+ expectPromiseVoid = wdioExpect.soft(element).not.toBeDisplayed()
+ expectPromiseVoid = wdioExpect.soft(chainableElement).not.toBeDisplayed()
+ expectPromiseVoid = wdioExpect.soft(chainableArray).not.toBeDisplayed()
+ await wdioExpect.soft(element).not.toBeDisplayed()
+ await wdioExpect.soft(chainableElement).not.toBeDisplayed()
+ await wdioExpect.soft(chainableArray).not.toBeDisplayed()
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(element).toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableArray).toBeDisplayed()
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(element).not.toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableElement).not.toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = wdioExpect.soft(chainableArray).not.toBeDisplayed()
+ })
+
+ // Those should return a Promise but soft assertions is not even working at runtime.
+ // See Jasmine point 6 in the following issue: https://github.com/webdriverio/expect-webdriverio/issues/1893
+ // it('should work with custom matcher and custom asymmetric matchers from `expect` module', async () => {
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty('text')
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(wdioExpect.stringContaining('text'))
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).not.toHaveCustomProperty(wdioExpect.not.stringContaining('text'))
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(
+ // wdioExpect.toHaveCustomProperty(chainableElement)
+ // )
+
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty('text')
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(wdioExpect.stringContaining('text'))
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).not.toHaveCustomProperty(wdioExpect.not.stringContaining('text'))
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(
+ // wdioExpect.toHaveCustomProperty(chainableElement)
+ // )
+
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty('text')
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(wdioExpect.stringContaining('text'))
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).not.toHaveCustomProperty(wdioExpect.not.stringContaining('text'))
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(
+ // wdioExpect.toHaveCustomProperty(chainableElement)
+ // )
+
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty('text')
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(wdioExpect.stringContaining('text'))
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).not.toHaveCustomProperty(wdioExpect.not.stringContaining('text'))
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).toHaveCustomProperty(
+ // wdioExpect.toHaveCustomProperty(chainableElement)
+ // )
+ // })
+
+ // it('should work with custom matcher and custom asymmetric matchers from `ExpectWebDriverIO` namespace', async () => {
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).toBeCustomPromise('text')
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(wdioExpect.stringContaining('text'))
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).not.toBeCustomPromise(wdioExpect.not.stringContaining('text'))
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(
+ // wdioExpect.toBeCustomPromise(chainableElement)
+ // )
+
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).toBeCustomPromise('text')
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(wdioExpect.stringContaining('text'))
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).not.toBeCustomPromise(wdioExpect.not.stringContaining('text'))
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(
+ // wdioExpect.toBeCustomPromise(chainableElement)
+ // )
+
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).toBeCustomPromise('text')
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(wdioExpect.stringContaining('text'))
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).not.toBeCustomPromise(wdioExpect.not.stringContaining('text'))
+ // expectPromiseVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(
+ // wdioExpect.toBeCustomPromise(chainableElement)
+ // )
+
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).toBeCustomPromise('text')
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(wdioExpect.stringContaining('text'))
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).not.toBeCustomPromise(wdioExpect.not.stringContaining('text'))
+ // // @ts-expect-error
+ // expectVoid = wdioExpect.soft(chainableElement).toBeCustomPromise(
+ // wdioExpect.toBeCustomPromise(chainableElement)
+ // )
+ // })
+ })
+
+ describe('wdioExpect.getSoftFailures', () => {
+ it('should be of type `SoftFailure`', async () => {
+ const expectSoftFailure1: ExpectWebdriverIO.SoftFailure[] = wdioExpect.getSoftFailures()
+
+ // @ts-expect-error
+ expectVoid = wdioExpect.getSoftFailures()
+ })
+ })
+
+ describe('wdioExpect.assertSoftFailures', () => {
+ it('should be of type void', async () => {
+ expectVoid = wdioExpect.assertSoftFailures()
+
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.assertSoftFailures()
+ })
+ })
+
+ describe('wdioExpect.clearSoftFailures', () => {
+ it('should be of type void', async () => {
+ expectVoid = wdioExpect.clearSoftFailures()
+
+ // @ts-expect-error
+ expectPromiseVoid = wdioExpect.clearSoftFailures()
+ })
+ })
+ })
+ })
+
+ describe('Asymmetric matchers', () => {
+ const string: string = 'WebdriverIO is a test framework'
+ const array: string[] = ['WebdriverIO', 'Test']
+ const object: { name: string } = { name: 'WebdriverIO' }
+ const number: number = 1
+
+ it('should have no ts error using asymmetric matchers', async () => {
+ wdioExpect(string).toEqual(wdioExpect.stringContaining('WebdriverIO'))
+ wdioExpect(array).toEqual(wdioExpect.arrayContaining(['WebdriverIO', 'Test']))
+ wdioExpect(object).toEqual(wdioExpect.objectContaining({ name: 'WebdriverIO' }))
+ wdioExpect(number).toEqual(wdioExpect.closeTo(1.0001, 0.0001))
+ wdioExpect(['apple', 'banana', 'cherry']).toEqual(wdioExpect.arrayOf(wdioExpect.any(String)))
+ })
+ })
+
+ describe('Jasmine only cases', () => {
+ let expectPromiseLikeVoid: PromiseLike
+
+ it('should not overwrite the jasmine global expect', async () => {
+ const expectVoid: jasmine.ArrayLikeMatchers = expect('test')
+ })
+ it('should support expectAsync correctly for non wdio types', async () => {
+ expectPromiseLikeVoid = expectAsync(Promise.resolve('test')).toBeResolved()
+ expectPromiseLikeVoid = expectAsync(Promise.resolve('test')).toBeResolvedTo(wdioExpect.stringContaining('test error'))
+ expectPromiseLikeVoid = expectAsync(Promise.resolve('test')).not.toBeResolvedTo(wdioExpect.not.stringContaining('test error'))
+ expectPromiseLikeVoid = expectAsync(Promise.resolve('test')).toBeRejected()
+ expectPromiseLikeVoid = expectAsync(Promise.resolve('test')).not.toBeResolved()
+ expectPromiseLikeVoid = expectAsync(Promise.resolve('test')).not.toBeRejected()
+ })
+ })
+})
diff --git a/test-types/jest-@jest_global/customMatchers/customMatchers-module-expect.d.ts b/test-types/jest-@jest_global/customMatchers/customMatchers-module-expect.d.ts
new file mode 100644
index 000000000..750d6e1ff
--- /dev/null
+++ b/test-types/jest-@jest_global/customMatchers/customMatchers-module-expect.d.ts
@@ -0,0 +1,26 @@
+import 'expect'
+
+/**
+ * Custom matchers under the `expect` module.
+ * @see {@link https://jestjs.io/docs/expect#expectextendmatchers}
+ */
+declare module 'expect' {
+ interface AsymmetricMatchers {
+ toBeWithinRange(floor: number, ceiling: number): void
+ toHaveSimpleCustomProperty(string: string): string
+ toHaveCustomProperty(element: ChainablePromiseElement | WebdriverIO.Element): Promise>
+ }
+
+ interface Matchers {
+ toBeWithinRange(floor: number, ceiling: number): R
+ toHaveSimpleCustomProperty(string: string | ExpectWebdriverIO.PartialMatcher): Promise
+ toHaveCustomProperty:
+ // Useful to typecheck the custom matcher so it is only used on elements
+ T extends ChainablePromiseElement | WebdriverIO.Element ?
+ (test: string | ExpectWebdriverIO.PartialMatcher |
+ // Needed for the custom asymmetric matcher defined above to be typed correctly
+ Promise>)
+ // Using `never` blocks the call on non-element types
+ => Promise : never;
+ }
+}
\ No newline at end of file
diff --git a/test-types/jest-@jest_global/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts b/test-types/jest-@jest_global/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts
new file mode 100644
index 000000000..7a833bd87
--- /dev/null
+++ b/test-types/jest-@jest_global/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts
@@ -0,0 +1,14 @@
+/**
+ * Custom matchers under the `ExpectWebdriverIO` namespace.
+ * @see {@link https://webdriver.io/docs/custommatchers/#typescript-support}
+ */
+declare namespace ExpectWebdriverIO {
+ interface AsymmetricMatchers {
+ toBeCustom(): ExpectWebdriverIO.PartialMatcher;
+ toBeCustomPromise(chainableElement: ChainablePromiseElement): Promise>;
+ }
+ interface Matchers {
+ toBeCustom(): R;
+ toBeCustomPromise: T extends ChainablePromiseElement ? (expected?: string | ExpectWebdriverIO.PartialMatcher | Promise>) => Promise : never;
+ }
+}
\ No newline at end of file
diff --git a/test-types/jest-@jest_global/tsconfig.json b/test-types/jest-@jest_global/tsconfig.json
new file mode 100644
index 000000000..06f3ef3c7
--- /dev/null
+++ b/test-types/jest-@jest_global/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "noEmit": true,
+ "noImplicitAny": true,
+ "target": "es2022",
+ "module": "node18",
+ "skipLibCheck": true,
+ }
+}
diff --git a/test-types/jest-@jest_global/types-jest.test.ts b/test-types/jest-@jest_global/types-jest.test.ts
new file mode 100644
index 000000000..193856f87
--- /dev/null
+++ b/test-types/jest-@jest_global/types-jest.test.ts
@@ -0,0 +1,925 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+
+import { expect } from 'expect-webdriverio'
+import { describe, it, expect as jestExpect } from '@jest/globals'
+
+describe('type assertions', async () => {
+ const chainableElement = {} as unknown as ChainablePromiseElement
+ const chainableArray = {} as ChainablePromiseArray
+
+ const element: WebdriverIO.Element = {} as unknown as WebdriverIO.Element
+ const elementArray: WebdriverIO.ElementArray = [] as unknown as WebdriverIO.ElementArray
+
+ const networkMock: WebdriverIO.Mock = {} as unknown as WebdriverIO.Mock
+
+ // Type assertions
+ let expectPromiseVoid: Promise
+ let expectVoid: void
+
+ describe('Browser', () => {
+ const browser: WebdriverIO.Browser = {} as unknown as WebdriverIO.Browser
+
+ describe('toHaveUrl', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expect(browser).toHaveUrl('https://example.com')
+ expectPromiseVoid = expect(browser).not.toHaveUrl('https://example.com')
+
+ // Asymmetric matchers
+ expectPromiseVoid = expect(browser).toHaveUrl(expect.stringContaining('WebdriverIO'))
+ expectPromiseVoid = expect(browser).toHaveUrl(expect.not.stringContaining('WebdriverIO'))
+ expectPromiseVoid = expect(browser).toHaveUrl(expect.any(String))
+ expectPromiseVoid = expect(browser).toHaveUrl(expect.anything())
+
+ // @ts-expect-error
+ expectVoid = expect(browser).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ expectVoid = expect(browser).not.toHaveUrl('https://example.com')
+ // @ts-expect-error
+ expectVoid = expect(browser).toHaveUrl(expect.stringContaining('WebdriverIO'))
+
+ // @ts-expect-error
+ await expect(browser).toHaveUrl(6)
+ })
+
+ it('should have ts errors when actual is not a Browser element', async () => {
+ // @ts-expect-error
+ await expect(element).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await expect(element).not.toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await expect(true).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await expect(true).not.toHaveUrl('https://example.com')
+ })
+ })
+
+ describe('toHaveTitle', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expect(browser).toHaveTitle('https://example.com')
+ expectPromiseVoid = expect(browser).not.toHaveTitle('https://example.com')
+
+ // Asymmetric matchers
+ expectPromiseVoid = expect(browser).toHaveTitle(expect.stringContaining('WebdriverIO'))
+ expectPromiseVoid = expect(browser).toHaveTitle(expect.any(String))
+ expectPromiseVoid = expect(browser).toHaveTitle(expect.anything())
+
+ // @ts-expect-error
+ expectVoid = expect(browser).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ expectVoid = expect(browser).not.toHaveTitle('https://example.com')
+ // @ts-expect-error
+ expectVoid = expect(browser).toHaveTitle(expect.stringContaining('WebdriverIO'))
+ })
+
+ it('should have ts errors when actual is not a Browser element', async () => {
+ // @ts-expect-error
+ await expect(element).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await expect(element).not.toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await expect(true).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await expect(true).not.toHaveTitle('https://example.com')
+ })
+ })
+ })
+
+ describe('element', () => {
+
+ describe('toBeDisabled', () => {
+ it('should be supported correctly', async () => {
+ // Element
+ expectPromiseVoid = expect(element).toBeDisabled()
+ expectPromiseVoid = expect(element).not.toBeDisabled()
+
+ // Element array
+ expectPromiseVoid = expect(elementArray).toBeDisabled()
+ expectPromiseVoid = expect(elementArray).not.toBeDisabled()
+
+ // Chainable element
+ expectPromiseVoid = expect(chainableElement).toBeDisabled()
+ expectPromiseVoid = expect(chainableElement).not.toBeDisabled()
+
+ // Chainable element array
+ expectPromiseVoid = expect(chainableArray).toBeDisabled()
+ expectPromiseVoid = expect(chainableArray).not.toBeDisabled()
+
+ // @ts-expect-error
+ expectVoid = expect(element).toBeDisabled()
+ // @ts-expect-error
+ expectVoid = expect(element).not.toBeDisabled()
+ })
+
+ it('should have ts errors when actual is not an element', async () => {
+ // @ts-expect-error
+ await expect(browser).toBeDisabled()
+ // @ts-expect-error
+ await expect(browser).not.toBeDisabled()
+ // @ts-expect-error
+ await expect(true).toBeDisabled()
+ // @ts-expect-error
+ await expect(true).not.toBeDisabled()
+ })
+ })
+
+ describe('toHaveText', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expect(element).toHaveText('text')
+ expectPromiseVoid = expect(element).toHaveText(/text/)
+ expectPromiseVoid = expect(element).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expect(element).toHaveText([expect.stringContaining('text1'), expect.stringContaining('text2')])
+ expectPromiseVoid = expect(element).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expect(element).toHaveText(['text1', /text1/, expect.stringContaining('text3')])
+ await expect(element).toHaveText(
+ 'My-Ex-Am-Ple',
+ {
+ replace: [[/-/g, ' '], [/[A-Z]+/g, (match: string) => match.toLowerCase()]]
+ }
+ )
+
+ expectPromiseVoid = expect(element).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expect(element).toHaveText('text')
+ // @ts-expect-error
+ await expect(element).toHaveText(6)
+
+ expectPromiseVoid = expect(chainableElement).toHaveText('text')
+ expectPromiseVoid = expect(chainableElement).toHaveText(/text/)
+ expectPromiseVoid = expect(chainableElement).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expect(chainableElement).toHaveText([expect.stringContaining('text1'), expect.stringContaining('text2')])
+ expectPromiseVoid = expect(chainableElement).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expect(chainableElement).toHaveText(['text1', /text1/, expect.stringContaining('text3')])
+
+ expectPromiseVoid = expect(chainableElement).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expect(chainableElement).toHaveText('text')
+ // @ts-expect-error
+ await expect(chainableElement).toHaveText(6)
+
+ expectPromiseVoid = expect(elementArray).toHaveText('text')
+ expectPromiseVoid = expect(elementArray).toHaveText(/text/)
+ expectPromiseVoid = expect(elementArray).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expect(elementArray).toHaveText([expect.stringContaining('text1'), expect.stringContaining('text2')])
+ expectPromiseVoid = expect(elementArray).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expect(elementArray).toHaveText(['text1', /text1/, expect.stringContaining('text3')])
+
+ expectPromiseVoid = expect(elementArray).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expect(elementArray).toHaveText('text')
+ // @ts-expect-error
+ await expect(elementArray).toHaveText(6)
+
+ expectPromiseVoid = expect(chainableArray).toHaveText('text')
+ expectPromiseVoid = expect(chainableArray).toHaveText(/text/)
+ expectPromiseVoid = expect(chainableArray).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expect(chainableArray).toHaveText([expect.stringContaining('text1'), expect.stringContaining('text2')])
+ expectPromiseVoid = expect(chainableArray).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expect(chainableArray).toHaveText(['text1', /text1/, expect.stringContaining('text3')])
+
+ expectPromiseVoid = expect(chainableArray).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expect(chainableArray).toHaveText('text')
+ // @ts-expect-error
+ await expect(chainableArray).toHaveText(6)
+
+ // @ts-expect-error
+ await expect(browser).toHaveText('text')
+ })
+
+ it('should have ts errors when actual is not an element', async () => {
+ // @ts-expect-error
+ await expect(browser).toHaveText('text')
+ // @ts-expect-error
+ await expect(browser).not.toHaveText('text')
+ // @ts-expect-error
+ await expect(true).toHaveText('text')
+ // @ts-expect-error
+ await expect(true).toHaveText('text')
+ })
+
+ it('should have ts errors when actual is string or Promise', async () => {
+ // @ts-expect-error
+ await expect('text').toHaveText('text')
+ // @ts-expect-error
+ await expect('text').not.toHaveText('text')
+ // @ts-expect-error
+ await expect(Promise.resolve('text')).toHaveText('text')
+ // @ts-expect-error
+ await expect(Promise.resolve('text')).toHaveText('text')
+ })
+ })
+
+ describe('toHaveHeight', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expect(element).toHaveHeight(100)
+ expectPromiseVoid = expect(element).toHaveHeight(100, { message: 'Custom error message' })
+ expectPromiseVoid = expect(element).not.toHaveHeight(100)
+ expectPromiseVoid = expect(element).not.toHaveHeight(100, { message: 'Custom error message' })
+
+ expectPromiseVoid = expect(element).toHaveHeight({ width: 100, height: 200 })
+ expectPromiseVoid = expect(element).toHaveHeight({ width: 100, height: 200 }, { message: 'Custom error message' })
+ expectPromiseVoid = expect(element).not.toHaveHeight({ width: 100, height: 200 })
+ expectPromiseVoid = expect(element).not.toHaveHeight({ width: 100, height: 200 }, { message: 'Custom error message' })
+
+ // @ts-expect-error
+ expectVoid = expect(element).toHaveHeight(100)
+ // @ts-expect-error
+ expectVoid = expect(element).not.toHaveHeight(100)
+
+ // @ts-expect-error
+ expectVoid = expect(element).toHaveHeight({ width: 100, height: 200 })
+ // @ts-expect-error
+ expectVoid = expect(element).not.toHaveHeight({ width: 100, height: 200 })
+
+ // @ts-expect-error
+ await expect(browser).toHaveHeight(100)
+ })
+
+ it('should have ts errors when actual is string or Promise', async () => {
+ // @ts-expect-error
+ await expect('text').toHaveText('text')
+ // @ts-expect-error
+ await expect('text').not.toHaveText('text')
+ // @ts-expect-error
+ await expect(Promise.resolve('text')).toHaveText('text')
+ // @ts-expect-error
+ await expect(Promise.resolve('text')).toHaveText('text')
+ })
+ })
+
+ describe('toMatchSnapshot', () => {
+
+ it('should be supported correctly', async () => {
+ expectVoid = expect(element).toMatchSnapshot()
+ expectVoid = expect(element).toMatchSnapshot('test label')
+ expectVoid = expect(element).not.toMatchSnapshot('test label')
+
+ expectPromiseVoid = expect(chainableElement).toMatchSnapshot()
+ expectPromiseVoid = expect(chainableElement).toMatchSnapshot('test label')
+ expectPromiseVoid = expect(chainableElement).not.toMatchSnapshot('test label')
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(element).toMatchSnapshot()
+ //@ts-expect-error
+ expectPromiseVoid = expect(element).not.toMatchSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).toMatchSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).not.toMatchSnapshot()
+ })
+ })
+
+ describe('toMatchInlineSnapshot', () => {
+
+ it('should be correctly supported', async () => {
+ expectVoid = expect(element).toMatchInlineSnapshot()
+ expectVoid = expect(element).toMatchInlineSnapshot('test snapshot')
+ expectVoid = expect(element).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ expectPromiseVoid = expect(chainableElement).toMatchInlineSnapshot()
+ expectPromiseVoid = expect(chainableElement).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expect(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(element).toMatchInlineSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+ })
+
+ it('should be correctly supported with getCSSProperty()', async () => {
+ expectPromiseVoid = expect(element.getCSSProperty('test')).toMatchInlineSnapshot()
+ expectPromiseVoid = expect(element.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expect(element.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ expectPromiseVoid = expect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot()
+ expectPromiseVoid = expect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(element).toMatchInlineSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+ })
+ })
+
+ describe('toBeElementsArrayOfSize', async () => {
+
+ it('should work correctly when actual is chainableArray', async () => {
+ expectPromiseVoid = expect(chainableArray).toBeElementsArrayOfSize(5)
+ expectPromiseVoid = expect(chainableArray).toBeElementsArrayOfSize({ lte: 10 })
+
+ // @ts-expect-error
+ expectVoid = expect(chainableArray).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ expectVoid = expect(chainableArray).toBeElementsArrayOfSize({ lte: 10 })
+ })
+
+ it('should not work when actual is not chainableArray', async () => {
+ // @ts-expect-error
+ await expect(chainableElement).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ await expect(chainableElement).toBeElementsArrayOfSize({ lte: 10 })
+ // @ts-expect-error
+ await expect(true).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ await expect(true).toBeElementsArrayOfSize({ lte: 10 })
+ })
+ })
+ })
+
+ describe('Custom matchers', () => {
+ describe('using `ExpectWebdriverIO` namespace augmentation', () => {
+ it('should supported correctly a non-promise custom matcher', async () => {
+ expectVoid = expect('test').toBeCustom()
+ expectVoid = expect('test').not.toBeCustom()
+
+ // @ts-expect-error
+ expectPromiseVoid = expect('test').toBeCustom()
+ // @ts-expect-error
+ expectPromiseVoid = expect('test').not.toBeCustom()
+
+ expectVoid = expect(1).toBeWithinRange(0, 2)
+ })
+
+ it('should supported correctly a promise custom matcher with only chainableElement as actual', async () => {
+ expectPromiseVoid = expect(chainableElement).toBeCustomPromise()
+ expectPromiseVoid = expect(chainableElement).toBeCustomPromise(expect.stringContaining('test'))
+ expectPromiseVoid = expect(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('test'))
+
+ // @ts-expect-error
+ expect('test').toBeCustomPromise()
+ // @ts-expect-error
+ expectVoid = expect(chainableElement).toBeCustomPromise()
+ // @ts-expect-error
+ expectVoid = expect(chainableElement).toBeCustomPromise(expect.stringContaining('test'))
+ // @ts-expect-error
+ expectVoid = expect(chainableElement).not.toBeCustomPromise(expect.stringContaining('test'))
+ // @ts-expect-error
+ expect(chainableElement).toBeCustomPromise(expect.stringContaining(6))
+ })
+
+ it('should support custom asymmetric matcher', async () => {
+ const expectString1 : ExpectWebdriverIO.PartialMatcher = expect.toBeCustom()
+ const expectString2 : ExpectWebdriverIO.PartialMatcher = expect.not.toBeCustom()
+
+ expectPromiseVoid = expect(chainableElement).toBeCustomPromise(expect.toBeCustom())
+
+ // @ts-expect-error
+ expectPromiseVoid = expect.toBeCustom()
+ // @ts-expect-error
+ expectPromiseVoid = expect.not.toBeCustom()
+
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).toBeCustomPromise(expect.toBeCustom())
+ })
+ })
+
+ describe('using `expect` module declaration', () => {
+
+ it('should support a simple matcher', async () => {
+ expectVoid = expect(5).toBeWithinRange(1, 10)
+
+ // Or as an asymmetric matcher:
+ expectVoid = expect({ value: 5 }).toEqual({
+ value: expect.toBeWithinRange(1, 10)
+ })
+
+ // @ts-expect-error
+ expectVoid = expect(5).toBeWithinRange(1, '10')
+ // @ts-expect-error
+ expectPromiseVoid = expect(5).toBeWithinRange('1')
+ })
+
+ it('should support a simple custom matcher with a chainable element matcher with promise', async () => {
+ expectPromiseVoid = expect(chainableElement).toHaveSimpleCustomProperty('text')
+ expectPromiseVoid = expect(chainableElement).toHaveSimpleCustomProperty(expect.stringContaining('text'))
+ expectPromiseVoid = expect(chainableElement).not.toHaveSimpleCustomProperty(expect.not.stringContaining('text'))
+
+ // Or as a custom asymmetric matcher:
+ expectPromiseVoid = expect(chainableElement).toHaveSimpleCustomProperty(
+ expect.toHaveSimpleCustomProperty('string')
+ )
+ const expectString1:string = expect.toHaveSimpleCustomProperty('string')
+ const expectString2:string = expect.not.toHaveSimpleCustomProperty('string')
+
+ // @ts-expect-error
+ expectVoid = expect.toHaveSimpleCustomProperty(chainableElement)
+ // @ts-expect-error
+ expectVoid = expect.not.toHaveSimpleCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = expect.toHaveSimpleCustomProperty(chainableElement)
+ })
+
+ it('should support a chainable element matcher with promise', async () => {
+ expectPromiseVoid = expect(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = expect(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ expectPromiseVoid = expect(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+
+ // Or as a custom asymmetric matcher:
+ expectPromiseVoid = expect(chainableElement).toHaveCustomProperty(
+ await expect.toHaveCustomProperty(chainableElement)
+ )
+ const expectPromiseWdioElement1: Promise> = expect.toHaveCustomProperty(chainableElement)
+ const expectPromiseWdioElement2: Promise> = expect.not.toHaveCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = expect.toHaveCustomProperty(chainableElement)
+ // @ts-expect-error
+ expectVoid = expect.not.toHaveCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = expect.toHaveCustomProperty(chainableElement)
+ // @ts-expect-error
+ expect.toHaveCustomProperty('test')
+
+ await expect(chainableElement).toHaveCustomProperty(
+ await expect.toHaveCustomProperty(chainableElement)
+ )
+ })
+ })
+ })
+
+ describe('toBe', () => {
+
+ it('should expect void type when actual is a boolean', async () => {
+ expectVoid = expect(true).toBe(true)
+ expectVoid = expect(true).not.toBe(true)
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(true).toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect(true).not.toBe(true)
+ })
+
+ it('should not expect Promise when actual is a chainable since toBe does not need to be awaited', async () => {
+ expectVoid = expect(chainableElement).toBe(true)
+ expectVoid = expect(chainableElement).not.toBe(true)
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(chainableElement).toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect(chainableElement).not.toBe(true)
+ })
+
+ it('should still expect void type when actual is a Promise since we do not overload them', async () => {
+ const promiseBoolean = Promise.resolve(true)
+
+ expectVoid = expect(promiseBoolean).toBeDefined()
+ expectVoid = expect(promiseBoolean).not.toBeDefined()
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(promiseBoolean).toBeDefined()
+ //@ts-expect-error
+ expectPromiseVoid = expect(promiseBoolean).toBe(true)
+ })
+
+ it('should work with string', async () => {
+ expectVoid = expect('text').toBe(true)
+ expectVoid = expect('text').not.toBe(true)
+ expectVoid = expect('text').toBe(expect.stringContaining('text'))
+ expectVoid = expect('text').not.toBe(expect.stringContaining('text'))
+
+ //@ts-expect-error
+ expectPromiseVoid = expect('text').toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect('text').not.toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect('text').toBe(expect.stringContaining('text'))
+ //@ts-expect-error
+ expectPromiseVoid = expect('text').not.toBe(expect.stringContaining('text'))
+ })
+ })
+
+ describe('Promise type assertions', () => {
+ const booleanPromise: Promise = Promise.resolve(true)
+
+ it('should work with resolves & rejects correctly', async () => {
+ expectPromiseVoid = expect(booleanPromise).resolves.toBe(true)
+ expectPromiseVoid = expect(booleanPromise).rejects.toBe(true)
+
+ //@ts-expect-error
+ expectVoid = expect(booleanPromise).resolves.toBe(true)
+ //@ts-expect-error
+ expectVoid = expect(booleanPromise).rejects.toBe(true)
+
+ })
+
+ it('should not support chainable and expect PromiseVoid with toBe', async () => {
+ //@ts-expect-error
+ expectPromiseVoid = expect(chainableElement).toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect(chainableElement).not.toBe(true)
+ })
+ })
+
+ describe('Network Matchers', () => {
+ const promiseNetworkMock = Promise.resolve(networkMock)
+
+ it('should not have ts errors when typing to Promise', async () => {
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequested()
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedTimes(2)
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedTimes({ gte: 5, lte: 10 })
+
+ expectPromiseVoid = expect(promiseNetworkMock).not.toBeRequested()
+ expectPromiseVoid = expect(promiseNetworkMock).not.toBeRequestedTimes(2)
+ expectPromiseVoid = expect(promiseNetworkMock).not.toBeRequestedTimes({ gte: 5, lte: 10 })
+
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedWith({
+ url: 'http://localhost:8080/api',
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: { Authorization: 'foo' },
+ responseHeaders: { Authorization: 'bar' },
+ postData: { title: 'foo', description: 'bar' },
+ response: { success: true },
+ })
+
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedWith({
+ url: expect.stringContaining('test'),
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: expect.objectContaining({ Authorization: 'foo' }),
+ responseHeaders: expect.objectContaining({ Authorization: 'bar' }),
+ postData: expect.objectContaining({ title: 'foo', description: 'bar' }),
+ response: expect.objectContaining({ success: true }),
+ })
+
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedWith({
+ url: expect.stringMatching(/.*\/api\/.*/i),
+ method: ['POST', 'PUT'],
+ statusCode: [401, 403],
+ requestHeaders: headers => headers.Authorization.startsWith('Bearer '),
+ postData: expect.objectContaining({ released: true, title: expect.stringContaining('foobar') }),
+ response: (r: { data: { items: unknown[] } }) => Array.isArray(r) && r.data.items.length === 20
+ })
+ })
+
+ it('should have ts errors when typing to void', async () => {
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequested()
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequestedTimes(2) // await expect(mock).toBeRequestedTimes({ eq: 2 })
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequestedTimes({ gte: 5, lte: 10 }) // request called at least 5 times but less than 11
+
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).not.toBeRequested()
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).not.toBeRequestedTimes(2) // await expect(mock).toBeRequestedTimes({ eq: 2 })
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).not.toBeRequestedTimes({ gte: 5, lte: 10 }) // request called at least 5 times but less than 11
+
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequestedWith({
+ url: 'http://localhost:8080/api',
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: { Authorization: 'foo' },
+ responseHeaders: { Authorization: 'bar' },
+ postData: { title: 'foo', description: 'bar' },
+ response: { success: true },
+ })
+
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequestedWith(expect.objectContaining({
+ response: { success: true },
+ }))
+ })
+ })
+
+ describe('Expect', () => {
+ it('should have ts errors when using a non existing expect.function', async () => {
+ // @ts-expect-error
+ expect.unimplementedFunction()
+ })
+
+ it('should support stringContaining, anything and more', async () => {
+ expect.stringContaining('WebdriverIO')
+ expect.stringMatching(/WebdriverIO/)
+ expect.arrayContaining(['WebdriverIO', 'Test'])
+ expect.objectContaining({ name: 'WebdriverIO' })
+ // Was not there but works!
+ expect.closeTo(5, 10)
+ expect.arrayContaining(['WebdriverIO', 'Test'])
+ // New from jest 30!!
+ expect.arrayOf(expect.stringContaining('WebdriverIO'))
+
+ expect.anything()
+ expect.any(Function)
+ expect.any(Number)
+ expect.any(Boolean)
+ expect.any(String)
+ expect.any(Symbol)
+ expect.any(Date)
+ expect.any(Error)
+
+ expect.not.stringContaining('WebdriverIO')
+ expect.not.stringMatching(/WebdriverIO/)
+ expect.not.arrayContaining(['WebdriverIO', 'Test'])
+ expect.not.objectContaining({ name: 'WebdriverIO' })
+ expect.not.closeTo(5, 10)
+ expect.not.arrayContaining(['WebdriverIO', 'Test'])
+ expect.not.arrayOf(expect.stringContaining('WebdriverIO'))
+
+ // expect.not.anything()
+ // expect.not.any(Function)
+ // expect.not.any(Number)
+ // expect.not.any(Boolean)
+ // expect.not.any(String)
+ // expect.not.any(Symbol)
+ // expect.not.any(Date)
+ // expect.not.any(Error)
+ })
+
+ describe('Soft Assertions', async () => {
+ const actualString: string = 'test'
+ const actualPromiseString: Promise = Promise.resolve('test')
+
+ describe('expect.soft', () => {
+ it('should not need to be awaited/be a promise if actual is non-promise type', async () => {
+ const expectWdioMatcher1: WdioCustomMatchers = expect.soft(actualString)
+ expectVoid = expect.soft(actualString).toBe('Test Page')
+ expectVoid = expect.soft(actualString).not.toBe('Test Page')
+ expectVoid = expect.soft(actualString).not.toBe(expect.stringContaining('Test Page'))
+
+ // @ts-expect-error
+ expectPromiseVoid = expect.soft(actualString).toBe('Test Page')
+ // @ts-expect-error
+ expectPromiseVoid = expect.soft(actualString).not.toBe('Test Page')
+ // @ts-expect-error
+ expectPromiseVoid = expect.soft(actualString).not.toBe(expect.stringContaining('Test Page'))
+ })
+
+ it('should need to be awaited/be a promise if actual is promise type', async () => {
+ const expectWdioMatcher1: ExpectWebdriverIO.MatchersAndInverse, Promise> = expect.soft(actualPromiseString)
+ expectPromiseVoid = expect.soft(actualPromiseString).toBe('Test Page')
+ expectPromiseVoid = expect.soft(actualPromiseString).not.toBe('Test Page')
+ expectPromiseVoid = expect.soft(actualPromiseString).not.toBe(expect.stringContaining('Test Page'))
+
+ // @ts-expect-error
+ expectVoid = expect.soft(actualPromiseString).toBe('Test Page')
+ // @ts-expect-error
+ expectVoid = expect.soft(actualPromiseString).not.toBe('Test Page')
+ // @ts-expect-error
+ expectVoid = expect.soft(actualPromiseString).not.toBe(expect.stringContaining('Test Page'))
+ })
+
+ it('should support chainable element', async () => {
+ const expectElement: ExpectWebdriverIO.MatchersAndInverse = expect.soft(element)
+ const expectElementChainable: ExpectWebdriverIO.MatchersAndInverse = expect.soft(chainableElement)
+
+ // @ts-expect-error
+ const expectElement2: ExpectWebdriverIO.MatchersAndInverse, WebdriverIO.Element> = expect.soft(element)
+ // @ts-expect-error
+ const expectElementChainable2: ExpectWebdriverIO.MatchersAndInverse, typeof chainableElement> = expect.soft(chainableElement)
+ })
+
+ it('should support chainable element with wdio Matchers', async () => {
+ expectPromiseVoid = expect.soft(element).toBeDisplayed()
+ expectPromiseVoid = expect.soft(chainableElement).toBeDisplayed()
+ expectPromiseVoid = expect.soft(chainableArray).toBeDisplayed()
+ await expect.soft(element).toBeDisplayed()
+ await expect.soft(chainableElement).toBeDisplayed()
+ await expect.soft(chainableArray).toBeDisplayed()
+
+ expectPromiseVoid = expect.soft(element).not.toBeDisplayed()
+ expectPromiseVoid = expect.soft(chainableElement).not.toBeDisplayed()
+ expectPromiseVoid = expect.soft(chainableArray).not.toBeDisplayed()
+ await expect.soft(element).not.toBeDisplayed()
+ await expect.soft(chainableElement).not.toBeDisplayed()
+ await expect.soft(chainableArray).not.toBeDisplayed()
+
+ // @ts-expect-error
+ expectVoid = expect.soft(element).toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableArray).toBeDisplayed()
+
+ // @ts-expect-error
+ expectVoid = expect.soft(element).not.toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableArray).not.toBeDisplayed()
+ })
+
+ it('should work with custom matcher and custom asymmetric matchers from `expect` module', async () => {
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty(
+ expect.toHaveCustomProperty(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty('text')
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty(
+ expect.toHaveCustomProperty(chainableElement)
+ )
+
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty(
+ expect.toHaveCustomProperty(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty('text')
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty(
+ expect.toHaveCustomProperty(chainableElement)
+ )
+ })
+
+ it('should work with custom matcher and custom asymmetric matchers from `ExpectWebDriverIO` namespace', async () => {
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise('text')
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise(expect.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise(
+ expect.toBeCustomPromise(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise('text')
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise(expect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise(
+ expect.toBeCustomPromise(chainableElement)
+ )
+
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise('text')
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise(expect.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise(
+ expect.toBeCustomPromise(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise('text')
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise(expect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise(
+ expect.toBeCustomPromise(chainableElement)
+ )
+ })
+ })
+
+ describe('expect.getSoftFailures', () => {
+ it('should be of type `SoftFailure`', async () => {
+ const expectSoftFailure1: ExpectWebdriverIO.SoftFailure[] = expect.getSoftFailures()
+
+ // @ts-expect-error
+ expectVoid = expect.getSoftFailures()
+ })
+ })
+
+ describe('expect.assertSoftFailures', () => {
+ it('should be of type void', async () => {
+ expectVoid = expect.assertSoftFailures()
+
+ // @ts-expect-error
+ expectPromiseVoid = expect.assertSoftFailures()
+ })
+ })
+
+ describe('expect.clearSoftFailures', () => {
+ it('should be of type void', async () => {
+ expectVoid = expect.clearSoftFailures()
+
+ // @ts-expect-error
+ expectPromiseVoid = expect.clearSoftFailures()
+ })
+ })
+ })
+ })
+
+ describe('Asymmetric matchers', () => {
+ const string: string = 'WebdriverIO is a test framework'
+ const array: string[] = ['WebdriverIO', 'Test']
+ const object: { name: string } = { name: 'WebdriverIO' }
+ const number: number = 1
+
+ it('should have no ts error using asymmetric matchers', async () => {
+ expect(string).toEqual(expect.stringContaining('WebdriverIO'))
+ expect(array).toEqual(expect.arrayContaining(['WebdriverIO', 'Test']))
+ expect(object).toEqual(expect.objectContaining({ name: 'WebdriverIO' }))
+ // This one is tested and is working correctly, surprisingly!
+ expect(number).toEqual(expect.closeTo(1.0001, 0.0001))
+ // New from jest 30, should work!
+ expect(['apple', 'banana', 'cherry']).toEqual(expect.arrayOf(expect.any(String)))
+ })
+ })
+
+ describe('@types/jest only - original Matchers', () => {
+
+ it('should support mock matchers existing only on JestExpect', () => {
+ const mockFn = () => {}
+
+ // Jest-specific mock matchers
+ expect(mockFn).toHaveBeenCalled()
+ })
+
+ describe('Jest-specific Promise matchers', () => {
+ it('should support resolves and rejects', async () => {
+ const stringPromise = Promise.resolve('Hello Jest')
+ const rejectedPromise = Promise.reject(new Error('Failed'))
+
+ expectPromiseVoid = jestExpect(stringPromise).resolves.toBe('Hello Jest')
+ expectPromiseVoid = jestExpect(rejectedPromise).rejects.toThrow('Failed')
+
+ // @ts-expect-error
+ expectVoid = jestExpect(stringPromise).resolves.toBe('Hello Jest')
+ // @ts-expect-error
+ expectVoid = jestExpect(rejectedPromise).rejects.toThrow('Failed')
+ })
+ })
+
+ describe('toMatchSnapshot & toMatchInlineSnapshot', () => {
+ const snapshotName: string = 'test-snapshot'
+
+ it('should work with string', async () => {
+ const jsonString: string = '{}'
+ const propertyMatchers = 'test'
+ expectVoid = jestExpect(jsonString).toMatchSnapshot(propertyMatchers)
+ expectVoid = jestExpect(jsonString).toMatchSnapshot(propertyMatchers, snapshotName)
+ expectVoid = jestExpect(jsonString).toMatchInlineSnapshot(propertyMatchers)
+ expectVoid = jestExpect(jsonString).toMatchInlineSnapshot(propertyMatchers, snapshotName)
+
+ expectVoid = jestExpect(jsonString).not.toMatchSnapshot(propertyMatchers)
+ expectVoid = jestExpect(jsonString).not.toMatchSnapshot(propertyMatchers, snapshotName)
+ expectVoid = jestExpect(jsonString).not.toMatchInlineSnapshot(propertyMatchers)
+ expectVoid = jestExpect(jsonString).not.toMatchInlineSnapshot(propertyMatchers, snapshotName)
+
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(jsonString).toMatchSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(jsonString).toMatchSnapshot(propertyMatchers, snapshotName)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(jsonString).toMatchInlineSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(jsonString).toMatchInlineSnapshot(propertyMatchers, snapshotName)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(jsonString).not.toMatchSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(jsonString).not.toMatchSnapshot(propertyMatchers, snapshotName)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(jsonString).not.toMatchInlineSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(jsonString).not.toMatchInlineSnapshot(propertyMatchers, snapshotName)
+ })
+
+ it('should with object', async () => {
+ const treeObject = { 1: 'test', 2: 'test2' }
+ const propertyMatchers = { 1: 'test' }
+ expectVoid = jestExpect(treeObject).toMatchSnapshot(propertyMatchers)
+ expectVoid = jestExpect(treeObject).toMatchSnapshot(propertyMatchers, snapshotName)
+ expectVoid = jestExpect(treeObject).toMatchInlineSnapshot(propertyMatchers)
+ expectVoid = jestExpect(treeObject).toMatchInlineSnapshot(propertyMatchers, snapshotName)
+
+ expectVoid = jestExpect(treeObject).not.toMatchSnapshot(propertyMatchers)
+ expectVoid = jestExpect(treeObject).not.toMatchSnapshot(propertyMatchers, snapshotName)
+ expectVoid = jestExpect(treeObject).not.toMatchInlineSnapshot(propertyMatchers)
+ expectVoid = jestExpect(treeObject).not.toMatchInlineSnapshot(propertyMatchers, snapshotName)
+
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(treeObject).toMatchSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(treeObject).toMatchSnapshot(propertyMatchers, snapshotName)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(treeObject).toMatchInlineSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(treeObject).toMatchInlineSnapshot(propertyMatchers, snapshotName)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(treeObject).not.toMatchSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(treeObject).not.toMatchSnapshot(propertyMatchers, snapshotName)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(treeObject).not.toMatchInlineSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = jestExpect(treeObject).not.toMatchInlineSnapshot(propertyMatchers, snapshotName)
+ })
+ })
+ })
+})
diff --git a/test-types/jest-@types_jest/customMatchers/customMatchers-module-expect.d.ts b/test-types/jest-@types_jest/customMatchers/customMatchers-module-expect.d.ts
new file mode 100644
index 000000000..750d6e1ff
--- /dev/null
+++ b/test-types/jest-@types_jest/customMatchers/customMatchers-module-expect.d.ts
@@ -0,0 +1,26 @@
+import 'expect'
+
+/**
+ * Custom matchers under the `expect` module.
+ * @see {@link https://jestjs.io/docs/expect#expectextendmatchers}
+ */
+declare module 'expect' {
+ interface AsymmetricMatchers {
+ toBeWithinRange(floor: number, ceiling: number): void
+ toHaveSimpleCustomProperty(string: string): string
+ toHaveCustomProperty(element: ChainablePromiseElement | WebdriverIO.Element): Promise>
+ }
+
+ interface Matchers {
+ toBeWithinRange(floor: number, ceiling: number): R
+ toHaveSimpleCustomProperty(string: string | ExpectWebdriverIO.PartialMatcher): Promise
+ toHaveCustomProperty:
+ // Useful to typecheck the custom matcher so it is only used on elements
+ T extends ChainablePromiseElement | WebdriverIO.Element ?
+ (test: string | ExpectWebdriverIO.PartialMatcher |
+ // Needed for the custom asymmetric matcher defined above to be typed correctly
+ Promise>)
+ // Using `never` blocks the call on non-element types
+ => Promise : never;
+ }
+}
\ No newline at end of file
diff --git a/test-types/jest-@types_jest/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts b/test-types/jest-@types_jest/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts
new file mode 100644
index 000000000..7a833bd87
--- /dev/null
+++ b/test-types/jest-@types_jest/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts
@@ -0,0 +1,14 @@
+/**
+ * Custom matchers under the `ExpectWebdriverIO` namespace.
+ * @see {@link https://webdriver.io/docs/custommatchers/#typescript-support}
+ */
+declare namespace ExpectWebdriverIO {
+ interface AsymmetricMatchers {
+ toBeCustom(): ExpectWebdriverIO.PartialMatcher;
+ toBeCustomPromise(chainableElement: ChainablePromiseElement): Promise>;
+ }
+ interface Matchers {
+ toBeCustom(): R;
+ toBeCustomPromise: T extends ChainablePromiseElement ? (expected?: string | ExpectWebdriverIO.PartialMatcher | Promise>) => Promise : never;
+ }
+}
\ No newline at end of file
diff --git a/test-types/jest-@types_jest/tsconfig.json b/test-types/jest-@types_jest/tsconfig.json
new file mode 100644
index 000000000..69ed6c969
--- /dev/null
+++ b/test-types/jest-@types_jest/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "noEmit": true,
+ "noImplicitAny": true,
+ "target": "es2022",
+ "module": "node18",
+ "skipLibCheck": true,
+ "types": [
+ "@types/jest",
+ "../../jest.d.ts", // Needed to be after @types/jest to override the toMatchSnapshot typing
+ ],
+ }
+}
diff --git a/test-types/jest-@types_jest/types-jest.test.ts b/test-types/jest-@types_jest/types-jest.test.ts
new file mode 100644
index 000000000..445b7758a
--- /dev/null
+++ b/test-types/jest-@types_jest/types-jest.test.ts
@@ -0,0 +1,908 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+
+describe('type assertions', async () => {
+ const chainableElement = {} as unknown as ChainablePromiseElement
+ const chainableArray = {} as ChainablePromiseArray
+
+ const element: WebdriverIO.Element = {} as unknown as WebdriverIO.Element
+ const elementArray: WebdriverIO.ElementArray = [] as unknown as WebdriverIO.ElementArray
+
+ const networkMock: WebdriverIO.Mock = {} as unknown as WebdriverIO.Mock
+
+ // Type assertions
+ let expectPromiseVoid: Promise
+ let expectVoid: void
+
+ describe('Browser', () => {
+ const browser: WebdriverIO.Browser = {} as unknown as WebdriverIO.Browser
+
+ describe('toHaveUrl', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expect(browser).toHaveUrl('https://example.com')
+ expectPromiseVoid = expect(browser).not.toHaveUrl('https://example.com')
+
+ // Asymmetric matchers
+ expectPromiseVoid = expect(browser).toHaveUrl(expect.stringContaining('WebdriverIO'))
+ expectPromiseVoid = expect(browser).toHaveUrl(expect.not.stringContaining('WebdriverIO'))
+ expectPromiseVoid = expect(browser).toHaveUrl(expect.any(String))
+ expectPromiseVoid = expect(browser).toHaveUrl(expect.anything())
+
+ // @ts-expect-error
+ expectVoid = expect(browser).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ expectVoid = expect(browser).not.toHaveUrl('https://example.com')
+ // @ts-expect-error
+ expectVoid = expect(browser).toHaveUrl(expect.stringContaining('WebdriverIO'))
+
+ // @ts-expect-error
+ await expect(browser).toHaveUrl(6)
+ })
+
+ it('should have ts errors when actual is not a Browser element', async () => {
+ // @ts-expect-error
+ await expect(element).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await expect(element).not.toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await expect(true).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await expect(true).not.toHaveUrl('https://example.com')
+ })
+ })
+
+ describe('toHaveTitle', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expect(browser).toHaveTitle('https://example.com')
+ expectPromiseVoid = expect(browser).not.toHaveTitle('https://example.com')
+
+ // Asymmetric matchers
+ expectPromiseVoid = expect(browser).toHaveTitle(expect.stringContaining('WebdriverIO'))
+ expectPromiseVoid = expect(browser).toHaveTitle(expect.any(String))
+ expectPromiseVoid = expect(browser).toHaveTitle(expect.anything())
+
+ // @ts-expect-error
+ expectVoid = expect(browser).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ expectVoid = expect(browser).not.toHaveTitle('https://example.com')
+ // @ts-expect-error
+ expectVoid = expect(browser).toHaveTitle(expect.stringContaining('WebdriverIO'))
+ })
+
+ it('should have ts errors when actual is not a Browser element', async () => {
+ // @ts-expect-error
+ await expect(element).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await expect(element).not.toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await expect(true).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await expect(true).not.toHaveTitle('https://example.com')
+ })
+ })
+ })
+
+ describe('element', () => {
+
+ describe('toBeDisabled', () => {
+ it('should be supported correctly', async () => {
+ // Element
+ expectPromiseVoid = expect(element).toBeDisabled()
+ expectPromiseVoid = expect(element).not.toBeDisabled()
+
+ // Element array
+ expectPromiseVoid = expect(elementArray).toBeDisabled()
+ expectPromiseVoid = expect(elementArray).not.toBeDisabled()
+
+ // Chainable element
+ expectPromiseVoid = expect(chainableElement).toBeDisabled()
+ expectPromiseVoid = expect(chainableElement).not.toBeDisabled()
+
+ // Chainable element array
+ expectPromiseVoid = expect(chainableArray).toBeDisabled()
+ expectPromiseVoid = expect(chainableArray).not.toBeDisabled()
+
+ // @ts-expect-error
+ expectVoid = expect(element).toBeDisabled()
+ // @ts-expect-error
+ expectVoid = expect(element).not.toBeDisabled()
+ })
+
+ it('should have ts errors when actual is not an element', async () => {
+ // @ts-expect-error
+ await expect(browser).toBeDisabled()
+ // @ts-expect-error
+ await expect(browser).not.toBeDisabled()
+ // @ts-expect-error
+ await expect(true).toBeDisabled()
+ // @ts-expect-error
+ await expect(true).not.toBeDisabled()
+ })
+ })
+
+ describe('toHaveText', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expect(element).toHaveText('text')
+ expectPromiseVoid = expect(element).toHaveText(/text/)
+ expectPromiseVoid = expect(element).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expect(element).toHaveText([expect.stringContaining('text1'), expect.stringContaining('text2')])
+ expectPromiseVoid = expect(element).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expect(element).toHaveText(['text1', /text1/, expect.stringContaining('text3')])
+ await expect(element).toHaveText(
+ 'My-Ex-Am-Ple',
+ {
+ replace: [[/-/g, ' '], [/[A-Z]+/g, (match: string) => match.toLowerCase()]]
+ }
+ )
+
+ expectPromiseVoid = expect(element).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expect(element).toHaveText('text')
+ // @ts-expect-error
+ await expect(element).toHaveText(6)
+
+ expectPromiseVoid = expect(chainableElement).toHaveText('text')
+ expectPromiseVoid = expect(chainableElement).toHaveText(/text/)
+ expectPromiseVoid = expect(chainableElement).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expect(chainableElement).toHaveText([expect.stringContaining('text1'), expect.stringContaining('text2')])
+ expectPromiseVoid = expect(chainableElement).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expect(chainableElement).toHaveText(['text1', /text1/, expect.stringContaining('text3')])
+
+ expectPromiseVoid = expect(chainableElement).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expect(chainableElement).toHaveText('text')
+ // @ts-expect-error
+ await expect(chainableElement).toHaveText(6)
+
+ expectPromiseVoid = expect(elementArray).toHaveText('text')
+ expectPromiseVoid = expect(elementArray).toHaveText(/text/)
+ expectPromiseVoid = expect(elementArray).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expect(elementArray).toHaveText([expect.stringContaining('text1'), expect.stringContaining('text2')])
+ expectPromiseVoid = expect(elementArray).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expect(elementArray).toHaveText(['text1', /text1/, expect.stringContaining('text3')])
+
+ expectPromiseVoid = expect(elementArray).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expect(elementArray).toHaveText('text')
+ // @ts-expect-error
+ await expect(elementArray).toHaveText(6)
+
+ expectPromiseVoid = expect(chainableArray).toHaveText('text')
+ expectPromiseVoid = expect(chainableArray).toHaveText(/text/)
+ expectPromiseVoid = expect(chainableArray).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expect(chainableArray).toHaveText([expect.stringContaining('text1'), expect.stringContaining('text2')])
+ expectPromiseVoid = expect(chainableArray).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expect(chainableArray).toHaveText(['text1', /text1/, expect.stringContaining('text3')])
+
+ expectPromiseVoid = expect(chainableArray).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expect(chainableArray).toHaveText('text')
+ // @ts-expect-error
+ await expect(chainableArray).toHaveText(6)
+
+ // @ts-expect-error
+ await expect(browser).toHaveText('text')
+ })
+
+ it('should have ts errors when actual is not an element', async () => {
+ // @ts-expect-error
+ await expect(browser).toHaveText('text')
+ // @ts-expect-error
+ await expect(browser).not.toHaveText('text')
+ // @ts-expect-error
+ await expect(true).toHaveText('text')
+ // @ts-expect-error
+ await expect(true).toHaveText('text')
+ })
+
+ it('should have ts errors when actual is string or Promise', async () => {
+ // @ts-expect-error
+ await expect('text').toHaveText('text')
+ // @ts-expect-error
+ await expect('text').not.toHaveText('text')
+ // @ts-expect-error
+ await expect(Promise.resolve('text')).toHaveText('text')
+ // @ts-expect-error
+ await expect(Promise.resolve('text')).toHaveText('text')
+ })
+ })
+
+ describe('toHaveHeight', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expect(element).toHaveHeight(100)
+ expectPromiseVoid = expect(element).toHaveHeight(100, { message: 'Custom error message' })
+ expectPromiseVoid = expect(element).not.toHaveHeight(100)
+ expectPromiseVoid = expect(element).not.toHaveHeight(100, { message: 'Custom error message' })
+
+ expectPromiseVoid = expect(element).toHaveHeight({ width: 100, height: 200 })
+ expectPromiseVoid = expect(element).toHaveHeight({ width: 100, height: 200 }, { message: 'Custom error message' })
+ expectPromiseVoid = expect(element).not.toHaveHeight({ width: 100, height: 200 })
+ expectPromiseVoid = expect(element).not.toHaveHeight({ width: 100, height: 200 }, { message: 'Custom error message' })
+
+ // @ts-expect-error
+ expectVoid = expect(element).toHaveHeight(100)
+ // @ts-expect-error
+ expectVoid = expect(element).not.toHaveHeight(100)
+
+ // @ts-expect-error
+ expectVoid = expect(element).toHaveHeight({ width: 100, height: 200 })
+ // @ts-expect-error
+ expectVoid = expect(element).not.toHaveHeight({ width: 100, height: 200 })
+
+ // @ts-expect-error
+ await expect(browser).toHaveHeight(100)
+ })
+
+ it('should have ts errors when actual is string or Promise', async () => {
+ // @ts-expect-error
+ await expect('text').toHaveText('text')
+ // @ts-expect-error
+ await expect('text').not.toHaveText('text')
+ // @ts-expect-error
+ await expect(Promise.resolve('text')).toHaveText('text')
+ // @ts-expect-error
+ await expect(Promise.resolve('text')).toHaveText('text')
+ })
+ })
+
+ describe('toMatchSnapshot', () => {
+
+ it('should be supported correctly', async () => {
+ expectVoid = expect(element).toMatchSnapshot()
+ expectVoid = expect(element).toMatchSnapshot('test label')
+ expectVoid = expect(element).not.toMatchSnapshot('test label')
+
+ expectPromiseVoid = expect(chainableElement).toMatchSnapshot()
+ expectPromiseVoid = expect(chainableElement).toMatchSnapshot('test label')
+ expectPromiseVoid = expect(chainableElement).not.toMatchSnapshot('test label')
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(element).toMatchSnapshot()
+ //@ts-expect-error
+ expectPromiseVoid = expect(element).not.toMatchSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).toMatchSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).not.toMatchSnapshot()
+ })
+ })
+
+ describe('toMatchInlineSnapshot', () => {
+
+ it('should be correctly supported', async () => {
+ expectVoid = expect(element).toMatchInlineSnapshot()
+ expectVoid = expect(element).toMatchInlineSnapshot('test snapshot')
+ expectVoid = expect(element).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ expectPromiseVoid = expect(chainableElement).toMatchInlineSnapshot()
+ expectPromiseVoid = expect(chainableElement).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expect(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(element).toMatchInlineSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+ })
+
+ it('should be correctly supported with getCSSProperty()', async () => {
+ expectPromiseVoid = expect(element.getCSSProperty('test')).toMatchInlineSnapshot()
+ expectPromiseVoid = expect(element.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expect(element.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ expectPromiseVoid = expect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot()
+ expectPromiseVoid = expect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(element).toMatchInlineSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+ })
+ })
+
+ describe('toBeElementsArrayOfSize', async () => {
+
+ it('should work correctly when actual is chainableArray', async () => {
+ expectPromiseVoid = expect(chainableArray).toBeElementsArrayOfSize(5)
+ expectPromiseVoid = expect(chainableArray).toBeElementsArrayOfSize({ lte: 10 })
+
+ // @ts-expect-error
+ expectVoid = expect(chainableArray).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ expectVoid = expect(chainableArray).toBeElementsArrayOfSize({ lte: 10 })
+ })
+
+ it('should not work when actual is not chainableArray', async () => {
+ // @ts-expect-error
+ await expect(chainableElement).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ await expect(chainableElement).toBeElementsArrayOfSize({ lte: 10 })
+ // @ts-expect-error
+ await expect(true).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ await expect(true).toBeElementsArrayOfSize({ lte: 10 })
+ })
+ })
+ })
+
+ describe('Custom matchers', () => {
+ describe('using `ExpectWebdriverIO` namespace augmentation', () => {
+ it('should supported correctly a non-promise custom matcher', async () => {
+ expectVoid = expect('test').toBeCustom()
+ expectVoid = expect('test').not.toBeCustom()
+
+ // @ts-expect-error
+ expectPromiseVoid = expect('test').toBeCustom()
+ // @ts-expect-error
+ expectPromiseVoid = expect('test').not.toBeCustom()
+
+ expectVoid = expect(1).toBeWithinRange(0, 2)
+ })
+
+ it('should supported correctly a promise custom matcher with only chainableElement as actual', async () => {
+ expectPromiseVoid = expect(chainableElement).toBeCustomPromise()
+ expectPromiseVoid = expect(chainableElement).toBeCustomPromise(expect.stringContaining('test'))
+ expectPromiseVoid = expect(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('test'))
+
+ // @ts-expect-error
+ expect('test').toBeCustomPromise()
+ // @ts-expect-error
+ expectVoid = expect(chainableElement).toBeCustomPromise()
+ // @ts-expect-error
+ expectVoid = expect(chainableElement).toBeCustomPromise(expect.stringContaining('test'))
+ // @ts-expect-error
+ expectVoid = expect(chainableElement).not.toBeCustomPromise(expect.stringContaining('test'))
+ // @ts-expect-error
+ expect(chainableElement).toBeCustomPromise(expect.stringContaining(6))
+ })
+
+ it('should support custom asymmetric matcher', async () => {
+ const expectString1 : ExpectWebdriverIO.PartialMatcher = expect.toBeCustom()
+ const expectString2 : ExpectWebdriverIO.PartialMatcher = expect.not.toBeCustom()
+
+ expectPromiseVoid = expect(chainableElement).toBeCustomPromise(expect.toBeCustom())
+
+ // @ts-expect-error
+ expectPromiseVoid = expect.toBeCustom()
+ // @ts-expect-error
+ expectPromiseVoid = expect.not.toBeCustom()
+
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).toBeCustomPromise(expect.toBeCustom())
+ })
+ })
+
+ describe('using `expect` module declaration', () => {
+
+ it('should support a simple matcher', async () => {
+ expectVoid = expect(5).toBeWithinRange(1, 10)
+
+ // Or as an asymmetric matcher:
+ expectVoid = expect({ value: 5 }).toEqual({
+ value: expect.toBeWithinRange(1, 10)
+ })
+
+ // @ts-expect-error
+ expectVoid = expect(5).toBeWithinRange(1, '10')
+ // @ts-expect-error
+ expectPromiseVoid = expect(5).toBeWithinRange('1')
+ })
+
+ it('should support a simple custom matcher with a chainable element matcher with promise', async () => {
+ expectPromiseVoid = expect(chainableElement).toHaveSimpleCustomProperty('text')
+ expectPromiseVoid = expect(chainableElement).toHaveSimpleCustomProperty(expect.stringContaining('text'))
+ expectPromiseVoid = expect(chainableElement).not.toHaveSimpleCustomProperty(expect.not.stringContaining('text'))
+
+ // Or as a custom asymmetric matcher:
+ expectPromiseVoid = expect(chainableElement).toHaveSimpleCustomProperty(
+ expect.toHaveSimpleCustomProperty('string')
+ )
+ const expectString1:string = expect.toHaveSimpleCustomProperty('string')
+ const expectString2:string = expect.not.toHaveSimpleCustomProperty('string')
+
+ // @ts-expect-error
+ expectVoid = expect.toHaveSimpleCustomProperty(chainableElement)
+ // @ts-expect-error
+ expectVoid = expect.not.toHaveSimpleCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = expect.toHaveSimpleCustomProperty(chainableElement)
+ })
+
+ it('should support a chainable element matcher with promise', async () => {
+ expectPromiseVoid = expect(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = expect(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ expectPromiseVoid = expect(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+
+ // Or as a custom asymmetric matcher:
+ expectPromiseVoid = expect(chainableElement).toHaveCustomProperty(
+ await expect.toHaveCustomProperty(chainableElement)
+ )
+ const expectPromiseWdioElement1: Promise> = expect.toHaveCustomProperty(chainableElement)
+ const expectPromiseWdioElement2: Promise> = expect.not.toHaveCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = expect.toHaveCustomProperty(chainableElement)
+ // @ts-expect-error
+ expectVoid = expect.not.toHaveCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = expect.toHaveCustomProperty(chainableElement)
+ // @ts-expect-error
+ expect.toHaveCustomProperty('test')
+
+ await expect(chainableElement).toHaveCustomProperty(
+ await expect.toHaveCustomProperty(chainableElement)
+ )
+ })
+ })
+ })
+
+ describe('toBe', () => {
+
+ it('should expect void type when actual is a boolean', async () => {
+ expectVoid = expect(true).toBe(true)
+ expectVoid = expect(true).not.toBe(true)
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(true).toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect(true).not.toBe(true)
+ })
+
+ it('should not expect Promise when actual is a chainable since toBe does not need to be awaited', async () => {
+ expectVoid = expect(chainableElement).toBe(true)
+ expectVoid = expect(chainableElement).not.toBe(true)
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(chainableElement).toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect(chainableElement).not.toBe(true)
+ })
+
+ it('should still expect void type when actual is a Promise since we do not overload them', async () => {
+ const promiseBoolean = Promise.resolve(true)
+
+ expectVoid = expect(promiseBoolean).toBe(true)
+ expectVoid = expect(promiseBoolean).not.toBe(true)
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(promiseBoolean).toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect(promiseBoolean).toBe(true)
+ })
+
+ it('should work with string', async () => {
+ expectVoid = expect('text').toBe(true)
+ expectVoid = expect('text').not.toBe(true)
+ expectVoid = expect('text').toBe(expect.stringContaining('text'))
+ expectVoid = expect('text').not.toBe(expect.stringContaining('text'))
+
+ //@ts-expect-error
+ expectPromiseVoid = expect('text').toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect('text').not.toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect('text').toBe(expect.stringContaining('text'))
+ //@ts-expect-error
+ expectPromiseVoid = expect('text').not.toBe(expect.stringContaining('text'))
+ })
+ })
+
+ describe('Promise type assertions', () => {
+ const booleanPromise: Promise = Promise.resolve(true)
+
+ it('should expect a Promise of type', async () => {
+ const expectPromiseBoolean1: jest.JestMatchers> = expect(booleanPromise)
+ const expectPromiseBoolean2: jest.Matchers> = expect(booleanPromise).not
+
+ // @ts-expect-error
+ const expectPromiseBoolean3: jest.JestMatchers = expect(booleanPromise)
+ //// @ts-expect-error
+ // const expectPromiseBoolean4: jest.Matchers = expect(booleanPromise).not
+ })
+
+ it('should work with resolves & rejects correctly', async () => {
+ expectPromiseVoid = expect(booleanPromise).resolves.toBe(true)
+ expectPromiseVoid = expect(booleanPromise).rejects.toBe(true)
+
+ //@ts-expect-error
+ expectVoid = expect(booleanPromise).resolves.toBe(true)
+ //@ts-expect-error
+ expectVoid = expect(booleanPromise).rejects.toBe(true)
+
+ })
+
+ it('should not support chainable and expect PromiseVoid with toBe', async () => {
+ //@ts-expect-error
+ expectPromiseVoid = expect(chainableElement).toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect(chainableElement).not.toBe(true)
+ })
+ })
+
+ describe('Network Matchers', () => {
+ const promiseNetworkMock = Promise.resolve(networkMock)
+
+ it('should not have ts errors when typing to Promise', async () => {
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequested()
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedTimes(2)
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedTimes({ gte: 5, lte: 10 })
+
+ expectPromiseVoid = expect(promiseNetworkMock).not.toBeRequested()
+ expectPromiseVoid = expect(promiseNetworkMock).not.toBeRequestedTimes(2)
+ expectPromiseVoid = expect(promiseNetworkMock).not.toBeRequestedTimes({ gte: 5, lte: 10 })
+
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedWith({
+ url: 'http://localhost:8080/api',
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: { Authorization: 'foo' },
+ responseHeaders: { Authorization: 'bar' },
+ postData: { title: 'foo', description: 'bar' },
+ response: { success: true },
+ })
+
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedWith({
+ url: expect.stringContaining('test'),
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: expect.objectContaining({ Authorization: 'foo' }),
+ responseHeaders: expect.objectContaining({ Authorization: 'bar' }),
+ postData: expect.objectContaining({ title: 'foo', description: 'bar' }),
+ response: expect.objectContaining({ success: true }),
+ })
+
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedWith({
+ url: expect.stringMatching(/.*\/api\/.*/i),
+ method: ['POST', 'PUT'],
+ statusCode: [401, 403],
+ requestHeaders: headers => headers.Authorization.startsWith('Bearer '),
+ postData: expect.objectContaining({ released: true, title: expect.stringContaining('foobar') }),
+ response: (r: { data: { items: unknown[] } }) => Array.isArray(r) && r.data.items.length === 20
+ })
+ })
+
+ it('should have ts errors when typing to void', async () => {
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequested()
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequestedTimes(2) // await expect(mock).toBeRequestedTimes({ eq: 2 })
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequestedTimes({ gte: 5, lte: 10 }) // request called at least 5 times but less than 11
+
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).not.toBeRequested()
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).not.toBeRequestedTimes(2) // await expect(mock).toBeRequestedTimes({ eq: 2 })
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).not.toBeRequestedTimes({ gte: 5, lte: 10 }) // request called at least 5 times but less than 11
+
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequestedWith({
+ url: 'http://localhost:8080/api',
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: { Authorization: 'foo' },
+ responseHeaders: { Authorization: 'bar' },
+ postData: { title: 'foo', description: 'bar' },
+ response: { success: true },
+ })
+
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequestedWith(expect.objectContaining({
+ response: { success: true },
+ }))
+ })
+ })
+
+ describe('Expect', () => {
+ it('should have ts errors when using a non existing expect.function', async () => {
+ // @ts-expect-error
+ expect.unimplementedFunction()
+ })
+
+ it('should support stringContaining, anything and more', async () => {
+ expect.stringContaining('WebdriverIO')
+ expect.stringMatching(/WebdriverIO/)
+ expect.arrayContaining(['WebdriverIO', 'Test'])
+ expect.objectContaining({ name: 'WebdriverIO' })
+ // Was not there but works!
+ expect.closeTo(5, 10)
+ expect.arrayContaining(['WebdriverIO', 'Test'])
+ // New from jest 30!!
+ expect.arrayOf(expect.stringContaining('WebdriverIO'))
+
+ expect.anything()
+ expect.any(Function)
+ expect.any(Number)
+ expect.any(Boolean)
+ expect.any(String)
+ expect.any(Symbol)
+ expect.any(Date)
+ expect.any(Error)
+
+ expect.not.stringContaining('WebdriverIO')
+ expect.not.stringMatching(/WebdriverIO/)
+ expect.not.arrayContaining(['WebdriverIO', 'Test'])
+ expect.not.objectContaining({ name: 'WebdriverIO' })
+ expect.not.closeTo(5, 10)
+ expect.not.arrayContaining(['WebdriverIO', 'Test'])
+ expect.not.arrayOf(expect.stringContaining('WebdriverIO'))
+ expect.not.anything()
+ expect.not.any(Function)
+ expect.not.any(Number)
+ expect.not.any(Boolean)
+ expect.not.any(String)
+ expect.not.any(Symbol)
+ expect.not.any(Date)
+ expect.not.any(Error)
+ })
+
+ describe('Soft Assertions', async () => {
+ const actualString: string = 'test'
+ const actualPromiseString: Promise = Promise.resolve('test')
+
+ describe('expect.soft', () => {
+ it('should not need to be awaited/be a promise if actual is non-promise type', async () => {
+ const expectWdioMatcher1: WdioCustomMatchers = expect.soft(actualString)
+ expectVoid = expect.soft(actualString).toBe('Test Page')
+ expectVoid = expect.soft(actualString).not.toBe('Test Page')
+ expectVoid = expect.soft(actualString).not.toBe(expect.stringContaining('Test Page'))
+
+ // @ts-expect-error
+ expectPromiseVoid = expect.soft(actualString).toBe('Test Page')
+ // @ts-expect-error
+ expectPromiseVoid = expect.soft(actualString).not.toBe('Test Page')
+ // @ts-expect-error
+ expectPromiseVoid = expect.soft(actualString).not.toBe(expect.stringContaining('Test Page'))
+ })
+
+ it('should need to be awaited/be a promise if actual is promise type', async () => {
+ const expectWdioMatcher1: ExpectWebdriverIO.MatchersAndInverse, Promise> = expect.soft(actualPromiseString)
+ expectPromiseVoid = expect.soft(actualPromiseString).toBe('Test Page')
+ expectPromiseVoid = expect.soft(actualPromiseString).not.toBe('Test Page')
+ expectPromiseVoid = expect.soft(actualPromiseString).not.toBe(expect.stringContaining('Test Page'))
+
+ // @ts-expect-error
+ expectVoid = expect.soft(actualPromiseString).toBe('Test Page')
+ // @ts-expect-error
+ expectVoid = expect.soft(actualPromiseString).not.toBe('Test Page')
+ // @ts-expect-error
+ expectVoid = expect.soft(actualPromiseString).not.toBe(expect.stringContaining('Test Page'))
+ })
+
+ it('should support chainable element', async () => {
+ const expectElement: ExpectWebdriverIO.MatchersAndInverse = expect.soft(element)
+ const expectElementChainable: ExpectWebdriverIO.MatchersAndInverse = expect.soft(chainableElement)
+
+ // @ts-expect-error
+ const expectElement2: ExpectWebdriverIO.MatchersAndInverse, WebdriverIO.Element> = expect.soft(element)
+ // @ts-expect-error
+ const expectElementChainable2: ExpectWebdriverIO.MatchersAndInverse, typeof chainableElement> = expect.soft(chainableElement)
+ })
+
+ it('should support chainable element with wdio Matchers', async () => {
+ expectPromiseVoid = expect.soft(element).toBeDisplayed()
+ expectPromiseVoid = expect.soft(chainableElement).toBeDisplayed()
+ expectPromiseVoid = expect.soft(chainableArray).toBeDisplayed()
+ await expect.soft(element).toBeDisplayed()
+ await expect.soft(chainableElement).toBeDisplayed()
+ await expect.soft(chainableArray).toBeDisplayed()
+
+ expectPromiseVoid = expect.soft(element).not.toBeDisplayed()
+ expectPromiseVoid = expect.soft(chainableElement).not.toBeDisplayed()
+ expectPromiseVoid = expect.soft(chainableArray).not.toBeDisplayed()
+ await expect.soft(element).not.toBeDisplayed()
+ await expect.soft(chainableElement).not.toBeDisplayed()
+ await expect.soft(chainableArray).not.toBeDisplayed()
+
+ // @ts-expect-error
+ expectVoid = expect.soft(element).toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableArray).toBeDisplayed()
+
+ // @ts-expect-error
+ expectVoid = expect.soft(element).not.toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableArray).not.toBeDisplayed()
+ })
+
+ it('should work with custom matcher and custom asymmetric matchers from `expect` module', async () => {
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty(
+ expect.toHaveCustomProperty(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty('text')
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty(
+ expect.toHaveCustomProperty(chainableElement)
+ )
+
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty(
+ expect.toHaveCustomProperty(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty('text')
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty(
+ expect.toHaveCustomProperty(chainableElement)
+ )
+ })
+
+ it('should work with custom matcher and custom asymmetric matchers from `ExpectWebDriverIO` namespace', async () => {
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise('text')
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise(expect.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise(
+ expect.toBeCustomPromise(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise('text')
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise(expect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise(
+ expect.toBeCustomPromise(chainableElement)
+ )
+
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise('text')
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise(expect.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise(
+ expect.toBeCustomPromise(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise('text')
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise(expect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise(
+ expect.toBeCustomPromise(chainableElement)
+ )
+ })
+ })
+
+ describe('expect.getSoftFailures', () => {
+ it('should be of type `SoftFailure`', async () => {
+ const expectSoftFailure1: ExpectWebdriverIO.SoftFailure[] = expect.getSoftFailures()
+
+ // @ts-expect-error
+ expectVoid = expect.getSoftFailures()
+ })
+ })
+
+ describe('expect.assertSoftFailures', () => {
+ it('should be of type void', async () => {
+ expectVoid = expect.assertSoftFailures()
+
+ // @ts-expect-error
+ expectPromiseVoid = expect.assertSoftFailures()
+ })
+ })
+
+ describe('expect.clearSoftFailures', () => {
+ it('should be of type void', async () => {
+ expectVoid = expect.clearSoftFailures()
+
+ // @ts-expect-error
+ expectPromiseVoid = expect.clearSoftFailures()
+ })
+ })
+ })
+ })
+
+ describe('Asymmetric matchers', () => {
+ const string: string = 'WebdriverIO is a test framework'
+ const array: string[] = ['WebdriverIO', 'Test']
+ const object: { name: string } = { name: 'WebdriverIO' }
+ const number: number = 1
+
+ it('should have no ts error using asymmetric matchers', async () => {
+ expect(string).toEqual(expect.stringContaining('WebdriverIO'))
+ expect(array).toEqual(expect.arrayContaining(['WebdriverIO', 'Test']))
+ expect(object).toEqual(expect.objectContaining({ name: 'WebdriverIO' }))
+ // This one is tested and is working correctly, surprisingly!
+ expect(number).toEqual(expect.closeTo(1.0001, 0.0001))
+ // New from jest 30, should work!
+ expect(['apple', 'banana', 'cherry']).toEqual(expect.arrayOf(expect.any(String)))
+ })
+ })
+
+ describe('@types/jest only - original Matchers', () => {
+ describe('toMatchSnapshot & toMatchInlineSnapshot', () => {
+ const snapshotName: string = 'test-snapshot'
+
+ it('should work with string', async () => {
+ const jsonString: string = '{}'
+ const propertyMatchers = 'test'
+ expectVoid = expect(jsonString).toMatchSnapshot(propertyMatchers)
+ expectVoid = expect(jsonString).toMatchSnapshot(propertyMatchers, snapshotName)
+ expectVoid = expect(jsonString).toMatchInlineSnapshot(propertyMatchers)
+ expectVoid = expect(jsonString).toMatchInlineSnapshot(propertyMatchers, snapshotName)
+
+ expectVoid = expect(jsonString).not.toMatchSnapshot(propertyMatchers)
+ expectVoid = expect(jsonString).not.toMatchSnapshot(propertyMatchers, snapshotName)
+ expectVoid = expect(jsonString).not.toMatchInlineSnapshot(propertyMatchers)
+ expectVoid = expect(jsonString).not.toMatchInlineSnapshot(propertyMatchers, snapshotName)
+
+ // @ts-expect-error
+ expectPromiseVoid = expect(jsonString).toMatchSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = expect(jsonString).toMatchSnapshot(propertyMatchers, snapshotName)
+ // @ts-expect-error
+ expectPromiseVoid = expect(jsonString).toMatchInlineSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = expect(jsonString).toMatchInlineSnapshot(propertyMatchers, snapshotName)
+ // @ts-expect-error
+ expectPromiseVoid = expect(jsonString).not.toMatchSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = expect(jsonString).not.toMatchSnapshot(propertyMatchers, snapshotName)
+ // @ts-expect-error
+ expectPromiseVoid = expect(jsonString).not.toMatchInlineSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = expect(jsonString).not.toMatchInlineSnapshot(propertyMatchers, snapshotName)
+ })
+
+ it('should with object', async () => {
+ const treeObject = { 1: 'test', 2: 'test2' }
+ const propertyMatchers = { 1: 'test' }
+ expectVoid = expect(treeObject).toMatchSnapshot(propertyMatchers)
+ expectVoid = expect(treeObject).toMatchSnapshot(propertyMatchers, snapshotName)
+ expectVoid = expect(treeObject).toMatchInlineSnapshot(propertyMatchers)
+ expectVoid = expect(treeObject).toMatchInlineSnapshot(propertyMatchers, snapshotName)
+
+ expectVoid = expect(treeObject).not.toMatchSnapshot(propertyMatchers)
+ expectVoid = expect(treeObject).not.toMatchSnapshot(propertyMatchers, snapshotName)
+ expectVoid = expect(treeObject).not.toMatchInlineSnapshot(propertyMatchers)
+ expectVoid = expect(treeObject).not.toMatchInlineSnapshot(propertyMatchers, snapshotName)
+
+ // @ts-expect-error
+ expectPromiseVoid = expect(treeObject).toMatchSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = expect(treeObject).toMatchSnapshot(propertyMatchers, snapshotName)
+ // @ts-expect-error
+ expectPromiseVoid = expect(treeObject).toMatchInlineSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = expect(treeObject).toMatchInlineSnapshot(propertyMatchers, snapshotName)
+ // @ts-expect-error
+ expectPromiseVoid = expect(treeObject).not.toMatchSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = expect(treeObject).not.toMatchSnapshot(propertyMatchers, snapshotName)
+ // @ts-expect-error
+ expectPromiseVoid = expect(treeObject).not.toMatchInlineSnapshot(propertyMatchers)
+ // @ts-expect-error
+ expectPromiseVoid = expect(treeObject).not.toMatchInlineSnapshot(propertyMatchers, snapshotName)
+ })
+ })
+ })
+})
diff --git a/test-types/jest/tsconfig.json b/test-types/jest/tsconfig.json
deleted file mode 100644
index 6407f175a..000000000
--- a/test-types/jest/tsconfig.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "compilerOptions": {
- "outDir": "dist",
- "noImplicitAny": true,
- "target": "ES2020",
- "module": "Node16",
- "skipLibCheck": true,
- "types": [
- "@types/jest",
- "expect-webdriverio/jest",
- "@wdio/globals/types"
- ]
- }
-}
diff --git a/test-types/mocha/customMatchers/customMatchers-module-expect.d.ts b/test-types/mocha/customMatchers/customMatchers-module-expect.d.ts
new file mode 100644
index 000000000..750d6e1ff
--- /dev/null
+++ b/test-types/mocha/customMatchers/customMatchers-module-expect.d.ts
@@ -0,0 +1,26 @@
+import 'expect'
+
+/**
+ * Custom matchers under the `expect` module.
+ * @see {@link https://jestjs.io/docs/expect#expectextendmatchers}
+ */
+declare module 'expect' {
+ interface AsymmetricMatchers {
+ toBeWithinRange(floor: number, ceiling: number): void
+ toHaveSimpleCustomProperty(string: string): string
+ toHaveCustomProperty(element: ChainablePromiseElement | WebdriverIO.Element): Promise>
+ }
+
+ interface Matchers {
+ toBeWithinRange(floor: number, ceiling: number): R
+ toHaveSimpleCustomProperty(string: string | ExpectWebdriverIO.PartialMatcher): Promise
+ toHaveCustomProperty:
+ // Useful to typecheck the custom matcher so it is only used on elements
+ T extends ChainablePromiseElement | WebdriverIO.Element ?
+ (test: string | ExpectWebdriverIO.PartialMatcher |
+ // Needed for the custom asymmetric matcher defined above to be typed correctly
+ Promise>)
+ // Using `never` blocks the call on non-element types
+ => Promise : never;
+ }
+}
\ No newline at end of file
diff --git a/test-types/mocha/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts b/test-types/mocha/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts
new file mode 100644
index 000000000..7a833bd87
--- /dev/null
+++ b/test-types/mocha/customMatchers/customMatchers-namespace-expectwebdriverio.d.ts
@@ -0,0 +1,14 @@
+/**
+ * Custom matchers under the `ExpectWebdriverIO` namespace.
+ * @see {@link https://webdriver.io/docs/custommatchers/#typescript-support}
+ */
+declare namespace ExpectWebdriverIO {
+ interface AsymmetricMatchers {
+ toBeCustom(): ExpectWebdriverIO.PartialMatcher;
+ toBeCustomPromise(chainableElement: ChainablePromiseElement): Promise>;
+ }
+ interface Matchers {
+ toBeCustom(): R;
+ toBeCustomPromise: T extends ChainablePromiseElement ? (expected?: string | ExpectWebdriverIO.PartialMatcher | Promise>) => Promise : never;
+ }
+}
\ No newline at end of file
diff --git a/test-types/mocha/tsconfig.json b/test-types/mocha/tsconfig.json
new file mode 100644
index 000000000..4a66cfff4
--- /dev/null
+++ b/test-types/mocha/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "noEmit": true,
+ "noImplicitAny": true,
+ "target": "es2022",
+ "module": "node18",
+ "skipLibCheck": true,
+ "types": [
+ "@types/mocha",
+ "../../types/expect-global.d.ts",
+ ]
+ }
+}
diff --git a/test-types/mocha/types-mocha.test.ts b/test-types/mocha/types-mocha.test.ts
new file mode 100644
index 000000000..4d5ebd886
--- /dev/null
+++ b/test-types/mocha/types-mocha.test.ts
@@ -0,0 +1,841 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import type { ChainablePromiseElement, ChainablePromiseArray } from 'webdriverio'
+
+describe('type assertions', () => {
+ const chainableElement = {} as unknown as ChainablePromiseElement
+ const chainableArray = {} as ChainablePromiseArray
+
+ const element: WebdriverIO.Element = {} as unknown as WebdriverIO.Element
+ const elementArray: WebdriverIO.ElementArray = [] as unknown as WebdriverIO.ElementArray
+
+ const networkMock: WebdriverIO.Mock = {} as unknown as WebdriverIO.Mock
+
+ // Type assertions
+ let expectPromiseVoid: Promise
+ let expectVoid: void
+
+ describe('Browser', () => {
+ const browser: WebdriverIO.Browser = {} as unknown as WebdriverIO.Browser
+
+ describe('toHaveUrl', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expect(browser).toHaveUrl('https://example.com')
+ expectPromiseVoid = expect(browser).not.toHaveUrl('https://example.com')
+
+ // Asymmetric matchers
+ expectPromiseVoid = expect(browser).toHaveUrl(expect.stringContaining('WebdriverIO'))
+ expectPromiseVoid = expect(browser).toHaveUrl(expect.not.stringContaining('WebdriverIO'))
+ expectPromiseVoid = expect(browser).toHaveUrl(expect.any(String))
+ expectPromiseVoid = expect(browser).toHaveUrl(expect.anything())
+
+ // @ts-expect-error
+ expectVoid = expect(browser).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ expectVoid = expect(browser).not.toHaveUrl('https://example.com')
+ // @ts-expect-error
+ expectVoid = expect(browser).toHaveUrl(expect.stringContaining('WebdriverIO'))
+
+ // @ts-expect-error
+ await expect(browser).toHaveUrl(6)
+ })
+
+ it('should have ts errors when actual is not a Browser element', async () => {
+ // @ts-expect-error
+ await expect(element).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await expect(element).not.toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await expect(true).toHaveUrl('https://example.com')
+ // @ts-expect-error
+ await expect(true).not.toHaveUrl('https://example.com')
+ })
+ })
+
+ describe('toHaveTitle', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expect(browser).toHaveTitle('https://example.com')
+ expectPromiseVoid = expect(browser).not.toHaveTitle('https://example.com')
+
+ // Asymmetric matchers
+ expectPromiseVoid = expect(browser).toHaveTitle(expect.stringContaining('WebdriverIO'))
+ expectPromiseVoid = expect(browser).toHaveTitle(expect.any(String))
+ expectPromiseVoid = expect(browser).toHaveTitle(expect.anything())
+
+ // @ts-expect-error
+ expectVoid = expect(browser).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ expectVoid = expect(browser).not.toHaveTitle('https://example.com')
+ // @ts-expect-error
+ expectVoid = expect(browser).toHaveTitle(expect.stringContaining('WebdriverIO'))
+ })
+
+ it('should have ts errors when actual is not a Browser element', async () => {
+ // @ts-expect-error
+ await expect(element).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await expect(element).not.toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await expect(true).toHaveTitle('https://example.com')
+ // @ts-expect-error
+ await expect(true).not.toHaveTitle('https://example.com')
+ })
+ })
+ })
+
+ describe('element', () => {
+
+ describe('toBeDisabled', () => {
+ it('should be supported correctly', async () => {
+ // Element
+ expectPromiseVoid = expect(element).toBeDisabled()
+ expectPromiseVoid = expect(element).not.toBeDisabled()
+
+ // Element array
+ expectPromiseVoid = expect(elementArray).toBeDisabled()
+ expectPromiseVoid = expect(elementArray).not.toBeDisabled()
+
+ // Chainable element
+ expectPromiseVoid = expect(chainableElement).toBeDisabled()
+ expectPromiseVoid = expect(chainableElement).not.toBeDisabled()
+
+ // Chainable element array
+ expectPromiseVoid = expect(chainableArray).toBeDisabled()
+ expectPromiseVoid = expect(chainableArray).not.toBeDisabled()
+
+ // @ts-expect-error
+ expectVoid = expect(element).toBeDisabled()
+ // @ts-expect-error
+ expectVoid = expect(element).not.toBeDisabled()
+ })
+
+ it('should have ts errors when actual is not an element', async () => {
+ // @ts-expect-error
+ await expect(browser).toBeDisabled()
+ // @ts-expect-error
+ await expect(browser).not.toBeDisabled()
+ // @ts-expect-error
+ await expect(true).toBeDisabled()
+ // @ts-expect-error
+ await expect(true).not.toBeDisabled()
+ })
+ })
+
+ describe('toHaveText', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expect(element).toHaveText('text')
+ expectPromiseVoid = expect(element).toHaveText(/text/)
+ expectPromiseVoid = expect(element).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expect(element).toHaveText([expect.stringContaining('text1'), expect.stringContaining('text2')])
+ expectPromiseVoid = expect(element).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expect(element).toHaveText(['text1', /text1/, expect.stringContaining('text3')])
+ await expect(element).toHaveText(
+ 'My-Ex-Am-Ple',
+ {
+ replace: [[/-/g, ' '], [/[A-Z]+/g, (match: string) => match.toLowerCase()]]
+ }
+ )
+
+ expectPromiseVoid = expect(element).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expect(element).toHaveText('text')
+ // @ts-expect-error
+ await expect(element).toHaveText(6)
+
+ expectPromiseVoid = expect(chainableElement).toHaveText('text')
+ expectPromiseVoid = expect(chainableElement).toHaveText(/text/)
+ expectPromiseVoid = expect(chainableElement).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expect(chainableElement).toHaveText([expect.stringContaining('text1'), expect.stringContaining('text2')])
+ expectPromiseVoid = expect(chainableElement).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expect(chainableElement).toHaveText(['text1', /text1/, expect.stringContaining('text3')])
+
+ expectPromiseVoid = expect(chainableElement).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expect(chainableElement).toHaveText('text')
+ // @ts-expect-error
+ await expect(chainableElement).toHaveText(6)
+
+ expectPromiseVoid = expect(elementArray).toHaveText('text')
+ expectPromiseVoid = expect(elementArray).toHaveText(/text/)
+ expectPromiseVoid = expect(elementArray).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expect(elementArray).toHaveText([expect.stringContaining('text1'), expect.stringContaining('text2')])
+ expectPromiseVoid = expect(elementArray).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expect(elementArray).toHaveText(['text1', /text1/, expect.stringContaining('text3')])
+
+ expectPromiseVoid = expect(elementArray).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expect(elementArray).toHaveText('text')
+ // @ts-expect-error
+ await expect(elementArray).toHaveText(6)
+
+ expectPromiseVoid = expect(chainableArray).toHaveText('text')
+ expectPromiseVoid = expect(chainableArray).toHaveText(/text/)
+ expectPromiseVoid = expect(chainableArray).toHaveText(['text1', 'text2'])
+ expectPromiseVoid = expect(chainableArray).toHaveText([expect.stringContaining('text1'), expect.stringContaining('text2')])
+ expectPromiseVoid = expect(chainableArray).toHaveText([/text1/, /text2/])
+ expectPromiseVoid = expect(chainableArray).toHaveText(['text1', /text1/, expect.stringContaining('text3')])
+
+ expectPromiseVoid = expect(chainableArray).not.toHaveText('text')
+
+ // @ts-expect-error
+ expectVoid = expect(chainableArray).toHaveText('text')
+ // @ts-expect-error
+ await expect(chainableArray).toHaveText(6)
+
+ // @ts-expect-error
+ await expect(browser).toHaveText('text')
+ })
+
+ it('should have ts errors when actual is not an element', async () => {
+ // @ts-expect-error
+ await expect(browser).toHaveText('text')
+ // @ts-expect-error
+ await expect(browser).not.toHaveText('text')
+ // @ts-expect-error
+ await expect(true).toHaveText('text')
+ // @ts-expect-error
+ await expect(true).toHaveText('text')
+ })
+
+ it('should have ts errors when actual is string or Promise', async () => {
+ // @ts-expect-error
+ await expect('text').toHaveText('text')
+ // @ts-expect-error
+ await expect('text').not.toHaveText('text')
+ // @ts-expect-error
+ await expect(Promise.resolve('text')).toHaveText('text')
+ // @ts-expect-error
+ await expect(Promise.resolve('text')).toHaveText('text')
+ })
+ })
+
+ describe('toHaveHeight', () => {
+ it('should be supported correctly', async () => {
+ expectPromiseVoid = expect(element).toHaveHeight(100)
+ expectPromiseVoid = expect(element).toHaveHeight(100, { message: 'Custom error message' })
+ expectPromiseVoid = expect(element).not.toHaveHeight(100)
+ expectPromiseVoid = expect(element).not.toHaveHeight(100, { message: 'Custom error message' })
+
+ expectPromiseVoid = expect(element).toHaveHeight({ width: 100, height: 200 })
+ expectPromiseVoid = expect(element).toHaveHeight({ width: 100, height: 200 }, { message: 'Custom error message' })
+ expectPromiseVoid = expect(element).not.toHaveHeight({ width: 100, height: 200 })
+ expectPromiseVoid = expect(element).not.toHaveHeight({ width: 100, height: 200 }, { message: 'Custom error message' })
+
+ // @ts-expect-error
+ expectVoid = expect(element).toHaveHeight(100)
+ // @ts-expect-error
+ expectVoid = expect(element).not.toHaveHeight(100)
+
+ // @ts-expect-error
+ expectVoid = expect(element).toHaveHeight({ width: 100, height: 200 })
+ // @ts-expect-error
+ expectVoid = expect(element).not.toHaveHeight({ width: 100, height: 200 })
+
+ // @ts-expect-error
+ await expect(browser).toHaveHeight(100)
+ })
+
+ it('should have ts errors when actual is string or Promise', async () => {
+ // @ts-expect-error
+ await expect('text').toHaveText('text')
+ // @ts-expect-error
+ await expect('text').not.toHaveText('text')
+ // @ts-expect-error
+ await expect(Promise.resolve('text')).toHaveText('text')
+ // @ts-expect-error
+ await expect(Promise.resolve('text')).toHaveText('text')
+ })
+ })
+
+ describe('toMatchSnapshot', () => {
+
+ it('should be supported correctly', async () => {
+ expectVoid = expect(element).toMatchSnapshot()
+ expectVoid = expect(element).toMatchSnapshot('test label')
+ expectVoid = expect(element).not.toMatchSnapshot('test label')
+
+ expectPromiseVoid = expect(chainableElement).toMatchSnapshot()
+ expectPromiseVoid = expect(chainableElement).toMatchSnapshot('test label')
+ expectPromiseVoid = expect(chainableElement).not.toMatchSnapshot('test label')
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(element).toMatchSnapshot()
+ //@ts-expect-error
+ expectPromiseVoid = expect(element).not.toMatchSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).toMatchSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).not.toMatchSnapshot()
+ })
+ })
+
+ describe('toMatchInlineSnapshot', () => {
+
+ it('should be correctly supported', async () => {
+ expectVoid = expect(element).toMatchInlineSnapshot()
+ expectVoid = expect(element).toMatchInlineSnapshot('test snapshot')
+ expectVoid = expect(element).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ expectPromiseVoid = expect(chainableElement).toMatchInlineSnapshot()
+ expectPromiseVoid = expect(chainableElement).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expect(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(element).toMatchInlineSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+ })
+
+ it('should be correctly supported with getCSSProperty()', async () => {
+ expectPromiseVoid = expect(element.getCSSProperty('test')).toMatchInlineSnapshot()
+ expectPromiseVoid = expect(element.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expect(element.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ expectPromiseVoid = expect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot()
+ expectPromiseVoid = expect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot')
+ expectPromiseVoid = expect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ expectVoid = expect(element).toMatchInlineSnapshot()
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(element).toMatchInlineSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).toMatchInlineSnapshot('test snapshot', 'test label')
+
+ //@ts-expect-error
+ expectVoid = expect(element.getCSSProperty('test')).toMatchInlineSnapshot()
+ //@ts-expect-error
+ expectVoid = expect(chainableElement.getCSSProperty('test')).toMatchInlineSnapshot()
+ })
+ })
+
+ describe('toBeElementsArrayOfSize', async () => {
+
+ it('should work correctly when actual is chainableArray', async () => {
+ expectPromiseVoid = expect(chainableArray).toBeElementsArrayOfSize(5)
+ expectPromiseVoid = expect(chainableArray).toBeElementsArrayOfSize({ lte: 10 })
+
+ // @ts-expect-error
+ expectVoid = expect(chainableArray).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ expectVoid = expect(chainableArray).toBeElementsArrayOfSize({ lte: 10 })
+ })
+
+ it('should not work when actual is not chainableArray', async () => {
+ // @ts-expect-error
+ await expect(chainableElement).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ await expect(chainableElement).toBeElementsArrayOfSize({ lte: 10 })
+ // @ts-expect-error
+ await expect(true).toBeElementsArrayOfSize(5)
+ // @ts-expect-error
+ await expect(true).toBeElementsArrayOfSize({ lte: 10 })
+ })
+ })
+ })
+
+ describe('Custom matchers', () => {
+ describe('using `ExpectWebdriverIO` namespace augmentation', () => {
+ it('should supported correctly a non-promise custom matcher', async () => {
+ expectVoid = expect('test').toBeCustom()
+ expectVoid = expect('test').not.toBeCustom()
+
+ // @ts-expect-error
+ expectPromiseVoid = expect('test').toBeCustom()
+ // @ts-expect-error
+ expectPromiseVoid = expect('test').not.toBeCustom()
+
+ expectVoid = expect(1).toBeWithinRange(0, 2)
+ })
+
+ it('should supported correctly a promise custom matcher with only chainableElement as actual', async () => {
+ expectPromiseVoid = expect(chainableElement).toBeCustomPromise()
+ expectPromiseVoid = expect(chainableElement).toBeCustomPromise(expect.stringContaining('test'))
+ expectPromiseVoid = expect(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('test'))
+
+ // @ts-expect-error
+ expect('test').toBeCustomPromise()
+ // @ts-expect-error
+ expectVoid = expect(chainableElement).toBeCustomPromise()
+ // @ts-expect-error
+ expectVoid = expect(chainableElement).toBeCustomPromise(expect.stringContaining('test'))
+ // @ts-expect-error
+ expectVoid = expect(chainableElement).not.toBeCustomPromise(expect.stringContaining('test'))
+ // @ts-expect-error
+ expect(chainableElement).toBeCustomPromise(expect.stringContaining(6))
+ })
+
+ it('should support custom asymmetric matcher', async () => {
+ const expectString1 : ExpectWebdriverIO.PartialMatcher = expect.toBeCustom()
+ const expectString2 : ExpectWebdriverIO.PartialMatcher = expect.not.toBeCustom()
+
+ expectPromiseVoid = expect(chainableElement).toBeCustomPromise(expect.toBeCustom())
+
+ // @ts-expect-error
+ expectPromiseVoid = expect.toBeCustom()
+ // @ts-expect-error
+ expectPromiseVoid = expect.not.toBeCustom()
+
+ //@ts-expect-error
+ expectVoid = expect(chainableElement).toBeCustomPromise(expect.toBeCustom())
+ })
+ })
+
+ describe('using `expect` module declaration', () => {
+
+ it('should support a simple matcher', async () => {
+ expectVoid = expect(5).toBeWithinRange(1, 10)
+
+ // Or as an asymmetric matcher:
+ expectVoid = expect({ value: 5 }).toEqual({
+ value: expect.toBeWithinRange(1, 10)
+ })
+
+ // @ts-expect-error
+ expectVoid = expect(5).toBeWithinRange(1, '10')
+ // @ts-expect-error
+ expectPromiseVoid = expect(5).toBeWithinRange('1')
+ })
+
+ it('should support a simple custom matcher with a chainable element matcher with promise', async () => {
+ expectPromiseVoid = expect(chainableElement).toHaveSimpleCustomProperty('text')
+ expectPromiseVoid = expect(chainableElement).toHaveSimpleCustomProperty(expect.stringContaining('text'))
+ expectPromiseVoid = expect(chainableElement).not.toHaveSimpleCustomProperty(expect.not.stringContaining('text'))
+
+ // Or as a custom asymmetric matcher:
+ expectPromiseVoid = expect(chainableElement).toHaveSimpleCustomProperty(
+ expect.toHaveSimpleCustomProperty('string')
+ )
+ const expectString1:string = expect.toHaveSimpleCustomProperty('string')
+ const expectString2:string = expect.not.toHaveSimpleCustomProperty('string')
+
+ // @ts-expect-error
+ expectVoid = expect.toHaveSimpleCustomProperty(chainableElement)
+ // @ts-expect-error
+ expectVoid = expect.not.toHaveSimpleCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = expect.toHaveSimpleCustomProperty(chainableElement)
+ })
+
+ it('should support a chainable element matcher with promise', async () => {
+ expectPromiseVoid = expect(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = expect(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ expectPromiseVoid = expect(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+
+ // Or as a custom asymmetric matcher:
+ expectPromiseVoid = expect(chainableElement).toHaveCustomProperty(
+ await expect.toHaveCustomProperty(chainableElement)
+ )
+ const expectPromiseWdioElement1: Promise> = expect.toHaveCustomProperty(chainableElement)
+ const expectPromiseWdioElement2: Promise> = expect.not.toHaveCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = expect.toHaveCustomProperty(chainableElement)
+ // @ts-expect-error
+ expectVoid = expect.not.toHaveCustomProperty(chainableElement)
+
+ // @ts-expect-error
+ expectVoid = expect.toHaveCustomProperty(chainableElement)
+ // @ts-expect-error
+ expect.toHaveCustomProperty('test')
+
+ await expect(chainableElement).toHaveCustomProperty(
+ await expect.toHaveCustomProperty(chainableElement)
+ )
+ })
+ })
+ })
+
+ describe('toBe', () => {
+
+ it('should expect void type when actual is a boolean', async () => {
+ expectVoid = expect(true).toBe(true)
+ expectVoid = expect(true).not.toBe(true)
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(true).toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect(true).not.toBe(true)
+ })
+
+ it('should not expect Promise when actual is a chainable since toBe does not need to be awaited', async () => {
+ expectVoid = expect(chainableElement).toBe(true)
+ expectVoid = expect(chainableElement).not.toBe(true)
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(chainableElement).toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect(chainableElement).not.toBe(true)
+ })
+
+ it('should still expect void type when actual is a Promise since we do not overload them', async () => {
+ const promiseBoolean = Promise.resolve(true)
+
+ expectVoid = expect(promiseBoolean).toBeDefined()
+ expectVoid = expect(promiseBoolean).not.toBeDefined()
+
+ //@ts-expect-error
+ expectPromiseVoid = expect(promiseBoolean).toBeDefined()
+ //@ts-expect-error
+ expectPromiseVoid = expect(promiseBoolean).toBeDefined()
+ })
+
+ it('should work with string', async () => {
+ expectVoid = expect('text').toBe(true)
+ expectVoid = expect('text').not.toBe(true)
+ expectVoid = expect('text').toBe(expect.stringContaining('text'))
+ expectVoid = expect('text').not.toBe(expect.stringContaining('text'))
+
+ //@ts-expect-error
+ expectPromiseVoid = expect('text').toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect('text').not.toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect('text').toBe(expect.stringContaining('text'))
+ //@ts-expect-error
+ expectPromiseVoid = expect('text').not.toBe(expect.stringContaining('text'))
+ })
+ })
+
+ describe('Promise type assertions', () => {
+ const booleanPromise: Promise = Promise.resolve(true)
+
+ it('should have expect return Matchers with a Promise', async () => {
+ const expectPromiseBoolean1: ExpectWebdriverIO.Matchers> & ExpectLibInverse>> & ExpectWebdriverIO.PromiseMatchers = expect(booleanPromise)
+ const expectPromiseBoolean2: ExpectWebdriverIO.Matchers> = expect(booleanPromise).not
+ })
+
+ it('should work with resolves & rejects correctly', async () => {
+ expectPromiseVoid = expect(booleanPromise).resolves.toBe(true)
+ expectPromiseVoid = expect(booleanPromise).rejects.toBe(true)
+ expectPromiseVoid = expect(booleanPromise).rejects.not.toBe(true)
+ expectPromiseVoid = expect(booleanPromise).resolves.not.toBe(true)
+
+ //@ts-expect-error
+ expectVoid = expect(booleanPromise).resolves.toBe(true)
+ //@ts-expect-error
+ expectVoid = expect(booleanPromise).rejects.toBe(true)
+
+ //@ts-expect-error
+ expect(true).resolves.toBe(true)
+ //@ts-expect-error
+ expect(true).rejects.toBe(true)
+ })
+
+ it('should not support chainable and expect PromiseVoid with toBe', async () => {
+ //@ts-expect-error
+ expectPromiseVoid = expect(chainableElement).toBe(true)
+ //@ts-expect-error
+ expectPromiseVoid = expect(chainableElement).not.toBe(true)
+ })
+ })
+
+ describe('Network Matchers', () => {
+ const promiseNetworkMock = Promise.resolve(networkMock)
+
+ it('should not have ts errors when typing to Promise', async () => {
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequested()
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedTimes(2)
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedTimes({ gte: 5, lte: 10 })
+
+ expectPromiseVoid = expect(promiseNetworkMock).not.toBeRequested()
+ expectPromiseVoid = expect(promiseNetworkMock).not.toBeRequestedTimes(2)
+ expectPromiseVoid = expect(promiseNetworkMock).not.toBeRequestedTimes({ gte: 5, lte: 10 })
+
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedWith({
+ url: 'http://localhost:8080/api',
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: { Authorization: 'foo' },
+ responseHeaders: { Authorization: 'bar' },
+ postData: { title: 'foo', description: 'bar' },
+ response: { success: true },
+ })
+
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedWith({
+ url: expect.stringContaining('test'),
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: expect.objectContaining({ Authorization: 'foo' }),
+ responseHeaders: expect.objectContaining({ Authorization: 'bar' }),
+ postData: expect.objectContaining({ title: 'foo', description: 'bar' }),
+ response: expect.objectContaining({ success: true }),
+ })
+
+ expectPromiseVoid = expect(promiseNetworkMock).toBeRequestedWith({
+ url: expect.stringMatching(/.*\/api\/.*/i),
+ method: ['POST', 'PUT'],
+ statusCode: [401, 403],
+ requestHeaders: headers => headers.Authorization.startsWith('Bearer '),
+ postData: expect.objectContaining({ released: true, title: expect.stringContaining('foobar') }),
+ response: (r: { data: { items: unknown[] } }) => Array.isArray(r) && r.data.items.length === 20
+ })
+ })
+
+ it('should have ts errors when typing to void', async () => {
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequested()
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequestedTimes(2) // await expect(mock).toBeRequestedTimes({ eq: 2 })
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequestedTimes({ gte: 5, lte: 10 }) // request called at least 5 times but less than 11
+
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).not.toBeRequested()
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).not.toBeRequestedTimes(2) // await expect(mock).toBeRequestedTimes({ eq: 2 })
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).not.toBeRequestedTimes({ gte: 5, lte: 10 }) // request called at least 5 times but less than 11
+
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequestedWith({
+ url: 'http://localhost:8080/api',
+ method: 'POST',
+ statusCode: 200,
+ requestHeaders: { Authorization: 'foo' },
+ responseHeaders: { Authorization: 'bar' },
+ postData: { title: 'foo', description: 'bar' },
+ response: { success: true },
+ })
+
+ // @ts-expect-error
+ expectVoid = expect(promiseNetworkMock).toBeRequestedWith(expect.objectContaining({
+ response: { success: true },
+ }))
+ })
+ })
+
+ describe('Expect', () => {
+ it('should have ts errors when using a non existing expect.function', async () => {
+ // @ts-expect-error
+ expect.unimplementedFunction()
+ })
+
+ it('should support stringContaining, anything and more', async () => {
+ expect.stringContaining('WebdriverIO')
+ expect.stringMatching(/WebdriverIO/)
+ expect.arrayContaining(['WebdriverIO', 'Test'])
+ expect.objectContaining({ name: 'WebdriverIO' })
+ // Was not there but works!
+ expect.closeTo(5, 10)
+ expect.arrayContaining(['WebdriverIO', 'Test'])
+ // New from jest 30!!
+ expect.arrayOf(expect.stringContaining('WebdriverIO'))
+
+ expect.anything()
+ expect.any(Function)
+ expect.any(Number)
+ expect.any(Boolean)
+ expect.any(String)
+ expect.any(Symbol)
+ expect.any(Date)
+ expect.any(Error)
+
+ expect.not.stringContaining('WebdriverIO')
+ expect.not.stringMatching(/WebdriverIO/)
+ expect.not.arrayContaining(['WebdriverIO', 'Test'])
+ expect.not.objectContaining({ name: 'WebdriverIO' })
+ expect.not.closeTo(5, 10)
+ expect.not.arrayContaining(['WebdriverIO', 'Test'])
+ expect.not.arrayOf(expect.stringContaining('WebdriverIO'))
+ })
+
+ describe('Soft Assertions', async () => {
+ const actualString: string = 'Test Page'
+ const actualPromiseString: Promise = Promise.resolve('Test Page')
+
+ describe('expect.soft', () => {
+ it('should not need to be awaited/be a promise if actual is non-promise type', async () => {
+ const expectWdioMatcher1: WdioCustomMatchers = expect.soft(actualString)
+ expectVoid = expect.soft(actualString).toBe('Test Page')
+ expectVoid = expect.soft(actualString).not.toBe('Test Page')
+ expectVoid = expect.soft(actualString).not.toBe(expect.stringContaining('Test Page'))
+
+ // @ts-expect-error
+ expectPromiseVoid = expect.soft(actualString).toBe('Test Page')
+ // @ts-expect-error
+ expectPromiseVoid = expect.soft(actualString).not.toBe('Test Page')
+ // @ts-expect-error
+ expectPromiseVoid = expect.soft(actualString).not.toBe(expect.stringContaining('Test Page'))
+ })
+
+ it('should need to be awaited/be a promise if actual is promise type', async () => {
+ const expectWdioMatcher1: ExpectWebdriverIO.MatchersAndInverse, Promise> = expect.soft(actualPromiseString)
+ expectPromiseVoid = expect.soft(actualPromiseString).toBe('Test Page')
+ expectPromiseVoid = expect.soft(actualPromiseString).not.toBe('Test Page')
+ expectPromiseVoid = expect.soft(actualPromiseString).not.toBe(expect.stringContaining('Test Page'))
+
+ // @ts-expect-error
+ expectVoid = expect.soft(actualPromiseString).toBe('Test Page')
+ // @ts-expect-error
+ expectVoid = expect.soft(actualPromiseString).not.toBe('Test Page')
+ // @ts-expect-error
+ expectVoid = expect.soft(actualPromiseString).not.toBe(expect.stringContaining('Test Page'))
+ })
+
+ it('should support chainable element', async () => {
+ const expectElement: ExpectWebdriverIO.MatchersAndInverse = expect.soft(element)
+ const expectElementChainable: ExpectWebdriverIO.MatchersAndInverse = expect.soft(chainableElement)
+
+ // @ts-expect-error
+ const expectElement2: ExpectWebdriverIO.MatchersAndInverse, WebdriverIO.Element> = expect.soft(element)
+ // @ts-expect-error
+ const expectElementChainable2: ExpectWebdriverIO.MatchersAndInverse, typeof chainableElement> = expect.soft(chainableElement)
+ })
+
+ it('should support chainable element with wdio Matchers', async () => {
+ expectPromiseVoid = expect.soft(element).toBeDisplayed()
+ expectPromiseVoid = expect.soft(chainableElement).toBeDisplayed()
+ expectPromiseVoid = expect.soft(chainableArray).toBeDisplayed()
+ await expect.soft(element).toBeDisplayed()
+ await expect.soft(chainableElement).toBeDisplayed()
+ await expect.soft(chainableArray).toBeDisplayed()
+
+ expectPromiseVoid = expect.soft(element).not.toBeDisplayed()
+ expectPromiseVoid = expect.soft(chainableElement).not.toBeDisplayed()
+ expectPromiseVoid = expect.soft(chainableArray).not.toBeDisplayed()
+ await expect.soft(element).not.toBeDisplayed()
+ await expect.soft(chainableElement).not.toBeDisplayed()
+ await expect.soft(chainableArray).not.toBeDisplayed()
+
+ // @ts-expect-error
+ expectVoid = expect.soft(element).toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableArray).toBeDisplayed()
+
+ // @ts-expect-error
+ expectVoid = expect.soft(element).not.toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toBeDisplayed()
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableArray).not.toBeDisplayed()
+ })
+
+ it('should work with custom matcher and custom asymmetric matchers from `expect` module', async () => {
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty(
+ expect.toHaveCustomProperty(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty('text')
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty(
+ expect.toHaveCustomProperty(chainableElement)
+ )
+
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty('text')
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).toHaveCustomProperty(
+ expect.toHaveCustomProperty(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty('text')
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty(expect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toHaveCustomProperty(expect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toHaveCustomProperty(
+ expect.toHaveCustomProperty(chainableElement)
+ )
+ })
+
+ it('should work with custom matcher and custom asymmetric matchers from `ExpectWebDriverIO` namespace', async () => {
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise('text')
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise(expect.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise(
+ expect.toBeCustomPromise(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise('text')
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise(expect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise(
+ expect.toBeCustomPromise(chainableElement)
+ )
+
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise('text')
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise(expect.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('text'))
+ expectPromiseVoid = expect.soft(chainableElement).toBeCustomPromise(
+ expect.toBeCustomPromise(chainableElement)
+ )
+
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise('text')
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise(expect.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).not.toBeCustomPromise(expect.not.stringContaining('text'))
+ // @ts-expect-error
+ expectVoid = expect.soft(chainableElement).toBeCustomPromise(
+ expect.toBeCustomPromise(chainableElement)
+ )
+ })
+ })
+
+ describe('expect.getSoftFailures', () => {
+ it('should be of type `SoftFailure`', async () => {
+ const expectSoftFailure1: ExpectWebdriverIO.SoftFailure[] = expect.getSoftFailures()
+
+ // @ts-expect-error
+ expectVoid = expect.getSoftFailures()
+ })
+ })
+
+ describe('expect.assertSoftFailures', () => {
+ it('should be of type void', async () => {
+ expectVoid = expect.assertSoftFailures()
+
+ // @ts-expect-error
+ expectPromiseVoid = expect.assertSoftFailures()
+ })
+ })
+
+ describe('expect.clearSoftFailures', () => {
+ it('should be of type void', async () => {
+ expectVoid = expect.clearSoftFailures()
+
+ // @ts-expect-error
+ expectPromiseVoid = expect.clearSoftFailures()
+ })
+ })
+ })
+ })
+
+ describe('Asymmetric matchers', () => {
+ const string: string = 'WebdriverIO is a test framework'
+ const array: string[] = ['WebdriverIO', 'Test']
+ const object: { name: string } = { name: 'WebdriverIO' }
+ const number: number = 1
+
+ it('should have no ts error using asymmetric matchers', async () => {
+ expect(string).toEqual(expect.stringContaining('WebdriverIO'))
+ expect(array).toEqual(expect.arrayContaining(['WebdriverIO', 'Test']))
+ expect(object).toEqual(expect.objectContaining({ name: 'WebdriverIO' }))
+ // This one is tested and is working correctly, surprisingly!
+ expect(number).toEqual(expect.closeTo(1.0001, 0.0001))
+ // New from jest 30, should work!
+ expect(['apple', 'banana', 'cherry']).toEqual(expect.arrayOf(expect.any(String)))
+ })
+ })
+})
diff --git a/test-types/types.ts b/test-types/types.ts
deleted file mode 100644
index fcf5b056e..000000000
--- a/test-types/types.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-///
-///
-const elem: WebdriverIO.Element = {} as unknown as WebdriverIO.Element
-const wdioExpect = ExpectWebdriverIO.expect
-
-wdioExpect(elem).toBeDisabled()
-wdioExpect(elem).toHaveAttr('test')
-wdioExpect(elem).not.toHaveAttr('test')
-wdioExpect(elem).toBe('bar')
-
-wdioExpect(elem).toHaveElementClass('bar')
-wdioExpect(elem).toHaveElementProperty('n', 'v', {})
-wdioExpect({ foo: 'bar' }).toMatchSnapshot()
-wdioExpect({ foo: 'bar' }).toMatchInlineSnapshot()
diff --git a/test/matchers.test.ts b/test/matchers.test.ts
index 5e5f15ab8..f0d642eb4 100644
--- a/test/matchers.test.ts
+++ b/test/matchers.test.ts
@@ -61,7 +61,13 @@ test('matchers', () => {
test('allows to add matcher', () => {
const matcher: any = vi.fn((actual: any, expected: any) => ({ pass: actual === expected }))
expectLib.extend({ toBeCustom: matcher })
+
// @ts-expect-error not in types
expectLib('foo').toBeCustom('foo')
expect(matchers.keys()).toContain('toBeCustom')
})
+
+test('Generic asymmetric matchers from Expect library should work', () => {
+ expectLib(1).toEqual(expectLib.closeTo(1.0001, 0.0001))
+ expectLib(['apple', 'banana', 'cherry']).toEqual(expectLib.arrayOf(expectLib.any(String)))
+})
diff --git a/test/snapshot.test.ts.snap b/test/snapshot.test.ts.snap
index a7e784bd3..7af66be0a 100644
--- a/test/snapshot.test.ts.snap
+++ b/test/snapshot.test.ts.snap
@@ -5,13 +5,3 @@ exports[`parent > test 1`] = `
"a": "a",
}
`;
-
-exports[`parent > test 2`] = `
-{
- "deep": {
- "nested": {
- "object": "value",
- },
- },
-}
-`;
diff --git a/test/softAssertions.test.ts b/test/softAssertions.test.ts
index bef7e9603..04734de9d 100644
--- a/test/softAssertions.test.ts
+++ b/test/softAssertions.test.ts
@@ -7,7 +7,7 @@ vi.mock('@wdio/globals')
describe('Soft Assertions', () => {
// Setup a mock element for testing
- let el: any
+ let el: ChainablePromiseElement
beforeEach(async () => {
el = $('sel')
@@ -106,6 +106,23 @@ describe('Soft Assertions', () => {
// Should be no failures now
expect(expectWdio.getSoftFailures().length).toBe(0)
})
+
+ /**
+ * TODO: Skipped since soft assertions are currently not supporting basic matchers like toBe or toEqual. To fix one day!
+ * @see https://github.com/webdriverio/expect-webdriverio/issues/1887
+ */
+ it.skip('should support basic text matching', async () => {
+ const softService = SoftAssertService.getInstance()
+ softService.setCurrentTest('test-7', 'test name', 'test file')
+ const text = await el.getText()
+
+ expectWdio.soft(text).toEqual('!Actual Text')
+
+ const failures = expectWdio.getSoftFailures()
+ expect(failures.length).toBe(1)
+ expect(failures[0].matcherName).toBe('toHaveText')
+ })
+
})
describe('SoftAssertService hooks', () => {
diff --git a/tsconfig.json b/tsconfig.json
index bd84d6376..e3870dc8b 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,16 +1,20 @@
{
+ /**
+ * Let's aligned with the WebdriverIO tsconfig.json.
+ * @see https://github.com/webdriverio/webdriverio/blob/main/tsconfig.json#L5
+ */
"compilerOptions": {
"outDir": "./lib/",
- "module": "ESNext",
- "target": "ES2020",
- "lib": ["ES2020", "DOM"],
+ "module": "esnext",
+ "target": "es2022",
+ "lib": ["ES2022", "DOM"],
"strictBindCallApply": true,
"removeComments": true,
"noImplicitAny": true,
"skipLibCheck": true,
"strictPropertyInitialization": true,
"strictNullChecks": true,
- "moduleResolution": "Node",
+ "moduleResolution": "node", // To review since this is equivalent to node10
"allowSyntheticDefaultImports": true,
"types": [
"node",
diff --git a/tsconfig.types.json b/tsconfig.types.json
new file mode 100644
index 000000000..8ab0f0185
--- /dev/null
+++ b/tsconfig.types.json
@@ -0,0 +1,30 @@
+{
+ /**
+ * TypeScript configuration for the type files.
+ *
+ * Running tsc on types is extremely sensible to the node_modules folder so we wrap the tsc command in a script
+ * @see types-checks-filter-out-node_modules.js
+ */
+ "compilerOptions": {
+ "target": "ES2022",
+ "strictBindCallApply": true,
+ "noImplicitAny": true,
+
+ // Must stay commented to ensure that the types are actually validated
+ // "skipLibCheck": true,
+
+ "strictPropertyInitialization": true,
+ "strictNullChecks": true,
+ "allowSyntheticDefaultImports": true,
+ "types": [
+ "@wdio/types",
+ ],
+ "moduleResolution": "node",
+ "noEmit": true,
+ },
+
+ "include": [
+ "*.d.ts",
+ "./types/*.d.ts"
+ ],
+}
\ No newline at end of file
diff --git a/types-checks-filter-out-node_modules.js b/types-checks-filter-out-node_modules.js
new file mode 100644
index 000000000..9b59fcc4e
--- /dev/null
+++ b/types-checks-filter-out-node_modules.js
@@ -0,0 +1,40 @@
+const node = await import('node:child_process')
+
+/**
+ * Running tsc on types is extremely sensible to node_modules types that we import and there is no way to exclude them.
+ * Note: Yes, we try `--exclude` and it is not excluding them.
+ * So this script just excludes expected errors but still allows to validate the types we release thoroughly
+ */
+
+// List of paths to exclude (relative or absolute, as needed)
+const excludeList = [
+ 'node_modules/@types/node/url.d.ts',
+ 'node_modules/urlpattern-polyfill/dist/index.d.ts',
+ 'node_modules/webdriverio/build/commands/browser/getPuppeteer.d.ts',
+ 'node_modules/webdriverio/build/types.d.ts',
+ // Add more paths or patterns as needed
+]
+
+node.exec('tsc --project tsconfig.types.json --noEmit', (error, stdout, stderr) => {
+ const output = stdout + stderr
+ const lines = output.split('\n')
+ let found = false
+
+ for (const line of lines) {
+ // Check if the line matches any exclusion pattern
+ const shouldExclude = excludeList.some(excludePath =>
+ line.trim().startsWith(excludePath)
+ )
+ if (!shouldExclude && line.includes('error TS')) {
+ found = true
+ console.log(line)
+ }
+ }
+
+ if (!found) {
+ console.log('SUCCESS: No type errors outside the exclusion list.')
+ } else {
+ console.error('\nERROR: Type errors found please fix them as much as possible or exclude them in the script `types-checks-filter-out-node_modules.js`')
+ process.exit(1)
+ }
+})
\ No newline at end of file
diff --git a/types/expect-global.d.ts b/types/expect-global.d.ts
new file mode 100644
index 000000000..b546de019
--- /dev/null
+++ b/types/expect-global.d.ts
@@ -0,0 +1,15 @@
+///
+
+/**
+ * Global declaration file for WebdriverIO's Expect library to force the expect.
+ * Required when used in standalone mode (mocha) or to override the one of Jasmine
+ */
+
+//// @ts-expect-error: IDE might flags this one but just does be concerned by it. This way the `tsc:root-types` can pass!
+declare const expect: ExpectWebdriverIO.Expect
+
+declare namespace NodeJS {
+ interface Global {
+ expect: ExpectWebdriverIO.Expect
+ }
+}
\ No newline at end of file
diff --git a/types/expect-webdriverio.d.ts b/types/expect-webdriverio.d.ts
index 40e535524..429c12c32 100644
--- a/types/expect-webdriverio.d.ts
+++ b/types/expect-webdriverio.d.ts
@@ -4,15 +4,527 @@ type Test = import('@wdio/types').Frameworks.Test
type TestResult = import('@wdio/types').Frameworks.TestResult
type PickleStep = import('@wdio/types').Frameworks.PickleStep
type Scenario = import('@wdio/types').Frameworks.Scenario
+
type SnapshotResult = import('@vitest/snapshot').SnapshotResult
type SnapshotUpdateState = import('@vitest/snapshot').SnapshotUpdateState
+type ChainablePromiseElement = import('webdriverio').ChainablePromiseElement
+type ChainablePromiseArray = import('webdriverio').ChainablePromiseArray
+
+type ExpectLibAsymmetricMatchers = import('expect').AsymmetricMatchers
+type ExpectLibAsymmetricMatcher = import('expect').AsymmetricMatcher
+type ExpectLibMatchers, T> = import('expect').Matchers
+type ExpectLibExpect = import('expect').Expect
+type ExpectLibInverse = import('expect').Inverse
+type ExpectLibSyncExpectationResult = import('expect').SyncExpectationResult
+type ExpectLibAsyncExpectationResult = import('expect').AsyncExpectationResult
+type ExpectLibExpectationResult = import('expect').ExpectationResult
+type ExpectLibMatcherContext = import('expect').MatcherContext
+
+// Extracted from the expect library, this is the type of the matcher function used in the expect library.
+type RawMatcherFn = {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (this: Context, actual: any, ...expected: Array): ExpectLibExpectationResult;
+}
+
+/**
+ * Real Promise and wdio chainable promise types.
+ */
+type WdioPromiseLike