1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge pull request #2426 from kazuhitoyokoi/master-fixuitest

Improvements of UI testing
This commit is contained in:
Nick O'Leary 2020-01-17 10:18:05 +00:00 committed by GitHub
commit 448de23f59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 105 additions and 106 deletions

View File

@ -28,7 +28,7 @@ module.exports = function(grunt) {
var nonHeadless = grunt.option('non-headless'); var nonHeadless = grunt.option('non-headless');
if (nonHeadless) { if (nonHeadless) {
process.env.NODE_RED_NON_HEADLESS = 'true'; process.env.NODE_RED_NON_HEADLESS = true;
} }
grunt.initConfig({ grunt.initConfig({
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),

View File

@ -57,6 +57,9 @@ function cleanup(flowFile) {
deleteFile(homeDir + "/." + flowFile + ".backup"); deleteFile(homeDir + "/." + flowFile + ".backup");
deleteFile(homeDir + "/" + credentialFile); deleteFile(homeDir + "/" + credentialFile);
deleteFile(homeDir + "/." + credentialFile + ".backup"); deleteFile(homeDir + "/." + credentialFile + ".backup");
deleteFile(homeDir + "/package.json");
deleteFile(homeDir + "/lib/flows");
deleteFile(homeDir + "/lib");
} }
function deleteFile(flowFile) { function deleteFile(flowFile) {
@ -66,7 +69,7 @@ function deleteFile(flowFile) {
fs.unlinkSync(flowFile); fs.unlinkSync(flowFile);
} }
} catch (e) {} } catch (e) {}
}; }
module.exports = { module.exports = {
startServer: function() { startServer: function() {
@ -104,7 +107,7 @@ module.exports = {
}); });
}); });
browser.url(url); browser.url(url);
browser.waitForExist(".red-ui-palette-node[data-palette-type='inject']") browser.waitForExist(".red-ui-palette-node[data-palette-type='inject']");
} catch (err) { } catch (err) {
console.log(err); console.log(err);
throw err; throw err;

View File

@ -15,18 +15,15 @@
**/ **/
var when = require("when"); var when = require("when");
var events = require("nr-test-utils").require("@node-red/runtime/lib/events.js"); var events = require("nr-test-utils").require("@node-red/runtime/lib/events.js");
var palette = require("./palette_page"); var palette = require("./palette_page");
var nodeFactory = require("../nodes/nodefactory_page"); var nodeFactory = require("../nodes/nodefactory_page");
var keyPage = require("../util/key_page");
var flowLayout = { var flowLayout = {
flowRightEnd : 600, flowRightEnd : 600,
widthInterval : 300, widthInterval : 300,
heightInterval : 80 heightInterval : 80
}; };
var previousX = -flowLayout.widthInterval; var previousX = -flowLayout.widthInterval;
var previousY = 0; var previousY = 0;
@ -44,6 +41,9 @@ function addNode(type, x, y) {
previousY = previousY + flowLayout.heightInterval; previousY = previousY + flowLayout.heightInterval;
} }
} }
browser.waitForVisible('#red-ui-palette-search');
browser.setValue('//*[@id="red-ui-palette-search"]/div/input', type.replace(/([A-Z])/g,' $1').toLowerCase());
browser.pause(300);
browser.waitForVisible(palette.getId(type)); browser.waitForVisible(palette.getId(type));
browser.moveToObject(palette.getId(type)); browser.moveToObject(palette.getId(type));
browser.buttonDown(); browser.buttonDown();
@ -57,13 +57,10 @@ function addNode(type, x, y) {
} }
function deleteAllNodes() { function deleteAllNodes() {
browser.waitForVisible('.red-ui-workspace-chart-event-layer'); browser.waitForVisible('//*[contains(@class, "active")]/a[@class="red-ui-tab-label"]');
try { browser.click('//*[contains(@class, "active")]/a[@class="red-ui-tab-label"]');
browser.click('.red-ui-workspace-chart-event-layer'); browser.pause(1000);
} catch (e) { browser.keys(keyPage.selectAll());
console.log(e);
}
browser.keys(['Control', 'a', 'a', 'Control']); // call twice to release the keys.
browser.keys(['Delete']); browser.keys(['Delete']);
} }
@ -78,7 +75,7 @@ function deploy() {
browser.clickWithWait('#red-ui-header-button-deploy'); browser.clickWithWait('#red-ui-header-button-deploy');
}); });
}); });
browser.waitForText('#red-ui-header-button-deploy', 2000); browser.waitForText('#red-ui-header-button-deploy', 10000);
// Need additional wait until buttons becomes clickable. // Need additional wait until buttons becomes clickable.
browser.pause(50); browser.pause(50);
} }

