domsuite
Version:
Browser testing/automation utilities with async/await
356 lines (350 loc) • 11.7 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
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;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// lib/index.js
var lib_exports = {};
__export(lib_exports, {
BROWSERS: () => BROWSERS,
FetchServer: () => FetchServer,
changeInput: () => changeInput,
clickElement: () => clickElement,
condition: () => condition,
getActiveElement: () => getActiveElement,
getDescendantTextParts: () => getDescendantTextParts,
isBrowser: () => isBrowser,
isVisibleElement: () => isVisibleElement,
mousedownElement: () => mousedownElement,
mouseenterElement: () => mouseenterElement,
mouseleaveElement: () => mouseleaveElement,
nextAnimationFrame: () => nextAnimationFrame,
queryShadowSelectors: () => queryShadowSelectors,
queryShadowSelectorsAll: () => queryShadowSelectorsAll,
retryable: () => retryable,
sendInput: () => sendInput,
sendInputToActiveElement: () => sendInputToActiveElement,
setDefaultWaitMsAfterAction: () => setDefaultWaitMsAfterAction,
sleep: () => sleep,
sleepAfterAction: () => sleepAfterAction
});
module.exports = __toCommonJS(lib_exports);
var import_detect_browser = require("detect-browser");
var import_lodash_unified2 = require("lodash-unified");
// lib/fetch-server.js
var sinon = __toESM(require("sinon"), 1);
var import_lodash_unified = require("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 = (0, import_lodash_unified.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) {
(0, import_lodash_unified.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 !(0, import_lodash_unified2.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 = (0, import_detect_browser.detect)();
return browser.name === browserName;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BROWSERS,
FetchServer,
changeInput,
clickElement,
condition,
getActiveElement,
getDescendantTextParts,
isBrowser,
isVisibleElement,
mousedownElement,
mouseenterElement,
mouseleaveElement,
nextAnimationFrame,
queryShadowSelectors,
queryShadowSelectorsAll,
retryable,
sendInput,
sendInputToActiveElement,
setDefaultWaitMsAfterAction,
sleep,
sleepAfterAction
});