This is an automated email from the ASF dual-hosted git repository.

shenyi pushed a commit to branch test-autorun
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git

commit 9921edfa40bbdc58049144d7bc016c665a3e195a
Author: pissang <bm2736...@gmail.com>
AuthorDate: Sat Sep 7 00:47:57 2019 +0800

    test: add interaction record tool, add headless control.
---
 package.json                       |   1 +
 test/runTest/blacklist.js          |   6 +-
 test/runTest/cli.js                |  21 ++++--
 test/runTest/client/client.css     |  28 ++++----
 test/runTest/client/client.js      |  22 ++++--
 test/runTest/client/index.html     |  16 +++--
 test/runTest/recorder/index.html   |  57 +++++++++++++++
 test/runTest/recorder/recorder.css | 110 ++++++++++++++++++++++++++++
 test/runTest/recorder/recorder.js  | 142 +++++++++++++++++++++++++++++++++++++
 test/runTest/server.js             |  70 +++++++++++++-----
 test/runTest/store.js              |   2 +-
 test/runTest/util.js               |   4 ++
 12 files changed, 430 insertions(+), 49 deletions(-)

diff --git a/package.json b/package.json
index 16fde6c..463980d 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
     "rollup-plugin-node-resolve": "3.0.0",
     "rollup-plugin-uglify": "2.0.1",
     "seedrandom": "^3.0.3",
+    "semver": "^6.3.0",
     "serve-handler": "^6.1.1",
     "slugify": "^1.3.4",
     "socket.io": "^2.2.0"
diff --git a/test/runTest/blacklist.js b/test/runTest/blacklist.js
index 9e9ad44..90982d8 100644
--- a/test/runTest/blacklist.js
+++ b/test/runTest/blacklist.js
@@ -1,4 +1,8 @@
 module.exports = [
     '-cases.html',
-    'geo-random-stream.html'
+    'geo-random-stream.html',
+    'chord.html',
+    'lines-ny.html',
+    'lines-ny-appendData.html',
+    'linesGL-ny-appendData.html'
 ];
diff --git a/test/runTest/cli.js b/test/runTest/cli.js
index e44ebe7..574cda8 100644
--- a/test/runTest/cli.js
+++ b/test/runTest/cli.js
@@ -6,6 +6,8 @@ const path = require('path');
 const compareScreenshot = require('./compareScreenshot');
 const {getTestName, getVersionDir, buildRuntimeCode} = require('./util');
 const {origin} = require('./config');
