未验证 提交 498132d3 编写于 作者: Y Yi Shen 提交者: GitHub

Merge pull request #11221 from apache/test-autorun

Visual regression testing tool: support SVG renderer test and bug fixes.
此差异已折叠。
......@@ -17,7 +17,7 @@
* under the License.
*/
module.exports = [
module.exports.blacklist = [
'-cases.html',
'geo-random-stream.html',
'chord.html',
......@@ -36,3 +36,10 @@ module.exports = [
'lines-bus.html',
'lines-stream-not-large.html'
];
module.exports.SVGBlacklist = [
'bar-stream-large.html',
'bar-stream-large1.html',
'candlestick-large2.html'
];
\ No newline at end of file
......@@ -34,13 +34,15 @@ program
.option('--no-headless', 'Not headless')
.option('-s, --speed <speed>', 'Playback speed')
.option('--expected <expected>', 'Expected version')
.option('--actual <actual>', 'Actual version');
.option('--actual <actual>', 'Actual version')
.option('--renderer <renderer>', 'svg/canvas renderer');
program.parse(process.argv);
program.speed = +program.speed || 1;
program.actual = program.actual || 'local';
program.expected = program.expected || '4.2.1';
program.renderer = (program.renderer || 'canvas').toLowerCase();
if (!program.tests) {
throw new Error('Tests are required');
......@@ -63,7 +65,7 @@ function getClientRelativePath(absPath) {
function replaceEChartsVersion(interceptedRequest, version) {
// TODO Extensions and maps
if (interceptedRequest.url().endsWith('dist/echarts.js')) {
console.log(getVersionDir(version));
console.log('Use echarts version: ' + getVersionDir(version));
interceptedRequest.continue({
url: `${origin}/test/runTest/${getVersionDir(version)}/echarts.js`
});
......@@ -73,7 +75,7 @@ function replaceEChartsVersion(interceptedRequest, version) {
}
}
async function takeScreenshot(page, fullPage, fileUrl, desc, version, minor) {
async function takeScreenshot(page, fullPage, fileUrl, desc, isExpected, minor) {
let screenshotName = testNameFromFile(fileUrl);
if (desc) {
screenshotName += '-' + slugify(desc, { replacement: '-', lower: true });
......@@ -81,7 +83,7 @@ async function takeScreenshot(page, fullPage, fileUrl, desc, version, minor) {
if (minor) {
screenshotName += '-' + minor;
}
let screenshotPrefix = version ? 'expected' : 'actual';
let screenshotPrefix = isExpected ? 'expected' : 'actual';
fse.ensureDirSync(path.join(__dirname, getScreenshotDir()));
let screenshotPath = path.join(__dirname, `${getScreenshotDir()}/${screenshotName}-${screenshotPrefix}.png`);
await page.screenshot({
......@@ -92,7 +94,7 @@ async function takeScreenshot(page, fullPage, fileUrl, desc, version, minor) {
return {screenshotName, screenshotPath};
}
async function runActions(page, testOpt, version, screenshots) {
async function runActions(page, testOpt, isExpected, screenshots) {
let timeline = new Timeline(page);
let actions;
try {
......@@ -114,10 +116,10 @@ async function runActions(page, testOpt, version, screenshots) {
let count = 0;
async function _innerTakeScreenshot() {
const desc = action.desc || action.name;
const {screenshotName, screenshotPath} = await takeScreenshot(page, false, testOpt.fileUrl, desc, version, count++);
const {screenshotName, screenshotPath} = await takeScreenshot(page, false, testOpt.fileUrl, desc, isExpected, count++);
screenshots.push({screenshotName, desc, screenshotPath});
}
await timeline.runAction(action, _innerTakeScreenshot, playbackSpeed);
await timeline.runAction(action, _innerTakeScreenshot, playbackSpeed);
if (count === 0) {
await waitTime(200);
......@@ -131,7 +133,7 @@ async function runActions(page, testOpt, version, screenshots) {
timeline.stop();
}
async function runTestPage(browser, testOpt, version, runtimeCode) {
async function runTestPage(browser, testOpt, version, runtimeCode, isExpected) {
const fileUrl = testOpt.fileUrl;
const screenshots = [];
const logs = [];
......@@ -155,7 +157,7 @@ async function runTestPage(browser, testOpt, version, runtimeCode) {
try {
await page.setViewport({width: 800, height: 600});
await page.goto(`${origin}/test/${fileUrl}`, {
await page.goto(`${origin}/test/${fileUrl}?__RENDERER__=${program.renderer}`, {
waitUntil: 'networkidle2',
timeout: 10000
});
......@@ -164,10 +166,10 @@ async function runTestPage(browser, testOpt, version, runtimeCode) {
// Final shot.
await page.mouse.move(0, 0);
let desc = 'Full Shot';
const {screenshotName, screenshotPath} = await takeScreenshot(page, true, fileUrl, desc, version);
const {screenshotName, screenshotPath} = await takeScreenshot(page, true, fileUrl, desc, isExpected);
screenshots.push({screenshotName, desc, screenshotPath});
await runActions(page, testOpt, version, screenshots);
await runActions(page, testOpt, isExpected, screenshots);
}
catch(e) {
console.error(e);
......@@ -191,9 +193,14 @@ async function writePNG(diffPNG, diffPath) {
};
async function runTest(browser, testOpt, runtimeCode, expectedVersion, actualVersion) {
if (program.renderer === 'svg' && testOpt.ignoreSVG) {
console.log(testOpt.name + ' don\'t support svg testing.');
return;
}
testOpt.status === 'running';
const expectedResult = await runTestPage(browser, testOpt, expectedVersion, runtimeCode);
const actualResult = await runTestPage(browser, testOpt, actualVersion, runtimeCode);
const expectedResult = await runTestPage(browser, testOpt, expectedVersion, runtimeCode, true);
const actualResult = await runTestPage(browser, testOpt, actualVersion, runtimeCode, false);
// sortScreenshots(expectedResult.screenshots);
// sortScreenshots(actualResult.screenshots);
......@@ -229,6 +236,7 @@ async function runTest(browser, testOpt, runtimeCode, expectedVersion, actualVer
testOpt.expectedErrors = expectedResult.errors;
testOpt.actualVersion = actualVersion;
testOpt.expectedVersion = expectedVersion;
testOpt.useSVG = program.renderer === 'svg';
testOpt.lastRun = Date.now();
}
......@@ -244,7 +252,7 @@ async function runTests(pendingTests) {
try {
for (let testOpt of pendingTests) {
console.log('Running Test', testOpt.name);
console.log(`Running test: ${testOpt.name}, renderer: ${program.renderer}`);
try {
await runTest(browser, testOpt, runtimeCode, program.expected, program.actual);
}
......@@ -269,6 +277,10 @@ runTests(program.tests.split(',').map(testName => {
fileUrl: fileNameFromTest(testName),
name: testName,
results: [],
status: 'unsettled'
actualLogs: [],
expectedLogs: [],
actualErrors: [],
expectedErrors: [],
status: 'pending'
};
}));
\ No newline at end of file
......@@ -18,6 +18,7 @@
*/
const socket = io('/client');
const LOCAL_SAVE_KEY = 'visual-regression-testing-config';
function processTestsData(tests, oldTestsData) {
tests.forEach((test, idx) => {
......@@ -74,6 +75,7 @@ const app = new Vue({
replaySpeed: 5,
actualVersion: 'local',
expectedVersion: null,
renderer: 'canvas',
threads: 1
}
},
......@@ -209,6 +211,15 @@ const app = new Vue({
}
});
// Save and restore
try {
Object.assign(app.runConfig, JSON.parse(localStorage.getItem(LOCAL_SAVE_KEY)));
}
catch (e) {}
app.$watch('runConfig', () => {
localStorage.setItem(LOCAL_SAVE_KEY, JSON.stringify(app.runConfig));
}, {deep: true});
function runTests(tests) {
if (!tests.length) {
app.$notify({
......@@ -230,6 +241,7 @@ function runTests(tests) {
expectedVersion: app.runConfig.expectedVersion,
actualVersion: app.runConfig.actualVersion,
threads: app.runConfig.threads,
renderer: app.runConfig.renderer,
noHeadless: app.runConfig.noHeadless,
replaySpeed: app.runConfig.noHeadless
? app.runConfig.replaySpeed
......@@ -258,7 +270,7 @@ socket.on('update', msg => {
}).catch(() => {});
}
// TODO
// app.running = !!msg.running;
app.running = !!msg.running;
app.fullTests = processTestsData(msg.tests, app.fullTests);
firstUpdate = false;
......@@ -274,6 +286,14 @@ socket.on('finish', res => {
console.log(`${res.count} test complete, Cost: ${(res.time / 1000).toFixed(1)} s. Threads: ${res.threads}`);
app.running = false;
});
socket.on('abort', res => {
app.$notify({
type: 'info',
title: `Aborted`,
duration: 4000
});
app.running = false;
});
socket.on('versions', versions => {
app.versions = versions.filter(version => {
return !version.startsWith('2.')
......
......@@ -24,6 +24,8 @@ under the License.
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="shortcut icon" href="https://www.echartsjs.com/zh/images/favicon.png">
<title>Visual Regression Testing Tool</title>
</head>
<body>
<div id="app" style="display: none">
......@@ -31,7 +33,7 @@ under the License.
<el-header class="header" height="50">
<div id="logo">
<img src="https://echarts.apache.org/zh/images/logo.png" />
<h1>Visual Regression Test</h1>
<h1>Visual Regression Testing Tool</h1>
</div>
</el-header>
<el-container style="min-height: 0"> <!-- https://juejin.im/post/5c642f2ff265da2de660ecfc -->
......@@ -77,7 +79,7 @@ under the License.
<el-slider style="width: 140px;" v-model="runConfig.threads" :step="1" :min="1" :max="8" show-stops></el-slider>
</div>
<div class="run-config-item">
<span>Version </span>
<span>Version</span>
<span style="font-size: 12px; color:#afafaf">Expected</span>
<el-select size="mini" v-model="runConfig.expectedVersion" placeholder="Select Version"
style="width: 80px;"
......@@ -91,6 +93,13 @@ under the License.
<el-option v-for="version in versions" :key="version" :label="version" :value="version"></el-option>
</el-select>
</div>
<div class="run-config-item">
<span>Renderer</span>
<el-select size="mini" v-model="runConfig.renderer" placeholder="Select Renderer">
<el-option key="canvas" label="canvas" value="canvas"></el-option>
<el-option key="svg" label="svg" value="svg"></el-option>
</el-select>
</div>
<i slot="reference" class="el-icon-setting"></i>
</el-popover>
......
......@@ -28,6 +28,7 @@ const {getTestsList, updateTestsList, saveTestsList, mergeTestsResults, updateAc
const {prepareEChartsLib, getActionsFullPath, fetchVersions} = require('./util');
const fse = require('fs-extra');
const fs = require('fs');
const open = require('open');
function serve() {
const server = http.createServer((request, response) => {
......@@ -51,6 +52,7 @@ function serve() {
let runningThreads = [];
let pendingTests;
let aborted = false;
function stopRunningTests() {
if (runningThreads) {
......@@ -75,17 +77,11 @@ class Thread {
this.onUpdate;
}
fork(noHeadless, replaySpeed, actualVersion, expectedVersion) {
fork(extraArgs) {
let p = fork(path.join(__dirname, 'cli.js'), [
'--tests',
this.tests.map(testOpt => testOpt.name).join(','),
'--speed',
replaySpeed || 5,
'--actual',
actualVersion,
'--expected',
expectedVersion,
...(noHeadless ? ['--no-headless'] : [])
...extraArgs
]);
this.p = p;
......@@ -114,7 +110,8 @@ function startTests(testsNameList, socket, {
threadsCount,
replaySpeed,
actualVersion,
expectedVersion
expectedVersion,
renderer
}) {
console.log('Received: ', testsNameList.join(','));
......@@ -131,18 +128,23 @@ function startTests(testsNameList, socket, {
testOpt.results = [];
});
socket.emit('update', {tests: getTestsList()});
if (!aborted) {
socket.emit('update', {tests: getTestsList(), running: true});
}
let runningCount = 0;
function onExit() {
runningCount--;
if (runningCount === 0) {
runningThreads = [];
resolve();
}
}
function onUpdate() {
// Merge tests.
socket.emit('update', {tests: getTestsList(), running: true});
if (!aborted) {
socket.emit('update', {tests: getTestsList(), running: true});
}
}
threadsCount = Math.min(threadsCount, pendingTests.length);
// Assigning tests to threads
......@@ -153,7 +155,13 @@ function startTests(testsNameList, socket, {
for (let i = 0; i < threadsCount; i++) {
runningThreads[i].onExit = onExit;
runningThreads[i].onUpdate = onUpdate;
runningThreads[i].fork(noHeadless, replaySpeed, actualVersion, expectedVersion);
runningThreads[i].fork([
'--speed', replaySpeed || 5,
'--actual', actualVersion,
'--expected', expectedVersion,
'--renderer', renderer,
...(noHeadless ? ['--no-headless'] : [])
]);
runningCount++;
}
// If something bad happens and no proccess are started successfully
......@@ -180,7 +188,10 @@ async function start() {
return;
}
let versions = await fetchVersions();
let [versions] = await Promise.all([
fetchVersions(),
updateTestsList(true)
]);
// let runtimeCode = await buildRuntimeCode();
// fse.outputFileSync(path.join(__dirname, 'tmp/testRuntime.js'), runtimeCode, 'utf-8');
......@@ -191,39 +202,58 @@ async function start() {
io.of('/client').on('connect', async socket => {
await updateTestsList();
socket.emit('update', {tests: getTestsList()});
socket.emit('update', {
tests: getTestsList(),
running: runningThreads.length > 0
});
socket.on('run', async data => {
let startTime = Date.now();
aborted = false;
await prepareEChartsLib(data.expectedVersion); // Expected version.
await prepareEChartsLib(data.actualVersion); // Version to test
if (aborted) { // If it is aborted when downloading echarts lib.
return;
}
// TODO Should broadcast to all sockets.
try {
await startTests(
data.tests,
socket,
io.of('/client'),
{
noHeadless: data.noHeadless,
threadsCount: data.threads,
replaySpeed: data.replaySpeed,
actualVersion: data.actualVersion,
expectedVersion: data.expectedVersion
expectedVersion: data.expectedVersion,
renderer: data.renderer
}
);
}
catch (e) { console.error(e); }
console.log('Finished');
socket.emit('finish', {
time: Date.now() - startTime,
count: data.tests.length,
threads: data.threads
});
catch (e) {
console.error(e);
}
if (!aborted) {
console.log('Finished');
io.of('/client').emit('finish', {
time: Date.now() - startTime,
count: data.tests.length,
threads: data.threads
});
}
else {
console.log('Aborted!');
}
});
socket.on('stop', () => {
stopRunningTests();
io.of('/client').emit('abort');
aborted = true;
});
socket.emit('versions', versions);
......@@ -261,7 +291,8 @@ async function start() {
threadsCount: 1,
replaySpeed: 2,
actualVersion: data.actualVersion,
expectedVersion: data.expectedVersion
expectedVersion: data.expectedVersion,
renderer: data.renderer
});
}
catch (e) { console.error(e); }
......@@ -280,8 +311,8 @@ async function start() {
});
console.log(`Dashboard: ${origin}/test/runTest/client/index.html`);
// console.log(`Interaction Recorder: ${origin}/test/runTest/recorder/index.html`);
// open(`${origin}/test/runTest/client/index.html`);
console.log(`Interaction Recorder: ${origin}/test/runTest/recorder/index.html`);
open(`${origin}/test/runTest/client/index.html`);
}
......
......@@ -23,11 +23,41 @@ const fs = require('fs');
const glob = require('glob');
const {testNameFromFile} = require('./util');
const util = require('util');
const blacklist = require('./blacklist');
const {blacklist, SVGBlacklist} = require('./blacklist');
let _tests = [];
let _testsMap = {};
class Test {
constructor(fileUrl) {
this.fileUrl = fileUrl;
this.name = testNameFromFile(fileUrl);
// If this test case ignore svg testing.
this.ignoreSVG = false;
this.status = 'unsettled';
// Run results
this.results = []; // Screenshots
this.actualLogs = [];
this.expectedLogs = [];
this.actualErrors = [];
this.expectedErrors = [];
// Use echarts versions.
this.actualVersion = null;
this.expectedVersion = null;
// Last timestamp
this.lastRun = 0;
// Use SVG
this.useSVG = false;
}
}
function getCacheFilePath() {
return path.join(__dirname, 'tmp/__cache__.json');;
}
......@@ -40,7 +70,7 @@ module.exports.getTestByFileUrl = function (url) {
return _testsMap[url];
};
module.exports.updateTestsList = async function () {
module.exports.updateTestsList = async function (setPendingTestToUnsettled) {
let tmpFolder = path.join(__dirname, 'tmp');
fse.ensureDirSync(tmpFolder);
_tests = [];
......@@ -51,8 +81,10 @@ module.exports.updateTestsList = async function () {
_tests.forEach(test => {
// In somehow tests are stopped and leave the status pending.
// Set the status to unsettled again.
if (test.status === 'pending') {
test.status = 'unsettled';
if (setPendingTestToUnsettled) {
if (test.status === 'pending') {
test.status = 'unsettled';
}
}
_testsMap[test.fileUrl] = test;
});
......@@ -70,13 +102,8 @@ module.exports.updateTestsList = async function () {
return;
}
let test = {
fileUrl,
name: testNameFromFile(fileUrl),
// Default status should be unkown
// status: 'pending',
results: []
};
let test = new Test(fileUrl);
test.ignoreSVG = SVGBlacklist.includes(fileUrl);
_tests.push(test);
_testsMap[fileUrl] = test;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册