Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@applitools/eyes.cypress

Package Overview
Dependencies
Maintainers
12
Versions
141
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@applitools/eyes.cypress - npm Package Compare versions

Comparing version 1.4.2 to 1.5.0

src/render-grid/sdk/absolutizeUrl.js

5

package.json
{
"name": "@applitools/eyes.cypress",
"version": "1.4.2",
"version": "1.5.0",
"main": "index.js",

@@ -24,4 +24,5 @@ "license": "MIT",

"@applitools/eyes.sdk.core": "^1.6.0",
"body-parser": "^1.18.2",
"cors": "^2.8.4",
"cssom": "0.3.1",
"cssom": "git+https://github.com/amitzur/CSSOM.git#925260ff2c8f8387cf76df4d5776a06044a644c8",
"dotenv": "^5.0.1",

@@ -28,0 +29,0 @@ "express": "^4.16.3",

39

src/cypress/commands.js

@@ -1,2 +0,2 @@

/* global Cypress, cy */
/* global Cypress,cy,window */
'use strict';

@@ -12,11 +12,23 @@ const extractResources = require('../render-grid/browser-util/extractResources');

open(args) {
return sendRequest('open', args);
return sendRequest({command: 'open', data: args});
},
checkWindow({resourceUrls, cdt, tag, sizeMode}) {
return sendRequest('checkWindow', {resourceUrls, cdt, tag, sizeMode});
putResource({url, type, value}) {
return sendRequest({
command: `resource/${url}`,
data: new window.frameElement.ownerDocument.defaultView.Blob([value]), // yucky! cypress uses socket.io to communicate between browser and node. In order to encode the data in binary format, socket.io checks for binary values. But `value instanceof Blob` is falsy since Blob from the cypress runner window is not the Blob from the command's window. So using the Blob from cypress runner window here.
method: 'PUT',
headers: {'Content-Type': type},
});
},
checkWindow({resourceUrls, blobs, cdt, tag, sizeMode}) {
const blobData = blobs.map(({url, type}) => ({url, type}));
return Promise.all(blobs.map(EyesServer.putResource)).then(() =>
sendRequest({command: 'checkWindow', data: {resourceUrls, cdt, tag, sizeMode, blobData}}),
);
},
close: poll(function({timeout}) {
return sendRequest('close', {timeout});
return sendRequest({command: 'close', data: {timeout}});
}),

@@ -43,7 +55,10 @@ };

Cypress.log({name: 'Eyes: check window'});
return cy.document({log: false}).then(doc => {
const cdt = domNodesToCdt(doc);
const resourceUrls = extractResources(doc);
return EyesServer.checkWindow({resourceUrls, cdt, tag, sizeMode});
});
return cy.document({log: false}).then(doc =>
cy.window().then(win => {
const cdt = domNodesToCdt(doc);
return extractResources(doc, win).then(({resourceUrls, blobs}) => {
return EyesServer.checkWindow({resourceUrls, blobs, cdt, tag, sizeMode});
});
}),
);
});

@@ -56,4 +71,4 @@

