diff --git a/test/runTest/client/client.js b/test/runTest/client/client.js
index 13f1af686a..34891725fd 100644
--- a/test/runTest/client/client.js
+++ b/test/runTest/client/client.js
@@ -110,8 +110,9 @@ try {
catch (e) {}
function getVersionFromSource(source, versions, nightlyVersions) {
- if (source === 'branch') {
- return 'master';
+ if (source === 'PR') {
+ // Default PR version can be empty since it needs to be manually selected
+ return '#';
}
else if (source === 'nightly') {
return nightlyVersions.length ? nightlyVersions[0] : null;
@@ -150,6 +151,7 @@ const app = new Vue({
versions: [],
nightlyVersions: [],
+ prVersions: [],
branchVersions: [],
runConfig: Object.assign({
@@ -178,6 +180,7 @@ const app = new Vue({
socket.on('syncRunConfig_return', res => {
this.versions = res.versions || [];
this.nightlyVersions = res.nightlyVersions || [];
+ this.prVersions = res.prVersions || [];
// Only set versions if they haven't been manually set
handlingSourceChange = true;
@@ -222,6 +225,16 @@ const app = new Vue({
this.pageInvisible = true;
}
});
+
+ socket.on('run_error', err => {
+ app.$notify({
+ title: 'Error',
+ message: err.message,
+ type: 'error',
+ duration: 5000
+ });
+ app.running = false;
+ });
},
computed: {
@@ -312,8 +325,8 @@ const app = new Vue({
return this.versions;
case 'nightly':
return this.nightlyVersions;
- case 'branch':
- return this.branchVersions;
+ case 'PR':
+ return this.prVersions;
case 'local':
return ['local'];
default:
@@ -327,8 +340,8 @@ const app = new Vue({
return this.versions;
case 'nightly':
return this.nightlyVersions;
- case 'branch':
- return this.branchVersions;
+ case 'PR':
+ return this.prVersions;
case 'local':
return ['local'];
default:
@@ -632,8 +645,16 @@ app.$watch('runConfig', (newVal, oldVal) => {
if (!app.pageInvisible && !handlingSourceChange) {
socket.emit('syncRunConfig', {
runConfig: app.runConfig,
- // Override server config from URL.
forceSet: true
+ }, err => {
+ if (err) {
+ app.$notify({
+ title: 'Error',
+ message: err,
+ type: 'error',
+ duration: 5000
+ });
+ }
});
}
}, { deep: true });
\ No newline at end of file
diff --git a/test/runTest/client/index.html b/test/runTest/client/index.html
index 6cdeb63cce..f5270a90a5 100644
--- a/test/runTest/client/index.html
+++ b/test/runTest/client/index.html
@@ -1,4 +1,3 @@
-
+
Actual
-
+
Visual Regression Testing Tool
>
+
Renderer
diff --git a/test/runTest/server.js b/test/runTest/server.js
index 93fce69ff8..a6f7e29337 100644
--- a/test/runTest/server.js
+++ b/test/runTest/server.js
@@ -51,7 +51,7 @@ const {
checkStoreVersion,
clearStaledResults
} = require('./store');
-const {prepareEChartsLib, getActionsFullPath, fetchVersions, cleanBranchDirectory} = require('./util');
+const {prepareEChartsLib, getActionsFullPath, fetchVersions, cleanBranchDirectory, fetchRecentPRs} = require('./util');
const fse = require('fs-extra');
const fs = require('fs');
const open = require('open');
@@ -237,11 +237,11 @@ function checkPuppeteer() {
async function start() {
- // Clean branch directory before starting
- cleanBranchDirectory();
+ // Clean PR directories before starting
+ const {cleanPRDirectories} = require('./util');
+ cleanPRDirectories();
if (!checkPuppeteer()) {
- // TODO Check version.
console.error(`Can't find puppeteer >= 9.0.0, run 'npm install' to update in the 'test/runTest' folder`);
return;
}
@@ -342,38 +342,43 @@ async function start() {
try {
await prepareEChartsLib(data.expectedSource, data.expectedVersion, useCNMirror);
await prepareEChartsLib(data.actualSource, data.actualVersion, useCNMirror);
-
- // If aborted in the time downloading lib.
- if (isAborted) {
- return;
- }
-
- // TODO Should broadcast to all sockets.
- if (!checkStoreVersion(data)) {
- throw new Error('Unmatched store version and run version.');
- }
-
- await startTests(
- data.tests,
- io.of('/client'),
- {
- noHeadless: data.noHeadless,
- threadsCount: data.threads,
- replaySpeed: data.replaySpeed,
- actualSource: data.actualSource,
- actualVersion: data.actualVersion,
- expectedSource: data.expectedSource,
- expectedVersion: data.expectedVersion,
- renderer: data.renderer,
- useCoarsePointer: data.useCoarsePointer,
- noSave: false
- }
- );
}
catch (e) {
console.error(e);
+ // Send error to client
+ socket.emit('run_error', {
+ message: e.toString()
+ });
+ return;
}
+ // If aborted in the time downloading lib.
+ if (isAborted) {
+ return;
+ }
+
+ // TODO Should broadcast to all sockets.
+ if (!checkStoreVersion(data)) {
+ throw new Error('Unmatched store version and run version.');
+ }
+
+ await startTests(
+ data.tests,
+ io.of('/client'),
+ {
+ noHeadless: data.noHeadless,
+ threadsCount: data.threads,
+ replaySpeed: data.replaySpeed,
+ actualSource: data.actualSource,
+ actualVersion: data.actualVersion,
+ expectedSource: data.expectedSource,
+ expectedVersion: data.expectedVersion,
+ renderer: data.renderer,
+ useCoarsePointer: data.useCoarsePointer,
+ noSave: false
+ }
+ );
+
if (!isAborted) {
const deltaTime = Date.now() - startTime;
console.log('Finished in ', Math.round(deltaTime / 1000) + ' second');
diff --git a/test/runTest/store.js b/test/runTest/store.js
index f6bd6aeaa8..21d1d2d49e 100644
--- a/test/runTest/store.js
+++ b/test/runTest/store.js
@@ -77,11 +77,19 @@ class Test {
* It depends on two versions and rendering mode.
*/
function getRunHash(params) {
+ // Replace # with PR- in the hash to avoid URL issues
+ const expectedVersion = params.expectedSource === 'PR'
+ ? params.expectedVersion.replace('#', 'PR-')
+ : params.expectedVersion;
+ const actualVersion = params.actualSource === 'PR'
+ ? params.actualVersion.replace('#', 'PR-')
+ : params.actualVersion;
+
return [
params.expectedSource,
- params.expectedVersion,
+ expectedVersion,
params.actualSource,
- params.actualVersion,
+ actualVersion,
params.renderer,
params.useCoarsePointer
].join(TEST_HASH_SPLITTER);
@@ -92,11 +100,19 @@ function getRunHash(params) {
*/
function parseRunHash(str) {
const parts = str.split(TEST_HASH_SPLITTER);
+ // Convert back PR-123 to #123 for PR versions
+ const expectedVersion = parts[0] === 'PR'
+ ? parts[1].replace('PR-', '#')
+ : parts[1];
+ const actualVersion = parts[2] === 'PR'
+ ? parts[3].replace('PR-', '#')
+ : parts[3];
+
return {
expectedSource: parts[0],
- expectedVersion: parts[1],
+ expectedVersion: expectedVersion,
actualSource: parts[2],
- actualVersion: parts[3],
+ actualVersion: actualVersion,
renderer: parts[4],
useCoarsePointer: parts[5]
};
diff --git a/test/runTest/util.js b/test/runTest/util.js
index 832e60ad5c..f573c7935e 100644
--- a/test/runTest/util.js
+++ b/test/runTest/util.js
@@ -44,9 +44,13 @@ module.exports.fileNameFromTest = function (testName) {
function getVersionDir(source, version) {
version = version || 'local';
- const dir = source === 'branch' ? 'branch/' : '';
- return `tmp/__version__/${dir}${version}`;
-};
+ if (source === 'PR') {
+ // For PR preview artifacts
+ const prNumber = version.replace(/^#/, '');
+ return `tmp/__version__/pr-${prNumber}`;
+ }
+ return `tmp/__version__/${version}`;
+}
module.exports.getVersionDir = getVersionDir;
module.exports.getActionsFullPath = function (testName) {
@@ -57,11 +61,16 @@ module.exports.getEChartsTestFileName = function () {
return `echarts.test-${config.testVersion}.js`;
};
-// Clean branch directory at the start of initing because code in branch may change
-module.exports.cleanBranchDirectory = function () {
- const branchDir = path.join(__dirname, 'tmp/__version__/branch');
- if (fs.existsSync(branchDir)) {
- fse.removeSync(branchDir);
+// Clean PR directories at the start of initing because PR code may change
+module.exports.cleanPRDirectories = function () {
+ const baseDir = path.join(__dirname, 'tmp/__version__');
+ if (fs.existsSync(baseDir)) {
+ const dirs = fs.readdirSync(baseDir);
+ dirs.forEach(dir => {
+ if (dir.startsWith('pr-')) {
+ fse.removeSync(path.join(baseDir, dir));
+ }
+ });
}
}
@@ -70,57 +79,68 @@ module.exports.prepareEChartsLib = function (source, version, useCNMirror) {
const versionFolder = path.join(__dirname, getVersionDir(source, version));
const ecDownloadPath = `${versionFolder}/echarts.js`;
+ const testLibPath = `${versionFolder}/${module.exports.getEChartsTestFileName()}`;
+
+ // Check if both files exist and are not empty
+ if (fs.existsSync(ecDownloadPath) && fs.existsSync(testLibPath) &&
+ fs.statSync(ecDownloadPath).size > 0 && fs.statSync(testLibPath).size > 0) {
+ return Promise.resolve();
+ }
+
fse.ensureDirSync(versionFolder);
if (!version || version === 'local') {
// Developing version, make sure it's new build
- fse.copySync(path.join(__dirname, '../../dist/echarts.js'), `${versionFolder}/echarts.js`);
+ fse.copySync(path.join(__dirname, '../../dist/echarts.js'), ecDownloadPath);
let code = modifyEChartsCode(fs.readFileSync(ecDownloadPath, 'utf-8'));
- fs.writeFileSync(`${versionFolder}/${module.exports.getEChartsTestFileName()}`, code, 'utf-8');
+ fs.writeFileSync(testLibPath, code, 'utf-8');
return Promise.resolve();
}
return new Promise((resolve, reject) => {
- const testLibPath = `${versionFolder}/${module.exports.getEChartsTestFileName()}`;
- if (!fs.existsSync(ecDownloadPath)) {
- const file = fs.createWriteStream(ecDownloadPath);
- let url;
+ let url;
- if (source === 'branch') {
- url = `https://raw.githubusercontent.com/apache/echarts/${version}/dist/echarts.js`;
+ if (source === 'PR') {
+ const prNumber = version.replace(/^#/, '');
+ if (!/^\d+$/.test(prNumber)) {
+ reject('Invalid PR number format. Should be #123');
+ return;
}
- else {
- const isNightly = source === 'nightly';
- const packageName = isNightly ? 'echarts-nightly' : 'echarts';
- url = useCNMirror
- ? `https://registry.npmmirror.com/${packageName}/${version}/files/dist/echarts.js`
- : `https://unpkg.com/${packageName}@${version}/dist/echarts.js`;
+ url = `https://echarts-pr-${prNumber}.surge.sh/dist/echarts.js`;
+ }
+ else {
+ const isNightly = source === 'nightly';
+ const packageName = isNightly ? 'echarts-nightly' : 'echarts';
+ url = useCNMirror
+ ? `https://registry.npmmirror.com/${packageName}/${version}/files/dist/echarts.js`
+ : `https://unpkg.com/${packageName}@${version}/dist/echarts.js`;
+ }
+
+ console.log(`Downloading ECharts from ${url}`);
+ https.get(url, response => {
+ if (response.statusCode === 404) {
+ reject(`PR artifact doesn't exist at ${url}. Make sure the PR build is complete.`);
+ return;
}
- console.log(`Downloading ECharts from ${url}`);
- https.get(url, response => {
- if (response.statusCode === 404) {
- reject(`Failed to download: ${url} (404 Not Found)`);
+ let data = '';
+ response.on('data', chunk => {
+ data += chunk;
+ });
+
+ response.on('end', () => {
+ if (!data) {
+ reject(`Downloaded file is empty from ${url}`);
return;
}
- response.pipe(file);
-
- file.on('finish', () => {
- const code = modifyEChartsCode(fs.readFileSync(ecDownloadPath, 'utf-8'));
- fs.writeFileSync(testLibPath, code, 'utf-8');
- resolve();
- });
- }).on('error', (e) => {
- reject(`Failed to download from ${url}: ${e}`);
+ fs.writeFileSync(ecDownloadPath, data, 'utf-8');
+ const code = modifyEChartsCode(data);
+ fs.writeFileSync(testLibPath, code, 'utf-8');
+ resolve();
});
- }
- else {
- // Always do code modification.
- // In case we need to do replacement on old downloads.
- let code = modifyEChartsCode(fs.readFileSync(ecDownloadPath, 'utf-8'));
- fs.writeFileSync(testLibPath, code, 'utf-8');
- resolve();
- }
+ }).on('error', (e) => {
+ reject(`Failed to download from ${url}: ${e}`);
+ });
});
};