@applitools/dom-utils
Advanced tools
Comparing version 1.0.0 to 1.1.0
@@ -7,8 +7,7 @@ 'use strict'; | ||
const url = require('url'); | ||
const isAbsoluteUrl = require('is-absolute-url'); | ||
const axios = require('axios'); | ||
const cssParser = require('css'); | ||
const shadyCss = require('shady-css-parser'); | ||
const cssUrlParser = require('css-url-parser'); | ||
const { Location, PerformanceUtils } = require('@applitools/eyes.sdk.core'); | ||
const { Location, GeneralUtils, PerformanceUtils } = require('@applitools/eyes.sdk.core'); | ||
@@ -28,2 +27,6 @@ class DomCapture { | ||
constructor() { | ||
this._frameBundledCssPromises = []; | ||
} | ||
/** | ||
@@ -42,3 +45,3 @@ * @param {Logger} logger A Logger instance. | ||
const dom = await this.getWindowDom(logger, driver); | ||
const dom = await DomCapture.getWindowDom(logger, driver); | ||
@@ -57,3 +60,3 @@ if (positionProvider) { | ||
*/ | ||
static getWindowDom(logger, driver) { | ||
static async getWindowDom(logger, driver) { | ||
const argsObj = { | ||
@@ -72,10 +75,2 @@ styleProps: [ | ||
attributeProps: null, | ||
/* | ||
[ | ||
{all: ["id", "class"]}, | ||
{IMG: ["src"]}, | ||
{IFRAME: ["src"]}, | ||
{A: ["href"]}, | ||
] | ||
*/ | ||
rectProps: [ | ||
@@ -87,6 +82,15 @@ 'width', | ||
], | ||
ignoredTagNames: ['HEAD', 'SCRIPT'], | ||
ignoredTagNames: [ | ||
'HEAD', | ||
'SCRIPT', | ||
], | ||
}; | ||
return DomCapture._getFrameDom(logger, driver, argsObj); | ||
const json = await driver.executeScript(DomCapture.CAPTURE_FRAME_SCRIPT, argsObj); | ||
const domTree = JSON.parse(json); | ||
const domCapture = new DomCapture(); | ||
await domCapture._getFrameDom(logger, driver, { childNodes: [domTree], tagName: 'OUTER_HTML' }); | ||
await Promise.all(domCapture._frameBundledCssPromises); | ||
return domTree; | ||
} | ||
@@ -97,80 +101,55 @@ | ||
* @param {EyesWebDriver} driver | ||
* @param {object} argsObj | ||
* @param {object} domTree | ||
* @return {Promise.<object>} | ||
* @private | ||
*/ | ||
static async _getFrameDom(logger, driver, argsObj) { | ||
try { | ||
const json = await driver.executeScript(DomCapture.CAPTURE_FRAME_SCRIPT, argsObj); | ||
const currentUrl = await driver.getCurrentUrl(); | ||
async _getFrameDom(logger, driver, domTree) { | ||
const tagName = domTree.tagName; | ||
const domTree = JSON.parse(json); | ||
if (!tagName) { | ||
return; | ||
} | ||
await DomCapture._traverseDomTree(logger, driver, argsObj, domTree, -1, currentUrl); | ||
let frameIndex = 0; | ||
return domTree; | ||
} catch (e) { | ||
throw new Error(`Error: ${e}`); | ||
} | ||
await this._loop(logger, driver, domTree, async domSubTree => { | ||
await driver.switchTo().frame(frameIndex); | ||
await this._getFrameDom(logger, driver, domSubTree); | ||
await driver.switchTo().parentFrame(); | ||
frameIndex += 1; | ||
}); | ||
} | ||
/** | ||
* @param {Logger} logger | ||
* @param {EyesWebDriver} driver | ||
* @param {object} argsObj | ||
* @param {object} domTree | ||
* @param {number} frameIndex | ||
* @param {string} baseUri | ||
* @param {function} fn | ||
* @return {Promise<void>} | ||
* @private | ||
*/ | ||
static async _traverseDomTree(logger, driver, argsObj, domTree, frameIndex, baseUri) { | ||
if (!domTree.tagName) { | ||
async _loop(logger, driver, domTree, fn) { | ||
const childNodes = domTree.childNodes; | ||
if (!childNodes) { | ||
return; | ||
} | ||
const tagNameObj = domTree.tagName; | ||
const iterateChildNodes = async nodeChilds => { | ||
for (const node of nodeChilds) { | ||
if (node && node.tagName.toUpperCase() === 'IFRAME') { | ||
await fn(node); | ||
} else { | ||
if (node && node.tagName.toUpperCase() === 'HTML') { | ||
await this._getFrameBundledCss(logger, driver, node); | ||
} | ||
if (frameIndex > -1) { | ||
let timeStart = PerformanceUtils.start(); | ||
await driver.switchTo().frame(frameIndex); | ||
logger.verbose(`switching to frame took ${timeStart.end().summary}`); | ||
timeStart = PerformanceUtils.start(); | ||
const json = await driver.executeScript(DomCapture.CAPTURE_FRAME_SCRIPT, argsObj); | ||
logger.verbose(`executing javascript to capture frame's script took ${timeStart.end().summary}`); | ||
const dom = JSON.parse(json); | ||
domTree.childNodes = dom; | ||
let srcUrl = null; | ||
if (domTree.attributes) { | ||
const attrsNodeObj = domTree.attributes; | ||
const attrsNode = attrsNodeObj; | ||
if (attrsNode.src) { | ||
const srcUrlObj = attrsNode.src; | ||
srcUrl = srcUrlObj.toString(); | ||
if (node.childNodes) { | ||
await iterateChildNodes(node.childNodes); | ||
} | ||
} | ||
} | ||
if (srcUrl == null) { | ||
logger.verbose('WARNING! IFRAME WITH NO SRC'); | ||
} | ||
}; | ||
const srcUri = url.resolve(baseUri, srcUrl); | ||
await DomCapture._traverseDomTree(logger, driver, argsObj, dom, -1, srcUri); | ||
timeStart = PerformanceUtils.start(); | ||
await driver.switchTo().parentFrame(); | ||
logger.verbose(`switching to parent frame took ${timeStart.end().summary}`); | ||
} | ||
const tagName = tagNameObj; | ||
const isHTML = tagName.toUpperCase() === 'HTML'; | ||
if (isHTML) { | ||
const css = await DomCapture.getFrameBundledCss(logger, driver, baseUri); | ||
domTree.css = css; | ||
} | ||
await DomCapture._loop(logger, driver, argsObj, domTree, baseUri); | ||
await iterateChildNodes(childNodes); | ||
} | ||
@@ -181,38 +160,19 @@ | ||
* @param {EyesWebDriver} driver | ||
* @param {object} argsObj | ||
* @param {object} domTree | ||
* @param {string} baseUri | ||
* @param node | ||
* @return {Promise<void>} | ||
* @private | ||
*/ | ||
static async _loop(logger, driver, argsObj, domTree, baseUri) { | ||
if (!domTree.childNodes) { | ||
return; | ||
async _getFrameBundledCss(logger, driver, node) { | ||
const currentUrl = await driver.getCurrentUrl(); | ||
if (!GeneralUtils.isAbsoluteUrl(currentUrl)) { | ||
logger.verbose('WARNING! Base URL is not an absolute URL!'); | ||
} | ||
const childNodes = domTree.childNodes; | ||
let index = 0; | ||
const timeStart = PerformanceUtils.start(); | ||
for (const node of childNodes) { | ||
const domSubTree = node; | ||
if (domSubTree) { | ||
const tagName = domSubTree.tagName; | ||
const isIframe = tagName.toUpperCase() === 'IFRAME'; | ||
const result = await driver.executeScript(DomCapture.CAPTURE_CSSOM_SCRIPT); | ||
logger.verbose(`executing javascript to capture css took ${timeStart.end().summary}`); | ||
if (isIframe) { | ||
await DomCapture._traverseDomTree(logger, driver, argsObj, domSubTree, index, baseUri); | ||
index += 1; | ||
} else { | ||
const childSubNodesObj = domSubTree.childNodes; | ||
if (!childSubNodesObj || childSubNodesObj.length === 0) { | ||
continue; | ||
} | ||
await DomCapture._traverseDomTree(logger, driver, argsObj, domSubTree, -1, baseUri); | ||
return; | ||
} | ||
} | ||
} | ||
logger.verbose(`looping through ${childNodes.length} child nodes (out of which ${index} inner iframes) took ${timeStart.end().summary}`); | ||
const promise = this._processFrameBundledCss(logger, driver, currentUrl, node, result); | ||
this._frameBundledCssPromises.push(promise); | ||
} | ||
@@ -223,16 +183,12 @@ | ||
* @param {EyesWebDriver} driver | ||
* @param {string} baseUri | ||
* @return {Promise<string>} | ||
* @param {string} currentUrl | ||
* @param node | ||
* @param result | ||
* @return {Promise<void>} | ||
* @private | ||
*/ | ||
static async getFrameBundledCss(logger, driver, baseUri) { | ||
if (!isAbsoluteUrl(baseUri)) { | ||
logger.verbose('WARNING! Base URL is not an absolute URL!'); | ||
} | ||
async _processFrameBundledCss(logger, driver, currentUrl, node, result) { | ||
let sb = ''; | ||
let timeStart = PerformanceUtils.start(); | ||
const result = await driver.executeScript(DomCapture.CAPTURE_CSSOM_SCRIPT); | ||
logger.verbose(`executing javascript to capture css took ${timeStart.end().summary}`); | ||
for (const item of result) { | ||
timeStart = PerformanceUtils.start(); | ||
let timeStart = PerformanceUtils.start(); | ||
const kind = item.substring(0, 5); | ||
@@ -243,7 +199,7 @@ const value = item.substring(5); | ||
if (kind === 'text:') { | ||
css = await DomCapture._parseAndSerializeCss(logger, baseUri, value); | ||
css = await this._parseAndSerializeCss(logger, currentUrl, value); | ||
} else { | ||
css = await DomCapture._downloadCss(logger, baseUri, value); | ||
css = await this._downloadCss(logger, currentUrl, value); | ||
} | ||
css = await DomCapture._parseAndSerializeCss(logger, baseUri, css); | ||
css = await this._parseAndSerializeCss(logger, currentUrl, css); | ||
timeStart = PerformanceUtils.start(); | ||
@@ -253,3 +209,4 @@ sb += css; | ||
} | ||
return sb; | ||
node.css = sb; | ||
} | ||
@@ -264,8 +221,14 @@ | ||
*/ | ||
static async _parseAndSerializeCss(logger, baseUri, css) { | ||
async _parseAndSerializeCss(logger, baseUri, css) { | ||
const timeStart = PerformanceUtils.start(); | ||
const stylesheet = cssParser.parse(css); | ||
let stylesheet; | ||
try { | ||
const parser = new shadyCss.Parser(); | ||
stylesheet = parser.parse(css); | ||
} catch (ignored) { | ||
return ''; | ||
} | ||
logger.verbose(`parsing CSS string took ${timeStart.end().summary}`); | ||
css = await DomCapture._serializeCss(logger, baseUri, stylesheet.stylesheet); | ||
css = await this._serializeCss(logger, baseUri, stylesheet); | ||
return css; | ||
@@ -281,3 +244,3 @@ } | ||
*/ | ||
static async _serializeCss(logger, baseUri, stylesheet) { | ||
async _serializeCss(logger, baseUri, stylesheet) { | ||
const timeStart = PerformanceUtils.start(); | ||
@@ -289,11 +252,11 @@ let sb = ''; | ||
let addAsIs = true; | ||
if (ruleSet.type === 'import') { | ||
if (ruleSet.name === 'import') { | ||
logger.verbose('encountered @import rule'); | ||
const href = cssUrlParser(ruleSet.import); | ||
css = await DomCapture._downloadCss(logger, baseUri, href[0]); | ||
const href = cssUrlParser(ruleSet.parameters); | ||
css = await this._downloadCss(logger, baseUri, href[0]); | ||
css = css.trim(); | ||
logger.verbose('imported CSS (whitespaces trimmed) length: {0}', css.length); | ||
logger.verbose(`imported CSS (whitespaces trimmed) length: ${css.length}`); | ||
addAsIs = css.length === 0; | ||
if (!addAsIs) { | ||
css = await DomCapture._parseAndSerializeCss(logger, baseUri, css); | ||
css = await this._parseAndSerializeCss(logger, baseUri, css); | ||
sb += css; | ||
@@ -305,10 +268,6 @@ } | ||
const node = { | ||
stylesheet: { | ||
rules: [ruleSet], | ||
}, | ||
rules: [ruleSet], | ||
}; | ||
sb += cssParser.stringify(node, { compress: true }); | ||
// sb += ruleSet.toString(); | ||
// sb += css; | ||
const stringifier = new shadyCss.Stringifier(); | ||
sb += stringifier.stringify(ruleSet); | ||
} | ||
@@ -329,8 +288,8 @@ } | ||
*/ | ||
static async _downloadCss(logger, baseUri, value, retriesCount = 1) { | ||
async _downloadCss(logger, baseUri, value, retriesCount = 1) { | ||
try { | ||
logger.verbose('Given URL to download: {0}', value); | ||
logger.verbose(`Given URL to download: ${value}`); | ||
// let href = cssParser.parse(value); | ||
let href = value; | ||
if (!isAbsoluteUrl(href)) { | ||
if (!GeneralUtils.isAbsoluteUrl(href)) { | ||
href = url.resolve(baseUri, href.toString()); | ||
@@ -347,4 +306,4 @@ } | ||
if (retriesCount > 0) { | ||
retriesCount--; | ||
return DomCapture._downloadCss(logger, baseUri, value, retriesCount); | ||
retriesCount -= 1; | ||
return this._downloadCss(logger, baseUri, value, retriesCount); | ||
} | ||
@@ -351,0 +310,0 @@ return ''; |
{ | ||
"name": "@applitools/dom-utils", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Applitools DOM Utils is a shared utility package", | ||
@@ -27,13 +27,12 @@ "keywords": [ | ||
"dependencies": { | ||
"@applitools/eyes.sdk.core": "^2.2.1", | ||
"@applitools/eyes.sdk.core": "^2.2.2", | ||
"axios": "^0.18.0", | ||
"css": "^2.2.4", | ||
"css-url-parser": "^1.1.3", | ||
"is-absolute-url": "^2.1.0" | ||
"shady-css-parser": "^0.1.0" | ||
}, | ||
"devDependencies": { | ||
"chromedriver": "^2.43.1", | ||
"dateformat": "^3.0.3", | ||
"mocha": "^5.2.0", | ||
"dateformat": "^3.0.3", | ||
"selenium-webdriver": "^3.6.0", | ||
"chromedriver": "^2.42.0" | ||
"selenium-webdriver": "^4.0.0-alpha.1" | ||
}, | ||
@@ -40,0 +39,0 @@ "scripts": { |
4
27408
357
+ Addedshady-css-parser@^0.1.0
+ Addedshady-css-parser@0.1.0(transitive)
- Removedcss@^2.2.4
- Removedis-absolute-url@^2.1.0
- Removedatob@2.1.2(transitive)
- Removedcss@2.2.4(transitive)
- Removeddecode-uri-component@0.2.2(transitive)
- Removedinherits@2.0.4(transitive)
- Removedis-absolute-url@2.1.0(transitive)
- Removedresolve-url@0.2.1(transitive)
- Removedsource-map@0.6.1(transitive)
- Removedsource-map-resolve@0.5.3(transitive)
- Removedsource-map-url@0.4.1(transitive)
- Removedurix@0.1.0(transitive)