domsuite
Version:
Browser testing/automation utilities with async/await
312 lines (306 loc) • 9.45 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
// lib/index.js
import { detect } from "detect-browser";
import { isEmpty } from "lodash-unified";
// lib/fetch-server.js
import * as sinon from "sinon";
import { isFunction } from "lodash-unified";
var FetchServer = class {
constructor(handlers, { debug = false, errorCallback = null, sort = false } = {}) {
this.handlers = handlers;
this.debug = debug;
this.errorCallback = errorCallback;
this.sort = sort;
}
handle(url, params) {
const routes = Object.entries(this.handlers);
if (this.sort) {
routes.sort(([aRoute], [bRoute]) => {
if (bRoute.length === aRoute.length) {
return bRoute.toLowerCase() < aRoute.toLowerCase() ? 1 : -1;
} else {
return bRoute.length - aRoute.length;
}
});
}
for (const [route, handler] of routes) {
if (url.includes(route)) {
if (params && params.body) {
try {
params = JSON.parse(params.body);
} catch (err) {
params = params.body;
}
}
let response = isFunction(handler) ? handler(url, params) : handler;
if (!response.then) {
const responseOptions = {
status: 200,
headers: {
"Content-Type": `application/json`
}
};
response = Promise.resolve(new Response(JSON.stringify(response), responseOptions));
}
this.debugLog({ url, params, response });
return response;
}
}
throw new Error(`Unexpected fetch: ${url} params: ${JSON.stringify(params)}`);
}
start() {
const fakeFetch = (url, params) => {
try {
return this.handle(url, params);
} catch (err) {
isFunction(this.errorCallback) && this.errorCallback(err);
throw err;
}
};
this.restore();
if (typeof window !== `undefined` && window.fetch && !Object.hasOwnProperty.call(window.fetch, `restore`)) {
sinon.stub(window, `fetch`).callsFake(fakeFetch);
}
if (typeof global !== `undefined` && global.fetch && !Object.hasOwnProperty.call(global.fetch, `restore`)) {
sinon.stub(global, `fetch`).callsFake(fakeFetch);
}
}
static restore() {
if (typeof window !== `undefined` && window.fetch && Object.hasOwnProperty.call(window.fetch, `restore`)) {
window.fetch.restore();
}
if (typeof global !== `undefined` && global.fetch && Object.hasOwnProperty.call(global.fetch, `restore`)) {
global.fetch.restore();
}
}
restore() {
FetchServer.restore();
}
debugLog({ url, params, response }) {
if (this.debug) {
const responseObjectPromise = response.then((r) => r.clone());
const responseBodyPromise = responseObjectPromise.then((clonedResponse) => clonedResponse.json());
Promise.all([responseObjectPromise, responseBodyPromise]).then(([response2, responseBody]) => {
console.groupCollapsed(`[fetch-server] ${url}`);
console.groupCollapsed(`Params`);
console.log(JSON.stringify(params, null, 2));
console.groupEnd();
console.groupCollapsed(`Response`);
console.log(JSON.stringify(response2, null, 2));
console.groupEnd();
console.group(`Response Body`);
console.log(JSON.stringify(responseBody, null, 2));
console.groupEnd();
console.groupEnd();
});
}
}
};
// lib/index.js
function queryShadowSelectorsAll(rootEl, selectors) {
if (!rootEl) {
return [];
}
let currEl = rootEl;
let elements = [];
selectors.some((selector, idx) => {
currEl = currEl.shadowRoot;
if (!currEl) {
return true;
}
if (idx !== selectors.length - 1) {
currEl = currEl.querySelector(selector);
} else {
elements = Array.from(currEl.querySelectorAll(selector));
}
});
return elements;
}
function queryShadowSelectors(rootEl, selectors) {
const el = queryShadowSelectorsAll(rootEl, selectors);
return !isEmpty(el) ? el[0] : null;
}
function getActiveElement() {
let activeEl = document.activeElement;
while (activeEl && activeEl.shadowRoot) {
activeEl = activeEl.shadowRoot.activeElement;
}
return activeEl;
}
function isVisibleElement(el) {
return !!(el && el.offsetParent);
}
function getDescendantTextParts(el) {
const textParts = [];
const treeWalker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
let currNode = treeWalker.nextNode();
while (currNode) {
textParts.push(currNode.textContent);
currNode = treeWalker.nextNode();
}
return textParts;
}
var defaultWaitMsAfterAction = 50;
function setDefaultWaitMsAfterAction(waitMs) {
defaultWaitMsAfterAction = waitMs;
}
async function nextAnimationFrame() {
return new Promise(requestAnimationFrame);
}
async function sleep(durationMs) {
return new Promise((resolve) => setTimeout(resolve, durationMs));
}
async function sleepAfterAction(waitMs = defaultWaitMsAfterAction) {
await nextAnimationFrame();
await sleep(waitMs);
}
async function retryable(retryableFunc, { maxWaitMs = 1e3 } = {}) {
const startTimestamp = new Date().getTime();
do {
let error = null;
try {
return await retryableFunc();
} catch (e) {
error = e;
}
const elapsedTime = new Date().getTime() - startTimestamp;
if (elapsedTime > maxWaitMs) {
const errorMessage = `Retryable did not complete in allocated time:
${error.stack}
Failing function:
${retryableFunc.toString()}
`;
console.error(errorMessage);
throw new Error(errorMessage);
}
await sleep(25);
} while (true);
}
async function condition(predicate, { maxWaitMs = 1e3 } = {}) {
const startTimestamp = new Date().getTime();
while (true) {
let error = null;
try {
const val = await predicate();
if (val) {
return val;
}
} catch (e) {
error = e;
}
const elapsedTime = new Date().getTime() - startTimestamp;
if (elapsedTime > maxWaitMs) {
const errorMessage = `Condition was not met in allocated time:
${error && error.stack}
Failing function:
${predicate.toString()}
`;
console.error(errorMessage);
throw new Error(errorMessage);
}
await sleep(25);
}
}
async function mouseenterElement(el, { waitMs = defaultWaitMsAfterAction } = {}) {
el.dispatchEvent(new MouseEvent(`mouseenter`, {
composed: true
}));
await sleepAfterAction(waitMs);
}
async function mouseleaveElement(el, { waitMs = defaultWaitMsAfterAction } = {}) {
el.dispatchEvent(new MouseEvent(`mouseleave`, {
composed: true
}));
await sleepAfterAction(waitMs);
}
async function mousedownElement(el, { waitMs = defaultWaitMsAfterAction } = {}) {
el.dispatchEvent(new MouseEvent(`mousedown`, {
bubbles: true,
composed: true
}));
await sleepAfterAction(waitMs);
}
async function clickElement(el, _a = {}) {
var _b = _a, { waitMs = defaultWaitMsAfterAction } = _b, mouseEventAttrs = __objRest(_b, ["waitMs"]);
el.dispatchEvent(new MouseEvent(`click`, __spreadValues({ bubbles: true }, mouseEventAttrs)));
await sleepAfterAction(waitMs);
}
async function changeInput(inputEl, value, { waitMs = defaultWaitMsAfterAction } = {}) {
inputEl.value = value;
inputEl.dispatchEvent(new Event(`change`, {
bubbles: true,
composed: true
}));
await sleepAfterAction(waitMs);
}
async function sendInput(inputEl, value, { waitMs = defaultWaitMsAfterAction } = {}) {
inputEl.value = value;
inputEl.dispatchEvent(new Event(`input`, {
bubbles: true,
composed: true
}));
await sleepAfterAction(waitMs);
}
async function sendInputToActiveElement(value, { waitMs = defaultWaitMsAfterAction } = {}) {
await sendInput(getActiveElement(), value, { waitMs });
}
var BROWSERS = {
CHROME: `chrome`,
FIREFOX: `firefox`,
SAFARI: `safari`,
EDGE: `edge`
};
function isBrowser(browserName) {
const browser = detect();
return browser.name === browserName;
}
export {
BROWSERS,
FetchServer,
changeInput,
clickElement,
condition,
getActiveElement,
getDescendantTextParts,
isBrowser,
isVisibleElement,
mousedownElement,
mouseenterElement,
mouseleaveElement,
nextAnimationFrame,
queryShadowSelectors,
queryShadowSelectorsAll,
retryable,
sendInput,
sendInputToActiveElement,
setDefaultWaitMsAfterAction,
sleep,
sleepAfterAction
};