expect-webdriverio 5.7.0 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +16 -14
  2. package/lib/constants.js +4 -0
  3. package/lib/index.js +30 -18
  4. package/lib/matchers/asymmetrics/asymmetricsUtils.js +24 -0
  5. package/lib/matchers/asymmetrics/oneOf.js +51 -0
  6. package/lib/matchers/browser/toHaveClipboardText.js +8 -9
  7. package/lib/matchers/browser/toHaveLocalStorageItem.js +22 -14
  8. package/lib/matchers/browser/toHaveTitle.js +9 -10
  9. package/lib/matchers/browser/toHaveUrl.js +8 -9
  10. package/lib/matchers/element/toBeExisting.js +8 -3
  11. package/lib/matchers/element/toBeSelected.js +6 -13
  12. package/lib/matchers/element/toHaveAttribute.js +46 -45
  13. package/lib/matchers/element/toHaveChildren.js +30 -32
  14. package/lib/matchers/element/toHaveComputedLabel.js +21 -20
  15. package/lib/matchers/element/toHaveComputedRole.js +21 -20
  16. package/lib/matchers/element/toHaveElementClass.js +57 -0
  17. package/lib/matchers/element/toHaveElementProperty.js +40 -32
  18. package/lib/matchers/element/toHaveHTML.js +22 -23
  19. package/lib/matchers/element/toHaveHeight.js +23 -28
  20. package/lib/matchers/element/toHaveSize.js +16 -14
  21. package/lib/matchers/element/toHaveStyle.js +20 -14
  22. package/lib/matchers/element/toHaveText.js +26 -44
  23. package/lib/matchers/element/toHaveWidth.js +23 -28
  24. package/lib/matchers/elements/toBeElementsArrayOfSize.js +25 -30
  25. package/lib/matchers/mock/toBeRequestedTimes.js +12 -17
  26. package/lib/matchers/mock/toBeRequestedWith.js +8 -9
  27. package/lib/matchers/modifiers/some.js +15 -0
  28. package/lib/matchers.js +1 -1
  29. package/lib/tsconfig.build.tsbuildinfo +1 -1
  30. package/lib/util/commandOptionsUtils.js +6 -0
  31. package/lib/util/elementsUtil.js +74 -4
  32. package/lib/util/executeCommand.js +94 -6
  33. package/lib/util/formatMessage.js +87 -32
  34. package/lib/util/numberOptionsUtil.js +92 -0
  35. package/lib/util/refetchElements.js +30 -5
  36. package/lib/util/stringUtil.js +12 -0
  37. package/lib/util/waitUntil.js +44 -0
  38. package/lib/utils.js +84 -91
  39. package/package.json +11 -10
  40. package/types/expect-webdriverio.d.ts +587 -134
  41. package/lib/matchers/element/toHaveClass.js +0 -61