+const program = require('commander');
+
 
 function getScreenshotDir() {
     return 'tmp/__screenshot__';
@@ -231,8 +233,8 @@ async function runTest(browser, testOpt, runtimeCode) {
 
 }
 
-async function runTests(pendingTests) {
-    const browser = await puppeteer.launch({ headless: true });
+async function runTests(pendingTests, headless) {
+    const browser = await puppeteer.launch({ headless: headless });
     // TODO Not hardcoded.
     // let runtimeCode = fs.readFileSync(path.join(__dirname, 
'tmp/testRuntime.js'), 'utf-8');
     let runtimeCode = await buildRuntimeCode();
@@ -260,12 +262,21 @@ async function runTests(pendingTests) {
 }
 
 // Handling input arguments.
-const testsFileUrlList = process.argv[2] || '';
-runTests(testsFileUrlList.split(',').map(fileUrl => {
+program
+    .option('-t, --tests <tests>', 'Tests names list')
+    .option('--no-headless', 'Not headless');
+
+program.parse(process.argv);
+
+if (!program.tests) {
+    throw new Error('Tests are required');
+}
+
+runTests(program.tests.split(',').map(fileUrl => {
     return {
         fileUrl,
         name: getTestName(fileUrl),
         results: [],
         status: 'unsettled'
     };
-}));
\ No newline at end of file
+}), program.headless);
\ No newline at end of file
diff --git a/test/runTest/client/client.css b/test/runTest/client/client.css
index cfaad1a..1991711 100644
--- a/test/runTest/client/client.css
+++ b/test/runTest/client/client.css
@@ -140,24 +140,28 @@
 
 
 ::-webkit-scrollbar {
-    height:8px;
-    width:8px;
-    transition:all 0.3s ease-in-out;
-    border-radius:2px;
-    background: transparent;
+    height: 8px;
+    width: 8px;
+    -webkit-transition: all 0.3s ease-in-out;
+    transition: all 0.3s ease-in-out;
+    -webkit-border-radius: 2px;
+    border-radius: 2px
 }
 
 ::-webkit-scrollbar-button {
-    display:none;
+    display: none
 }
 
 ::-webkit-scrollbar-thumb {
-    width:8px;
-    min-height:15px;
-    background:rgba(50, 50, 50, 0.6) !important;
-    transition:all 0.3s ease-in-out;border-radius:2px;
+    width: 8px;
+    min-height: 15px;
+    background: rgba(50,50,50,0.6) !important;
+    -webkit-transition: all 0.3s ease-in-out;
+    transition: all 0.3s ease-in-out;
+    -webkit-border-radius: 2px;
+    border-radius: 2px
 }
 
 ::-webkit-scrollbar-thumb:hover {
-    background:rgba(0, 0, 0, 0.5) !important;
-}
\ No newline at end of file
+    background: rgba(0,0,0,0.5) !important
+}
diff --git a/test/runTest/client/client.js b/test/runTest/client/client.js
index 0dc2607..38f4a75 100644
--- a/test/runTest/client/client.js
+++ b/test/runTest/client/client.js
@@ -1,4 +1,4 @@
-const socket = io();
+const socket = io('/client');
 
 function processTestsData(tests, oldTestsData) {
     tests.forEach((test, idx) => {
@@ -44,7 +44,9 @@ socket.on('connect', () => {
             running: false,
 
             allSelected: false,
-            lastSelectedIndex: -1
+            lastSelectedIndex: -1,
+
+            noHeadless: false,
         },
         computed: {
             tests() {
@@ -76,7 +78,11 @@ socket.on('connect', () => {
             },
 
             currentTestUrl() {
-                return window.location.origin + '/test/' + 
this.currentTest.fileUrl;
+                return window.location.origin + '/test/' + 
this.currentTestName + '.html';
+            },
+
+            currentTestRecordUrl() {
+                return window.location.origin + 
'/test/runTest/recorder/index.html#' + this.currentTestName;
             },
 
             isSelectAllIndeterminate: {
@@ -132,7 +138,7 @@ socket.on('connect', () => {
                 });
                 if (tests.length > 0) {
                     this.running = true;
-                    socket.emit('run', tests);
+                    socket.emit('run', {tests, noHeadless: this.noHeadless});
                 }
             },
             stopTests() {
@@ -154,7 +160,10 @@ socket.on('connect', () => {
                 center: true
             }).then(value => {
                 app.running = true;
-                socket.emit('run', msg.tests.map(test => test.name));
+                socket.emit('run', {
+                    tests: msg.tests.map(test => test.name),
+                    noHeadless: this.noHeadless
+                });
             }).catch(() => {});
         }
         // TODO
@@ -172,8 +181,7 @@ socket.on('connect', () => {
     });
 
     function updateTestHash() {
-        let testName = window.location.hash.slice(1);
-        app.currentTestName = testName;
+        app.currentTestName = window.location.hash.slice(1);
     }
 
     updateTestHash();
diff --git a/test/runTest/client/index.html b/test/runTest/client/index.html
index c45b49d..07880e0 100644
--- a/test/runTest/client/index.html
+++ b/test/runTest/client/index.html
@@ -20,15 +20,18 @@
                     <el-input v-model="searchString" size="mini" 
placeholder="Filter Tests"></el-input>
                     <div class="controls">
                         <el-checkbox :indeterminate="isSelectAllIndeterminate" 
v-model="allSelected" @change="handleSelectAllChange"></el-checkbox>
-                        <el-button-group>
+                        <el-button
+                            title="Sort By Failue Percentage" 
@click="toggleSort" circle size="mini" type="primary" icon="el-icon-sort"
+                        ></el-button>
+
+                        <el-button-group style="margin-left: 10px">
                             <el-button title="Run Selected" 
@click="runSelectedTests" :loading="running" circle size="mini" type="primary" 
icon="el-icon-caret-right"></el-button>
                             <el-button v-if="running" title="Run Selected" 
@click="stopTests" circle size="mini" type="primary" 
icon="el-icon-close"></el-button>
                         </el-button-group>
 
-                        <el-button
-                            style="margin-left: 10px"
-                            title="Sort By Failue Percentage" 
@click="toggleSort" circle size="mini" type="primary" icon="el-icon-sort"
-                        ></el-button>
+                        <el-checkbox v-model="noHeadless" 
label="Playback"></el-checkbox>
+
+
                         <!-- <el-button-group>
                             <el-button title="Select All" 
@click="selectAllTests" circle size="mini" type="primary" 
icon="el-icon-check"></el-button>
                             <el-button title="Unselect All" 
@click="unselectAllTests" circle size="mini" type="primary" 
icon="el-icon-close"></el-button>
@@ -55,7 +58,6 @@
                             :status="test.summary"
                         ></el-progress>
                         <a :href="'#' + test.name" 
class="menu-link">{{test.name}}</a>
-
                     </li>
                 </ul>
             </el-aside>
@@ -64,6 +66,7 @@
                     <div class="title">
                         <h3>{{currentTest.name}}</h3>
                         <a target="_blank" :href="currentTestUrl"><i 
class="el-icon-link"></i>Open Demo</a>
+                        <a target="_blank" :href="currentTestRecordUrl"><i 
class="el-icon-video-camera"></i>Record Interaction</a>
                     </div>
 
                     <div class="test-screenshots" v-for="result in 
currentTest.results">
@@ -162,7 +165,6 @@
 <script src="client.js"></script>
 
 <link rel="stylesheet" href="client.css">
-<link rel="stylesheet" href="">
 
 </body>
 </html>
\ No newline at end of file
diff --git a/test/runTest/recorder/index.html b/test/runTest/recorder/index.html
new file mode 100644
index 0000000..883828d
--- /dev/null
+++ b/test/runTest/recorder/index.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
+</head>
+<body>
+<div id="app" style="display: none">
+    <iframe :src="url"></iframe>
+    <div id="recording-status" v-if="currentAction">
+        <el-button
+            :icon="recordingAction ? 'el-icon-video-camera' : 
'el-icon-video-camera'"
+            class="recording-button" circle :type="recordingAction ? 'danger' 
: 'info'"
+        >
+        </el-button>
+        <div class="hint"><span class="emphasis">SHIFT + R</span>to 
{{recordingAction ? 'stop' : 'start'}} recording</div>
+    </div>
+    <div id="actions">
+        <el-card class="box-card">
+            <div slot="header" class="clearfix">
+                <span>Actions</span>
+                <el-button style="float: right;" type="primary" size="mini" 
icon="el-icon-circle-plus" @click="newAction">New</el-button>
+            </div>
+            <div v-for="action in actions" :class="{'action-item': true, 
'active': action.name === currentAction.name}" 
@click.self="select(action.name)">
+                {{action.name}}
+
+                <div style="float:right" class="operations">
+                    <span>Data ({{action.ops.length}})</span>
+                    <el-popover>
+                        <div style="text-align: center">{ scrollX: 
{{action.scrollX}}, scrollY: {{action.scrollY}} }</div>
+                        <div v-for="op in action.ops" style="text-align: 
center">{{op}}</div>
+                        <!-- <el-button slot="reference" size="mini">OP 
({{action.ops.length}})</el-button> -->
+                        <i slot="reference" class="el-icon-caret-bottom"></i>
+                    </el-popover>
+                    <i slot="reference" class="el-icon-delete" 
@click="doDelete(action.name)"></i>
+                </div>
+            </div>
+        </el-card>
+
+    </div>
+</div>
+
+<script 
src="../../../node_modules/socket.io-client/dist/socket.io.js"></script>
+<script src="https://unpkg.com/vue@2.6.10/dist/vue.js";></script>
+
+<!-- Element UI -->
+<link rel="stylesheet" 
href="https://unpkg.com/element-ui@2.11.1/lib/theme-chalk/index.css";>
+<script src="https://unpkg.com/element-ui@2.11.1/lib/index.js";></script>
+<script src="https://unpkg.com/lodash@4.17.15/lodash.js";></script>
+
+<script src="recorder.js"></script>
+
+<link rel="stylesheet" href="recorder.css">
+
+</body>
+</html>
\ No newline at end of file
diff --git a/test/runTest/recorder/recorder.css 
b/test/runTest/recorder/recorder.css
new file mode 100644
index 0000000..66d50f0
--- /dev/null
+++ b/test/runTest/recorder/recorder.css
@@ -0,0 +1,110 @@
+* {
+    font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans 
GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
+}
+
+iframe {
+    border: none;
+    position: absolute;
+    top: 50px;
+    left: 50%;
+
+    width: 800px;
+    height: 600px;
+    margin-left: -400px;
+
+    box-shadow: 0 0 30px rgba(0, 0, 0, 0.2);
+    overflow-x: hidden;
+}
+
+#recording-status {
+    position: absolute;
+    bottom: 30px;
+    width: 100%;
+    border-radius: 20px;
+    text-align: center;
+}
+
+#recording-status .recording-button {
+    width: 80px;;
+    height: 80px;;
+    border-radius: 50px;
+    font-size: 50px;
+}
+
+#recording-status .hint {
+    font-size: 26px;
+    font-weight: 200;
+    margin: 20px;
+}
+
+#recording-status .hint .emphasis {
+    color: #F56C6C;
+    font-weight: 400;
+    margin: 0 10px;
+}
+
+#actions {
+    position: fixed;
+    right: 10px;
+    width: 300px;
+}
+
+#actions .action-item {
+    line-height: 40px;
+    padding: 0 20px;
+    margin: 0 -20px;
+    cursor: pointer;
+}
+
+
+#actions .action-item:hover {
+    background: #eee;
+}
+
+#actions .action-item.active {
+    background: #409Eff;
+    color: #ffffff;
+}
+
+#actions .action-item .operations {
+    height: 30px;
+    font-size: 14px;
+}
+#actions .action-item .operations>* {
+    display: inline-block;
+    vertical-align: middle;
+}
+
+#actions .action-item .operations .el-icon-delete {
+    color: #F56C6C;
+    margin-left: 10px;
+}
+
+
+
+::-webkit-scrollbar {
+    height: 8px;
+    width: 8px;
+    -webkit-transition: all 0.3s ease-in-out;
+    transition: all 0.3s ease-in-out;
+    -webkit-border-radius: 2px;
+    border-radius: 2px
+}
+
+::-webkit-scrollbar-button {
+    display: none
+}
+
+::-webkit-scrollbar-thumb {
+    width: 8px;
+    min-height: 15px;
+    background: rgba(50,50,50,0.6) !important;
+    -webkit-transition: all 0.3s ease-in-out;
+    transition: all 0.3s ease-in-out;
+    -webkit-border-radius: 2px;
+    border-radius: 2px
+}
+
+::-webkit-scrollbar-thumb:hover {
+    background: rgba(0,0,0,0.5) !important
+}
diff --git a/test/runTest/recorder/recorder.js 
b/test/runTest/recorder/recorder.js
new file mode 100644
index 0000000..73d3f22
--- /dev/null
+++ b/test/runTest/recorder/recorder.js
@@ -0,0 +1,142 @@
+const socket = io('/recorder');
+
+const app = new Vue({
+    el: '#app',
+    data: {
+        currentTestName: '',
+        actions: [],
+        currentAction: null,
+        recordingAction: null,
+
+        deletePopoverVisible: false
+    },
+    computed: {
+        url() {
+            if (!this.currentTestName) {
+                return '';
+            }
+            return window.location.origin + '/test/' + this.currentTestName + 
'.html';
+        }
+    },
+    methods: {
+        newAction() {
+            this.currentAction = {
+                name: 'Action ' + (this.actions.length + 1),
+                ops: []
+            };
+            this.actions.push(this.currentAction);
+        },
+        select(actionName) {
+            this.currentAction = this.actions.find(action => {
+                return action.name === actionName;
+            });
+        },
+
+        doDelete(actionName) {
+            app.$confirm('Aure you sure?', 'Delete this action', {
+                confirmButtonText: 'Yes',
+                cancelButtonText: 'No',
+                type: 'warning'
+            }).then(() => {
+                this.deletePopoverVisible = false;
+                let idx = _.findIndex(this.actions, action => action.name === 
actionName);
+                if (idx >= 0) {
+                    this.actions.splice(idx, 1);
+                    saveData();
+                }
+            }).catch(e => {});
+        }
+    }
+});
+
+function saveData() {
+    // Save
+    if (app.currentTestName) {
+        socket.emit('save', {
+            testName: app.currentTestName,
+            actions: app.actions
+        });
+    }
+}
+
+function recordIframeEvents(iframe, app) {
+    let innerDocument = iframe.contentWindow.document;
+
+    let startTime;
+    innerDocument.addEventListener('keyup', e => {
+        if (e.key.toLowerCase() === 'r' && e.shiftKey) {
+            if (!app.recordingAction) {
+                app.recordingAction = app.currentAction;
+                if (app.recordingAction) {
+                    app.recordingAction.ops = [];
+                    app.recordingAction.scrollY = iframe.contentWindow.scrollY;
+                    app.recordingAction.scrollX = iframe.contentWindow.scrollX;
+                    startTime = app.recordingAction.timestamp = Date.now();
+                }
+            }
+            else {
+                app.recordingAction = null;
+                saveData();
+            }
+            // Get scroll
+        }
+    });
+
+    function addMouseOp(type, e) {
+        if (app.recordingAction) {
+            app.recordingAction.ops.push({
+                type,
+                time: Date.now() - startTime,
+                x: e.clientX,
+                y: e.clientY
+            });
+            app.$notify.info({
+                title: type,
+                message: `{x: ${e.clientX}, y: ${e.clientY}}`,
+                position: 'top-left'
+            });
+        }
+    }
+
+    innerDocument.body.addEventListener('mousemove', _.throttle(e => {
+        addMouseOp('mousemove', e);
+    }, 200), true);
+
+    ['mouseup', 'mousedown', 'click'].forEach(eventType => {
+        innerDocument.body.addEventListener(eventType, e => {
+            addMouseOp(eventType, e);
+        }, true);
+    });
+}
+
+function init() {
+    app.$el.style.display = 'block';
+
+    socket.on('update', data => {
+        if (data.testName === app.currentTestName) {
+            app.actions = data.actions;
+            if (!app.currentAction) {
+                app.currentAction = app.actions[0];
+            }
+        }
+    });
+
+    let $iframe = document.body.querySelector('iframe');
+    $iframe.onload = () => {
+        console.log('loaded:' + app.currentTestName);
+        recordIframeEvents($iframe, app);
+    };
+
+    function updateTestHash() {
+        app.currentTestName = window.location.hash.slice(1);
+        socket.emit('changeTest', {testName: app.currentTestName});
+
+    }
+    updateTestHash();
+    window.addEventListener('hashchange', updateTestHash);
+}
+
+socket.on('connect', () => {
+    console.log('Connected');
+    init();
+});
\ No newline at end of file
diff --git a/test/runTest/server.js b/test/runTest/server.js
index 8900f94..aa51450 100644
--- a/test/runTest/server.js
+++ b/test/runTest/server.js
@@ -2,11 +2,13 @@ const handler = require('serve-handler');
 const http = require('http');
 const path = require('path');
 // const open = require('open');
