diff --git a/package.json b/package.json index 0dcd300e2..de004cac2 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "http-proxy": "^1.16.2", "istanbul": "0.4.5", "mocha": "^5.2.0", + "mosca": "^2.8.3", "node-red-node-test-helper": "0.1.7", "should": "^8.4.0", "sinon": "1.17.7", diff --git a/test/editor/pageobjects/editor/palette_page.js b/test/editor/pageobjects/editor/palette_page.js index 5717b16f3..aed011650 100644 --- a/test/editor/pageobjects/editor/palette_page.js +++ b/test/editor/pageobjects/editor/palette_page.js @@ -18,9 +18,11 @@ var idMap = { // input "inject": "#palette_node_inject", "httpin": "#palette_node_http_in", + "mqttIn": "#palette_node_mqtt_in", // output "debug": "#palette_node_debug", "httpResponse": "#palette_node_http_response", + "mqttOut": "#palette_node_mqtt_out", // function "function": "#palette_node_function", "template": "#palette_node_template", @@ -28,6 +30,7 @@ var idMap = { "range": "#palette_node_range", "httpRequest": "#palette_node_http_request", "html": "#palette_node_html", + "json": "#palette_node_json", // storage "filein": "#palette_node_file_in", }; diff --git a/test/editor/pageobjects/nodes/core/io/10-mqttconfig_page.js b/test/editor/pageobjects/nodes/core/io/10-mqttconfig_page.js new file mode 100644 index 000000000..69266f9e8 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/io/10-mqttconfig_page.js @@ -0,0 +1,39 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +function setServer(broker, port) { + browser.setValue('#node-config-input-broker', broker); + port = port ? port : 1883; + browser.setValue('#node-config-input-port', port); +} + +function clickOk() { + browser.clickWithWait('#node-config-dialog-ok'); + // Wait until an config dialog closes. + browser.waitForVisible('#node-config-dialog-ok', 2000, true); +} + +function edit() { + browser.clickWithWait('#node-input-lookup-broker'); + // Wait until a config dialog opens. + browser.waitForVisible('#node-config-dialog-ok', 2000); +} + +module.exports = { + setServer: setServer, + clickOk: clickOk, + edit: edit +}; diff --git a/test/editor/pageobjects/nodes/core/io/10-mqttin_page.js b/test/editor/pageobjects/nodes/core/io/10-mqttin_page.js new file mode 100644 index 000000000..31b909116 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/io/10-mqttin_page.js @@ -0,0 +1,35 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); + +var nodePage = require("../../node_page"); + +function mqttInNode(id) { + nodePage.call(this, id); +} + +util.inherits(mqttInNode, nodePage); + +mqttInNode.prototype.setTopic = function (topic) { + browser.setValue('#node-input-topic', topic); +} + +mqttInNode.prototype.setQoS = function (qos) { + browser.selectWithWait('#node-input-qos', qos); +} + +module.exports = mqttInNode; diff --git a/test/editor/pageobjects/nodes/core/io/10-mqttout_page.js b/test/editor/pageobjects/nodes/core/io/10-mqttout_page.js new file mode 100644 index 000000000..783d87b55 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/io/10-mqttout_page.js @@ -0,0 +1,35 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); + +var nodePage = require("../../node_page"); + +function mqttOutNode(id) { + nodePage.call(this, id); +} + +util.inherits(mqttOutNode, nodePage); + +mqttOutNode.prototype.setTopic = function(topic) { + browser.setValue('#node-input-topic', topic); +} + +mqttOutNode.prototype.setRetain = function (retain) { + browser.selectWithWait('#node-input-retain', retain); +} + +module.exports = mqttOutNode; \ No newline at end of file diff --git a/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js new file mode 100644 index 000000000..10a7e648f --- /dev/null +++ b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js @@ -0,0 +1,35 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); + +var nodePage = require("../../node_page"); + +function jsonNode(id) { + nodePage.call(this, id); +} + +util.inherits(jsonNode, nodePage); + +jsonNode.prototype.setAction = function (action) { + browser.setValue('node-input-action', action); +} + +jsonNode.prototype.setProperty = function(property) { + browser.setValue('//*[@id="dialog-form"]/div[2]/div/div/input', property); +} + +module.exports = jsonNode; diff --git a/test/editor/pageobjects/nodes/nodefactory_page.js b/test/editor/pageobjects/nodes/nodefactory_page.js index 74083f91f..8cb364ad1 100644 --- a/test/editor/pageobjects/nodes/nodefactory_page.js +++ b/test/editor/pageobjects/nodes/nodefactory_page.js @@ -18,12 +18,15 @@ var injectNode = require('./core/core/20-inject_page'); var debugNode = require('./core/core/58-debug_page'); var templateNode = require('./core/core/80-template_page'); var functionNode = require('./core/core/80-function_page'); +var mqttInNode = require('./core/io/10-mqttin_page'); +var mqttOutNode = require('./core/io/10-mqttout_page'); var httpinNode = require('./core/io/21-httpin_page'); var httpResponseNode = require('./core/io/21-httpresponse_page'); var changeNode = require('./core/logic/15-change_page'); var rangeNode = require('./core/logic/16-range_page'); var httpRequestNode = require('./core/io/21-httprequest_page'); var htmlNode = require('./core/parsers/70-HTML_page'); +var jsonNode = require('./core/parsers/70-JSON_page'); var fileinNode = require('./core/storage/50-filein_page'); @@ -31,9 +34,11 @@ var nodeCatalog = { // input "inject": injectNode, "httpin": httpinNode, + "mqttIn":mqttInNode, // output "debug": debugNode, "httpResponse": httpResponseNode, + "mqttOut": mqttOutNode, // function "function": functionNode, "template": templateNode, @@ -41,6 +46,7 @@ var nodeCatalog = { "range": rangeNode, "httpRequest": httpRequestNode, "html": htmlNode, + "json":jsonNode, // storage "filein": fileinNode, } diff --git a/test/editor/specs/scenario/cookbook_mqtt_uispec.js b/test/editor/specs/scenario/cookbook_mqtt_uispec.js new file mode 100644 index 000000000..f8aeb3c87 --- /dev/null +++ b/test/editor/specs/scenario/cookbook_mqtt_uispec.js @@ -0,0 +1,228 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require('when'); +var should = require("should"); +var fs = require('fs-extra'); + +var helper = require("../../editor_helper"); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); +var mqttConfig = require('../../pageobjects/nodes/core/io/10-mqttconfig_page.js'); + +var nodeWidth = 200; +var nodeHeight = 100; +var httpNodeRoot = "/api"; + +var mqttServer; +var mosca = require('mosca'); +var moscaSettings = { + port: 1883, + persistence: { + // Needs for retaining messages. + factory: mosca.persistence.Memory + } +}; + + +// https://cookbook.nodered.org/ +describe('cookbook', function() { + beforeEach(function() { + workspace.deleteAllNodes(); + }); + + before(function() { + browser.call(function() { + return new Promise(function(resolve, reject) { + mqttServer = new mosca.Server(moscaSettings, function() { + resolve(); + }); + }); + }); + helper.startServer(); + }); + + after(function() { + browser.call(function() { + return new Promise(function(resolve, reject) { + mqttServer.close(function() { + resolve(); + }); + }); + }); + helper.stopServer(); + }); + + describe('MQTT', function() { + it('Add an MQTT broker to prepare for UI test', function() { + var mqttOutNode = workspace.addNode("mqttOut", nodeWidth); + + mqttOutNode.edit(); + mqttConfig.edit(); + mqttConfig.setServer("localhost"); + mqttConfig.clickOk(); + mqttOutNode.clickOk(); + + workspace.deploy(); + }); + + it('Connect to an MQTT broker', function() { + var injectNode = workspace.addNode("inject"); + var mqttOutNode = workspace.addNode("mqttOut", nodeWidth); + + var mqttInNode = workspace.addNode("mqttIn", 0, nodeHeight); + var debugNode = workspace.addNode("debug", nodeWidth * 2, nodeHeight); + + injectNode.edit(); + injectNode.setPayload("num", 22); + injectNode.clickOk(); + + mqttOutNode.edit(); + mqttOutNode.setTopic("sensors/livingroom/temp"); + mqttOutNode.clickOk(); + + injectNode.connect(mqttOutNode); + + mqttInNode.edit(); + mqttInNode.setTopic("sensors/livingroom/temp"); + mqttInNode.setQoS("2"); + mqttInNode.clickOk(); + + mqttInNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + debugTab.clearMessage(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"22"'); + }); + + // skip this case since it is same as other cases. + it.skip('Publish messages to a topic'); + + it('Set the topic of a published message', function() { + var injectNode = workspace.addNode("inject"); + var mqttOutNode = workspace.addNode("mqttOut", nodeWidth * 2); + + injectNode.edit(); + injectNode.setPayload("num", 22); + injectNode.setTopic("sensors/kitchen/temperature"); + injectNode.clickOk(); + + mqttOutNode.edit(); + mqttOutNode.clickOk(); + + injectNode.connect(mqttOutNode); + + // The code for confirmation starts from here. + var mqttInNode = workspace.addNode("mqttIn", 0, nodeHeight); + var debugNode = workspace.addNode("debug", nodeWidth * 2, nodeHeight); + + mqttInNode.edit(); + mqttInNode.setTopic("sensors/kitchen/temperature"); + mqttInNode.clickOk(); + + mqttInNode.connect(debugNode); + // The code for confirmation ends here. + + workspace.deploy(); + + debugTab.open(); + debugTab.clearMessage(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"22"'); + }); + + it('Publish a retained message to a topic', function() { + var injectNode = workspace.addNode("inject"); + var mqttOutNode = workspace.addNode("mqttOut", nodeWidth); + + injectNode.edit(); + injectNode.setPayload("num", 22); + injectNode.clickOk(); + + mqttOutNode.edit(); + mqttOutNode.setTopic("sensors/livingroom/temp"); + mqttOutNode.setRetain("true"); + mqttOutNode.clickOk(); + + injectNode.connect(mqttOutNode); + + workspace.deploy(); + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.clearMessage(); + + // The code for confirmation starts from here. + var mqttInNode = workspace.addNode("mqttIn", 0, nodeHeight); + var debugNode = workspace.addNode("debug", nodeWidth * 2, nodeHeight); + + mqttInNode.edit(); + mqttInNode.setTopic("sensors/livingroom/temp"); + mqttInNode.clickOk(); + + mqttInNode.connect(debugNode); + // The code for confirmation ends here. + + workspace.deploy(); + debugTab.open(); + debugTab.getMessage().should.eql('"22"'); + }); + + // skip this case since it is same as other cases. + it.skip('Subscribe to a topic'); + + it('Receive a parsed JSON message', function() { + var injectNode = workspace.addNode("inject"); + var mqttOutNode = workspace.addNode("mqttOut", nodeWidth); + + var mqttInNode = workspace.addNode("mqttIn", 0, nodeHeight); + var jsonNode = workspace.addNode("json", nodeWidth, nodeHeight); + var debugNode = workspace.addNode("debug", nodeWidth * 2, nodeHeight); + + injectNode.edit(); + injectNode.setPayload("json", '{"sensor_id": 1234, "temperature": 13 }'); + injectNode.clickOk(); + + mqttOutNode.edit(); + mqttOutNode.setTopic("sensors/livingroom/temp"); + mqttOutNode.clickOk(); + + injectNode.connect(mqttOutNode); + + mqttInNode.edit(); + mqttInNode.setTopic("sensors/#"); + mqttInNode.setQoS("2"); + mqttInNode.clickOk(); + + jsonNode.edit(); + jsonNode.setProperty("payload"); + jsonNode.clickOk(); + + mqttInNode.connect(jsonNode); + jsonNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + debugTab.clearMessage(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql(['1234', '13']); + }); + }); +});