test-real-styles
Version:
utilities to test real styling of dom elements
273 lines (262 loc) • 7.27 kB
JavaScript
// src/launch.ts
import playwright from "playwright";
// src/resolveStyleInput.ts
async function resolveStyleInput(stylesP) {
const styles = await stylesP;
if (typeof styles !== "string") {
return styles;
}
if (styles.match(/^http/)) {
return { url: styles };
} else if (styles.match(/\{/)) {
return { content: styles };
}
return { path: styles };
}
// src/launch.ts
import domToPlaywrightModule from "dom-to-playwright";
// src/getStyles.ts
import kebabCase from "lodash.kebabcase";
// src/normalize.ts
import rgbHex from "rgb-hex";
import namer from "color-namer";
function normalize(value) {
return value.replace(/rgb\([0-9, ]+\)/gi, (match) => {
return `#${rgbHex(match)}`;
}).replace(
/(#([0-9a-f]{6}|[0-9a-f]{3}))([^0-9a-f]|$)/gi,
(_, hex, __, delm) => {
const namedColor = namer(hex).html[0];
return `${(namedColor.distance === 0 ? namedColor.name : hex).toLowerCase()}${delm}`;
}
);
}
// src/extractStyles.ts
function extractStyles(element, { styles, pseudoElt }) {
const computed = window.getComputedStyle(element, pseudoElt);
return styles.reduce((memo, { key, name }) => {
memo[name] = computed.getPropertyValue(key);
return memo;
}, {});
}
// src/getStyles.ts
async function getStyles(page, selector, props, { normalize: normalize2 = normalize, pseudoElt } = {}) {
const styles = props.map((prop) => {
if (typeof prop !== "string") {
throw new Error(`Unexpected prop ${prop} of type ${typeof prop}`);
}
return {
name: prop,
key: kebabCase(prop).toLowerCase()
};
});
const computedStyles = await page.$eval(selector, extractStyles, {
styles,
pseudoElt
});
return Object.keys(computedStyles).reduce((memo, key) => {
memo[key] = normalize2(computedStyles[key], key);
return memo;
}, {});
}
// src/withTimeout.ts
function withTimeout(promisedValue, message = "Timed out after %n", timeout = 1e4) {
return Promise.race([
promisedValue,
new Promise(
(_, rej) => setTimeout(
() => rej(
new Error(
message.replace(
"%n",
timeout >= 1e3 ? `${Math.round(timeout / 1e3)}s` : `${timeout}ms`
)
)
),
timeout
)
)
]);
}
// src/launch.ts
import { createQueue } from "ichschwoer";
var domToPlaywright = (
/* @ts-ignore */
domToPlaywrightModule.__esModule ? (
/* @ts-ignore */
domToPlaywrightModule.default
) : domToPlaywrightModule
);
if (!global.setImmediate) {
global.setImmediate = setTimeout;
}
var PLAYWRIGHT_BROWSERS = [
"chromium",
"firefox",
"webkit"
];
var cache = {};
var DISABLE_TRANSITIONS = `
* {
-moz-transition: none !important;
-webkit-transition: none !important;
transition: none !important;
}`;
function launchPage(browserName, stylesInput) {
if (!cache[browserName]) {
cache[browserName] = withTimeout(
playwright[browserName].launch(),
`Failed to launch ${browserName} in under %n`
);
}
const browserP = cache[browserName];
const contextP = withTimeout(
browserP.then((b) => b.newContext()),
`Failed to create context in ${browserName} in under %n`
);
const pageP = withTimeout(
contextP.then((c) => c.newPage()),
`Failed to create page in ${browserName} in under %n`
);
const dtpP = pageP.then(domToPlaywright);
const globalStylesP = withTimeout(
resolveStyleInput(stylesInput),
`Failed to global resolve style input for ${browserName} in under %n`
);
const q = createQueue(1);
return {
name: browserName,
async getPlaywright() {
return {
browser: await browserP,
context: await contextP,
page: await pageP
};
},
updatePage(element, { transitions = false, styles: pageStylesP } = {}) {
return q.push(async () => {
const { update } = await dtpP;
const page = await pageP;
const styles = await globalStylesP;
await withTimeout(
update(element),
`Failed to update page within ${browserName} in under %n`
);
await withTimeout(
page.addStyleTag(styles),
`Failed to add global styles to page within ${browserName} in under %n`
);
if (pageStylesP) {
const pageStyles = await withTimeout(
resolveStyleInput(pageStylesP),
`Failed to resolve page style input for ${browserName} in under %n`
);
await withTimeout(
page.addStyleTag(pageStyles),
`Failed to add page styles to page within ${browserName} in under %n`
);
}
if (!transitions) {
await withTimeout(
page.addStyleTag({ content: DISABLE_TRANSITIONS }),
`Failed to disable transitions within ${browserName} in under %n`
);
}
});
},
getStyles(element, styles, options) {
return q.push(async () => {
const { select } = await dtpP;
const page = await pageP;
return withTimeout(
getStyles(page, select(element), styles, options),
`Failed to get styles within ${browserName} in under %n`
);
});
},
hover(element) {
return q.push(async () => {
const { select } = await dtpP;
const page = await pageP;
await withTimeout(
page.hover(select(element)),
`Failed to hover element within ${browserName} in under %n`
);
});
},
focus(element) {
return q.push(async () => {
const { select } = await dtpP;
const page = await pageP;
await withTimeout(
page.focus(select(element)),
`Failed to focus element within ${browserName} in under %n`
);
});
}
};
}
async function cleanup() {
const toBeCleared = { ...cache };
for (const key in cache) {
delete cache[key];
}
for (const key in toBeCleared) {
const browser = await toBeCleared[key];
for (const context of browser.contexts()) {
await context.close();
}
await browser.close();
}
}
if (typeof afterAll === "function") {
afterAll(cleanup);
}
// src/getRealStyles.ts
function isDocument(elm) {
return Boolean(elm.body);
}
function getElement(elm) {
return isDocument(elm) ? elm.body : elm;
}
async function getRealStyles({
browser = "chromium",
css,
doc,
element = getElement(doc),
hover,
focus,
getStyles: getStyles2,
transitions,
options,
preparePage
}) {
const sb = launchPage(browser, css);
const { page, context } = await sb.getPlaywright();
await preparePage?.(page);
await sb.updatePage(doc, { transitions });
if (focus) {
await sb.focus(focus);
}
if (hover) {
await sb.hover(hover);
}
const styles = await sb.getStyles(element, getStyles2, options);
await page.close();
await context.close();
return styles;
}
// src/toCss.ts
import kebabCase2 from "lodash.kebabcase";
function toCss(style) {
return Object.keys(style).map((key) => `${kebabCase2(key).toLowerCase()}: ${style[key]};`).join("\n");
}
export {
PLAYWRIGHT_BROWSERS,
cleanup,
getRealStyles,
launchPage as launch,
normalize as normalizeValue,
toCss
};
//# sourceMappingURL=index.mjs.map