提交 a7c84f74 编写于 作者: P pissang

test: add select change event. fix page size issue caused by scrollbar. fix some html cases.

vertical scroll bar may lead to chart container width less than 800. And it's not same in the iframe and puppeteer enviroment. It may cause recorded interaction can't replay correctly.
上级 ed15eebd
......@@ -33,7 +33,7 @@ under the License.
margin: 0;
}
#main {
width: 1000px;
width: 100%;
background: #fff;
}
</style>
......
......@@ -77,6 +77,17 @@
});
}
// Not let scrollbar affect page size.
// It will AFFECT interaction in the automatic testing.
// TODO it only works on webkit
var styleEl = document.createElement('style');
document.head.appendChild(styleEl);
styleEl.innerHTML = `
body {
overflow: overlay!important;
}
`;
// It is not a good solution.
// Do not need it any more:
......
......@@ -92,6 +92,11 @@ module.exports = class Timeline {
case 'screenshot':
await takeScreenshot();
break;
case 'valuechange':
if (op.target === 'select') {
await page.select(op.selector, op.value);
}
break;
}
this._currentOpIndex++;
......
const glob = require('glob');
const fs = require('fs');
const result = {};
glob('*.json', (err, files) => {
files.forEach(file => {
if (file.match('__meta__')) {
return;
}
const actions = JSON.parse(fs.readFileSync(file, 'utf-8'));
result[file.replace(/.json$/, '')] = actions.length;
});
fs.writeFileSync('__meta__.json', JSON.stringify(result, null, 2), 'utf-8');
});
\ No newline at end of file
......@@ -5,6 +5,7 @@ const fs = require('fs');
const path = require('path');
const program = require('commander');
const compareScreenshot = require('./compareScreenshot');
const {installMouseHelper} = require('./installMouseHelper');
const {testNameFromFile, fileNameFromTest, getVersionDir, buildRuntimeCode, waitTime} = require('./util');
const {origin} = require('./config');
const Timeline = require('./Timeline');
......@@ -18,13 +19,11 @@ program
program.parse(process.argv);
program.speed = +program.speed || 1;
console.log(program.speed);
if (!program.tests) {
throw new Error('Tests are required');
}
function getScreenshotDir() {
return 'tmp/__screenshot__';
}
......@@ -119,6 +118,8 @@ async function runTestPage(browser, testOpt, version, runtimeCode) {
const page = await browser.newPage();
page.setRequestInterception(true);
page.on('request', replaceEChartsVersion);
installMouseHelper(page);
await page.evaluateOnNewDocument(runtimeCode);
page.on('console', msg => {
......
......@@ -75,7 +75,11 @@
:percentage="test.percentage"
:status="test.summary"
></el-progress>
<a :href="'#' + test.name" class="menu-link">{{test.name}}<i v-if="test.hasActions" class="el-icon-video-camera-solid"></i></a>
<a :href="'#' + test.name" class="menu-link">
{{test.name}}
<i v-if="test.actions" class="el-icon-video-camera-solid"></i>
<span v-if="test.actions" style="font-size: 12px;">({{test.actions}})</span>
</a>
</li>
</ul>
</el-aside>
......
// https://gist.github.com/aslushnikov/94108a4094532c7752135c42e12a00eb
// This injects a box into the page that moves with the mouse;
// Useful for debugging
async function installMouseHelper(page) {
await page.evaluateOnNewDocument(() => {
// Install mouse helper only for top-level frame.
if (window !== window.parent)
return;
window.addEventListener('DOMContentLoaded', () => {
const box = document.createElement('puppeteer-mouse-pointer');
const styleElement = document.createElement('style');
styleElement.innerHTML = `
puppeteer-mouse-pointer {
pointer-events: none;
position: absolute;
top: 0;
z-index: 10000;
left: 0;
width: 20px;
height: 20px;
background: rgba(0,0,0,.4);
border: 1px solid white;
border-radius: 10px;
margin: -10px 0 0 -10px;
padding: 0;
transition: background .2s, border-radius .2s, border-color .2s;
}
puppeteer-mouse-pointer.button-1 {
transition: none;
background: rgba(0,0,0,0.9);
}
puppeteer-mouse-pointer.button-2 {
transition: none;
border-color: rgba(0,0,255,0.9);
}
puppeteer-mouse-pointer.button-3 {
transition: none;
border-radius: 4px;
}
puppeteer-mouse-pointer.button-4 {
transition: none;
border-color: rgba(255,0,0,0.9);
}
puppeteer-mouse-pointer.button-5 {
transition: none;
border-color: rgba(0,255,0,0.9);
}
`;
document.head.appendChild(styleElement);
document.body.appendChild(box);
document.addEventListener('mousemove', event => {
box.style.left = event.pageX + 'px';
box.style.top = event.pageY + 'px';
updateButtons(event.buttons);
}, true);
document.addEventListener('mousedown', event => {
updateButtons(event.buttons);
box.classList.add('button-' + event.which);
}, true);
document.addEventListener('mouseup', event => {
updateButtons(event.buttons);
box.classList.remove('button-' + event.which);
}, true);
function updateButtons(buttons) {
for (let i = 0; i < 5; i++)
box.classList.toggle('button-' + i, buttons & (1 << i));
}
}, false);
});
};
module.exports = {installMouseHelper};
......@@ -64,7 +64,8 @@
<li v-for="test in tests">
<a :href="'#' + test.name" :class="{active: test.name === currentTestName}">
{{test.name}}
<i v-if="test.hasActions" class="el-icon-video-camera-solid"></i>
<i v-if="test.actions" class="el-icon-video-camera-solid"></i>
<span v-if="test.actions" style="font-size: 14px;">({{test.actions}})</span>
</a>
</li>
</ul>
......@@ -80,6 +81,7 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.11.1/lib/theme-chalk/index.css">
<script src="https://cdn.jsdelivr.net/npm/element-ui@2.11.1/lib/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.js"></script>
<script src="recorder.js"></script>
<link rel="stylesheet" href="recorder.css">
......
const socket = io('/recorder');
function getUniqueSelector(el) {
if (el.tagName.toLowerCase() === 'body') {
return '';
}
let selector = '';
if (el.id) {
// id has highest priority.
return el.id;
}
else {
selector = el.tagName.toLowerCase();
for (let className of el.classList) {
selector += '.' + className;
}
}
let parentSelector = el.parentNode && getUniqueSelector(el.parentNode);
if (parentSelector) {
selector = parentSelector + '>' + selector;
}
return selector;
}
const app = new Vue({
el: '#app',
data: {
......@@ -66,6 +88,9 @@ const app = new Vue({
this.deletePopoverVisible = false;
let idx = _.findIndex(this.actions, action => action.name === actionName);
if (idx >= 0) {
if (this.currentAction === this.actions[idx]) {
this.currentAction = this.actions[idx + 1] || this.actions[idx - 1];
}
this.actions.splice(idx, 1);
saveData();
}
......@@ -116,9 +141,24 @@ function saveData() {
testName: app.currentTestName,
actions: app.actions
});
let test = app.tests.find(testOpt => testOpt.name === app.currentTestName);
test.actions = app.actions.length;
}
}
function getEventTime() {
return Date.now() - app.recordingAction.timestamp;
}
function notify(title, message) {
app.$notify.info({
title,
message,
position: 'top-left',
customClass: 'op-notice'
});
}
function keyboardRecordingHandler(e) {
if (e.key.toLowerCase() === 'r' && e.shiftKey) {
if (!app.recordingAction) {
......@@ -147,13 +187,9 @@ function keyboardRecordingHandler(e) {
if (app.recordingAction) {
app.recordingAction.ops.push({
type: 'screenshot',
time: Date.now() - app.recordingAction.timestamp
});
app.$notify.info({
title: 'screenshot',
position: 'top-left',
customClass: 'op-notice'
time: getEventTime()
});
notify('screenshot', '');
}
}
}
......@@ -161,9 +197,10 @@ function keyboardRecordingHandler(e) {
function recordIframeEvents(iframe, app) {
let innerDocument = iframe.contentWindow.document;
function addMouseOp(type, e) {
if (app.recordingAction) {
let time = Date.now() - app.recordingAction.timestamp;
let time = getEventTime();
app.recordingAction.ops.push({
type,
time: time,
......@@ -171,31 +208,69 @@ function recordIframeEvents(iframe, app) {
y: e.clientY
});
if (type === 'mouseup' && app.config.screenshotAfterMouseUp) {
// Add a auto screenshot after mouseup
app.recordingAction.ops.push({
time: time + 1, // TODO, Add delay time?
time: time + 1,
delay: app.config.screenshotDelay,
type: 'screenshot-auto'
});
}
app.$notify.info({
title: type,
message: `(x: ${e.clientX}, y: ${e.clientY})`,
position: 'top-left',
customClass: 'op-notice'
});
notify(type, `(x: ${e.clientX}, y: ${e.clientY})`);
}
}
innerDocument.addEventListener('keyup', keyboardRecordingHandler);
let preventRecordingFollowingMouseEvents = false;
innerDocument.body.addEventListener('mousemove', _.throttle(e => {
addMouseOp('mousemove', e);
if (!preventRecordingFollowingMouseEvents) {
addMouseOp('mousemove', e);
}
}, 200), true);
innerDocument.body.addEventListener('mousedown', e => {
// Can't recording mouse event on select.
// So just prevent it and add a specific 'select' change event.
if (e.target.tagName.toLowerCase() === 'select') {
preventRecordingFollowingMouseEvents = true;
return;
}
addMouseOp('mousedown', e);
}, true);
innerDocument.body.addEventListener('mouseup', e => {
if (!preventRecordingFollowingMouseEvents) {
addMouseOp('mouseup', e);
}
preventRecordingFollowingMouseEvents = false;
}, true);
['mouseup', 'mousedown'].forEach(eventType => {
innerDocument.body.addEventListener(eventType, e => {
addMouseOp(eventType, e);
}, true);
innerDocument.body.addEventListener('change', e => {
if (app.recordingAction) {
let selector = getUniqueSelector(e.target);
let time = getEventTime();
let commonData = {
type: 'valuechange',
selector,
value: e.target.value,
time: time
};
if (e.target.tagName.toLowerCase() === 'select') {
commonData.target = 'select';
notify('valuechange', `select(${commonData.value})`);
}
if (commonData.target) {
app.recordingAction.ops.push(commonData);
if (app.config.screenshotAfterMouseUp) {
// Add a auto screenshot after mouseup
app.recordingAction.ops.push({
time: time + 1,
delay: app.config.screenshotDelay,
type: 'screenshot-auto'
});
}
}
}
});
}
......@@ -219,7 +294,6 @@ function init() {
let $iframe = getIframe();
$iframe.onload = () => {
console.log('loaded:' + app.currentTestName);
recordIframeEvents($iframe, app);
};
......
......@@ -5,10 +5,9 @@ if (typeof __TEST_PLAYBACK_SPEED__ === 'undefined') {
window.__TEST_PLAYBACK_SPEED__ = 1;
}
let myRandom = new seedrandom('echarts-test');
// Fixed random generator
Math.random = function () {
const val = myRandom();
return val;
};
};
\ No newline at end of file
......@@ -5,7 +5,7 @@ const path = require('path');
const {fork} = require('child_process');
const semver = require('semver');
const {port, origin} = require('./config');
const {getTestsList, updateTestsList, saveTestsList, mergeTestsResults} = require('./store');
const {getTestsList, updateTestsList, saveTestsList, mergeTestsResults, updateActionsMeta} = require('./store');
const {prepareEChartsVersion, getActionsFullPath} = require('./util');
const fse = require('fs-extra');
const fs = require('fs');
......@@ -205,6 +205,7 @@ async function start() {
JSON.stringify(data.actions),
'utf-8'
);
updateActionsMeta(data.testName, data.actions);
}
// TODO Broadcast the change?
});
......@@ -224,7 +225,8 @@ async function start() {
try {
await startTests([data.testName], socket, {
noHeadless: true,
threadsCount: 1
threadsCount: 1,
replaySpeed: 2
});
}
catch (e) { console.error(e); }
......@@ -236,7 +238,7 @@ async function start() {
tests: getTestsList().map(test => {
return {
name: test.name,
hasActions: test.hasActions
actions: test.actions
};
})
});
......
......@@ -62,18 +62,17 @@ module.exports.updateTestsList = async function () {
_tests.push(test);
_testsMap[fileUrl] = test;
});
let statAsync = util.promisify(fs.stat);
// Find if file has actions.
await Promise.all(_tests.map(testOpt => {
return statAsync(path.join(__dirname, 'actions', testOpt.name + '.json'))
.then(() => {
testOpt.hasActions = true;
})
.catch(() => {
testOpt.hasActions = false;
});
}));
let actionsMetaData = {};
let metaPath = path.join(__dirname, 'actions/__meta__.json');
try {
actionsMetaData = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
}
catch(e) {}
_tests.forEach(testOpt => {
testOpt.actions = actionsMetaData[testOpt.name] || 0;
});
return _tests;
};
......@@ -87,4 +86,17 @@ module.exports.mergeTestsResults = function (testsResults) {
Object.assign(_testsMap[testResult.fileUrl], testResult);
}
});
};
module.exports.updateActionsMeta = function (testName, actions) {
let metaData;
let metaPath = path.join(__dirname, 'actions/__meta__.json');
try {
metaData = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
}
catch(e) {
metaData = {};
}
metaData[testName] = actions.length;
fs.writeFileSync(metaPath, JSON.stringify(metaData, null, 2), 'utf-8');
};
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册