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}`); + }); }); };