blob: 20767102a35731a8ad88d89b231a77fb7b0642c7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const puppeteer = require('puppeteer');
const slugify = require('slugify');
const fse = require('fs-extra');
const fs = require('fs');
const path = require('path');
const program = require('commander');
const compareScreenshot = require('./compareScreenshot');
const {testNameFromFile, fileNameFromTest, getVersionDir, buildRuntimeCode, waitTime, getEChartsTestFileName} = require('./util');
const {origin} = require('./config');
const Timeline = require('./Timeline');
// Handling input arguments.
program
.option('-t, --tests <tests>', 'Tests names list')
.option('--no-headless', 'Not headless')
.option('-s, --speed <speed>', 'Playback speed')
.option('--expected <expected>', 'Expected version')
.option('--actual <actual>', 'Actual version')
.option('--renderer <renderer>', 'svg/canvas renderer')
.option('--no-save', 'Don\'t save result');
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');
}
function getScreenshotDir() {
return 'tmp/__screenshot__';
}
function sortScreenshots(list) {
return list.sort((a, b) => {
return a.screenshotName.localeCompare(b.screenshotName);
});
}
function getClientRelativePath(absPath) {
return path.join('../', path.relative(__dirname, absPath));
}
function replaceEChartsVersion(interceptedRequest, version) {
// TODO Extensions and maps
if (interceptedRequest.url().endsWith('dist/echarts.js')) {
console.log('Use echarts version: ' + version);
interceptedRequest.continue({
url: `${origin}/test/runTest/${getVersionDir(version)}/${getEChartsTestFileName()}`
});
}
else {
interceptedRequest.continue();
}
}
async function takeScreenshot(page, fullPage, fileUrl, desc, isExpected, minor) {
let screenshotName = testNameFromFile(fileUrl);
if (desc) {
screenshotName += '-' + slugify(desc, { replacement: '-', lower: true });
}
if (minor) {
screenshotName += '-' + minor;
}
let screenshotPrefix = isExpected ? 'expected' : 'actual';
fse.ensureDirSync(path.join(__dirname, getScreenshotDir()));
let screenshotPath = path.join(__dirname, `${getScreenshotDir()}/${screenshotName}-${screenshotPrefix}.png`);
await page.screenshot({
path: screenshotPath,
fullPage
});
return {screenshotName, screenshotPath};
}
async function runActions(page, testOpt, isExpected, screenshots) {
let timeline = new Timeline(page);
let actions;
try {
let actContent = fs.readFileSync(path.join(__dirname, 'actions', testOpt.name + '.json'));
actions = JSON.parse(actContent);
}
catch (e) {
// Can't find actions
return;
}
let playbackSpeed = +program.speed;
for (let action of actions) {
await page.evaluate((x, y) => {
window.scrollTo(x, y);
}, action.scrollX, action.scrollY);
let count = 0;
async function _innerTakeScreenshot() {
if (!program.save) {
return;
}
const desc = action.desc || action.name;
const {screenshotName, screenshotPath} = await takeScreenshot(page, false, testOpt.fileUrl, desc, isExpected, count++);
screenshots.push({screenshotName, desc, screenshotPath});
}
await timeline.runAction(action, _innerTakeScreenshot, playbackSpeed);
if (count === 0) {
await waitTime(200);
await _innerTakeScreenshot();
}
// const desc = action.desc || action.name;
// const {screenshotName, screenshotPath} = await takeScreenshot(page, false, testOpt.fileUrl, desc, version);
// screenshots.push({screenshotName, desc, screenshotPath});
}
timeline.stop();
}
async function runTestPage(browser, testOpt, version, runtimeCode, isExpected) {
const fileUrl = testOpt.fileUrl;
const screenshots = [];
const logs = [];
const errors = [];
const page = await browser.newPage();
page.setRequestInterception(true);
page.on('request', request => replaceEChartsVersion(request, version));
await page.evaluateOnNewDocument(runtimeCode);
page.on('console', msg => {
logs.push(msg.text());
});
page.on('pageerror', error => {
errors.push(error.toString());
});
page.on('dialog', async dialog => {
await dialog.dismiss();
});
try {
await page.setViewport({width: 800, height: 600});
await page.goto(`${origin}/test/${fileUrl}?__RENDERER__=${program.renderer}`, {
waitUntil: 'networkidle2',
timeout: 10000
});
await waitTime(500); // Wait for animation or something else. Pending
// Final shot.
await page.mouse.move(0, 0);
if (program.save) {
let desc = 'Full Shot';
const {screenshotName, screenshotPath} = await takeScreenshot(page, true, fileUrl, desc, isExpected);
screenshots.push({screenshotName, desc, screenshotPath});
}
await runActions(page, testOpt, isExpected, screenshots);
}
catch(e) {
console.error(e);
}
await page.close();
return {
logs,
errors,
screenshots: screenshots
};
}
async function writePNG(diffPNG, diffPath) {
return new Promise(resolve => {
let writer = fs.createWriteStream(diffPath);
diffPNG.pack().pipe(writer);
writer.on('finish', () => {resolve();});
});
};
async function runTest(browser, testOpt, runtimeCode, expectedVersion, actualVersion) {
if (program.renderer === 'svg' && testOpt.ignoreSVG) {
console.log(testOpt.name + ' don\'t support svg testing.');
return;
}
if (program.save) {
testOpt.status === 'running';
const expectedResult = await runTestPage(browser, testOpt, expectedVersion, runtimeCode, true);
const actualResult = await runTestPage(browser, testOpt, actualVersion, runtimeCode, false);
// sortScreenshots(expectedResult.screenshots);
// sortScreenshots(actualResult.screenshots);
const screenshots = [];
let idx = 0;
for (let shot of expectedResult.screenshots) {
let expected = shot;
let actual = actualResult.screenshots[idx++];
let result = {
actual: getClientRelativePath(actual.screenshotPath),
expected: getClientRelativePath(expected.screenshotPath),
name: actual.screenshotName,
desc: actual.desc
};
try {
let {diffRatio, diffPNG} = await compareScreenshot(
expected.screenshotPath,
actual.screenshotPath
);
let diffPath = `${path.resolve(__dirname, getScreenshotDir())}/${shot.screenshotName}-diff.png`;
await writePNG(diffPNG, diffPath);
result.diff = getClientRelativePath(diffPath);
result.diffRatio = diffRatio;
}
catch(e) {
result.diff = '';
result.diffRatio = 1;
console.log(e);
}
screenshots.push(result);
}
testOpt.results = screenshots;
testOpt.status = 'finished';
testOpt.actualLogs = actualResult.logs;
testOpt.expectedLogs = expectedResult.logs;
testOpt.actualErrors = actualResult.errors;
testOpt.expectedErrors = expectedResult.errors;
testOpt.actualVersion = actualVersion;
testOpt.expectedVersion = expectedVersion;
testOpt.useSVG = program.renderer === 'svg';
testOpt.lastRun = Date.now();
}
else {
// Only run once
await runTestPage(browser, testOpt, 'local', runtimeCode, true);
}
}
async function runTests(pendingTests) {
const browser = await puppeteer.launch({
headless: program.headless,
args: [`--window-size=830,750`] // new option
});
// TODO Not hardcoded.
// let runtimeCode = fs.readFileSync(path.join(__dirname, 'tmp/testRuntime.js'), 'utf-8');
let runtimeCode = await buildRuntimeCode();
runtimeCode = `window.__TEST_PLAYBACK_SPEED__ = ${program.speed || 1};\n${runtimeCode}`;
try {
for (let testOpt of pendingTests) {
console.log(`Running test: ${testOpt.name}, renderer: ${program.renderer}`);
try {
await runTest(browser, testOpt, runtimeCode, program.expected, program.actual);
}
catch (e) {
// Restore status
testOpt.status = 'unsettled';
console.log(e);
}
if (program.save) {
process.send(testOpt);
}
}
}
catch(e) {
console.log(e);
}
await browser.close();
}
runTests(program.tests.split(',').map(testName => {
return {
fileUrl: fileNameFromTest(testName),
name: testName,
results: [],
actualLogs: [],
expectedLogs: [],
actualErrors: [],
expectedErrors: [],
status: 'pending'
};
}));