Add screenshot collecting server

This commit is contained in:
Niklas von Hertzen 2017-08-28 21:27:39 +08:00
parent 5dbb197a82
commit b75fd70042
9 changed files with 180 additions and 26 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ node_modules/
npm-debug.log npm-debug.log
debug.log debug.log
tests/reftests.js tests/reftests.js
*.log

View File

@ -10,10 +10,11 @@ module.exports = function(config) {
platform: 'Windows 10', platform: 'Windows 10',
version: 'beta' version: 'beta'
}, },
sl_stable_firefox: { sl_beta_firefox: {
base: 'SauceLabs', base: 'SauceLabs',
browserName: 'firefox', browserName: 'firefox',
platform: 'Windows 10' platform: 'Windows 8.1',
version: 'beta'
}, },
sl_ie9: { sl_ie9: {
base: 'SauceLabs', base: 'SauceLabs',
@ -90,16 +91,6 @@ module.exports = function(config) {
} }
}); });
/*
'sl_opera_12.15': {
base: 'SauceLabs',
browserName: 'opera',
platform: 'Linux',
version: '12.15'
},
*/
config.set({ config.set({
// base path that will be used to resolve all patterns (eg. files, exclude) // base path that will be used to resolve all patterns (eg. files, exclude)
@ -173,6 +164,7 @@ module.exports = function(config) {
'/node_modules': `http://localhost:${port}/base/node_modules`, '/node_modules': `http://localhost:${port}/base/node_modules`,
'/tests': `http://localhost:${port}/base/tests`, '/tests': `http://localhost:${port}/base/tests`,
'/assets': `http://localhost:${port}/base/tests/assets`, '/assets': `http://localhost:${port}/base/tests/assets`,
'/screenshot': `http://localhost:8081/screenshot`,
}, },
client: { client: {

View File

@ -28,12 +28,14 @@
"babel-preset-es2015": "6.24.1", "babel-preset-es2015": "6.24.1",
"babel-preset-flow": "6.23.0", "babel-preset-flow": "6.23.0",
"base64-arraybuffer": "0.1.5", "base64-arraybuffer": "0.1.5",
"body-parser": "1.17.2",
"chai": "4.1.1", "chai": "4.1.1",
"chromeless": "^1.2.0", "chromeless": "^1.2.0",
"eslint": "4.2.0", "eslint": "4.2.0",
"eslint-plugin-flowtype": "2.35.0", "eslint-plugin-flowtype": "2.35.0",
"eslint-plugin-prettier": "2.1.2", "eslint-plugin-prettier": "2.1.2",
"express": "4.15.4", "express": "4.15.4",
"filenamify-url": "1.0.0",
"flow-bin": "0.50.0", "flow-bin": "0.50.0",
"glob": "7.1.2", "glob": "7.1.2",
"jquery": "3.2.1", "jquery": "3.2.1",
@ -45,6 +47,7 @@
"karma-mocha": "1.3.0", "karma-mocha": "1.3.0",
"karma-sauce-launcher": "1.1.0", "karma-sauce-launcher": "1.1.0",
"mocha": "3.5.0", "mocha": "3.5.0",
"platform": "1.3.4",
"prettier": "1.5.3", "prettier": "1.5.3",
"promise-polyfill": "6.0.2", "promise-polyfill": "6.0.2",
"rimraf": "2.6.1", "rimraf": "2.6.1",
@ -59,7 +62,7 @@
"flow": "flow", "flow": "flow",
"lint": "eslint src/**", "lint": "eslint src/**",
"test": "npm run flow && npm run lint && npm run karma", "test": "npm run flow && npm run lint && npm run karma",
"karma": "karma start --single-run", "karma": "karma start",
"watch": "webpack --progress --colors --watch" "watch": "webpack --progress --colors --watch"
}, },
"homepage": "https://html2canvas.hertzen.com", "homepage": "https://html2canvas.hertzen.com",

View File

@ -0,0 +1,69 @@
const path = require('path');
const fs = require('fs');
const express = require('express');
const bodyParser = require('body-parser');
const filenamifyUrl = require('filenamify-url');
const app = express();
app.use(
bodyParser.json({
limit: '15mb',
type: '*/*'
})
);
const prefix = 'data:image/png;base64,';
const writeScreenshot = (buffer, body) => {
const filename = `${filenamifyUrl(
body.test.replace(/^\/tests\/reftests\//, '').replace(/\.html$/, ''),
{replacement: '-'}
)}!${body.platform.name}-${body.platform.version}.png`;
fs.writeFileSync(path.resolve(__dirname, '../tests/results/', filename), buffer);
};
app.post('/screenshot', (req, res) => {
if (!req.body || !req.body.screenshot) {
return res.sendStatus(400);
}
const buffer = new Buffer(req.body.screenshot.substring(prefix.length), 'base64');
writeScreenshot(buffer, req.body);
return res.sendStatus(200);
});
const chunks = {};
app.post('/screenshot/chunk', (req, res) => {
if (!req.body || !req.body.screenshot) {
return res.sendStatus(400);
}
const key = `${req.body.platform.name}-${req.body.platform.version}-${req.body.test
.replace(/^\/tests\/reftests\//, '')
.replace(/\.html$/, '')}`;
if (!Array.isArray(chunks[key])) {
chunks[key] = Array.from(Array(req.body.totalCount));
}
chunks[key][req.body.part] = req.body.screenshot;
if (chunks[key].every(s => typeof s === 'string')) {
const str = chunks[key].reduce((acc, s) => acc + s, '');
const buffer = new Buffer(str.substring(prefix.length), 'base64');
delete chunks[key];
writeScreenshot(buffer, req.body);
}
return res.sendStatus(200);
});
app.use((error, req, res, next) => {
console.error(error);
next();
});
const listener = app.listen(8081, () => {
console.log(listener.address().port);
});

View File

@ -75,8 +75,15 @@ const testSVG = document => {
const testForeignObject = document => { const testForeignObject = document => {
const img = new Image(); const img = new Image();
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
img.src = `data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><foreignObject><div></div></foreignObject></svg>`; ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 1, 1);
img.src = `data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><foreignObject><img src="${encodeURIComponent(
canvas.toDataURL()
)}" /></foreignObject></svg>`;
return new Promise(resolve => { return new Promise(resolve => {
const onload = () => { const onload = () => {

View File

@ -18,6 +18,7 @@ import Color, {TRANSPARENT} from './Color';
export type Options = { export type Options = {
async: ?boolean, async: ?boolean,
allowTaint: ?boolean, allowTaint: ?boolean,
backgroundColor: string,
canvas: ?HTMLCanvasElement, canvas: ?HTMLCanvasElement,
imageTimeout: number, imageTimeout: number,
proxy: ?string, proxy: ?string,
@ -71,14 +72,18 @@ const html2canvas = (element: HTMLElement, config: Options): Promise<*> => {
const documentBackgroundColor = ownerDocument.documentElement const documentBackgroundColor = ownerDocument.documentElement
? new Color(getComputedStyle(ownerDocument.documentElement).backgroundColor) ? new Color(getComputedStyle(ownerDocument.documentElement).backgroundColor)
: TRANSPARENT; : TRANSPARENT;
const bodyBackgroundColor = ownerDocument.body
? new Color(getComputedStyle(ownerDocument.body).backgroundColor)
: TRANSPARENT;
const backgroundColor = const backgroundColor =
element === ownerDocument.documentElement element === ownerDocument.documentElement
? documentBackgroundColor.isTransparent() ? documentBackgroundColor.isTransparent()
? ownerDocument.body ? bodyBackgroundColor.isTransparent()
? new Color(getComputedStyle(ownerDocument.body).backgroundColor) ? options.backgroundColor ? new Color(options.backgroundColor) : null
: null : bodyBackgroundColor
: documentBackgroundColor : documentBackgroundColor
: null; : options.backgroundColor ? new Color(options.backgroundColor) : null;
// $FlowFixMe // $FlowFixMe
const result = Feature.SUPPORT_FOREIGNOBJECT_DRAWING.then( const result = Feature.SUPPORT_FOREIGNOBJECT_DRAWING.then(

View File

@ -1,5 +0,0 @@
/tests/reftests/background/radial-gradient.html
/tests/reftests/text/chinese.html
[Edge]/tests/reftests/acid2.html
[Edge]/tests/reftests/pseudoelements.html
[Edge]/tests/reftests/text/multiple.html

View File

@ -4,7 +4,12 @@ sauceConnectLauncher(
{ {
username: process.env.SAUCE_USERNAME, username: process.env.SAUCE_USERNAME,
accessKey: process.env.SAUCE_ACCESS_KEY, accessKey: process.env.SAUCE_ACCESS_KEY,
logger: console.log logger: console.log,
// Log output from the `sc` process to stdout?
verbose: true,
// Enable verbose debugging (optional)
verboseDebugging: true
}, },
err => { err => {
if (err) { if (err) {

View File

@ -2,6 +2,8 @@ import {expect} from 'chai';
import parseRefTest from '../scripts/parse-reftest'; import parseRefTest from '../scripts/parse-reftest';
import reftests from './reftests'; import reftests from './reftests';
import querystring from 'querystring'; import querystring from 'querystring';
import platform from 'platform';
import Promise from 'promise-polyfill';
const DOWNLOAD_REFTESTS = false; const DOWNLOAD_REFTESTS = false;
const query = querystring.parse(location.search.replace(/^\?/, '')); const query = querystring.parse(location.search.replace(/^\?/, ''));
@ -78,7 +80,7 @@ const assertPath = (result, expected, desc) => {
}) })
.forEach(url => { .forEach(url => {
describe(url, function() { describe(url, function() {
this.timeout(30000); this.timeout(60000);
const windowWidth = 800; const windowWidth = 800;
const windowHeight = 600; const windowHeight = 600;
const testContainer = document.createElement('iframe'); const testContainer = document.createElement('iframe');
@ -113,7 +115,8 @@ const assertPath = (result, expected, desc) => {
it('Should render untainted canvas', () => { it('Should render untainted canvas', () => {
return testContainer.contentWindow return testContainer.contentWindow
.html2canvas(testContainer.contentWindow.document.documentElement, { .html2canvas(testContainer.contentWindow.document.documentElement, {
removeContainer: true removeContainer: true,
backgroundColor: '#ffffff'
}) })
.then(canvas => { .then(canvas => {
try { try {
@ -330,6 +333,80 @@ const assertPath = (result, expected, desc) => {
result result
); );
} }
// window.__karma__
if (false) {
const MAX_CHUNK_SIZE = 75000;
const sendScreenshot = (tries, body, url) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = () => {
if (
typeof xhr.status !== 'number' ||
xhr.status === 200
) {
resolve();
} else {
reject(
`Failed to send screenshot with status ${xhr.status}`
);
}
};
// xhr.onerror = reject;
xhr.open('POST', url, true);
xhr.send(body);
}).catch(e => {
if (tries > 0) {
// Older edge browsers and some safari browsers have issues sending large xhr through saucetunnel
const data = canvas.toDataURL();
const totalCount = Math.ceil(
data.length / MAX_CHUNK_SIZE
);
return Promise.all(
Array.apply(
null,
Array(totalCount)
).map((x, part) =>
sendScreenshot(
0,
JSON.stringify({
screenshot: data.substr(
part * MAX_CHUNK_SIZE,
MAX_CHUNK_SIZE
),
part,
totalCount,
test: url,
platform: {
name: platform.name,
version: platform.version
}
}),
'/screenshot/chunk'
)
)
);
}
return Promise.reject(e);
});
};
return sendScreenshot(
1,
JSON.stringify({
screenshot: canvas.toDataURL(),
test: url,
platform: {
name: platform.name,
version: platform.version
}
}),
'/screenshot'
);
}
}); });
}); });
}); });