View File

@ -26,16 +26,13 @@ function debugNode(id) {
util.inherits(debugNode, nodePage); util.inherits(debugNode, nodePage);
debugNode.prototype.setOutput = function(complete) { debugNode.prototype.setOutput = function (complete) {
// Open a payload type list. // Open a payload type list.
browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-container")]/button'); browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-container")]/button');
if (complete !== 'true') { if (complete !== 'true') {
// Select the "msg" type. // Select the "msg" type.
browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options")][1]/a[1]'); browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options")][1]/a[1]');
// Input the path in msg. // Input the path in msg.
browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-input")]/input');
browser.keys(keyPage.selectAll());
browser.keys(['Delete']);
browser.setValue('//*[contains(@class, "red-ui-typedInput-input")]/input', complete); browser.setValue('//*[contains(@class, "red-ui-typedInput-input")]/input', complete);
} else { } else {
// Select the "complete msg object" type. // Select the "complete msg object" type.

View File

@ -26,12 +26,11 @@ function functionNode(id) {
util.inherits(functionNode, nodePage); util.inherits(functionNode, nodePage);
functionNode.prototype.setFunction = function(func) { functionNode.prototype.setFunction = function (func) {
browser.clickWithWait('#node-input-func-editor'); browser.clickWithWait('#node-input-func-editor');
browser.keys(keyPage.selectAll()); browser.keys(keyPage.selectAll());
for (var i = 0; i < func.length; i++) { browser.keys(['Delete']);
browser.keys([func.charAt(i)]); browser.keys(func);
}
// Delete the unnecessary code that ace editor does the autocompletion. // Delete the unnecessary code that ace editor does the autocompletion.
browser.keys(keyPage.selectToEnd()); browser.keys(keyPage.selectToEnd());
browser.keys(['Delete']); browser.keys(['Delete']);

View File

@ -49,8 +49,8 @@ function setT(t, index) {
} }
// It is better to create a function whose input value is the object type in the future, // It is better to create a function whose input value is the object type in the future,
changeNode.prototype.ruleSet = function(p, pt, to, tot, index) { changeNode.prototype.ruleSet = function (p, pt, to, tot, index) {
index = index ? index : 1; index = index || 1;
setT("set", index); setT("set", index);
if (pt) { if (pt) {
browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]');
@ -68,23 +68,23 @@ changeNode.prototype.ruleSet = function(p, pt, to, tot, index) {
browser.clickWithWait(totXPath); browser.clickWithWait(totXPath);
} }
if (to) { if (to) {
browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/div/input' , to); browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/div/input', to);
} }
} }
changeNode.prototype.ruleDelete = function(index) { changeNode.prototype.ruleDelete = function (index) {
index = index ? index : 1; index = index || 1;
setT("delete", index); setT("delete", index);
} }
changeNode.prototype.ruleMove = function(p, to, index) { changeNode.prototype.ruleMove = function (p, to, index) {
index = index ? index : 1; index = index || 1;
setT("move", index); setT("move", index);
browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p);
browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/div/input', to); browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/div/input', to);
} }
changeNode.prototype.addRule = function() { changeNode.prototype.addRule = function () {
browser.clickWithWait('//*[@id="dialog-form"]/div[3]/div/a'); browser.clickWithWait('//*[@id="dialog-form"]/div[3]/div/a');
} }

View File

