提交 c7f9a4be 编写于 作者: P pissang

test(visual): add test runs manager

上级 2a9fd861
......@@ -327,6 +327,10 @@ async function runTests(pendingTests) {
let runtimeCode = await buildRuntimeCode();
runtimeCode = `window.__TEST_PLAYBACK_SPEED__ = ${program.speed || 1};\n${runtimeCode}`;
process.on('exit', () => {
browser.close();
});
try {
for (let testOpt of pendingTests) {
console.log(`Running test: ${testOpt.name}, renderer: ${program.renderer}`);
......
......@@ -267,6 +267,11 @@ iframe {
overflow: overlay;
}
#tests-runs-dialog .el-dialog {
width: 90%;
max-width: 1400px;
}
::-webkit-scrollbar {
height: 8px;
......
......@@ -109,6 +109,10 @@ const app = new Vue({
previewIframeSrc: '',
previewTitle: '',
// List of all runs.
showRunsDialog: false,
testsRuns: [],
runConfig: Object.assign({
sortBy: 'name',
......@@ -336,6 +340,34 @@ const app = new Vue({
}
}
});
},
showAllTestsRuns() {
this.showRunsDialog = true;
socket.emit('getAllTestsRuns');
},
switchTestsRun(runResult) {
},
genTestsRunReport(runResult) {
},
delTestsRun(runResult) {
app.$confirm('Are you sure to delete this run?', 'Warn', {
confirmButtonText: 'Yes',
cancelButtonText: 'No',
center: true
}).then(value => {
const idx = this.testsRuns.indexOf(runResult);
if (idx >= 0) {
this.testsRuns.splice(idx, 1);
}
socket.emit('delTestsRun', {
id: runResult.id
});
}).catch(() => {});
}
}
});
......@@ -415,6 +447,10 @@ socket.on('abort', res => {
app.running = false;
});
socket.on('getAllTestsRuns_return', res => {
app.testsRuns = res.runs;
});
function updateUrl(notRefresh) {
const searchUrl = assembleParams({
test: app.currentTestName,
......
......@@ -37,6 +37,7 @@ under the License.
</div>
</el-header>
<el-container style="min-height: 0"> <!-- https://juejin.im/post/5c642f2ff265da2de660ecfc -->
<!-------- Tests List ----------->
<el-aside width="350px">
<div class="nav-toolbar">
<el-row class="filters" :gutter="10" :align="'middle'">
......@@ -119,6 +120,16 @@ under the License.
</el-button-group>
</div>
<div class="run-config-item">
<el-tooltip content="Show All Tests Runs">
<i
style="font-size: 20px; cursor: pointer;"
class="el-icon-files"
@click="showAllTestsRuns"
></i>
</el-tooltip>
</div>
<!-- <div class="run-config-item">
<el-checkbox v-model="runConfig.noHeadless">Replay</el-checkbox>
......@@ -169,7 +180,7 @@ under the License.
</div>
</div>
<!-------- Single Test Reusult ----------->
<div v-if="currentTest" class="test-result">
<div class="title">
<el-progress
......@@ -268,6 +279,33 @@ under the License.
</div>
<iframe :src="previewIframeSrc" width="800" height="600"></iframe>
</el-dialog>
<!-------- All Tests Runs ----------->
<el-dialog
id="tests-runs-dialog"
:visible.sync="showRunsDialog"
title="All Tests Runs"
>
<el-table :data="testsRuns">
<el-table-column property="expectedVersion" label="Expected"></el-table-column>
<el-table-column property="actualVersion" label="Actual"></el-table-column>
<el-table-column property="renderer" label="Renderer" width="100"></el-table-column>
<el-table-column property="lastRunTime" label="lastRunTime"></el-table-column>
<el-table-column property="total" label="Total Tests" width="100"></el-table-column>
<el-table-column property="finished" label="Finished" width="100"></el-table-column>
<el-table-column property="passed" label="Passed" width="100"></el-table-column>
<el-table-column
fixed="right"
label=""
width="160">
<template slot-scope="scope">
<el-button type="text" @click="switchTestsRun(scope.row)" size="small">View</el-button>
<el-button type="text" @click="genTestsRunReport(scope.row)" size="small">Report</el-button>
<el-button type="text" @click="delTestsRun(scope.row)" size="small">Delete</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</el-main>
</el-container>
</el-container>
......
......@@ -24,14 +24,22 @@ const path = require('path');
const {fork} = require('child_process');
const semver = require('semver');
const {port, origin} = require('./config');
const {getTestsList, updateTestsList, saveTestsList, mergeTestsResults, updateActionsMeta, getResultBaseDir} = require('./store');
const {
getTestsList,
updateTestsList,
saveTestsList,
mergeTestsResults,
updateActionsMeta,
getResultBaseDir,
getRunHash,
getAllTestsRuns,
delTestsRun
} = require('./store');
const {prepareEChartsLib, getActionsFullPath} = require('./util');
const fse = require('fs-extra');
const fs = require('fs');
const open = require('open');
const TEST_HASH_SPLITTER = '__';
function serve() {
const server = http.createServer((request, response) => {
return handler(request, response, {
......@@ -199,13 +207,6 @@ function checkPuppeteer() {
}
}
function getTestHash(params) {
return [
params.expectedVersion,
params.actualVersion,
params.renderer
].join(TEST_HASH_SPLITTER);
}
async function start() {
if (!checkPuppeteer()) {
......@@ -234,12 +235,12 @@ async function start() {
}
socket.on('setTestVersions', async (params) => {
if (_currentTestHash !== getTestHash(params)) {
if (_currentTestHash !== getRunHash(params)) {
abortTests();
}
await updateTestsList(
_currentTestHash = getTestHash(params),
_currentTestHash = getRunHash(params),
!running // Set to unsettled if not running
);
......@@ -249,6 +250,17 @@ async function start() {
});
});
socket.on('getAllTestsRuns', async () => {
socket.emit('getAllTestsRuns_return', {
runs: await getAllTestsRuns()
});
});
socket.on('delTestsRun', async (params) => {
delTestsRun(params.id);
console.log('Deleted', params.id);
});
socket.on('run', async data => {
let startTime = Date.now();
......
......@@ -20,14 +20,18 @@
const path = require('path');
const fse = require('fs-extra');
const fs = require('fs');
const glob = require('glob');
const globby = require('globby');
const {testNameFromFile} = require('./util');
const util = require('util');
const {blacklist, SVGBlacklist} = require('./blacklist');
let _tests = [];
let _testsMap = {};
let _testHash = '';
let _runHash = '';
const RESULT_FILE_NAME = '__result__.json';
const RESULTS_ROOT_DIR = path.join(__dirname, 'tmp', 'result');
const TEST_HASH_SPLITTER = '__';
class Test {
constructor(fileUrl) {
......@@ -59,14 +63,40 @@ class Test {
}
}
/**
* hash of each run is mainly for storing the results.
* It depends on two versions and rendering mode.
*/
function getRunHash(params) {
return [
params.expectedVersion,
params.actualVersion,
params.renderer
].join(TEST_HASH_SPLITTER);
}
/**
* Parse versions and rendering mode from run hash.
*/
function parseRunHash(str) {
const parts = str.split(TEST_HASH_SPLITTER);
return {
expectedVersion: parts[0],
actualVersion: parts[1],
renderer: parts[2]
};
}
function getResultBaseDir() {
return path.join(__dirname, 'tmp', 'result', _testHash);
return path.join(RESULTS_ROOT_DIR, _runHash);
}
module.exports.getResultBaseDir = getResultBaseDir;
module.exports.getRunHash = getRunHash;
function getCacheFilePath(baseDir) {
return path.join(getResultBaseDir(), '__result__.json');;
function getCacheFilePath() {
return path.join(getResultBaseDir(), RESULT_FILE_NAME);
}
module.exports.getTestsList = function () {
......@@ -78,10 +108,10 @@ module.exports.getTestByFileUrl = function (url) {
};
module.exports.updateTestsList = async function (
testHash,
runHash,
setPendingTestToUnsettled
) {
_testHash = testHash;
_runHash = runHash;
_tests = [];
_testsMap = {};
_testsExists = {};
......@@ -105,7 +135,7 @@ module.exports.updateTestsList = async function (
catch(e) {
}
// Find if there is new html file
const files = await util.promisify(glob)('**.html', { cwd: path.resolve(__dirname, '../') });
const files = await globby('**.html', { cwd: path.resolve(__dirname, '../') });
files.forEach(fileUrl => {
if (blacklist.includes(fileUrl)) {
return;
......@@ -169,4 +199,55 @@ module.exports.updateActionsMeta = function (testName, actions) {
fs.writeFileSync(metaPath, JSON.stringify(
metaData, Object.keys(metaData).sort((a, b) => a.localeCompare(b)), 2
), 'utf-8');
};
\ No newline at end of file
};
/**
* Get results of all runs
* @return [ { id, expectedVersion, actualVersion, renderer, lastRunTime, total, finished, passed } ]
*/
module.exports.getAllTestsRuns = async function () {
const dirs = await globby('*', { cwd: RESULTS_ROOT_DIR, onlyDirectories: true });
return dirs.map((dir) => {
const params = parseRunHash(dir);
const resultJson = JSON.parse(fs.readFileSync(path.join(
RESULTS_ROOT_DIR,
dir,
RESULT_FILE_NAME
), 'utf-8'));
const total = resultJson.length;
let lastRunTime = 0;
let finishedCount = 0;
let passedCount = 0;
resultJson.forEach(test => {
lastRunTime = Math.max(test.lastRun, lastRunTime);
if (test.status === 'finished') {
finishedCount++;
let passed = true;
test.results.forEach(result => {
// Threshold?
if (result.diffRatio > 0.0001) {
passed = false;
}
});
if (passed) {
passedCount++;
}
}
});
params.lastRunTime = lastRunTime > 0 ? new Date(lastRunTime).toISOString() : 'N/A';
params.total = total;
params.passed = passedCount;
params.finished = finishedCount;
params.id = dir;
return params;
});
}
module.exports.delTestsRun = async function (hash) {
fse.removeSync(path.join(RESULTS_ROOT_DIR, hash));
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册