@@ -0,0 +1,92 @@
1
+ import { AsymmetricMatcher } from 'expect';
2
+ import { isDefinedPlainObject } from './commandOptionsUtils.js';
3
+ export const isNumber = (value) => typeof value === 'number' && !isNaN(value);
4
+ export const isDefinedNotNumber = (value) => value !== undefined && !isNumber(value);
5
+ export const isDefinedNumberOrNonEmptyObject = (value) => typeof value === 'number' || (typeof value === 'object' && value !== null && !Array.isArray(value) && Object.keys(value).length > 0);
6
+ export function validateNumberAndExtractOptions(expectedValue, commandOptions, { supportDefaultAsGteThen1 } = {}) {
7
+ let defaultExpectedValue = undefined;
8
+ if (supportDefaultAsGteThen1 && (expectedValue === undefined || (isDefinedPlainObject(expectedValue) && expectedValue.eq === undefined && expectedValue.gte === undefined && expectedValue.lte === undefined))) {
9
+ defaultExpectedValue = new NumberMatcher({ gte: 1 });
10
+ }
11
+ else if (isNumber(expectedValue)) {
12
+ return { numberMatcher: new NumberMatcher({ eq: expectedValue }), commandOptions };
13
+ }
14
+ else if (!isDefinedNumberOrNonEmptyObject(expectedValue) || isDefinedNotNumber(expectedValue.eq) || isDefinedNotNumber(expectedValue.gte) || isDefinedNotNumber(expectedValue.lte)
15
+ || (expectedValue.eq === undefined && expectedValue.gte === undefined && expectedValue.lte === undefined)) {
16
+ throw new Error(`Invalid NumberMatcher. Received: ${JSON.stringify(expectedValue)}`);
17
+ }
18
+ const { eq, gte, lte, ...restCommandOptions } = expectedValue ?? {};
19
+ if (isNumber(gte) && isNumber(lte) && gte > lte) {
20
+ throw new Error(`Invalid NumberMatcher range: 'gte' (${gte}) cannot be greater than 'lte' (${lte}).`);
21
+ }
22
+ return {
23
+ numberMatcher: defaultExpectedValue ?? new NumberMatcher({ eq, gte, lte }),
24
+ commandOptions: { ...commandOptions, ...restCommandOptions }
25
+ };
26
+ }
27
+ export function validateNumberArrayAndExtractOptions(expectedValues, commandOptions, { supportDefaultAsGteThen1 } = {}) {
28
+ if (Array.isArray(expectedValues)) {
29
+ const allNumbers = expectedValues.map((value) => validateNumberAndExtractOptions(value, commandOptions, { supportDefaultAsGteThen1 }));
30
+ return { numberMatcher: allNumbers.map(({ numberMatcher }) => numberMatcher), commandOptions };
31
+ }
32
+ return validateNumberAndExtractOptions(expectedValues, commandOptions, { supportDefaultAsGteThen1 });
33
+ }
34
+ export class NumberMatcher extends AsymmetricMatcher {
35
+ sample;
36
+ constructor(sample) {
37
+ super(sample);
38
+ this.sample = sample;
39
+ }
40
+ asymmetricMatch(actual) {
41
+ if (actual === undefined) {
42
+ return false;
43
+ }
44
+ if (isNumber(this.sample)) {
45
+ return actual === this.sample;
46
+ }
47
+ if (isNumber(this.sample.eq)) {
48
+ return actual === this.sample.eq;
49
+ }
50
+ if (isNumber(this.sample.gte) && isNumber(this.sample.lte)) {
51
+ return actual >= this.sample.gte && actual <= this.sample.lte;
52
+ }
53
+ if (isNumber(this.sample.gte)) {
54
+ return actual >= this.sample.gte;
55
+ }
56
+ if (isNumber(this.sample.lte)) {
57
+ return actual <= this.sample.lte;
58
+ }
59
+ return false;
60
+ }
61
+ toAsymmetricMatcher() {
62
+ if (isNumber(this.sample)) {
63
+ return `${this.sample}`;
64
+ }
65
+ if (isNumber(this.sample.eq)) {
66
+ return `${this.sample.eq}`;
67
+ }
68
+ if (isNumber(this.sample.gte) && isNumber(this.sample.lte)) {
69
+ return `>= ${this.sample.gte} && <= ${this.sample.lte}`;
70
+ }
71
+ if (isNumber(this.sample.gte)) {
72
+ return `>= ${this.sample.gte}`;
73
+ }
74
+ if (isNumber(this.sample.lte)) {
75
+ return `<= ${this.sample.lte}`;
76
+ }
77
+ return 'Incorrect number options provided';
78
+ }
79
+ toString() {
80
+ return this.toAsymmetricMatcher();
81
+ }
82
+ jasmineToString() {
83
+ return this.toString();
84
+ }
85
+ }
86
+ export const isEmptyOrLegacyNumberOptions = (value) => {
87
+ if (!isDefinedPlainObject(value)) {
88
+ return false;
89
+ }
90
+ const keys = Object.keys(value);
91
+ return keys.length === 0 || keys.filter((key) => !['eq', 'gte', 'lte'].includes(key)).length > 0;
92
+ };
@@ -1,10 +1,35 @@
1
- import { DEFAULT_OPTIONS } from '../constants.js';
2
- import { isElementArray } from './elementsUtil.js';
3
- export const refetchElements = async (elements, wait = DEFAULT_OPTIONS.wait, full = false) => {
4
- if (elements && wait > 0 && (elements.length === 0 || full) && isElementArray(elements) && elements.parent && elements.foundWith && elements.foundWith in elements.parent) {
1
+ import { isStrictlyElementArray } from './elementsUtil.js';
2
+ export const refetchElements = async (elements) => {
3
+ if (elements
4
+ && isStrictlyElementArray(elements)
5
+ && elements.parent && elements.foundWith && elements.foundWith in elements.parent) {
5
6
  const browser = elements.parent;
6
7
  const $$ = browser[elements.foundWith];
7
- elements = await $$.call(browser, elements.selector, ...elements.props);
8
+ return await $$.call(browser, elements.selector, ...elements.props);
8
9
  }
9
10
  return elements;
10
11
  };
12
+ export const syncronizeElements = async (subject, refetchedElements) => {
13
+ if (subject instanceof Promise) {
14
+ await syncronizeChainableElementArray(subject, refetchedElements);
15
+ }
16
+ else if (isStrictlyElementArray(subject)) {
17
+ synchronizeElementArray(subject, refetchedElements);
18
+ }
19
+ };
20
+ export const syncronizeChainableElementArray = async (subject, refetchedElements) => {
21
+ const awaitedSubject = await subject;
22
+ if (isStrictlyElementArray(awaitedSubject) && refetchedElements) {
23
+ synchronizeElementArray(await awaitedSubject.getElements(), refetchedElements);
24
+ }
25
+ };
26
+ export const synchronizeElementArray = (subject, refetchedElements) => {
27
+ subject.length = refetchedElements.length;
28
+ for (let index = 0; index < refetchedElements.length; index++) {
29
+ subject[index] = refetchedElements[index];
30
+ }
31
+ };
32
+ export const refreshElementArray = async (subject) => {
33
+ const refetchedElements = await refetchElements(subject);
34
+ synchronizeElementArray(subject, refetchedElements);
35
+ };
@@ -0,0 +1,12 @@
1
+ export const isString = (value) => typeof value === 'string';
2
+ export const toJsonString = (value) => {
3
+ if (isString(value)) {
4
+ return value;
5
+ }
6
+ try {
7
+ return JSON.stringify(value) ?? String(value);
8
+ }
9
+ catch {
10
+ return String(value);
11
+ }
12
+ };
@@ -0,0 +1,44 @@
1
+ import { DEFAULT_OPTIONS } from '../constants.js';
2
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3
+ export const waitUntil = async (condition, isNot = false, { wait = DEFAULT_OPTIONS.wait, interval = DEFAULT_OPTIONS.interval } = {}) => {
4
+ if (wait === 0) {
5
+ return await condition(0);
6
+ }
7
+ let error;
8
+ let result;
9
+ let iteration = 0;
10
+ try {
11
+ const start = Date.now();
12
+ while (true) {
13
+ if (Date.now() - start > wait) {
14
+ throw new Error('timeout');
15
+ }
16
+ try {
17
+ result = await condition(iteration++);
18
+ const passed = isNot !== result?.success;
19
+ error = undefined;
20
+ if (passed) {
21
+ break;
22
+ }
23
+ else if (result?.abort) {
24
+ return { ...result, success: isNot };
25
+ }
26
+ await sleep(interval);
27
+ }
28
+ catch (err) {
29
+ error = err;
30
+ await sleep(interval);
31
+ }
32
+ }
33
+ if (error) {
34
+ throw error;
35
+ }
36
+ return { ...result, success: !isNot };
37
+ }
38
+ catch {
39
+ if (error) {
40
+ throw error;
41
+ }
42
+ return { ...result, subject: result?.subject, success: isNot, actual: result?.actual };
43
+ }
44
+ };
package/lib/utils.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import deepEql from 'deep-eql';
2
2
  import { expect } from 'expect';
3
- import { DEFAULT_OPTIONS } from './constants.js';
4
3
  import { wrapExpectedWithArray } from './util/elementsUtil.js';
5
- import { executeCommand } from './util/executeCommand.js';
6
- import { enhanceError, enhanceErrorBe, numberError } from './util/formatMessage.js';
7
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4
+ import { executeCommandWithStrategy } from './util/executeCommand.js';
5
+ import { enhanceError, enhanceErrorBe } from './util/formatMessage.js';
6
+ import { waitUntil } from './util/waitUntil.js';
7
+ import { DEFAULT_FEATURE_FLAGS } from './constants.js';
8
+ import { OneOfMatcher } from './matchers/asymmetrics/oneOf.js';
8
9
  export function isJasmineStringAsymmetricMatcher(expected) {
9
- return isAsymmetricMatcher(expected) && 'expected' in expected;
10
+ return isAsymmetricMatcher(expected) && !('toAsymmetricMatcher' in expected) && 'jasmineToString' in expected && typeof expected.jasmineToString === 'function';
10
11
  }
11
12
  export function isAsymmetricMatcher(expected) {
12
13
  return (typeof expected === 'object' &&
@@ -47,51 +48,21 @@ export function getAsymmetricMatcherValue(expected) {
47
48
  }
48
49
  return undefined;
49
50
  }
50
- const waitUntil = async (condition, isNot = false, { wait = DEFAULT_OPTIONS.wait, interval = DEFAULT_OPTIONS.interval } = {}) => {
51
- if (wait === 0) {
52
- return await condition();
53
- }
54
- let error;
55
- try {
56
- const start = Date.now();
57
- while (true) {
58
- if (Date.now() - start > wait) {
59
- throw new Error('timeout');
60
- }
61
- try {
62
- const result = isNot !== (await condition());
63
- error = undefined;
64
- if (result) {
65
- break;
66
- }
67
- await sleep(interval);
68
- }
69
- catch (err) {
70
- error = err;
71
- await sleep(interval);
72
- }
73
- }
74
- if (error) {
75
- throw error;
76
- }
77
- return !isNot;
78
- }
79
- catch {
80
- if (error) {
81
- throw error;
82
- }
83
- return isNot;
84
- }
85
- };
86
- async function executeCommandBe(received, command, options) {
87
- const { isNot, verb = 'be' } = this;
88
- let el = await received?.getElement();
89
- const pass = await waitUntil(async () => {
90
- const result = await executeCommand.call(this, el, async (element) => ({ result: await command(element) }), options);
91
- el = result.el;
92
- return result.success;
93
- }, isNot, options);
94
- const message = enhanceErrorBe(el, { ...this, verb }, options);
51
+ async function executeCommandBe(received, command, options = {}) {
52
+ const { isNot, verb = 'be', allowEmptyElements = false } = this;
53
+ const { success: pass, actual, subject, context: { isSome } = {} } = await waitUntil(async (iteration) => {
54
+ return await executeCommandWithStrategy({
55
+ unresolvedElements: received,
56
+ expectedValues: true,
57
+ singleElementCompare: async (element) => {
58
+ const result = await command(element);
59
+ return { success: result, actual: result };
60
+ },
61
+ context: { isNot, iteration },
62
+ strictConfiguration: { allowEmptyElements },
63
+ });
64
+ }, isNot, { wait: options.wait, interval: options.interval });
65
+ const message = enhanceErrorBe(subject, actual, { ...this, verb, isSome }, options);
95
66
  return {
96
67
  pass,
97
68
  message: () => message,
@@ -112,11 +83,30 @@ const compareNumbers = (actual, options = {}) => {
112
83
  }
113
84
  return false;
114
85
  };
86
+ export const compareTextOrArray = (actualText, expectedTexts, options) => {
87
+ if (expectedTexts === undefined) {
88
+ return { actual: actualText, success: false };
89
+ }
90
+ if (Array.isArray(expectedTexts)) {
91
+ if (expectedTexts.some((expected) => expected instanceof OneOfMatcher)) {
92
+ throw new Error('OneOf is not supported in array under legacy behavior. Please enable `useToHaveTextStrictMultiElementsCompareStrategy` feature flag to use the new strict index based matching strategy with `expect.oneOf()`.');
93
+ }
94
+ else {
95
+ console.warn('Array of expected values is deprecated. Please use `expect.oneOf()` asymmetric matcher to compare against multiple expected values. This will be removed in v8.0.0.');
96
+ expectedTexts = new OneOfMatcher(...expectedTexts).withOptions(options);
97
+ }
98
+ }
99
+ if (expectedTexts instanceof OneOfMatcher) {
100
+ return { success: expectedTexts.asymmetricMatch(actualText), actual: actualText };
101
+ }
102
+ const compareResults = compareText(actualText, expectedTexts, options);
103
+ return { success: compareResults.success, actual: actualText };
104
+ };
115
105
  export const compareText = (actual, expected, { ignoreCase = false, trim = true, containing = false, atStart = false, atEnd = false, atIndex, replace, }) => {
116
- if (typeof actual !== 'string') {
106
+ if (typeof actual !== 'string' || expected === null || expected === undefined) {
117
107
  return {
118
- value: actual,
119
- result: false,
108
+ actual,
109
+ success: false,
120
110
  };
121
111
  }
122
112
  if (trim) {
@@ -140,50 +130,50 @@ export const compareText = (actual, expected, { ignoreCase = false, trim = true,
140
130
  if (isAsymmetricMatcher(expected)) {
141
131
  const result = expected.asymmetricMatch(actual);
142
132
  return {
143
- value: actual,
144
- result
133
+ actual,
134
+ success: result
145
135
  };
146
136
  }
147
137
  if (expected instanceof RegExp) {
148
138
  return {
149
- value: actual,
150
- result: !!actual.match(expected),
139
+ actual,
140
+ success: !!actual.match(expected),
151
141
  };
152
142
  }
153
143
  if (containing) {
154
144
  return {
155
- value: actual,
156
- result: actual.includes(expected),
145
+ actual,
146
+ success: actual.includes(expected),
157
147
  };
158
148
  }
159
149
  if (atStart) {
160
150
  return {
161
- value: actual,
162
- result: actual.startsWith(expected),
151
+ actual,
152
+ success: actual.startsWith(expected),
163
153
  };
164
154
  }
165
155
  if (atEnd) {
166
156
  return {
167
- value: actual,
168
- result: actual.endsWith(expected),
157
+ actual,
158
+ success: actual.endsWith(expected),
169
159
  };
170
160
  }
171
161
  if (atIndex) {
172
162
  return {
173
- value: actual,
174
- result: actual.substring(atIndex, actual.length).startsWith(expected),
163
+ actual,
164
+ success: actual.substring(atIndex, actual.length).startsWith(expected),
175
165
  };
176
166
  }
177
167
  return {
178
- value: actual,
179
- result: actual === expected,
168
+ actual,
169
+ success: actual === expected,
180
170
  };
181
171
  };
182
172
  export const compareTextWithArray = (actual, expectedArray, { ignoreCase = false, trim = false, containing = false, atStart = false, atEnd = false, atIndex, replace, }) => {
183
173
  if (typeof actual !== 'string') {
184
174
  return {
185
- value: actual,
186
- result: false,
175
+ actual,
176
+ success: false,
187
177
  };
188
178
  }
189
179
  if (trim) {
@@ -207,7 +197,7 @@ export const compareTextWithArray = (actual, expectedArray, { ignoreCase = false
207
197
  return item;
208
198
  });
209
199
  }
210
- const textInArray = expectedArray.some((expected) => {
200
+ const hasFoundTextInArray = expectedArray.some((expected) => {
211
201
  if (expected instanceof RegExp) {
212
202
  return !!actual.match(expected);
213
203
  }
@@ -229,24 +219,24 @@ export const compareTextWithArray = (actual, expectedArray, { ignoreCase = false
229
219
  return actual === expected;
230
220
  });
231
221
  return {
232
- value: actual,
233
- result: textInArray,
222
+ actual,
223
+ success: hasFoundTextInArray,
234
224
  };
235
225
  };
236
226
  export const compareObject = (actual, expected) => {
237
227
  if (typeof actual !== 'object' || Array.isArray(actual)) {
238
228
  return {
239
- value: actual,
240
- result: false,
229
+ actual,
230
+ success: false,
241
231
  };
242
232
  }
243
233
  return {
244
- value: actual,
245
- result: deepEql(actual, expected),
234
+ actual,
235
+ success: deepEql(actual, expected),
246
236
  };
247
237
  };
248
238
  export const compareStyle = async (actualEl, style, { ignoreCase = false, trim = true, containing = false, atStart = false, atEnd = false, atIndex, replace, }) => {
249
- let result = true;
239
+ let success = true;
250
240
  const actual = {};
251
241
  for (const key in style) {
252
242
  const css = await actualEl.getCSSProperty(key);
@@ -261,42 +251,37 @@ export const compareStyle = async (actualEl, style, { ignoreCase = false, trim =
261
251
  expectedVal = expectedVal.toLowerCase();
262
252
  }
263
253
  if (containing) {
264
- result = actualVal.includes(expectedVal);
254
+ success = actualVal.includes(expectedVal);
265
255
  actual[key] = actualVal;
266
256
  }
267
257
  else if (atStart) {
268
- result = actualVal.startsWith(expectedVal);
258
+ success = actualVal.startsWith(expectedVal);
269
259
  actual[key] = actualVal;
270
260
  }
271
261
  else if (atEnd) {
272
- result = actualVal.endsWith(expectedVal);
262
+ success = actualVal.endsWith(expectedVal);
273
263
  actual[key] = actualVal;
274
264
  }
275
265
  else if (atIndex) {
276
- result = actualVal.substring(atIndex, actualVal.length).startsWith(expectedVal);
266
+ success = actualVal.substring(atIndex, actualVal.length).startsWith(expectedVal);
277
267
  actual[key] = actualVal;
278
268
  }
279
269
  else if (replace) {
280
270
  const replacedActual = replaceActual(replace, actualVal);
281
- result = replacedActual === expectedVal;
271
+ success = replacedActual === expectedVal;
282
272
  actual[key] = replacedActual;
283
273
  }
284
274
  else {
285
- result = result && actualVal === expectedVal;
275
+ success = success && actualVal === expectedVal;
286
276
  actual[key] = css.value;
287
277
  }
288
278
  }
289
279
  return {
290
- value: actual,
291
- result,
280
+ actual,
281
+ success,
292
282
  };
293
283
  };
294
- function aliasFn(fn, { verb, expectation, } = {}, ...args) {
295
- this.verb = verb;
296
- this.expectation = expectation;
297
- return fn.apply(this, args);
298
- }
299
- export { aliasFn, compareNumbers, enhanceError, executeCommand, executeCommandBe, numberError, waitUntil, wrapExpectedWithArray };
284
+ export { compareNumbers, enhanceError, executeCommandBe, waitUntil, wrapExpectedWithArray };
300
285
  function replaceActual(replace, actual) {
301
286
  const hasMultipleReplacers = replace.every((r) => Array.isArray(r));
302
287
  const replacers = hasMultipleReplacers
@@ -311,3 +296,11 @@ function replaceActual(replace, actual) {
311
296
  }
312
297
  return actual;
313
298
  }
299
+ export const getFeatureFlagValue = ({ featureFlags }, featureFlag) => {
300
+ const providedFeatureFlagValue = featureFlags?.[featureFlag];
301
+ if (providedFeatureFlagValue !== undefined) {
302
+ return providedFeatureFlagValue;
303
+ }
304
+ return DEFAULT_FEATURE_FLAGS[featureFlag] ?? false;
305
+ };
306
+ export const toArray = (value) => value === undefined ? [] : Array.isArray(value) ? value : [value];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expect-webdriverio",
3
- "version": "5.7.0",
3
+ "version": "6.0.1",
4
4
  "author": "Mykola Grybyk <mykola.grybyk@gmail.com>",
5
5
  "description": "WebdriverIO Assertion Library",
6
6
  "license": "MIT",
@@ -77,7 +77,7 @@
77
77
  "playgrounds:snapshots:update": "cd playgrounds && npm run snapshots:update"
78
78
  },
79
79
  "dependencies": {
80
- "@vitest/snapshot": "^4.1.7",
80
+ "@vitest/snapshot": "^4.1.10",
81
81
  "deep-eql": "^5.0.2",
82
82
  "expect": "^30.4.1",
83
83
  "jest-matcher-utils": "^30.4.1"
@@ -88,19 +88,20 @@
88
88
  "@types/jasmine": "^5.1.15",
89
89
  "@types/jest": "^30.0.0",
90
90
  "@types/mocha": "^10.0.10",
91
- "@types/node": "^24.12.4",
92
- "@vitest/coverage-v8": "^4.1.7",
91
+ "@types/node": "^24.13.3",
92
+ "@vitest/coverage-v8": "^4.1.10",
93
+ "@vitest/eslint-plugin": "^1.6.26",
93
94
  "@wdio/eslint": "^0.1.3",
94
- "@wdio/types": "^9.27.2",
95
- "eslint": "^9.39.4",
95
+ "@wdio/types": "^9.30.1",
96
+ "eslint": "^9.39.5",
96
97
  "husky": "^9.1.7",
97
- "npm-run-all2": "^9.0.1",
98
- "release-it": "^20.2.0",
98
+ "npm-run-all2": "^9.0.3",
99
+ "release-it": "^21.0.1",
99
100
  "rimraf": "^6.1.3",
100
101
  "strip-ansi": "^7.2.0",
101
102
  "typescript": "^5.9.3",
102
- "vitest": "^4.1.7",
103
- "webdriverio": "^9.27.2"
103
+ "vitest": "^4.1.10",
104
+ "webdriverio": "^9.30.1"
104
105
  },
105
106
  "peerDependencies": {
106
107
  "@wdio/globals": "^9.0.0",