expect-webdriverio 6.1.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -5
- package/jest.d.ts +4 -2
- package/lib/api/index.js +2 -0
- package/lib/index.js +2 -0
- package/lib/matchers/asymmetrics/asymmetricsUtils.js +10 -0
- package/lib/matchers/asymmetrics/multiRemote.js +36 -0
- package/lib/matchers/browser/toHaveClipboardText.js +18 -8
- package/lib/matchers/browser/toHaveLocalStorageItem.js +22 -11
- package/lib/matchers/browser/toHaveTitle.js +16 -6
- package/lib/matchers/browser/toHaveUrl.js +16 -6
- package/lib/matchers/element/toHaveAttribute.js +2 -2
- package/lib/matchers/element/toHaveChildren.js +4 -3
- package/lib/matchers/element/toHaveComputedLabel.js +4 -4
- package/lib/matchers/element/toHaveComputedRole.js +4 -4
- package/lib/matchers/element/toHaveElementClass.js +4 -4
- package/lib/matchers/element/toHaveElementProperty.js +4 -4
- package/lib/matchers/element/toHaveHTML.js +4 -4
- package/lib/matchers/element/toHaveHeight.js +2 -2
- package/lib/matchers/element/toHaveSize.js +3 -3
- package/lib/matchers/element/toHaveStyle.js +3 -3
- package/lib/matchers/element/toHaveText.js +5 -5
- package/lib/matchers/element/toHaveValue.js +1 -1
- package/lib/matchers/element/toHaveWidth.js +2 -2
- package/lib/matchers/elements/toBeElementsArrayOfSize.js +70 -3
- package/lib/matchers/mock/toBeRequestedTimes.js +28 -5
- package/lib/matchers/mock/toBeRequestedWith.js +40 -12
- package/lib/matchers/snapshot.js +38 -7
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/lib/util/elementsUtil.js +34 -6
- package/lib/util/executeBrowserCommand.js +37 -0
- package/lib/util/executeCommand.js +107 -19
- package/lib/util/formatMessage.js +80 -6
- package/lib/util/multiRemoteUtils.js +85 -0
- package/lib/util/numberOptionsUtil.js +6 -0
- package/lib/util/refetchElements.js +24 -2
- package/lib/utils.js +7 -2
- package/package.json +3 -3
- package/types/expect-webdriverio.d.ts +384 -39
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
2
|
-
import {
|
|
2
|
+
import { compareTextOrOneOf, enhanceError, getFeatureFlagValue, isArrayContainingMatcher, waitUntil, } from '../../utils.js';
|
|
3
3
|
import { executeCommandWithStrategy } from '../../util/executeCommand.js';
|
|
4
4
|
import { fillSingleExpectedForElementArray } from '../../util/elementsUtil.js';
|
|
5
5
|
import { buildWdioAsymmetricMatchersWithOptions } from '../asymmetrics/asymmetricsUtils.js';
|
|
6
6
|
async function compareElement(el, expectedText, options) {
|
|
7
7
|
const actualText = await el.getText();
|
|
8
|
-
return
|
|
8
|
+
return compareTextOrOneOf(actualText, expectedText, options);
|
|
9
9
|
}
|
|
10
10
|
export async function toHaveText(received, expectedValue, options = DEFAULT_OPTIONS) {
|
|
11
11
|
const { expectation = 'text', verb = 'have', isNot, matcherName = 'toHaveText' } = this;
|
|
@@ -16,7 +16,7 @@ export async function toHaveText(received, expectedValue, options = DEFAULT_OPTI
|
|
|
16
16
|
});
|
|
17
17
|
expectedValue = buildWdioAsymmetricMatchersWithOptions(expectedValue, options);
|
|
18
18
|
const isNewStrictCompare = getFeatureFlagValue(options, 'useToHaveTextStrictMultiElementsCompareStrategy');
|
|
19
|
-
const { success: pass, actual: actualText, subject: subject, context: { isSome } = {} } = await waitUntil(async (iteration) => {
|
|
19
|
+
const { success: pass, actual: actualText, subject: subject, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
|
|
20
20
|
return await executeCommandWithStrategy({
|
|
21
21
|
unresolvedElements: received,
|
|
22
22
|
expectedValues: expectedValue,
|
|
@@ -32,8 +32,8 @@ export async function toHaveText(received, expectedValue, options = DEFAULT_OPTI
|
|
|
32
32
|
if (isArrayContainingMatcher(expectedValue) && actualText === undefined) {
|
|
33
33
|
throw new Error('toHaveText with arrayContaining requires an array of elements');
|
|
34
34
|
}
|
|
35
|
-
const
|
|
36
|
-
const message = enhanceError(subject,
|
|
35
|
+
const finalExpected = expected ?? fillSingleExpectedForElementArray(subject, expectedValue);
|
|
36
|
+
const message = enhanceError(subject, finalExpected, actualText, { isNot, isSome }, verb, expectation, '', options);
|
|
37
37
|
const result = {
|
|
38
38
|
pass,
|
|
39
39
|
message: () => message
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toHaveElementProperty } from './toHaveElementProperty.js';
|
|
2
2
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
3
3
|
export function toHaveValue(el, value, options = DEFAULT_OPTIONS) {
|
|
4
|
-
return toHaveElementProperty.call(this, el, 'value', value, options);
|
|
4
|
+
return toHaveElementProperty.call({ ...this, allowObjectExpectedValue: false }, el, 'value', value, options);
|
|
5
5
|
}
|
|
@@ -18,7 +18,7 @@ export async function toHaveWidth(received, expectedValue, options = DEFAULT_OPT
|
|
|
18
18
|
options,
|
|
19
19
|
});
|
|
20
20
|
const { numberMatcher: expectedNumber, commandOptions } = validateNumberArrayAndExtractOptions(expectedValue, options);
|
|
21
|
-
const { success: pass, actual: actualWidth, subject: elements, context: { isSome } = {} } = await waitUntil(async (iteration) => {
|
|
21
|
+
const { success: pass, actual: actualWidth, subject: elements, context: { isSome } = {}, expected } = await waitUntil(async (iteration) => {
|
|
22
22
|
return await executeCommandWithStrategy({
|
|
23
23
|
unresolvedElements: received,
|
|
24
24
|
expectedValues: expectedNumber,
|
|
@@ -28,7 +28,7 @@ export async function toHaveWidth(received, expectedValue, options = DEFAULT_OPT
|
|
|
28
28
|
strictConfiguration: { allowArrayWithSingleElement: false }
|
|
29
29
|
});
|
|
30
30
|
}, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
|
|
31
|
-
const expectedValues = wrapExpectedWithArray(elements, actualWidth, expectedNumber);
|
|
31
|
+
const expectedValues = expected ?? wrapExpectedWithArray(elements, actualWidth, expectedNumber);
|
|
32
32
|
const message = enhanceError(elements, expectedValues, actualWidth, { isNot, isSome }, verb, expectation, '', commandOptions);
|
|
33
33
|
const result = {
|
|
34
34
|
pass,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { waitUntil, enhanceError, } from '../../utils.js';
|
|
2
|
-
import { refetchElements, syncronizeElements } from '../../util/refetchElements.js';
|
|
2
|
+
import { refetchElements, synchronizeElementArray, syncronizeElements } from '../../util/refetchElements.js';
|
|
3
3
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
4
4
|
import { validateNumberAndExtractOptions } from '../../util/numberOptionsUtil.js';
|
|
5
|
-
import { awaitElementArray, isStrictlyElementArray } from '../../util/elementsUtil.js';
|
|
5
|
+
import { awaitElementArray, isMultiRemoteElementArray, isMultiRemoteElements, isMultiRemoteElementsLike, isStrictlyElementArray } from '../../util/elementsUtil.js';
|
|
6
|
+
import { getElementsPerInstance, getGlobalMultiRemoteInstanceNames, hasSameInstanceNames, isGlobalBrowserSingleRemote, isMultiRemoteMatcher } from '../../util/multiRemoteUtils.js';
|
|
6
7
|
export async function toBeElementsArrayOfSize(received, expectedValue, options = DEFAULT_OPTIONS) {
|
|
7
8
|
const { expectation = 'elements array of size', verb = 'be', isNot, matcherName = 'toBeElementsArrayOfSize' } = this;
|
|
8
9
|
await options.beforeAssertion?.({
|
|
@@ -10,8 +11,17 @@ export async function toBeElementsArrayOfSize(received, expectedValue, options =
|
|
|
10
11
|
expectedValue,
|
|
11
12
|
options,
|
|
12
13
|
});
|
|
13
|
-
const { numberMatcher: expectedNumber, commandOptions } = validateNumberAndExtractOptions(expectedValue, options);
|
|
14
14
|
let { elements, other } = await awaitElementArray(received);
|
|
15
|
+
const awaitedMultiRemote = other ?? elements;
|
|
16
|
+
const isEmptyWithPerInstanceSizes = Array.isArray(awaitedMultiRemote) && awaitedMultiRemote.length === 0
|
|
17
|
+
&& !isStrictlyElementArray(awaitedMultiRemote) && !isGlobalBrowserSingleRemote()
|
|
18
|
+
&& getPerInstanceSizes(expectedValue) !== undefined;
|
|
19
|
+
if (isMultiRemoteElementsLike(awaitedMultiRemote) || isEmptyWithPerInstanceSizes) {
|
|
20
|
+
const result = await multiRemoteElementsArrayOfSize(awaitedMultiRemote, expectedValue, options, { context: this, verb, expectation });
|
|
21
|
+
await options.afterAssertion?.({ matcherName, expectedValue, options, result });
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
const { numberMatcher: expectedNumber, commandOptions } = validateNumberAndExtractOptions(expectedValue, options);
|
|
15
25
|
const originalLength = elements ? elements.length : undefined;
|
|
16
26
|
const { success: pass } = await waitUntil(async (iteration) => {
|
|
17
27
|
if (!elements) {
|
|
@@ -43,3 +53,60 @@ export async function toBeElementsArrayOfSize(received, expectedValue, options =
|
|
|
43
53
|
});
|
|
44
54
|
return result;
|
|
45
55
|
}
|
|
56
|
+
const multiRemoteElementsArrayOfSize = async (received, expectedValue, options, { context, verb, expectation }) => {
|
|
57
|
+
const { isNot } = context;
|
|
58
|
+
const perInstanceSizes = getPerInstanceSizes(expectedValue);
|
|
59
|
+
const instances = getMultiRemoteInstanceNames(received)
|
|
60
|
+
?? getGlobalMultiRemoteInstanceNames()
|
|
61
|
+
?? (perInstanceSizes ? Object.keys(perInstanceSizes) : []);
|
|
62
|
+
let commandOptions = options;
|
|
63
|
+
let expected;
|
|
64
|
+
let instanceMismatch = false;
|
|
65
|
+
if (perInstanceSizes) {
|
|
66
|
+
instanceMismatch = !hasSameInstanceNames(perInstanceSizes, instances);
|
|
67
|
+
expected = Object.fromEntries(Object.entries(perInstanceSizes).map(([name, value]) => [name, validateNumberAndExtractOptions(value, options).numberMatcher]));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const validated = validateNumberAndExtractOptions(expectedValue, options);
|
|
71
|
+
commandOptions = validated.commandOptions;
|
|
72
|
+
expected = Object.fromEntries(instances.map((name) => [name, validated.numberMatcher]));
|
|
73
|
+
}
|
|
74
|
+
let refetchSource = received;
|
|
75
|
+
let elements = received;
|
|
76
|
+
let actual = countElementsPerInstance(elements, instances);
|
|
77
|
+
const { success: pass } = await waitUntil(async (iteration) => {
|
|
78
|
+
if (iteration > 0) {
|
|
79
|
+
elements = await refetchElements(refetchSource);
|
|
80
|
+
if (elements.length > 0 || !isMultiRemoteElements(refetchSource)) {
|
|
81
|
+
refetchSource = elements;
|
|
82
|
+
}
|
|
83
|
+
actual = countElementsPerInstance(elements, instances);
|
|
84
|
+
}
|
|
85
|
+
if (instanceMismatch) {
|
|
86
|
+
return { success: !!isNot, subject: refetchSource, actual, abort: true };
|
|
87
|
+
}
|
|
88
|
+
const matches = (name) => expected[name].asymmetricMatch(actual[name]);
|
|
89
|
+
const success = isNot ? instances.some(matches) : instances.every(matches);
|
|
90
|
+
return { success, subject: refetchSource, actual };
|
|
91
|
+
}, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
|
|
92
|
+
if (pass && elements !== received) {
|
|
93
|
+
synchronizeElementArray(received, elements);
|
|
94
|
+
}
|
|
95
|
+
const message = enhanceError(refetchSource, expected, actual, { isNot }, verb, expectation, '', commandOptions);
|
|
96
|
+
return { pass, message: () => message };
|
|
97
|
+
};
|
|
98
|
+
const getPerInstanceSizes = (value) => {
|
|
99
|
+
return isMultiRemoteMatcher(value) ? value.sample : undefined;
|
|
100
|
+
};
|
|
101
|
+
const getMultiRemoteInstanceNames = (elements) => {
|
|
102
|
+
const first = elements[0];
|
|
103
|
+
if (first) {
|
|
104
|
+
return first.instances;
|
|
105
|
+
}
|
|
106
|
+
const parent = isMultiRemoteElementArray(elements) ? elements.parent : undefined;
|
|
107
|
+
return parent?.instances;
|
|
108
|
+
};
|
|
109
|
+
const countElementsPerInstance = (elements, instances) => {
|
|
110
|
+
const elementsPerInstance = getElementsPerInstance(elements, instances);
|
|
111
|
+
return Object.fromEntries(instances.map((name) => [name, elementsPerInstance[name].length]));
|
|
112
|
+
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { waitUntil, enhanceError } from '../../utils.js';
|
|
2
2
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
3
3
|
import { validateNumberAndExtractOptions } from '../../util/numberOptionsUtil.js';
|
|
4
|
+
import { awaitMocks, getMockInstanceNames, isMockArray } from '../../util/multiRemoteUtils.js';
|
|
5
|
+
import { formatMultiRemoteMocks, labelMultiRemoteValues } from '../../util/formatMessage.js';
|
|
4
6
|
export async function toBeRequestedTimes(received, expectedValue, options = DEFAULT_OPTIONS) {
|
|
5
7
|
const { verb = 'be', isNot, matcherName = 'toBeRequestedTimes', expectation = `called${typeof expectedValue === 'number' ? ' ' + expectedValue : ''} time${expectedValue !== 1 ? 's' : ''}`, } = this;
|
|
6
8
|
await options.beforeAssertion?.({
|
|
@@ -9,11 +11,32 @@ export async function toBeRequestedTimes(received, expectedValue, options = DEFA
|
|
|
9
11
|
options,
|
|
10
12
|
});
|
|
11
13
|
const { numberMatcher: expectedNumber, commandOptions } = validateNumberAndExtractOptions(expectedValue, options);
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const mocks = await awaitMocks(received);
|
|
15
|
+
let message;
|
|
16
|
+
let pass;
|
|
17
|
+
if (isMockArray(mocks)) {
|
|
18
|
+
const instanceNames = getMockInstanceNames(mocks);
|
|
19
|
+
const { names } = instanceNames;
|
|
20
|
+
const label = (value) => instanceNames.isNamedByInstance ? labelMultiRemoteValues(value) : value;
|
|
21
|
+
const result = await waitUntil(async () => {
|
|
22
|
+
const actual = Object.fromEntries(mocks.map((mock, index) => [names[index], mock.calls.length]));
|
|
23
|
+
const matches = (name) => expectedNumber.asymmetricMatch(actual[name]);
|
|
24
|
+
const success = isNot ? names.some(matches) : names.every(matches);
|
|
25
|
+
return { success, subject: mocks, actual };
|
|
26
|
+
}, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
|
|
27
|
+
pass = result.success;
|
|
28
|
+
const expected = Object.fromEntries(names.map((name) => [name, expectedNumber]));
|
|
29
|
+
message = enhanceError(formatMultiRemoteMocks(instanceNames), label(expected), label(result.actual), this, verb, expectation, '', commandOptions);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const mock = mocks;
|
|
33
|
+
const result = await waitUntil(async () => {
|
|
34
|
+
const actual = mock.calls.length;
|
|
35
|
+
return { success: expectedNumber.asymmetricMatch(actual), subject: mock, actual };
|
|
36
|
+
}, isNot, { wait: commandOptions.wait, interval: commandOptions.interval });
|
|
37
|
+
pass = result.success;
|
|
38
|
+
message = enhanceError('mock', expectedNumber, result.actual, this, verb, expectation, '', commandOptions);
|
|
39
|
+
}
|
|
17
40
|
const result = {
|
|
18
41
|
pass,
|
|
19
42
|
message: () => message
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { waitUntil, enhanceError, isAsymmetricMatcher, getAsymmetricMatcherValue } from '../../utils.js';
|
|
2
2
|
import { equals } from '../../jasmineUtils.js';
|
|
3
3
|
import { DEFAULT_OPTIONS } from '../../constants.js';
|
|
4
|
+
import { awaitMocks, getMockInstanceNames, isMockArray } from '../../util/multiRemoteUtils.js';
|
|
5
|
+
import { formatMultiRemoteMocks, labelMultiRemoteValues } from '../../util/formatMessage.js';
|
|
4
6
|
const STR_LIMIT = 80;
|
|
5
7
|
const KEY_LIMIT = 12;
|
|
6
8
|
function reduceHeaders(headers) {
|
|
@@ -16,9 +18,42 @@ export async function toBeRequestedWith(received, expectedValue = {}, options =
|
|
|
16
18
|
expectedValue,
|
|
17
19
|
options,
|
|
18
20
|
});
|
|
21
|
+
const parseCache = new Map();
|
|
22
|
+
const mocks = await awaitMocks(received);
|
|
23
|
+
let pass;
|
|
24
|
+
let message;
|
|
25
|
+
if (isMockArray(mocks)) {
|
|
26
|
+
const instanceNames = getMockInstanceNames(mocks);
|
|
27
|
+
const { names } = instanceNames;
|
|
28
|
+
const label = (value) => instanceNames.isNamedByInstance ? labelMultiRemoteValues(value) : value;
|
|
29
|
+
const results = await Promise.all(mocks.map((mock) => checkRequestedWith(mock, expectedValue, options, !!isNot, parseCache)));
|
|
30
|
+
pass = isNot ? results.some((result) => result.pass) : results.every((result) => result.pass);
|
|
31
|
+
const expected = Object.fromEntries(names.map((name) => [name, minifyRequestedWith(expectedValue)]));
|
|
32
|
+
const actual = Object.fromEntries(results.map((result, index) => [names[index], result.actual]));
|
|
33
|
+
const payloadNeverCollected = results.some((result) => !result.pass && result.payloadNeverCollected);
|
|
34
|
+
message = enhanceError(formatMultiRemoteMocks(instanceNames), label(expected), label(actual), this, verb, expectation, '', options)
|
|
35
|
+
+ (!pass && !isNot && payloadNeverCollected ? payloadCollectionHint(expectedValue) : '');
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const result = await checkRequestedWith(mocks, expectedValue, options, !!isNot, parseCache);
|
|
39
|
+
pass = result.pass;
|
|
40
|
+
message = enhanceError('mock', minifyRequestedWith(expectedValue), result.actual, this, verb, expectation, '', options) + (!pass && !isNot && result.payloadNeverCollected ? payloadCollectionHint(expectedValue) : '');
|
|
41
|
+
}
|
|
42
|
+
const result = {
|
|
43
|
+
pass,
|
|
44
|
+
message: () => message
|
|
45
|
+
};
|
|
46
|
+
await options.afterAssertion?.({
|
|
47
|
+
matcherName,
|
|
48
|
+
expectedValue,
|
|
49
|
+
options,
|
|
50
|
+
result
|
|
51
|
+
});
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
const checkRequestedWith = async (received, expectedValue, options, isNot, parseCache) => {
|
|
19
55
|
const hasPayloadExpectation = expectedValue.postData !== undefined || expectedValue.response !== undefined;
|
|
20
56
|
const waitForPayloadOnNot = isNot && hasPayloadExpectation;
|
|
21
|
-
const parseCache = new Map();
|
|
22
57
|
let payloadNeverCollected = false;
|
|
23
58
|
const { success: pass, actual } = await waitUntil(async () => {
|
|
24
59
|
let lastCall;
|
|
@@ -50,19 +85,12 @@ export async function toBeRequestedWith(received, expectedValue = {}, options =
|
|
|
50
85
|
abort: waitForPayloadOnNot && !hasPendingPayloadCandidate,
|
|
51
86
|
};
|
|
52
87
|
}, waitForPayloadOnNot ? false : isNot, { ...options, wait: (isNot && !waitForPayloadOnNot) ? 0 : options.wait });
|
|
53
|
-
|
|
54
|
-
const result = {
|
|
88
|
+
return {
|
|
55
89
|
pass,
|
|
56
|
-
|
|
90
|
+
actual: minifyRequestMock(actual, expectedValue, parseCache) || 'was not called',
|
|
91
|
+
payloadNeverCollected,
|
|
57
92
|
};
|
|
58
|
-
|
|
59
|
-
matcherName,
|
|
60
|
-
expectedValue,
|
|
61
|
-
options,
|
|
62
|
-
result
|
|
63
|
-
});
|
|
64
|
-
return result;
|
|
65
|
-
}
|
|
93
|
+
};
|
|
66
94
|
const payloadCollectionHint = (expectedValue) => {
|
|
67
95
|
const fields = [
|
|
68
96
|
expectedValue.postData !== undefined ? 'postData' : undefined,
|
package/lib/matchers/snapshot.js
CHANGED
|
@@ -2,6 +2,8 @@ import path from 'node:path';
|
|
|
2
2
|
import { expect } from 'expect';
|
|
3
3
|
import { stripSnapshotIndentation } from '@vitest/snapshot';
|
|
4
4
|
import { SnapshotService } from '../snapshot.js';
|
|
5
|
+
import { awaitElementOrArray, isElementOrArrayOrMultiRemoteElementLike, isMultiRemoteElement, isMultiRemoteElementArray, isMultiRemoteElementsLike, isStrictlyElementArray } from '../util/elementsUtil.js';
|
|
6
|
+
import { getElementsPerInstance } from '../util/multiRemoteUtils.js';
|
|
5
7
|
function returnSnapshotError(snapshotError) {
|
|
6
8
|
try {
|
|
7
9
|
expect(snapshotError.actual).toBe(snapshotError.expected);
|
|
@@ -38,12 +40,41 @@ function toMatchSnapshotAssert(received, message, inlineOptions) {
|
|
|
38
40
|
return returnSnapshotError(e);
|
|
39
41
|
}
|
|
40
42
|
}
|
|
43
|
+
const isElementsToSnapshot = (received) => {
|
|
44
|
+
const isEmptyPlainArray = Array.isArray(received) && received.length === 0 && !isStrictlyElementArray(received) && !isMultiRemoteElementArray(received);
|
|
45
|
+
return isElementOrArrayOrMultiRemoteElementLike(received) && !isEmptyPlainArray;
|
|
46
|
+
};
|
|
47
|
+
const getOuterHTML = (element) => element.getHTML({ includeSelectorTag: true });
|
|
48
|
+
const getMultiRemoteOuterHTML = async (multiRemoteElements) => {
|
|
49
|
+
const isSingleElement = isMultiRemoteElement(multiRemoteElements);
|
|
50
|
+
if (!isSingleElement && multiRemoteElements.length === 0) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const instances = [...(isSingleElement ? multiRemoteElements : multiRemoteElements[0]).instances].sort();
|
|
54
|
+
const elementsPerInstance = isSingleElement ? undefined : getElementsPerInstance(multiRemoteElements, instances);
|
|
55
|
+
const htmlPerInstance = Object.fromEntries(await Promise.all(instances.map(async (instance) => [
|
|
56
|
+
instance,
|
|
57
|
+
elementsPerInstance
|
|
58
|
+
? await Promise.all(elementsPerInstance[instance].map(getOuterHTML))
|
|
59
|
+
: await getOuterHTML(multiRemoteElements.getInstance(instance))
|
|
60
|
+
])));
|
|
61
|
+
const htmls = Object.values(htmlPerInstance);
|
|
62
|
+
return htmls.every((html) => JSON.stringify(html) === JSON.stringify(htmls[0])) ? htmls[0] : htmlPerInstance;
|
|
63
|
+
};
|
|
41
64
|
async function toMatchSnapshotAsync(asyncReceived, message, inlineOptions) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
const { element, elements, multiRemoteSelector, other } = await awaitElementOrArray(await asyncReceived);
|
|
66
|
+
let received = other;
|
|
67
|
+
if (multiRemoteSelector) {
|
|
68
|
+
received = await getMultiRemoteOuterHTML(multiRemoteSelector);
|
|
69
|
+
}
|
|
70
|
+
else if (elements && isMultiRemoteElementsLike(elements)) {
|
|
71
|
+
received = await getMultiRemoteOuterHTML(elements);
|
|
72
|
+
}
|
|
73
|
+
else if (elements) {
|
|
74
|
+
received = await Promise.all(Array.from(elements).map(getOuterHTML));
|
|
75
|
+
}
|
|
76
|
+
else if (element) {
|
|
77
|
+
received = await getOuterHTML(element);
|
|
47
78
|
}
|
|
48
79
|
return toMatchSnapshotAssert(received, message, inlineOptions);
|
|
49
80
|
}
|
|
@@ -53,8 +84,8 @@ function toMatchSnapshotHelper(received, message, inlineOptions) {
|
|
|
53
84
|
throw new Error('Snapshot service is not initialized');
|
|
54
85
|
}
|
|
55
86
|
if (received && typeof received === 'object' &&
|
|
56
|
-
('
|
|
57
|
-
|
|
87
|
+
('then' in received ||
|
|
88
|
+
isElementsToSnapshot(received))) {
|
|
58
89
|
return toMatchSnapshotAsync(received, message, inlineOptions);
|
|
59
90
|
}
|
|
60
91
|
return toMatchSnapshotAssert(received, message, inlineOptions);
|
|
@@ -1 +1 @@
|
|
|
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/api/index.ts","../src/matchers/snapshot.ts","../src/matchers/asymmetrics/asymmetricsUtils.ts","../src/matchers/asymmetrics/oneOf.ts","../src/matchers/browser/toHaveClipboardText.ts","../src/matchers/browser/toHaveLocalStorageItem.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/toHaveComputedLabel.ts","../src/matchers/element/toHaveComputedRole.ts","../src/matchers/element/toHaveElementClass.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/matchers/modifiers/some.ts","../src/util/commandOptionsUtils.ts","../src/util/elementsUtil.ts","../src/util/executeCommand.ts","../src/util/formatMessage.ts","../src/util/globalSingleton.ts","../src/util/numberOptionsUtil.ts","../src/util/refetchElements.ts","../src/util/stringUtil.ts","../src/util/waitUntil.ts"],"version":"5.9.3"}
|
|
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/api/index.ts","../src/matchers/snapshot.ts","../src/matchers/asymmetrics/asymmetricsUtils.ts","../src/matchers/asymmetrics/multiRemote.ts","../src/matchers/asymmetrics/oneOf.ts","../src/matchers/browser/toHaveClipboardText.ts","../src/matchers/browser/toHaveLocalStorageItem.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/toHaveComputedLabel.ts","../src/matchers/element/toHaveComputedRole.ts","../src/matchers/element/toHaveElementClass.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/matchers/modifiers/some.ts","../src/util/commandOptionsUtils.ts","../src/util/elementsUtil.ts","../src/util/executeBrowserCommand.ts","../src/util/executeCommand.ts","../src/util/formatMessage.ts","../src/util/globalSingleton.ts","../src/util/multiRemoteUtils.ts","../src/util/numberOptionsUtil.ts","../src/util/refetchElements.ts","../src/util/stringUtil.ts","../src/util/waitUntil.ts"],"version":"5.9.3"}
|
package/lib/util/elementsUtil.js
CHANGED
|
@@ -22,27 +22,33 @@ const isSelector = (obj) => {
|
|
|
22
22
|
};
|
|
23
23
|
export const isElementArray = (obj) => {
|
|
24
24
|
return isSelector(obj)
|
|
25
|
-
&& 'foundWith' in obj
|
|
25
|
+
&& 'foundWith' in obj
|
|
26
|
+
&& !isMultiRemote(obj);
|
|
26
27
|
};
|
|
27
28
|
export const isStrictlyElementArray = (obj) => {
|
|
28
29
|
return isElementArray(obj)
|
|
29
30
|
&& Array.isArray(obj)
|
|
30
|
-
&& 'getElements' in obj
|
|
31
|
+
&& 'getElements' in obj
|
|
32
|
+
&& !isMultiRemote(obj);
|
|
31
33
|
};
|
|
32
34
|
export const isElement = (obj) => {
|
|
33
35
|
return isSelector(obj)
|
|
34
36
|
&& !Array.isArray(obj)
|
|
35
|
-
&& 'getElement' in obj
|
|
37
|
+
&& 'getElement' in obj
|
|
38
|
+
&& !isMultiRemote(obj);
|
|
36
39
|
};
|
|
37
40
|
export const isElementArrayLike = (obj) => {
|
|
38
|
-
return !!obj && (isStrictlyElementArray(obj) || (Array.isArray(obj) &&
|
|
41
|
+
return !!obj && (isStrictlyElementArray(obj) || (Array.isArray(obj) && Array.prototype.every.call(obj, isElement)));
|
|
39
42
|
};
|
|
40
43
|
export const isArrayOfElement = (obj) => {
|
|
41
|
-
return Array.isArray(obj) && obj.every(isElement);
|
|
44
|
+
return Array.isArray(obj) && !isMultiRemote(obj) && Array.prototype.every.call(obj, isElement);
|
|
42
45
|
};
|
|
43
46
|
export const isElementOrArrayLike = (obj) => {
|
|
44
47
|
return !!obj && (isElement(obj) || isElementArrayLike(obj));
|
|
45
48
|
};
|
|
49
|
+
export const isElementOrArrayOrMultiRemoteElementLike = (obj) => {
|
|
50
|
+
return !!obj && (isElement(obj) || isElementArrayLike(obj) || isMultiRemoteElementLike(obj));
|
|
51
|
+
};
|
|
46
52
|
export const awaitElementOrArray = async (received) => {
|
|
47
53
|
if (!received || typeof received !== 'object') {
|
|
48
54
|
return { other: received };
|
|
@@ -51,9 +57,12 @@ export const awaitElementOrArray = async (received) => {
|
|
|
51
57
|
if (awaitedElements instanceof Promise) {
|
|
52
58
|
awaitedElements = await awaitedElements;
|
|
53
59
|
}
|
|
54
|
-
if (!
|
|
60
|
+
if (!isElementOrArrayOrMultiRemoteElementLike(awaitedElements)) {
|
|
55
61
|
return { other: awaitedElements };
|
|
56
62
|
}
|
|
63
|
+
if (isMultiRemoteElement(awaitedElements)) {
|
|
64
|
+
return { selector: awaitedElements, multiRemoteSelector: awaitedElements };
|
|
65
|
+
}
|
|
57
66
|
if ('getElement' in awaitedElements) {
|
|
58
67
|
const element = await awaitedElements.getElement();
|
|
59
68
|
return { selector: element, element };
|
|
@@ -77,3 +86,22 @@ export const awaitElementArray = async (received) => {
|
|
|
77
86
|
}
|
|
78
87
|
return { elements: awaitedElements };
|
|
79
88
|
};
|
|
89
|
+
const isMultiRemote = (obj) => {
|
|
90
|
+
return !!obj && typeof obj === 'object' && ('isMultiremote' in obj && obj.isMultiremote === true || 'isMultiRemote' in obj && obj.isMultiRemote === true);
|
|
91
|
+
};
|
|
92
|
+
export const isMultiRemoteElement = (obj) => {
|
|
93
|
+
return isMultiRemote(obj) && !Array.isArray(obj) && 'selector' in obj;
|
|
94
|
+
};
|
|
95
|
+
export const isMultiRemoteElements = (obj) => {
|
|
96
|
+
return Array.isArray(obj) && obj.length > 0 && !isMultiRemoteElementArray(obj)
|
|
97
|
+
&& Array.prototype.every.call(obj, isMultiRemoteElement);
|
|
98
|
+
};
|
|
99
|
+
export const isMultiRemoteElementArray = (obj) => {
|
|
100
|
+
return !!obj && typeof obj === 'object' && 'isMultiremote' in obj && obj.isMultiremote === true && 'parent' in obj && 'foundWith' in obj && 'selector' in obj;
|
|
101
|
+
};
|
|
102
|
+
export const isMultiRemoteElementLike = (obj) => {
|
|
103
|
+
return isMultiRemoteElement(obj) || isMultiRemoteElementsLike(obj);
|
|
104
|
+
};
|
|
105
|
+
export const isMultiRemoteElementsLike = (obj) => {
|
|
106
|
+
return isMultiRemoteElementArray(obj) || isMultiRemoteElements(obj);
|
|
107
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getPerInstanceValues, hasSameInstanceNames } from './multiRemoteUtils.js';
|
|
2
|
+
export async function executeBrowserCommand({ browser, expectedValue, compare, isNot = false, }) {
|
|
3
|
+
if (browser.isMultiremote) {
|
|
4
|
+
const { instances } = browser;
|
|
5
|
+
const perInstanceValues = getPerInstanceValues(expectedValue);
|
|
6
|
+
const expected = perInstanceValues ?? Object.fromEntries(instances.map((name) => [name, expectedValue]));
|
|
7
|
+
const forceFailure = (!!perInstanceValues && !hasSameInstanceNames(perInstanceValues, instances))
|
|
8
|
+
|| Object.values(expected).some(Array.isArray);
|
|
9
|
+
const arrayResults = await Promise.all(instances.map(async (name) => {
|
|
10
|
+
let singleBrowser;
|
|
11
|
+
try {
|
|
12
|
+
singleBrowser = browser.getInstance(name);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return { success: false, actual: undefined, multiRemoteBrowserName: name };
|
|
16
|
+
}
|
|
17
|
+
const isExpected = name in expected;
|
|
18
|
+
const results = await compare(singleBrowser, forceFailure || !isExpected ? undefined : expected[name]);
|
|
19
|
+
return { ...results, multiRemoteBrowserName: name };
|
|
20
|
+
}));
|
|
21
|
+
const actual = arrayResults.reduce((acc, result) => {
|
|
22
|
+
acc[result.multiRemoteBrowserName] = result.actual;
|
|
23
|
+
return acc;
|
|
24
|
+
}, {});
|
|
25
|
+
if (forceFailure) {
|
|
26
|
+
return { actual, success: isNot, abort: true, subject: browser, expected };
|
|
27
|
+
}
|
|
28
|
+
const success = isNot ? arrayResults.some(result => result.success) : arrayResults.every(result => result.success);
|
|
29
|
+
return { actual, success, subject: browser, expected };
|
|
30
|
+
}
|
|
31
|
+
const forceFailure = getPerInstanceValues(expectedValue) !== undefined || Array.isArray(expectedValue);
|
|
32
|
+
const results = await compare(browser, forceFailure ? undefined : expectedValue);
|
|
33
|
+
if (forceFailure) {
|
|
34
|
+
return { ...results, success: isNot, abort: true, subject: browser, expected: expectedValue };
|
|
35
|
+
}
|
|
36
|
+
return { ...results, subject: browser, expected: expectedValue };
|
|
37
|
+
}
|