@ -26,21 +26,19 @@ function templateNode(id) {
util.inherits(templateNode, nodePage); util.inherits(templateNode, nodePage);
templateNode.prototype.setSyntax = function(syntax) { templateNode.prototype.setSyntax = function (syntax) {
browser.selectWithWait('#node-input-syntax', syntax); browser.selectWithWait('#node-input-syntax', syntax);
} }
templateNode.prototype.setFormat = function(format) { templateNode.prototype.setFormat = function (format) {
browser.selectWithWait('#node-input-format', format); browser.selectWithWait('#node-input-format', format);
} }
templateNode.prototype.setTemplate = function(template) { templateNode.prototype.setTemplate = function (template) {
browser.clickWithWait('#node-input-template-editor'); browser.clickWithWait('#node-input-template-editor');
browser.keys(keyPage.selectAll()); browser.keys(keyPage.selectAll());
// Need to add a character one by one since some words such as 'Control' are treated as a special word. browser.keys(['Delete']);
for (var i = 0; i < template.length; i++) { browser.keys(template);
browser.keys([template.charAt(i)]);
}
browser.keys(keyPage.selectToEnd()); browser.keys(keyPage.selectToEnd());
browser.keys(['Delete']); browser.keys(['Delete']);
// Need to wait until ace editor correctly checks the syntax. // Need to wait until ace editor correctly checks the syntax.

View File

@ -23,14 +23,14 @@ function setServer(broker, port) {
function clickOk() { function clickOk() {
browser.clickWithWait('#node-config-dialog-ok'); browser.clickWithWait('#node-config-dialog-ok');
// Wait until an config dialog closes. // Wait until an config dialog closes.
browser.waitForVisible('#node-config-dialog-ok', 4000, true); browser.waitForVisible('#node-config-dialog-ok', 10000, true);
} }
function edit() { function edit() {
browser.waitForVisible('#node-input-lookup-broker'); browser.waitForVisible('#node-input-lookup-broker');
browser.click('#node-input-lookup-broker'); browser.click('#node-input-lookup-broker');
// Wait until a config dialog opens. // Wait until a config dialog opens.
browser.waitForVisible('#node-config-dialog-ok', 4000); browser.waitForVisible('#node-config-dialog-ok', 10000);
} }
module.exports = { module.exports = {

View File

@ -18,21 +18,24 @@ function Node(id) {
this.id = '//*[@id="' + id + '"]'; this.id = '//*[@id="' + id + '"]';
} }
Node.prototype.edit = function() { Node.prototype.edit = function () {
browser.clickWithWait(this.id); browser.waitForVisible(this.id);
browser.clickWithWait(this.id); browser.moveToObject(this.id);
browser.buttonDown();
browser.buttonUp();
browser.keys(['Enter']);
// Wait until an edit dialog opens. // Wait until an edit dialog opens.
browser.waitForVisible('#node-dialog-ok', 4000); browser.waitForVisible('#node-dialog-ok', 10000);
} }
Node.prototype.clickOk = function() { Node.prototype.clickOk = function () {
browser.clickWithWait('#node-dialog-ok'); browser.clickWithWait('#node-dialog-ok');
// Wait untile an edit dialog closes. // Wait untile an edit dialog closes.
browser.waitForVisible('#node-dialog-ok', 4000, true); browser.waitForVisible('#node-dialog-ok', 10000, true);
browser.pause(50); browser.pause(50);
} }
Node.prototype.connect = function(targetNode) { Node.prototype.connect = function (targetNode) {
var outputPort = this.id + '/*[@class="red-ui-flow-port-output"]'; var outputPort = this.id + '/*[@class="red-ui-flow-port-output"]';
var inputPort = targetNode.id + '/*[@class="red-ui-flow-port-input"]'; var inputPort = targetNode.id + '/*[@class="red-ui-flow-port-input"]';
browser.dragAndDrop(outputPort, inputPort) browser.dragAndDrop(outputPort, inputPort)

View File

@ -14,46 +14,46 @@
* limitations under the License. * limitations under the License.
**/ **/
var injectNode = require('./core/core/20-inject_page'); var injectNode = require('./core/common/20-inject_page');
var debugNode = require('./core/core/58-debug_page'); var debugNode = require('./core/common/21-debug_page');
var templateNode = require('./core/core/80-template_page'); var functionNode = require('./core/function/10-function_page');
var functionNode = require('./core/core/80-function_page'); var changeNode = require('./core/function/15-change_page');
var mqttInNode = require('./core/io/10-mqttin_page'); var rangeNode = require('./core/function/16-range_page');
var mqttOutNode = require('./core/io/10-mqttout_page'); var templateNode = require('./core/function/80-template_page');
var httpInNode = require('./core/io/21-httpin_page'); var mqttInNode = require('./core/network/10-mqttin_page');
var httpResponseNode = require('./core/io/21-httpresponse_page'); var mqttOutNode = require('./core/network/10-mqttout_page');
var changeNode = require('./core/logic/15-change_page'); var httpInNode = require('./core/network/21-httpin_page');
var rangeNode = require('./core/logic/16-range_page'); var httpResponseNode = require('./core/network/21-httpresponse_page');
var httpRequestNode = require('./core/io/21-httprequest_page'); var httpRequestNode = require('./core/network/21-httprequest_page');
var htmlNode = require('./core/parsers/70-HTML_page'); var htmlNode = require('./core/parsers/70-HTML_page');
var jsonNode = require('./core/parsers/70-JSON_page'); var jsonNode = require('./core/parsers/70-JSON_page');
var fileInNode = require('./core/storage/50-filein_page'); var fileInNode = require('./core/storage/10-filein_page');
var nodeCatalog = { var nodeCatalog = {
// input // common
"inject": injectNode, "inject": injectNode,
"httpIn": httpInNode,
"mqttIn":mqttInNode,
// output
"debug": debugNode, "debug": debugNode,
"httpResponse": httpResponseNode,
"mqttOut": mqttOutNode,
// function // function
"function": functionNode, "function": functionNode,
"template": templateNode,
"change": changeNode, "change": changeNode,
"range": rangeNode, "range": rangeNode,
"template": templateNode,
// network
"mqttIn": mqttInNode,
"mqttOut": mqttOutNode,
"httpIn": httpInNode,
"httpResponse": httpResponseNode,
"httpRequest": httpRequestNode, "httpRequest": httpRequestNode,
// parser
"html": htmlNode, "html": htmlNode,
"json":jsonNode, "json": jsonNode,
// storage // storage
"fileIn": fileInNode, "fileIn": fileInNode
} };
function create(type, id) { function create(type, id) {
var node = nodeCatalog[type]; var Node = nodeCatalog[type];
return new node(id); return new Node(id);
} }
module.exports = { module.exports = {

View File

@ -17,21 +17,20 @@
var os = require("os"); var os = require("os");
var shortCutKeyMap = { var shortCutKeyMap = {
"selectAll": ['Control', 'a', 'Control'], "selectAll": ['Control', 'a', 'a', 'Control'],
"selectToEnd": ['Control', 'Shift', 'End', 'Shift', 'Control'], "selectToEnd": ['Control', 'Shift', 'End', 'Shift', 'Control'],
}; };
var shortCutKeyMapForMac = { var shortCutKeyMapForMac = {
"selectAll": ['Command', 'a', 'Command'], "selectAll": ['Command', 'a', 'a', 'Command'],
"selectToEnd": ['Command', 'Shift', 'ArrowDown', 'Shift', 'Command'], "selectToEnd": ['Command', 'Shift', 'ArrowDown', 'Shift', 'Command'],
}; };
function getShortCutKey(type) { function getShortCutKey(type) {
if (os.type() === "Darwin") { if (os.type() === 'Darwin') {
return shortCutKeyMapForMac[type]; return shortCutKeyMapForMac[type];
} else {
return shortCutKeyMap[type];
} }
return shortCutKeyMap[type];
} }
function selectAll() { function selectAll() {

View File

@ -22,42 +22,45 @@ var helper = require("../../editor_helper");
var debugTab = require('../../pageobjects/editor/debugTab_page'); var debugTab = require('../../pageobjects/editor/debugTab_page');
var workspace = require('../../pageobjects/editor/workspace_page'); var workspace = require('../../pageobjects/editor/workspace_page');
var specUtil = require('../../pageobjects/util/spec_util_page'); var specUtil = require('../../pageobjects/util/spec_util_page');
var mqttConfig = require('../../pageobjects/nodes/core/io/10-mqttconfig_page.js'); var mqttConfig = require('../../pageobjects/nodes/core/network/10-mqttconfig_page.js');
var httpNodeRoot = "/api"; var httpNodeRoot = "/api";
var mqttServer; var mqttServer;
var mosca = require('mosca'); var mosca = require('mosca');
var moscaSettings = { var moscaSettings = {
port: 1883, port: parseInt(Math.random() * 16383 + 49152),
persistence: { persistence: {
// Needs for retaining messages. // Needs for retaining messages.
factory: mosca.persistence.Memory factory: mosca.persistence.Memory
} }
}; };
// https://cookbook.nodered.org/ // https://cookbook.nodered.org/
describe('cookbook', function() { describe('cookbook', function () {
beforeEach(function() { beforeEach(function () {
workspace.init(); workspace.init();
}); });
before(function() { before(function () {
browser.call(function() { browser.call(function () {
return new Promise(function(resolve, reject) { return new Promise(function (resolve, reject) {
mqttServer = new mosca.Server(moscaSettings, function() { mqttServer = new mosca.Server(moscaSettings, function (err) {
resolve(); if (err) {
reject(err);
} else {
resolve();
}
}); });
}); });
}); });
helper.startServer(); helper.startServer();
}); });
after(function() { after(function () {
browser.call(function() { browser.call(function () {
return new Promise(function(resolve, reject) { return new Promise(function (resolve, reject) {
mqttServer.close(function() { mqttServer.close(function () {
resolve(); resolve();
}); });
}); });
@ -65,20 +68,20 @@ describe('cookbook', function() {
helper.stopServer(); helper.stopServer();
}); });
describe('MQTT', function() { describe('MQTT', function () {
it('Add an MQTT broker to prepare for UI test', function() { it('Add an MQTT broker to prepare for UI test', function () {
var mqttOutNode = workspace.addNode("mqttOut"); var mqttOutNode = workspace.addNode("mqttOut");
mqttOutNode.edit(); mqttOutNode.edit();
mqttConfig.edit(); mqttConfig.edit();
mqttConfig.setServer("localhost"); mqttConfig.setServer("localhost", moscaSettings.port);
mqttConfig.clickOk(); mqttConfig.clickOk();
mqttOutNode.clickOk(); mqttOutNode.clickOk();
workspace.deploy(); workspace.deploy();
}); });
it('Connect to an MQTT broker', function() { it('Connect to an MQTT broker', function () {
var injectNode = workspace.addNode("inject"); var injectNode = workspace.addNode("inject");
var mqttOutNode = workspace.addNode("mqttOut"); var mqttOutNode = workspace.addNode("mqttOut");
@ -112,7 +115,7 @@ describe('cookbook', function() {
// skip this case since it is same as other cases. // skip this case since it is same as other cases.
it.skip('Publish messages to a topic'); it.skip('Publish messages to a topic');
it('Set the topic of a published message', function() { it('Set the topic of a published message', function () {
var injectNode = workspace.addNode("inject"); var injectNode = workspace.addNode("inject");
var mqttOutNode = workspace.addNode("mqttOut"); var mqttOutNode = workspace.addNode("mqttOut");
@ -144,7 +147,7 @@ describe('cookbook', function() {
debugTab.getMessage().should.eql('"22"'); debugTab.getMessage().should.eql('"22"');
}); });
it('Publish a retained message to a topic', function() { it('Publish a retained message to a topic', function () {
var injectNode = workspace.addNode("inject"); var injectNode = workspace.addNode("inject");
var mqttOutNode = workspace.addNode("mqttOut"); var mqttOutNode = workspace.addNode("mqttOut");
@ -182,7 +185,7 @@ describe('cookbook', function() {
// skip this case since it is same as other cases. // skip this case since it is same as other cases.
it.skip('Subscribe to a topic'); it.skip('Subscribe to a topic');
it('Receive a parsed JSON message', function() { it('Receive a parsed JSON message', function () {
var injectNode = workspace.addNode("inject"); var injectNode = workspace.addNode("inject");
var mqttOutNode = workspace.addNode("mqttOut"); var mqttOutNode = workspace.addNode("mqttOut");

View File

@ -331,7 +331,7 @@ describe('cookbook', function() {
httpRequetNode.clickOk(); httpRequetNode.clickOk();
debugNode.edit(); debugNode.edit();
debugNode.setOutput("payload.title"); debugNode.setOutput(".title");
debugNode.clickOk(); debugNode.clickOk();
injectNode.connect(changeNodeSetPost); injectNode.connect(changeNodeSetPost);

View File

@ -15,7 +15,7 @@
**/ **/
exports.config = { exports.config = {
// //
// ================== // ==================
// Specify Test Files // Specify Test Files
@ -150,12 +150,12 @@ exports.config = {
// The only one supported by default is 'dot' // The only one supported by default is 'dot'
// see also: http://webdriver.io/guide/reporters/dot.html // see also: http://webdriver.io/guide/reporters/dot.html
reporters: ['spec'], reporters: ['spec'],
// //
// Options to be passed to Mocha. // Options to be passed to Mocha.
// See the full list at http://mochajs.org/ // See the full list at http://mochajs.org/
mochaOpts: { mochaOpts: {
timeout: 60000, timeout: 100000,
ui: 'bdd' ui: 'bdd'
}, },
// //
@ -197,7 +197,7 @@ exports.config = {
*/ */
// beforeCommand: function (commandName, args) { // beforeCommand: function (commandName, args) {
// }, // },
/** /**
* Hook that gets executed before the suite starts * Hook that gets executed before the suite starts
* @param {Object} suite suite details * @param {Object} suite suite details
@ -217,13 +217,13 @@ exports.config = {
// beforeHook: function () { // beforeHook: function () {
// }, // },
/** /**
* Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling * Hook that gets executed _after_ a hook within the suite ends (e.g. runs after calling
* afterEach in Mocha) * afterEach in Mocha)
*/ */
// afterHook: function () { // afterHook: function () {
// }, // },
/** /**
* Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts. * Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) ends.
* @param {Object} test test details * @param {Object} test test details
*/ */
// afterTest: function (test) { // afterTest: function (test) {
@ -234,7 +234,7 @@ exports.config = {
*/ */
// afterSuite: function (suite) { // afterSuite: function (suite) {
// }, // },
/** /**
* Runs after a WebdriverIO command gets executed * Runs after a WebdriverIO command gets executed
* @param {String} commandName hook command name * @param {String} commandName hook command name