@applitools/eyes.cypress
Advanced tools
Comparing version 1.2.3 to 1.2.4
{ | ||
"name": "@applitools/eyes.cypress", | ||
"version": "1.2.3", | ||
"version": "1.2.4", | ||
"main": "index.js", | ||
"license": "MIT", | ||
"scripts": { | ||
"test:mocha": ": ${APPLITOOLS_API_KEY:?} && mocha --no-timeouts 'tests/**/*.test.js'", | ||
"test:mocha": "mocha --no-timeouts 'tests/**/*.test.js'", | ||
"eslint": "eslint '**/*.js'", | ||
@@ -44,4 +44,5 @@ "test": "npm run test:mocha && npm run eslint", | ||
"nock": "^9.2.6", | ||
"prettier": "^1.13.0" | ||
"prettier": "^1.13.0", | ||
"puppeteer": "^1.5.0" | ||
} | ||
} |
@@ -9,4 +9,4 @@ /* global fetch, Cypress, cy */ | ||
const EyesServer = { | ||
open(url, appName, testName, viewportSize) { | ||
return this._send('open', {url, appName, testName, viewportSize}); | ||
open(url, appName, testName, viewportSize, isVerbose) { | ||
return this._send('open', {url, appName, testName, viewportSize, isVerbose}); | ||
}, | ||
@@ -30,6 +30,6 @@ | ||
Cypress.Commands.add('eyesOpen', (appName, testName, viewportSize) => { | ||
Cypress.Commands.add('eyesOpen', (appName, testName, viewportSize, isVerbose) => { | ||
Cypress.log({name: 'Eyes: open'}); | ||
return cy.window({log: false}).then(win => { | ||
return EyesServer.open(win.location.href, appName, testName, viewportSize); | ||
return EyesServer.open(win.location.href, appName, testName, viewportSize, isVerbose); | ||
}); | ||
@@ -36,0 +36,0 @@ }); |
@@ -10,5 +10,12 @@ require('dotenv').config(); | ||
/*****************************/ | ||
/******* Eyes API key *******/ | ||
/*****************************/ | ||
const apiKey = process.env.APPLITOOLS_API_KEY; | ||
if (!apiKey) { | ||
throw new Error('APPLITOOLS_API_KEY env variable is not defined'); | ||
} | ||
/*****************************/ | ||
/******* Eyes Commands *******/ | ||
/*****************************/ | ||
const apiKey = process.env.APPLITOOLS_API_KEY; | ||
let checkWindow, close; | ||
@@ -15,0 +22,0 @@ |
@@ -1,4 +0,35 @@ | ||
const {uniq} = require('lodash'); | ||
'use strict'; | ||
module.exports = el => { | ||
function extractResources(el) { | ||
function extractResourcesFromStyleSheet(styleSheet) { | ||
const resourceUrls = [...styleSheet.cssRules].reduce((acc, rule) => { | ||
if (isRuleOfType(rule, 'CSSImportRule')) { | ||
return acc.concat(rule.href); | ||
} else if (isRuleOfType(rule, 'CSSFontFaceRule')) { | ||
return acc.concat(getUrlFromCssText(rule.style.getPropertyValue('src'))); | ||
} else if (isRuleOfType(rule, 'CSSStyleRule')) { | ||
for (let i = 0, ii = rule.style.length; i < ii; i++) { | ||
const url = getUrlFromCssText(rule.style.getPropertyValue(rule.style[i])); | ||
url && acc.push(url); | ||
} | ||
} | ||
return acc; | ||
}, []); | ||
return [...new Set(resourceUrls)]; | ||
} | ||
// NOTE: this is also implemented on the server side (copy pasted to enable unit testing `extractResources` with puppeteer) | ||
function getUrlFromCssText(cssText) { | ||
const match = cssText.match(/url\((?!['"]?(?:data|http):)['"]?([^'"\)]*)['"]?\)/); | ||
return match ? match[1] : match; | ||
} | ||
function isRuleOfType(rule, ruleType) { | ||
return rule instanceof rule.parentStyleSheet.ownerNode.ownerDocument.defaultView[ruleType]; | ||
} | ||
function uniq(arr) { | ||
return Array.from(new Set(arr)); | ||
} | ||
const srcUrls = [...el.querySelectorAll('img[src]')].map(srcEl => srcEl.getAttribute('src')); | ||
@@ -10,3 +41,14 @@ | ||
return uniq([...srcUrls, ...cssUrls]); | ||
}; | ||
const urlsFromStyleElements = [...el.getElementsByTagName('style')] | ||
.map(styleEl => styleEl.sheet) | ||
.reduce((acc, curr) => { | ||
console.log('acc', acc); | ||
const resourceUrls = extractResourcesFromStyleSheet(curr); | ||
console.log('resources', resourceUrls); | ||
return acc.concat(resourceUrls); | ||
}, []); | ||
return uniq([...srcUrls, ...cssUrls, ...urlsFromStyleElements]); | ||
} | ||
module.exports = extractResources; |
@@ -1,70 +0,8 @@ | ||
const fetch = require('node-fetch'); | ||
'use strict'; | ||
const {keyBy} = require('lodash'); | ||
const {parse, CSSImportRule, CSSStyleRule, CSSFontFaceRule} = require('cssom'); | ||
const {URL} = require('url'); | ||
const fetchResource = require('./fetchResource'); | ||
function getUrlFromCssText(cssText) { | ||
const match = cssText.match(/url\((?!['"]?(?:data|http):)['"]?([^'"\)]*)['"]?\)/); | ||
return match ? match[1] : match; | ||
} | ||
function absolutizeUrl(url, absoluteUrl) { | ||
return new URL(url, absoluteUrl).href; | ||
} | ||
function extractResourcesFromStyleSheet(styleSheet, absoluteUrl) { | ||
const resourceUrls = [...styleSheet.cssRules].reduce((acc, rule) => { | ||
if (rule instanceof CSSImportRule) { | ||
return acc.concat(absolutizeUrl(rule.href, absoluteUrl)); | ||
} else if (rule instanceof CSSFontFaceRule) { | ||
return acc.concat( | ||
absolutizeUrl(getUrlFromCssText(rule.style.getPropertyValue('src')), absoluteUrl), | ||
); | ||
} else if (rule instanceof CSSStyleRule) { | ||
for (let i = 0, ii = rule.style.length; i < ii; i++) { | ||
const url = getUrlFromCssText(rule.style.getPropertyValue(rule.style[i])); | ||
url && acc.push(absolutizeUrl(url, absoluteUrl)); | ||
} | ||
} | ||
return acc; | ||
}, []); | ||
return [...new Set(resourceUrls)]; | ||
} | ||
function extractCssResources(cssText, absoluteUrl) { | ||
const styleSheet = parse(cssText); | ||
return extractResourcesFromStyleSheet(styleSheet, absoluteUrl); | ||
} | ||
function fetchResources(urls) { | ||
function doFetch(resourceUrls) { | ||
console.log('fetching ', resourceUrls); | ||
return Promise.all( | ||
resourceUrls.map(url => | ||
fetch(url).then(resp => | ||
resp.buffer().then(buff => { | ||
const contentType = resp.headers.get('Content-Type'); | ||
resources.push({ | ||
url, | ||
type: contentType, | ||
value: buff, | ||
}); | ||
if (/text\/css/.test(contentType)) { | ||
return doFetch(extractCssResources(buff.toString(), url)); | ||
} else { | ||
return true; | ||
} | ||
}), | ||
), | ||
), | ||
); | ||
} | ||
const resources = []; | ||
return doFetch(urls).then(() => { | ||
console.log('done fetching'); | ||
return keyBy(resources, 'url'); | ||
}); | ||
} | ||
module.exports = fetchResources; | ||
module.exports = urls => { | ||
const promises = urls.map(fetchResource); | ||
return Promise.all(promises).then(resourceContents => keyBy(resourceContents, 'url')); | ||
}; |
@@ -1,3 +0,4 @@ | ||
const fetchResources = require('./fetchResources'); | ||
const log = require('./log'); | ||
'use strict'; | ||
const fetchResource = require('./fetchResource'); | ||
const extractCssResources = require('./extractCssResources'); | ||
const {mapValues} = require('lodash'); | ||
@@ -7,4 +8,25 @@ const {RGridResource} = require('@applitools/eyes.sdk.core'); | ||
// NOTE: `let` and not `const` because of tests | ||
let allResources = {}; | ||
let allResources = {_cache: {}}; | ||
allResources.add = (entry, dependencies) => { | ||
allResources._cache[entry.url] = Object.assign({dependencies}, entry); | ||
}; | ||
allResources.getWithDependencies = key => { | ||
function doGet(_key) { | ||
const entry = allResources._cache[_key]; | ||
if (!entry) return; | ||
const ret = {}; | ||
ret[_key] = entry; // TODO omit dependencies | ||
if (entry.dependencies) { | ||
entry.dependencies.forEach(dep => { | ||
Object.assign(ret, doGet(dep)); | ||
}); | ||
} | ||
return ret; | ||
} | ||
return doGet(key); | ||
}; | ||
function fromCacheToRGridResource({url, type, hash}) { | ||
@@ -34,20 +56,29 @@ const resource = new RGridResource(); | ||
async function getAllResources(absoluteUrls = []) { | ||
async function getOrFetchResources(resourceUrls, cache) { | ||
const resources = {}; | ||
for (const url of absoluteUrls) { | ||
const cacheEntry = allResources[url]; | ||
const missingResourceUrls = []; | ||
for (const url of resourceUrls) { | ||
const cacheEntry = cache.getWithDependencies(url); | ||
if (cacheEntry) { | ||
resources[url] = fromCacheToRGridResource(cacheEntry); | ||
Object.assign(resources, mapValues(cacheEntry, fromCacheToRGridResource)); | ||
} else { | ||
missingResourceUrls.push(url); | ||
} | ||
} | ||
const missingResourceUrls = absoluteUrls.filter(resourceUrl => !allResources[resourceUrl]); | ||
if (missingResourceUrls.length) { | ||
log(`fetching missing resources: ${missingResourceUrls}`); | ||
const fetchedResources = await fetchResources(missingResourceUrls); | ||
const fetchedResourcesToReturn = mapValues(fetchedResources, fromFetchedToRGridResource); | ||
const fetchedResourcesForCache = mapValues(fetchedResourcesToReturn, toCacheEntry); | ||
Object.assign(allResources, fetchedResourcesForCache); // add to cache without the buffer | ||
Object.assign(resources, fetchedResourcesToReturn); | ||
} | ||
await Promise.all( | ||
missingResourceUrls.map(url => | ||
fetchResource(url).then(async resource => { | ||
let dependentResources; | ||
if (/text\/css/.test(resource.type)) { | ||
dependentResources = extractCssResources(resource.value.toString(), url); | ||
const fetchedResources = await getOrFetchResources(dependentResources, cache); | ||
Object.assign(resources, fetchedResources); | ||
} | ||
const rGridResource = fromFetchedToRGridResource(resource); | ||
resources[url] = rGridResource; | ||
cache.add(toCacheEntry(rGridResource), dependentResources); | ||
}), | ||
), | ||
); | ||
@@ -57,7 +88,11 @@ return resources; | ||
async function getAllResources(absoluteUrls = []) { | ||
return await getOrFetchResources(absoluteUrls, allResources); | ||
} | ||
// NOTE: ugly, because of tests. Other alternative is to export a "createGetAllResources" which would initialize the cache. | ||
getAllResources.clearCache = () => { | ||
allResources = {}; | ||
allResources._cache = {}; | ||
}; | ||
module.exports = getAllResources; |
const EyesWrapper = require('./EyesWrapper'); | ||
const getAllResources = require('./getAllResources'); | ||
const getRenderStatus = require('./getRenderStatus'); | ||
const waitForRenderedStatus = require('./waitForRenderedStatus'); | ||
const {URL} = require('url'); | ||
@@ -40,3 +40,3 @@ // const saveData = require('../troubleshoot/saveData'); | ||
const screenshotUrl = await getRenderStatus(renderId, wrapper); | ||
const screenshotUrl = await waitForRenderedStatus(renderId, wrapper); | ||
@@ -43,0 +43,0 @@ return {screenshotUrl, tag}; |
@@ -76,2 +76,3 @@ // 'use strict'; | ||
if (!domNodes) return ''; | ||
return renderCdtDomNode(domNodes, 0); | ||
@@ -117,2 +118,4 @@ } | ||
if (!domNodes || !domNodes.length) return domNodes; | ||
const newDomNodes = [...domNodes]; | ||
@@ -119,0 +122,0 @@ |
@@ -14,3 +14,7 @@ 'use strict'; | ||
const path = resolve(__dirname, '../../../.applitools', renderId); // TODO production path | ||
await mkdir(path); | ||
try { | ||
await mkdir(path); | ||
} catch (ex) { | ||
log(`${path} already exists`); | ||
} | ||
writeFile(resolve(path, 'cdt.json'), JSON.stringify(cdt)); | ||
@@ -24,4 +28,6 @@ const absolutizedCdt = createAbsolutizedDomNodes(cdt, resources, url); | ||
if (content) { | ||
log(`saving resource: ${resourceUrl}`); | ||
return writeFile(resolve(path, getResourceName(resource)), content); | ||
} else { | ||
log(`NOT saving resource (missing content): ${resourceUrl}`); | ||
return Promise.resolve(); | ||
@@ -28,0 +34,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32642
21
813
12