UNPKG

domsuite

Version:

Browser testing/automation utilities with async/await

356 lines (350 loc) 11.7 kB
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 });