expect-webdriverio 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +18 -1
- package/lib/matchers/snapshot.js +1 -1
- package/lib/softAssert.js +76 -0
- package/lib/softAssertService.js +47 -0
- package/lib/softExpect.js +68 -0
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/lib/utils.js +1 -1
- package/package.json +4 -4
- package/types/expect-webdriverio.d.ts +80 -0
package/lib/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { expect as expectLib } from 'expect';
|
|
2
2
|
import wdioMatchers from './matchers.js';
|
|
3
3
|
import { DEFAULT_OPTIONS } from './constants.js';
|
|
4
|
+
import createSoftExpect from './softExpect.js';
|
|
5
|
+
import { SoftAssertService } from './softAssert.js';
|
|
4
6
|
export const matchers = new Map();
|
|
5
7
|
const extend = expectLib.extend;
|
|
6
8
|
expectLib.extend = (m) => {
|
|
@@ -11,7 +13,20 @@ expectLib.extend = (m) => {
|
|
|
11
13
|
return extend(m);
|
|
12
14
|
};
|
|
13
15
|
expectLib.extend(wdioMatchers);
|
|
14
|
-
|
|
16
|
+
const expectWithSoft = expectLib;
|
|
17
|
+
Object.defineProperty(expectWithSoft, 'soft', {
|
|
18
|
+
value: (actual) => createSoftExpect(actual)
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(expectWithSoft, 'getSoftFailures', {
|
|
21
|
+
value: (testId) => SoftAssertService.getInstance().getFailures(testId)
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(expectWithSoft, 'assertSoftFailures', {
|
|
24
|
+
value: (testId) => SoftAssertService.getInstance().assertNoFailures(testId)
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(expectWithSoft, 'clearSoftFailures', {
|
|
27
|
+
value: (testId) => SoftAssertService.getInstance().clearFailures(testId)
|
|
28
|
+
});
|
|
29
|
+
export const expect = expectWithSoft;
|
|
15
30
|
export const getConfig = () => DEFAULT_OPTIONS;
|
|
16
31
|
export const setDefaultOptions = (options = {}) => {
|
|
17
32
|
Object.entries(options).forEach(([key, value]) => {
|
|
@@ -22,4 +37,6 @@ export const setDefaultOptions = (options = {}) => {
|
|
|
22
37
|
};
|
|
23
38
|
export const setOptions = setDefaultOptions;
|
|
24
39
|
export { SnapshotService } from './snapshot.js';
|
|
40
|
+
export { SoftAssertService } from './softAssert.js';
|
|
41
|
+
export { SoftAssertionService } from './softAssertService.js';
|
|
25
42
|
export * as utils from './utils.js';
|
package/lib/matchers/snapshot.js
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export class SoftAssertService {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.failureMap = new Map();
|
|
4
|
+
this.currentTest = null;
|
|
5
|
+
}
|
|
6
|
+
static getInstance() {
|
|
7
|
+
if (!SoftAssertService.instance) {
|
|
8
|
+
SoftAssertService.instance = new SoftAssertService();
|
|
9
|
+
}
|
|
10
|
+
return SoftAssertService.instance;
|
|
11
|
+
}
|
|
12
|
+
setCurrentTest(testId, testName, testFile) {
|
|
13
|
+
this.currentTest = { id: testId, name: testName, file: testFile };
|
|
14
|
+
if (!this.failureMap.has(testId)) {
|
|
15
|
+
this.failureMap.set(testId, []);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
clearCurrentTest() {
|
|
19
|
+
this.currentTest = null;
|
|
20
|
+
}
|
|
21
|
+
getCurrentTestId() {
|
|
22
|
+
return this.currentTest?.id || null;
|
|
23
|
+
}
|
|
24
|
+
addFailure(error, matcherName) {
|
|
25
|
+
const testId = this.getCurrentTestId();
|
|
26
|
+
if (!testId) {
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
const stackLines = error.stack?.split('\n') || [];
|
|
30
|
+
let location = '';
|
|
31
|
+
for (const line of stackLines) {
|
|
32
|
+
if (line && !line.includes('expect-webdriverio') && !line.includes('node_modules')) {
|
|
33
|
+
location = line.trim();
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const failures = this.failureMap.get(testId) || [];
|
|
38
|
+
failures.push({ error, matcherName, location });
|
|
39
|
+
this.failureMap.set(testId, failures);
|
|
40
|
+
}
|
|
41
|
+
getFailures(testId) {
|
|
42
|
+
const id = testId || this.getCurrentTestId();
|
|
43
|
+
if (!id) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
return this.failureMap.get(id) || [];
|
|
47
|
+
}
|
|
48
|
+
clearFailures(testId) {
|
|
49
|
+
const id = testId || this.getCurrentTestId();
|
|
50
|
+
if (id) {
|
|
51
|
+
this.failureMap.delete(id);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
assertNoFailures(testId) {
|
|
55
|
+
const id = testId || this.getCurrentTestId();
|
|
56
|
+
if (!id) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const failures = this.getFailures(id);
|
|
60
|
+
if (failures.length === 0) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
let message = `${failures.length} soft assertion failure${failures.length > 1 ? 's' : ''}:\n\n`;
|
|
64
|
+
failures.forEach((failure, index) => {
|
|
65
|
+
message += `${index + 1}) ${failure.matcherName}: ${failure.error.message}\n`;
|
|
66
|
+
if (failure.location) {
|
|
67
|
+
message += ` at ${failure.location}\n`;
|
|
68
|
+
}
|
|
69
|
+
message += '\n';
|
|
70
|
+
});
|
|
71
|
+
this.clearFailures(id);
|
|
72
|
+
const error = new Error(message);
|
|
73
|
+
error.name = 'SoftAssertionsError';
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { SoftAssertService } from './softAssert';
|
|
2
|
+
export class SoftAssertionService {
|
|
3
|
+
constructor(serviceOptions) {
|
|
4
|
+
this.softAssertService = SoftAssertService.getInstance();
|
|
5
|
+
this.options = {
|
|
6
|
+
autoAssertOnTestEnd: true,
|
|
7
|
+
...serviceOptions
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
beforeTest(test) {
|
|
11
|
+
const testId = this.getTestId(test);
|
|
12
|
+
this.softAssertService.setCurrentTest(testId, test.title, test.file);
|
|
13
|
+
}
|
|
14
|
+
beforeStep(step, scenario) {
|
|
15
|
+
const stepId = `${scenario.uri || ''}:${scenario.name || ''}:${step.text || ''}`;
|
|
16
|
+
this.softAssertService.setCurrentTest(stepId, step.text, scenario.uri);
|
|
17
|
+
}
|
|
18
|
+
afterTest(test, _, result) {
|
|
19
|
+
if (!result.error && this.options.autoAssertOnTestEnd) {
|
|
20
|
+
try {
|
|
21
|
+
const testId = this.getTestId(test);
|
|
22
|
+
this.softAssertService.assertNoFailures(testId);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
result.error = error;
|
|
26
|
+
result.passed = false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
this.softAssertService.clearCurrentTest();
|
|
30
|
+
}
|
|
31
|
+
afterStep(step, scenario, result) {
|
|
32
|
+
if (result.passed) {
|
|
33
|
+
try {
|
|
34
|
+
const stepId = `${scenario.uri || ''}:${scenario.name || ''}:${step.text || ''}`;
|
|
35
|
+
this.softAssertService.assertNoFailures(stepId);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
result.error = error;
|
|
39
|
+
result.passed = false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.softAssertService.clearCurrentTest();
|
|
43
|
+
}
|
|
44
|
+
getTestId(test) {
|
|
45
|
+
return `${test.file || ''}:${test.parent || ''}:${test.title || ''}`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { expect, matchers } from './index.js';
|
|
2
|
+
import { SoftAssertService } from './softAssert.js';
|
|
3
|
+
const createSoftExpect = (actual) => {
|
|
4
|
+
const softService = SoftAssertService.getInstance();
|
|
5
|
+
return new Proxy({}, {
|
|
6
|
+
get(target, prop) {
|
|
7
|
+
const propName = String(prop);
|
|
8
|
+
if (propName === 'not') {
|
|
9
|
+
return createSoftNotProxy(actual, softService);
|
|
10
|
+
}
|
|
11
|
+
if (propName === 'resolves' || propName === 'rejects') {
|
|
12
|
+
return createSoftChainProxy(actual, propName, softService);
|
|
13
|
+
}
|
|
14
|
+
if (matchers.has(propName)) {
|
|
15
|
+
return createSoftMatcher(actual, propName, softService);
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
const createSoftNotProxy = (actual, softService) => {
|
|
22
|
+
return new Proxy({}, {
|
|
23
|
+
get(target, prop) {
|
|
24
|
+
const propName = String(prop);
|
|
25
|
+
if (matchers.has(propName)) {
|
|
26
|
+
return createSoftMatcher(actual, propName, softService, 'not');
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const createSoftChainProxy = (actual, chainType, softService) => {
|
|
33
|
+
return new Proxy({}, {
|
|
34
|
+
get(target, prop) {
|
|
35
|
+
const propName = String(prop);
|
|
36
|
+
if (matchers.has(propName)) {
|
|
37
|
+
return createSoftMatcher(actual, propName, softService, chainType);
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
const createSoftMatcher = (actual, matcherName, softService, prefix) => {
|
|
44
|
+
return async (...args) => {
|
|
45
|
+
try {
|
|
46
|
+
let expectChain = expect(actual);
|
|
47
|
+
if (prefix === 'not') {
|
|
48
|
+
expectChain = expectChain.not;
|
|
49
|
+
}
|
|
50
|
+
else if (prefix === 'resolves') {
|
|
51
|
+
expectChain = expectChain.resolves;
|
|
52
|
+
}
|
|
53
|
+
else if (prefix === 'rejects') {
|
|
54
|
+
expectChain = expectChain.rejects;
|
|
55
|
+
}
|
|
56
|
+
return await expectChain[matcherName](...args);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const fullMatcherName = prefix ? `${prefix}.${matcherName}` : matcherName;
|
|
60
|
+
softService.addFailure(error, fullMatcherName);
|
|
61
|
+
return {
|
|
62
|
+
pass: true,
|
|
63
|
+
message: () => `Soft assertion failed: ${fullMatcherName}`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
export default createSoftExpect;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/constants.ts","../src/index.ts","../src/jasmineUtils.ts","../src/matchers.ts","../src/snapshot.ts","../src/types.ts","../src/utils.ts","../src/matchers/snapshot.ts","../src/matchers/browser/toHaveClipboardText.ts","../src/matchers/browser/toHaveTitle.ts","../src/matchers/browser/toHaveUrl.ts","../src/matchers/element/toBeClickable.ts","../src/matchers/element/toBeDisabled.ts","../src/matchers/element/toBeDisplayed.ts","../src/matchers/element/toBeDisplayedInViewport.ts","../src/matchers/element/toBeEnabled.ts","../src/matchers/element/toBeExisting.ts","../src/matchers/element/toBeFocused.ts","../src/matchers/element/toBeSelected.ts","../src/matchers/element/toHaveAttribute.ts","../src/matchers/element/toHaveChildren.ts","../src/matchers/element/toHaveClass.ts","../src/matchers/element/toHaveComputedLabel.ts","../src/matchers/element/toHaveComputedRole.ts","../src/matchers/element/toHaveElementProperty.ts","../src/matchers/element/toHaveHTML.ts","../src/matchers/element/toHaveHeight.ts","../src/matchers/element/toHaveHref.ts","../src/matchers/element/toHaveId.ts","../src/matchers/element/toHaveSize.ts","../src/matchers/element/toHaveStyle.ts","../src/matchers/element/toHaveText.ts","../src/matchers/element/toHaveValue.ts","../src/matchers/element/toHaveWidth.ts","../src/matchers/elements/toBeElementsArrayOfSize.ts","../src/matchers/mock/toBeRequested.ts","../src/matchers/mock/toBeRequestedTimes.ts","../src/matchers/mock/toBeRequestedWith.ts","../src/util/elementsUtil.ts","../src/util/executeCommand.ts","../src/util/formatMessage.ts","../src/util/refetchElements.ts"],"version":"5.
|
|
1
|
+
{"root":["../src/constants.ts","../src/index.ts","../src/jasmineUtils.ts","../src/matchers.ts","../src/snapshot.ts","../src/softAssert.ts","../src/softAssertService.ts","../src/softExpect.ts","../src/types.ts","../src/utils.ts","../src/matchers/snapshot.ts","../src/matchers/browser/toHaveClipboardText.ts","../src/matchers/browser/toHaveTitle.ts","../src/matchers/browser/toHaveUrl.ts","../src/matchers/element/toBeClickable.ts","../src/matchers/element/toBeDisabled.ts","../src/matchers/element/toBeDisplayed.ts","../src/matchers/element/toBeDisplayedInViewport.ts","../src/matchers/element/toBeEnabled.ts","../src/matchers/element/toBeExisting.ts","../src/matchers/element/toBeFocused.ts","../src/matchers/element/toBeSelected.ts","../src/matchers/element/toHaveAttribute.ts","../src/matchers/element/toHaveChildren.ts","../src/matchers/element/toHaveClass.ts","../src/matchers/element/toHaveComputedLabel.ts","../src/matchers/element/toHaveComputedRole.ts","../src/matchers/element/toHaveElementProperty.ts","../src/matchers/element/toHaveHTML.ts","../src/matchers/element/toHaveHeight.ts","../src/matchers/element/toHaveHref.ts","../src/matchers/element/toHaveId.ts","../src/matchers/element/toHaveSize.ts","../src/matchers/element/toHaveStyle.ts","../src/matchers/element/toHaveText.ts","../src/matchers/element/toHaveValue.ts","../src/matchers/element/toHaveWidth.ts","../src/matchers/elements/toBeElementsArrayOfSize.ts","../src/matchers/mock/toBeRequested.ts","../src/matchers/mock/toBeRequestedTimes.ts","../src/matchers/mock/toBeRequestedWith.ts","../src/util/elementsUtil.ts","../src/util/executeCommand.ts","../src/util/formatMessage.ts","../src/util/refetchElements.ts"],"version":"5.8.3"}
|
package/lib/utils.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import isEqual from 'lodash.isequal';
|
|
2
|
-
import { expect } from '
|
|
2
|
+
import { expect } from 'expect';
|
|
3
3
|
import { DEFAULT_OPTIONS } from './constants.js';
|
|
4
4
|
import { wrapExpectedWithArray } from './util/elementsUtil.js';
|
|
5
5
|
import { executeCommand } from './util/executeCommand.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expect-webdriverio",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"author": "Mykola Grybyk <mykola.grybyk@gmail.com>",
|
|
5
5
|
"description": "WebdriverIO Assertion Library",
|
|
6
6
|
"license": "MIT",
|
|
@@ -70,14 +70,14 @@
|
|
|
70
70
|
"@types/node": "^22.9.0",
|
|
71
71
|
"@wdio/types": "^9.9.0",
|
|
72
72
|
"@vitest/coverage-v8": "^3.0.5",
|
|
73
|
-
"@wdio/eslint": "^0.0
|
|
73
|
+
"@wdio/eslint": "^0.1.0",
|
|
74
74
|
"c8": "^10.1.2",
|
|
75
75
|
"eslint": "^9.15.0",
|
|
76
76
|
"husky": "^9.1.5",
|
|
77
77
|
"npm-run-all2": "^7.0.1",
|
|
78
|
-
"release-it": "^
|
|
78
|
+
"release-it": "^19.0.1",
|
|
79
79
|
"rimraf": "^6.0.1",
|
|
80
|
-
"shelljs": "^0.
|
|
80
|
+
"shelljs": "^0.9.1",
|
|
81
81
|
"typescript": "^5.5.4",
|
|
82
82
|
"vitest": "^3.0.4",
|
|
83
83
|
"webdriverio": "^9.2.11"
|
|
@@ -14,6 +14,35 @@ declare namespace ExpectWebdriverIO {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
interface SoftFailure {
|
|
18
|
+
error: Error;
|
|
19
|
+
matcherName: string;
|
|
20
|
+
location?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class SoftAssertService {
|
|
24
|
+
static getInstance(): SoftAssertService;
|
|
25
|
+
setCurrentTest(testId: string, testName?: string, testFile?: string): void;
|
|
26
|
+
clearCurrentTest(): void;
|
|
27
|
+
getCurrentTestId(): string | null;
|
|
28
|
+
addFailure(error: Error, matcherName: string): void;
|
|
29
|
+
getFailures(testId?: string): SoftFailure[];
|
|
30
|
+
clearFailures(testId?: string): void;
|
|
31
|
+
assertNoFailures(testId?: string): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface SoftAssertionServiceOptions {
|
|
35
|
+
autoAssertOnTestEnd?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class SoftAssertionService implements import('@wdio/types').Services.ServiceInstance {
|
|
39
|
+
constructor(serviceOptions?: SoftAssertionServiceOptions, capabilities?: any, config?: any);
|
|
40
|
+
beforeTest(test: import('@wdio/types').Frameworks.Test): void;
|
|
41
|
+
beforeStep(step: import('@wdio/types').Frameworks.PickleStep, scenario: import('@wdio/types').Frameworks.Scenario): void;
|
|
42
|
+
afterTest(test: import('@wdio/types').Frameworks.Test, context: any, result: import('@wdio/types').Frameworks.TestResult): void;
|
|
43
|
+
afterStep(step: import('@wdio/types').Frameworks.PickleStep, scenario: import('@wdio/types').Frameworks.Scenario, result: { passed: boolean, error?: Error }): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
17
46
|
interface AssertionResult {
|
|
18
47
|
pass: boolean
|
|
19
48
|
message(): string
|
|
@@ -447,6 +476,57 @@ declare namespace ExpectWebdriverIO {
|
|
|
447
476
|
asymmetricMatch(...args: any[]): boolean
|
|
448
477
|
toString(): string
|
|
449
478
|
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* expect function declaration, containing two generics:
|
|
482
|
+
* - T: the type of the actual value, e.g. WebdriverIO.Browser or WebdriverIO.Element
|
|
483
|
+
* - R: the type of the return value, e.g. Promise<void> or void
|
|
484
|
+
*/
|
|
485
|
+
interface Expect {
|
|
486
|
+
<T = unknown, R extends void | Promise<void> = void | Promise<void>>(actual: T): Matchers<R, T>
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Creates a soft assertion wrapper around standard expect
|
|
490
|
+
* Soft assertions record failures but don't throw errors immediately
|
|
491
|
+
* All failures are collected and reported at the end of the test
|
|
492
|
+
*/
|
|
493
|
+
soft<T = unknown>(actual: T): Matchers<Promise<void>, T>
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Get all current soft assertion failures
|
|
497
|
+
*/
|
|
498
|
+
getSoftFailures(testId?: string): SoftFailure[]
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Manually assert all soft failures (throws an error if any failures exist)
|
|
502
|
+
*/
|
|
503
|
+
assertSoftFailures(testId?: string): void
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Clear all current soft assertion failures
|
|
507
|
+
*/
|
|
508
|
+
clearSoftFailures(testId?: string): void
|
|
509
|
+
|
|
510
|
+
// Standard asymmetric matchers from Jest
|
|
511
|
+
extend(map: Record<string, Function>): void
|
|
512
|
+
anything(): PartialMatcher
|
|
513
|
+
any(sample: unknown): PartialMatcher
|
|
514
|
+
stringContaining(expected: string): PartialMatcher
|
|
515
|
+
objectContaining(sample: Record<string, unknown>): PartialMatcher
|
|
516
|
+
arrayContaining(sample: Array<unknown>): PartialMatcher
|
|
517
|
+
stringMatching(expected: string | RegExp): PartialMatcher
|
|
518
|
+
not: AsymmetricMatchers
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
interface AsymmetricMatchers {
|
|
522
|
+
any(expectedObject: any): PartialMatcher
|
|
523
|
+
anything(): PartialMatcher
|
|
524
|
+
arrayContaining(sample: Array<unknown>): PartialMatcher
|
|
525
|
+
objectContaining(sample: Record<string, unknown>): PartialMatcher
|
|
526
|
+
stringContaining(expected: string): PartialMatcher
|
|
527
|
+
stringMatching(expected: string | RegExp): PartialMatcher
|
|
528
|
+
not: AsymmetricMatchers
|
|
529
|
+
}
|
|
450
530
|
}
|
|
451
531
|
|
|
452
532
|
declare module 'expect-webdriverio' {
|