expect-webdriverio
Version:
WebdriverIO Assertion Library
314 lines (313 loc) • 10.1 kB
JavaScript
import deepEql from 'deep-eql';
import { expect } from 'expect';
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));
export function isJasmineStringAsymmetricMatcher(expected) {
return isAsymmetricMatcher(expected) && 'expected' in expected;
}
export function isAsymmetricMatcher(expected) {
return (typeof expected === 'object' &&
!!expected &&
'asymmetricMatch' in expected &&
!!expected.asymmetricMatch);
}
export function isStringContainingMatcherLike(expected) {
return !!expected && expected.constructor.name === 'StringContaining';
}
export function isInversedStringContainingMatcher(expected) {
return isStringContainingMatcherLike(expected) && expected.inverse === true;
}
export function getStringAsymmetricMatcherValue(expected) {
if ('expected' in expected) {
return expected.expected;
}
else if ('regexp' in expected) {
return expected.regexp;
}
else if ('sample' in expected) {
return expected.sample;
}
throw new Error(`Could not extract value from asymmetric matcher: ${expected}. Please report this issue to the expect-webdriverio maintainers.`);
}
export function getAsymmetricMatcherValue(expected) {
if ('expected' in expected) {
return expected.expected;
}
else if ('expectedObject' in expected) {
return expected.expectedObject;
}
else if ('regexp' in expected) {
return expected.regexp;
}
else if ('sample' in expected) {
return expected.sample;
}
return undefined;
}
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');
}
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, 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, { ...this, verb }, 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 (isStringContainingMatcherLike(expected)) {
const sample = getStringAsymmetricMatcherValue(expected).toString().toLocaleLowerCase();
expected = (isInversedStringContainingMatcher(expected)
? expect.not.stringContaining(sample)
: expect.stringContaining(sample));
}
}
if (isAsymmetricMatcher(expected)) {
const result = expected.asymmetricMatch(actual);
return {
value: actual,
result
};
}
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 (isStringContainingMatcherLike(item)) {
const sample = getStringAsymmetricMatcherValue(item).toString().toLocaleLowerCase();
return (isInversedStringContainingMatcher(item)
? expect.not.stringContaining(sample)
: expect.stringContaining(sample));
}
return item;
});
}
const textInArray = expectedArray.some((expected) => {
if (expected instanceof RegExp) {
return !!actual.match(expected);
}
if (isAsymmetricMatcher(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: deepEql(actual, expected),
};
};
export const compareStyle = async (actualEl, style, { ignoreCase = false, trim = true, containing = false, atStart = false, atEnd = false, atIndex, replace, }) => {
let result = true;
const actual = {};
for (const key in style) {
const css = await actualEl.getCSSProperty(key);
let actualVal = String(css.value || '');
let expectedVal = style[key];
if (trim) {
actualVal = actualVal.trim();
expectedVal = expectedVal.trim();
}
if (ignoreCase) {
actualVal = actualVal.toLowerCase();
expectedVal = expectedVal.toLowerCase();
}
if (containing) {
result = actualVal.includes(expectedVal);
actual[key] = actualVal;
}
else if (atStart) {
result = actualVal.startsWith(expectedVal);
actual[key] = actualVal;
}
else if (atEnd) {
result = actualVal.endsWith(expectedVal);
actual[key] = actualVal;
}
else if (atIndex) {
result = actualVal.substring(atIndex, actualVal.length).startsWith(expectedVal);
actual[key] = actualVal;
}
else if (replace) {
const replacedActual = replaceActual(replace, actualVal);
result = replacedActual === expectedVal;
actual[key] = replacedActual;
}
else {
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;
}