expect-webdriverio
Version:
WebdriverIO Assertion Library
264 lines (263 loc) • 8.47 kB
JavaScript
import isEqual from 'lodash.isequal';
import { expect } from './index.js';
import { DEFAULT_OPTIONS } from './constants.js';
import { wrapExpectedWithArray } from './util/elementsUtil.js';
import { executeCommand } from './util/executeCommand.js';
import { enhanceError, enhanceErrorBe, numberError } from './util/formatMessage.js';
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const asymmetricMatcher = typeof Symbol === 'function' && Symbol.for
? Symbol.for('jest.asymmetricMatcher')
: 1267621;
export function isAsymmeyricMatcher(expected) {
return (typeof expected === 'object' &&
typeof expected === 'object' &&
expected &&
'$$typeof' in expected &&
'asymmetricMatch' in expected &&
expected.$$typeof === asymmetricMatcher &&
Boolean(expected.asymmetricMatch));
}
function isStringContainingMatcher(expected) {
return isAsymmeyricMatcher(expected) && ['StringContaining', 'StringNotContaining'].includes(expected.toString());
}
const waitUntil = async (condition, isNot = false, { wait = DEFAULT_OPTIONS.wait, interval = DEFAULT_OPTIONS.interval } = {}) => {
if (wait === 0) {
return await condition();
}
let error;
try {
const start = Date.now();
while (true) {
if (Date.now() - start > wait) {
throw new Error('timeout');
}
error = undefined;
try {
const result = isNot !== (await condition());
error = undefined;
if (result) {
break;
}
await sleep(interval);
}
catch (err) {
error = err;
await sleep(interval);
}
}
if (error) {
throw error;
}
return !isNot;
}
catch {
if (error) {
throw error;
}
return isNot;
}
};
async function executeCommandBe(received, command, options) {
const { isNot, expectation, verb = 'be' } = this;
let el = await received?.getElement();
const pass = await waitUntil(async () => {
const result = await executeCommand.call(this, el, async (element) => ({ result: await command(element) }), options);
el = result.el;
return result.success;
}, isNot, options);
const message = enhanceErrorBe(el, pass, this, verb, expectation, options);
return {
pass,
message: () => message,
};
}
const compareNumbers = (actual, options = {}) => {
if (typeof options.eq === 'number') {
return actual === options.eq;
}
if (typeof options.gte === 'number' && typeof options.lte === 'number') {
return actual >= options.gte && actual <= options.lte;
}
if (typeof options.gte === 'number') {
return actual >= options.gte;
}
if (typeof options.lte === 'number') {
return actual <= options.lte;
}
return false;
};
export const compareText = (actual, expected, { ignoreCase = false, trim = true, containing = false, atStart = false, atEnd = false, atIndex, replace, }) => {
if (typeof actual !== 'string') {
return {
value: actual,
result: false,
};
}
if (trim) {
actual = actual.trim();
}
if (Array.isArray(replace)) {
actual = replaceActual(replace, actual);
}
if (ignoreCase) {
actual = actual.toLowerCase();
if (typeof expected === 'string') {
expected = expected.toLowerCase();
}
else if (isStringContainingMatcher(expected)) {
expected = (expected.toString() === 'StringContaining'
? expect.stringContaining(expected.sample?.toString().toLowerCase())
: expect.not.stringContaining(expected.sample?.toString().toLowerCase()));
}
}
if (isAsymmeyricMatcher(expected)) {
const result = expected.asymmetricMatch(actual);
return {
value: actual,
result
};
}
expected = expected;
if (expected instanceof RegExp) {
return {
value: actual,
result: !!actual.match(expected),
};
}
if (containing) {
return {
value: actual,
result: actual.includes(expected),
};
}
if (atStart) {
return {
value: actual,
result: actual.startsWith(expected),
};
}
if (atEnd) {
return {
value: actual,
result: actual.endsWith(expected),
};
}
if (atIndex) {
return {
value: actual,
result: actual.substring(atIndex, actual.length).startsWith(expected),
};
}
return {
value: actual,
result: actual === expected,
};
};
export const compareTextWithArray = (actual, expectedArray, { ignoreCase = false, trim = false, containing = false, atStart = false, atEnd = false, atIndex, replace, }) => {
if (typeof actual !== 'string') {
return {
value: actual,
result: false,
};
}
if (trim) {
actual = actual.trim();
}
if (Array.isArray(replace)) {
actual = replaceActual(replace, actual);
}
if (ignoreCase) {
actual = actual.toLowerCase();
expectedArray = expectedArray.map((item) => {
if (typeof item === 'string') {
return item.toLowerCase();
}
if (isStringContainingMatcher(item)) {
return (item.toString() === 'StringContaining'
? expect.stringContaining(item.sample?.toString().toLowerCase())
: expect.not.stringContaining(item.sample?.toString().toLowerCase()));
}
return item;
});
}
const textInArray = expectedArray.some((expected) => {
if (expected instanceof RegExp) {
return !!actual.match(expected);
}
if (isAsymmeyricMatcher(expected)) {
return expected.asymmetricMatch(actual);
}
if (containing) {
return actual.includes(expected);
}
if (atStart) {
return actual.startsWith(expected);
}
if (atEnd) {
return actual.endsWith(expected);
}
if (atIndex) {
return actual.substring(atIndex, actual.length).startsWith(expected);
}
return actual === expected;
});
return {
value: actual,
result: textInArray,
};
};
export const compareObject = (actual, expected) => {
if (typeof actual !== 'object' || Array.isArray(actual)) {
return {
value: actual,
result: false,
};
}
return {
value: actual,
result: isEqual(actual, expected),
};
};
export const compareStyle = async (actualEl, style, { ignoreCase = true, trim = false }) => {
let result = true;
const actual = {};
for (const key in style) {
const css = await actualEl.getCSSProperty(key);
let actualVal = css.value;
let expectedVal = style[key];
if (trim) {
actualVal = actualVal.trim();
expectedVal = expectedVal.trim();
}
if (ignoreCase) {
actualVal = actualVal.toLowerCase();
expectedVal = expectedVal.toLowerCase();
}
result = result && actualVal === expectedVal;
actual[key] = css.value;
}
return {
value: actual,
result,
};
};
function aliasFn(fn, { verb, expectation, } = {}, ...args) {
this.verb = verb;
this.expectation = expectation;
return fn.apply(this, args);
}
export { aliasFn, compareNumbers, enhanceError, executeCommand, executeCommandBe, numberError, waitUntil, wrapExpectedWithArray };
function replaceActual(replace, actual) {
const hasMultipleReplacers = replace.every((r) => Array.isArray(r));
const replacers = hasMultipleReplacers
? replace
: [replace];
if (replacers.some((r) => Array.isArray(r) && r.length !== 2)) {
throw new Error('Replacers need to have a searchValue and a replaceValue');
}
for (const replacer of replacers) {
const [searchValue, replaceValue] = replacer;
actual = actual.replace(searchValue, replaceValue);
}
return actual;
}