-const fse = require('fs-extra');
 const {fork} = require('child_process');
+const semver = require('semver');
 const {port, origin} = require('./config');
-const {getTestsList, prepareTestsList, saveTestsList, mergeTestsResults} = 
require('./store');
-const {prepareEChartsVersion, buildRuntimeCode} = require('./util');
+const {getTestsList, updateTestsList, saveTestsList, mergeTestsResults} = 
require('./store');
+const {prepareEChartsVersion, getActionsFullPath} = require('./util');
+const fse = require('fs-extra');
+const fs = require('fs');
 
 function serve() {
     const server = http.createServer((request, response) => {
@@ -18,7 +20,7 @@ function serve() {
     });
 
     server.listen(port, () => {
-        console.log(`Server started. ${origin}`);
+        // console.log(`Server started. ${origin}`);
     });
 
 
@@ -46,7 +48,7 @@ function stopRunningTests() {
     }
 }
 
-function startTests(testsNameList, socket) {
+function startTests(testsNameList, socket, noHeadless) {
     console.log(testsNameList.join(','));
 
     stopRunningTests();
@@ -64,7 +66,9 @@ function startTests(testsNameList, socket) {
         socket.emit('update', {tests: getTestsList()});
 
         testProcess = fork(path.join(__dirname, 'cli.js'), [
-            pendingTests.map(testOpt => testOpt.fileUrl)
+            '--tests',
+            pendingTests.map(testOpt => testOpt.fileUrl).join(','),
+            ...(noHeadless ? ['--no-headless'] : [])
         ]);
         // Finished one test
         testProcess.on('message', testOpt => {
@@ -82,36 +86,44 @@ function startTests(testsNameList, socket) {
 
 function checkPuppeteer() {
     try {
-        return require('puppeteer');
+        const packageConfig = require('puppeteer/package.json');
+        return semver.satisfies(packageConfig.version, '>=1.19.0');
     }
     catch (e) {
-        return null;
+        return false;
     }
 }
 
 async function start() {
     if (!checkPuppeteer()) {
         // TODO Check version.
-        console.error(`Can't find puppeteer, use 'npm install puppeteer' to 
install`);
+        console.error(`Can't find puppeteer >= 1.19.0, use 'npm install 
puppeteer' to install or update`);
         return;
     }
 
     await prepareEChartsVersion('4.2.1'); // Expected version.
     await prepareEChartsVersion(); // Version to test
 
-    let runtimeCode = await buildRuntimeCode();
-    fse.outputFileSync(path.join(__dirname, 'tmp/testRuntime.js'), 
runtimeCode, 'utf-8');
+    // let runtimeCode = await buildRuntimeCode();
+    // fse.outputFileSync(path.join(__dirname, 'tmp/testRuntime.js'), 
runtimeCode, 'utf-8');
 
     // Start a static server for puppeteer open the html test cases.
     let {io} = serve();
 
-    io.on('connect', async socket => {
-        await prepareTestsList();
+    io.of('/client').on('connect', async socket => {
+        await updateTestsList();
 
         socket.emit('update', {tests: getTestsList()});
-        // TODO Stop previous?
-        socket.on('run', async testsNameList => {
-            await startTests(testsNameList, socket);
+
+        socket.on('run', async data => {
+            // TODO Should broadcast to all sockets.
+            try {
+                console.log(data);
+                await startTests(data.tests, socket, data.noHeadless);
+            }
+            catch (e) {
+                console.error(e);
+            }
             socket.emit('finish');
         });
         socket.on('stop', () => {
@@ -119,6 +131,32 @@ async function start() {
         });
     });
 
+    io.of('/recorder').on('connect', async socket => {
+        // await updateTestsList();
+        socket.on('save', data => {
+            if (data.testName) {
+                fse.outputFile(
+                    getActionsFullPath(data.testName),
+                    JSON.stringify(data.actions, null, 2),
+                    'utf-8'
+                );
+            }
+            // TODO Broadcast the change?
+        });
+        socket.on('changeTest', data => {
+            try {
+                const actionData = 
fs.readFileSync(getActionsFullPath(data.testName), 'utf-8');
+                socket.emit('update', {
+                    testName: data.testName,
+                    actions: JSON.parse(actionData)
+                });
+            }
+            catch(e) {
+                // Can't find file.
+            }
+        });
+    });
+
     console.log(`Dashboard: ${origin}/test/runTest/client/index.html`);
     // open(`${origin}/test/runTest/client/index.html`);
 
diff --git a/test/runTest/store.js b/test/runTest/store.js
index 4cd4fc9..9851b02 100644
--- a/test/runTest/store.js
+++ b/test/runTest/store.js
@@ -21,7 +21,7 @@ module.exports.getTestByFileUrl = function (url) {
     return _testsMap[url];
 };
 
-module.exports.prepareTestsList = async function () {
+module.exports.updateTestsList = async function () {
     let tmpFolder = path.join(__dirname, 'tmp');
     fse.ensureDirSync(tmpFolder);
     _tests = [];
diff --git a/test/runTest/util.js b/test/runTest/util.js
index 95cab47..91c5819 100644
--- a/test/runTest/util.js
+++ b/test/runTest/util.js
@@ -17,6 +17,10 @@ function getVersionDir(version) {
 };
 module.exports.getVersionDir = getVersionDir;
 
+module.exports.getActionsFullPath = function (testName) {
+    return path.join(__dirname, 'actions', testName + '.json');
+};
+
 
 module.exports.prepareEChartsVersion = function (version) {
     let versionFolder = path.join(__dirname, getVersionDir(version));


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@echarts.apache.org
For additional commands, e-mail: commits-h...@echarts.apache.org

Reply via email to