function sendRequest(command, data) {
return send(command, data).then(resp => {
function sendRequest(args) {
return send(args).then(resp => {
if (!resp.body.success) {

@@ -60,0 +75,0 @@ throw new Error(resp.body.error);

'use strict';
module.exports = (port, fetch) => (command, data) =>
module.exports = (port, fetch) => ({command, data, method = 'POST', headers}) =>
fetch({
url: `http://localhost:${port}/eyes/${command}`,
method: 'POST',
method,
body: data,
log: false,
headers,
});

@@ -34,3 +34,5 @@ 'use strict';

return config => {
return Object.assign({}, priorConfig, config);
const ret = Object.assign({}, priorConfig, config);
console.log('running with config:', ret);
return ret;
};

@@ -37,0 +39,0 @@ }

@@ -6,6 +6,4 @@ 'use strict';

const DEFAULT_TIMEOUT = 120000;
function makeHandlers(openEyes) {
let checkWindow, close;
let checkWindow, close, resources;

@@ -18,6 +16,14 @@ return {

close = pollingHandler(eyes.close);
resources = {};
return eyes;
},
checkWindow: async ({resourceUrls, cdt, tag}) => {
putResource: (id, buffer) => {
if (!resources) {
throw new Error('Please call cy.eyesOpen() before calling cy.eyesCheckWindow()');
}
resources[id] = buffer;
},
checkWindow: async ({resourceUrls, cdt, tag, blobData = [], sizeMode}) => {
if (!checkWindow) {

@@ -27,6 +33,11 @@ throw new Error('Please call cy.eyesOpen() before calling cy.eyesCheckWindow()');

return await checkWindow({resourceUrls, cdt, tag});
const resourceContents = blobData.reduce((acc, {url, type}) => {
acc[url] = {url, type, value: resources[url]};
return acc;
}, {});
return await checkWindow({resourceUrls, resourceContents, cdt, tag, sizeMode});
},
close: async ({timeout = DEFAULT_TIMEOUT} = {}) => {
close: async ({timeout} = {}) => {
if (!close) {

@@ -36,3 +47,4 @@ throw new Error('Please call cy.eyesOpen() before calling cy.eyesClose()');

return await close({timeout});
resources = null;
return await close(timeout);
},

@@ -39,0 +51,0 @@ };

@@ -10,2 +10,4 @@ 'use strict';

const DEFAULT_TIMEOUT = 120000;
const TIMEOUT_MSG =

@@ -20,3 +22,3 @@ "The cy.eyesClose command timed out. The default timeout is 2 minutes. It's possible to increase this timeout by passing a larger value, e.g. for 3 minutes: cy.eyesClose({ timeout: 180000 })";

return ({timeout}) => {
return (timeout = DEFAULT_TIMEOUT) => {
switch (pollingStatus) {

@@ -23,0 +25,0 @@ case PollingStatus.IDLE:

'use strict';
const express = require('express');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const cors = require('cors');

@@ -40,2 +41,7 @@ const log = require('../../render-grid/sdk/log');

app.put('/eyes/resource/:id', bodyParser.raw({type: '*/*'}), async (req, res) => {
handlers.putResource(req.params.id, Buffer.from(JSON.parse(req.body).data));
res.status(200).send({success: true});
});
app.post('/eyes/:command', express.json({limit: '100mb'}), async (req, res) => {

@@ -42,0 +48,0 @@ log(`eyes api: ${req.params.command}, ${Object.keys(req.body)}`);

@@ -38,13 +38,24 @@ /* eslint-disable no-use-before-define */

if (nodeType === NODE_TYPES.ELEMENT) {
node = {
nodeType: NODE_TYPES.ELEMENT,
nodeName: elementNode.nodeName,
attributes: Object.keys(elementNode.attributes).map(key => ({
name: elementNode.attributes[key].localName,
value: elementNode.attributes[key].value,
})),
childNodeIndexes: elementNode.childNodes.length
? childrenFactory(domNodes, elementNode.childNodes)
: [],
};
if (elementNode.nodeName !== 'SCRIPT') {
node = {
nodeType: NODE_TYPES.ELEMENT,
nodeName: elementNode.nodeName,
attributes: Object.keys(elementNode.attributes).map(key => {
let value = elementNode.attributes[key].value;
const name = elementNode.attributes[key].localName;
if (/^blob:/.test(value)) {
value = value.replace(/^blob:http:\/\/localhost:\d+\/(.+)/, '$1'); // TODO don't replace localhost once render-grid implements absolute urls
}
return {
name,
value,
};
}),
childNodeIndexes: elementNode.childNodes.length
? childrenFactory(domNodes, elementNode.childNodes)
: [],
};
}
} else if (nodeType === NODE_TYPES.TEXT) {

@@ -51,0 +62,0 @@ node = {

@@ -7,30 +7,3 @@ 'use strict';

*/
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 extractResources(el, win) {
function uniq(arr) {

@@ -52,12 +25,31 @@ return Array.from(new Set(arr));

const urlsFromStyleElements = [...el.getElementsByTagName('style')]
.map(styleEl => styleEl.sheet)
.reduce((acc, curr) => {
const resourceUrls = extractResourcesFromStyleSheet(curr);
return acc.concat(resourceUrls);
}, []);
const allResourceUrls = uniq([...srcUrls, ...cssUrls, ...videoPosterUrls]);
return uniq([...srcUrls, ...cssUrls, ...urlsFromStyleElements, ...videoPosterUrls]);
const blobUrls = [],
resourceUrls = [];
allResourceUrls.forEach(url => {
if (/^blob:/.test(url)) {
blobUrls.push(url);
} else {
resourceUrls.push(url);
}
});
return Promise.all(
blobUrls.map(blobUrl =>
win.fetch(blobUrl).then(resp =>
resp.arrayBuffer().then(buff => ({
url: blobUrl.replace(/^blob:http:\/\/localhost:\d+\/(.+)/, '$1'), // TODO don't replace localhost once render-grid implements absolute urls
type: resp.headers.get('Content-Type'),
value: buff,
})),
),
),
).then(blobs => ({
resourceUrls,
blobs,
}));
}
module.exports = extractResources;
'use strict';
const {parse, CSSImportRule, CSSStyleRule, CSSFontFaceRule} = require('cssom');
const {URL} = require('url');
const {parse, CSSImportRule, CSSStyleRule, CSSFontFaceRule, CSSSupportsRule} = require('cssom');
const absolutizeUrl = require('./absolutizeUrl');
// NOTE: this is also implemented on the client side (copy pasted to enable unit testing `extractResources` with puppeteer)
function getUrlFromCssText(cssText) {

@@ -11,8 +10,2 @@ const match = cssText.match(/url\((?!['"]?:)['"]?([^'"\)]*)['"]?\)/);

// should this simple yet repetitive thing be exported as a separate module?
function absolutizeUrl(url, absoluteUrl) {
return new URL(url, absoluteUrl).href;
}
// NOTE: this is also implemented on the client side (copy pasted to enable unit testing `extractResources` with puppeteer)
function extractResourcesFromStyleSheet(styleSheet) {

@@ -24,2 +17,4 @@ const resourceUrls = [...styleSheet.cssRules].reduce((acc, rule) => {

return acc.concat(getUrlFromCssText(rule.style.getPropertyValue('src')));
} else if (rule instanceof CSSSupportsRule) {
return acc.concat(extractResourcesFromStyleSheet(rule));
} else if (rule instanceof CSSStyleRule) {

@@ -26,0 +21,0 @@ for (let i = 0, ii = rule.style.length; i < ii; i++) {

@@ -55,4 +55,25 @@ 'use strict';

async function getOrFetchResources(resourceUrls, cache) {
async function getDependantResources({url, type, value}, cache) {
let dependentResources, fetchedResources;
if (/text\/css/.test(type)) {
dependentResources = extractCssResources(value.toString(), url);
fetchedResources = await getOrFetchResources(dependentResources, cache);
}
return {dependentResources, fetchedResources};
}
async function processResource(resource, cache) {
let {dependentResources, fetchedResources} = await getDependantResources(resource, cache);
const rGridResource = fromFetchedToRGridResource(resource);
cache.add(toCacheEntry(rGridResource), dependentResources);
return Object.assign({[resource.url]: rGridResource}, fetchedResources);
}
async function getOrFetchResources(resourceUrls, cache, preResources = {}) {
const resources = {};
for (const url in preResources) {
Object.assign(resources, await processResource(preResources[url], cache));
}
const missingResourceUrls = [];

@@ -70,13 +91,5 @@ for (const url of resourceUrls) {

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);
}),
fetchResource(url).then(async resource =>
Object.assign(resources, await processResource(resource, cache)),
),
),

@@ -88,4 +101,4 @@ );

async function getAllResources(absoluteUrls = []) {
return await getOrFetchResources(absoluteUrls, allResources);
async function getAllResources(absoluteUrls = [], preResources) {
return await getOrFetchResources(absoluteUrls, allResources, preResources);
}

@@ -92,0 +105,0 @@

@@ -5,3 +5,4 @@ 'use strict';

const waitForRenderedStatus = require('./waitForRenderedStatus');
const {URL} = require('url');
const absolutizeUrl = require('./absolutizeUrl');
const {mapKeys, mapValues} = require('lodash');
const saveData = require('../troubleshoot/saveData');

@@ -12,2 +13,3 @@ const {setIsVerbose} = require('./log');

const {BatchInfo} = require('@applitools/eyes.sdk.core');
const extractCssResourcesFromCdt = require('./extractCssResourcesFromCdt');

@@ -30,6 +32,18 @@ // TODO replace with getInferredEnvironment once render service returns userAgent

}) {
async function checkWindow({resourceUrls, cdt, tag, sizeMode}) {
async function checkWindow({
resourceUrls = [],
resourceContents = {},
cdt,
tag,
sizeMode = 'full-page',
}) {
async function checkWindowJob(renderPromise, prevJobPromise, index) {
const renderId = (await renderPromise)[index];
renderWrapper._logger.log(
`render request complete for ${renderId}. tag=${tag} sizeMode=${sizeMode} browser: ${JSON.stringify(
browsers[index],
)}`,
);
const [screenshotUrl] = await waitForRenderedStatus([renderId], renderWrapper);
renderWrapper._logger.log(`screenshot available for ${renderId} at ${screenshotUrl}`);
await prevJobPromise;

@@ -42,6 +56,2 @@ results.push(await wrappers[index].checkWindow({screenshotUrl, tag}));

const absoluteUrls =
resourceUrls && resourceUrls.map(resourceUrl => new URL(resourceUrl, url).href);
const resources = await getAllResources(absoluteUrls);
const renderRequests = createRenderRequests({

@@ -67,2 +77,9 @@ url,

/******* checkWindow body start *******/
const resourceUrlsWithCss = resourceUrls.concat(extractCssResourcesFromCdt(cdt, url));
const absoluteUrls = resourceUrlsWithCss.map(resourceUrl => absolutizeUrl(resourceUrl, url));
const absoluteResourceContents = mapValues(
mapKeys(resourceContents, (_value, key) => absolutizeUrl(key, url)),
({url: resourceUrl, type, value}) => ({url: absolutizeUrl(resourceUrl, url), type, value}),
);
const resources = await getAllResources(absoluteUrls, absoluteResourceContents);

@@ -69,0 +86,0 @@ const renderPromise = startRender();

@@ -29,3 +29,10 @@ 'use strict';

resolve(path, 'resources.json'),
JSON.stringify(mapValues(resources, resource => resource.getContentType()), null, 2),
JSON.stringify(
mapValues(resources, resource => ({
type: resource.getContentType(),
hash: resource.getSha256Hash(),
})),
null,
2,
),
);

@@ -32,0 +39,0 @@ Object.keys(resources).map(resourceUrl => {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc