topological-nodered-wdio 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "topological-nodered-wdio",
3
3
  "description": "Open source WebdriverIO nodes for Node-RED",
4
- "version": "1.0.0",
4
+ "version": "1.1.1",
5
5
  "author": "topological",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -13,7 +13,6 @@ module.exports = function(RED) {
13
13
 
14
14
  let browser = await common.getBrowser(node.context())
15
15
  let locator = await common.getLocator(
16
- browser,
17
16
  locateUsing,
18
17
  locateValue
19
18
  )
@@ -1,5 +1,5 @@
1
1
  <script type="text/javascript">
2
- function setAction() {
2
+ const setAction = () => {
3
3
  let action = $('#node-input-action').val()
4
4
  $('#actionSendKeys').hide()
5
5
  $('#getAttribute').hide()
@@ -11,13 +11,61 @@
11
11
  }
12
12
  }
13
13
 
14
+ const showSelectors = () => {
15
+ let locateType = $('#node-input-locateType').prop('checked')
16
+ if (locateType) {
17
+ $('.node-input-locateValues-container-row').show()
18
+ $('#node-input-locateValue').parent().hide()
19
+ $('#node-input-locateUsing').parent().hide()
20
+ } else {
21
+ $('.node-input-locateValues-container-row').hide()
22
+ $('#node-input-locateValue').parent().show()
23
+ $('#node-input-locateUsing').parent().show()
24
+ }
25
+ }
26
+
27
+ const multiSelectorsUI = (node) => {
28
+ $('#node-input-locateValues-container').css('min-height', '50vh').css('min-width', '600px').editableList({
29
+ addItem: function (container, index, data) {
30
+ container.css({
31
+ overflow: 'hidden',
32
+ whiteSpace: 'nowrap'
33
+ });
34
+ let fragment = document.createDocumentFragment();
35
+ var row = $('<div/>', { style: "display:flex;" }).appendTo(fragment);
36
+ var selectField = $('<select/>', { class: "node-input-locateValues-using", style: "width:30%; margin-right:10px;border-radius:5px; border: 1px solid lightgray; outline-color: rgba(85, 150, 230, 0.8); outline-width: thin;" }).appendTo(row);
37
+ selectField.append($('<option>', { value: 'id', text: 'id' }));
38
+ selectField.append($('<option>', { value: 'name', text: 'name' }));
39
+ selectField.append($('<option>', { value: 'css selector', text: 'CSS selector' }));
40
+ selectField.append($('<option>', { value: 'link text', text: 'Link text' }));
41
+ selectField.append($('<option>', { value: 'partial link text', text: 'Partial link text' }));
42
+ selectField.append($('<option>', { value: 'tag name', text: 'Tag name' }));
43
+ selectField.append($('<option>', { value: 'xpath', text: 'XPath' }));
44
+ var inputField = $('<input/>', { class: "node-input-locateValues-value", style: "width:65%; margin-right:10px;border-radius:5px; border: 1px solid lightgray; outline-color: rgba(85, 150, 230, 0.8); outline-width: thin;", placeholder: "selector" }).appendTo(row);
45
+ if (data === null || data === undefined) {
46
+ data = { using: 'xpath', value: '' }
47
+ }
48
+ selectField.val(data.using)
49
+ inputField.val(data.value)
50
+ container.append(fragment)
51
+ },
52
+ removable: true,
53
+ sortable: true
54
+ })
55
+
56
+ $('#node-input-locateValues-container').editableList('addItems', node.locateValues || []);
57
+ }
58
+
59
+
14
60
  RED.nodes.registerType('element-action', {
15
61
  category: 'Webdriver IO',
16
62
  color: '#a6bbcf',
17
63
  defaults: {
18
64
  name: { value: '' },
19
65
  locateUsing: { value: 'xpath' },
66
+ locateType: { value: false },
20
67
  locateValue: { value: '' },
68
+ locateValues: { value: [] },
21
69
  action: { value: 'click' },
22
70
  sendKeys: {
23
71
  value: '',
@@ -29,38 +77,46 @@
29
77
  inputs: 1,
30
78
  outputs: 1,
31
79
  icon: 'white-globe.png',
32
- label: function() {
80
+ label: function () {
33
81
  return this.name || 'element action'
34
82
  },
35
- oneditprepare: function() {
83
+ oneditprepare: function () {
84
+ var that = this
36
85
  setAction()
86
+ showSelectors()
87
+ multiSelectorsUI(that)
37
88
  $('#node-input-sendKeys').typedInput({
38
89
  default: 'str',
39
90
  typeField: $('#node-input-object'),
40
91
  types: ['msg', 'flow', 'global', 'str']
41
92
  })
93
+ },
94
+ oneditsave: function () {
95
+ var node = this
96
+ var locateType = $('#node-input-locateType').prop('checked')
97
+ node.locateType = locateType
98
+ if (locateType) {
99
+ var locateValues = $('#node-input-locateValues-container').editableList('items')
100
+ node.locateValues = []
101
+ locateValues.each(function () {
102
+ var using = $(this).find('.node-input-locateValues-using').val()
103
+ var value = $(this).find('.node-input-locateValues-value').val()
104
+ node.locateValues.push({ using, value })
105
+ })
106
+ } else {
107
+ node.locateValues = []
108
+ node.locateValue = $('#node-input-locateValue').val()
109
+ node.locateUsing = $('#node-input-locateUsing').val()
110
+ }
42
111
  }
43
112
  })
44
113
  </script>
45
114
 
46
115
  <script type="text/x-red" data-template-name="element-action">
47
116
  <div class="form-row">
48
- <label for="node-input-locateUsing"><i class="fa fa-tasks"></i> Locate Method</label>
49
- <select type="text" id="node-input-locateUsing" style="width:70%;">
50
- <option value="id">id</option>
51
- <option value="name">name</option>
52
- <option value="css selector">CSS selector</option>
53
- <option value="link text">Link text</option>
54
- <option value="partial link text">Partial link text</option>
55
- <option value="tag name">Tag name</option>
56
- <option value="xpath" selected>XPath</option>
57
- </select>
58
- </div>
59
- <div class="form-row">
60
- <label for="node-input-locateValue"><i class="fa fa-tasks"></i> Selector</label>
61
- <input id="node-input-locateValue" type="text">
117
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
118
+ <input id="node-input-name" type="text">
62
119
  </div>
63
-
64
120
  <div class="form-row">
65
121
  <label for="node-input-action"><i class="fa fa-tasks"></i> Action</label>
66
122
  <select type="text" id="node-input-action" style="width:70%;" onchange="setAction()">
@@ -84,9 +140,29 @@
84
140
  <input id="node-input-attribute" type="text">
85
141
  </div>
86
142
  <div class="form-row">
87
- <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
88
- <input id="node-input-name" type="text">
143
+ <label for="node-input-locateType"><i class="fa fa-tasks"></i> Multiple Selector?</label>
144
+ <input id="node-input-locateType" type="checkbox" style="width:30px;" onchange="showSelectors()">
145
+ </div>
146
+ <div class="form-row">
147
+ <label for="node-input-locateUsing"><i class="fa fa-tasks"></i> Locate Using</label>
148
+ <select type="text" id="node-input-locateUsing" style="width:70%;">
149
+ <option value="id">id</option>
150
+ <option value="name">name</option>
151
+ <option value="css selector">CSS selector</option>
152
+ <option value="link text">Link text</option>
153
+ <option value="partial link text">Partial link text</option>
154
+ <option value="tag name">Tag name</option>
155
+ <option value="xpath" selected>XPath</option>
156
+ </select>
157
+ </div>
158
+ <div class="form-row">
159
+ <label for="node-input-locateValue"><i class="fa fa-tasks"></i> Selector</label>
160
+ <input id="node-input-locateValue" type="text">
89
161
  </div>
162
+ <div class="form-row node-input-locateValues-container-row" style="position:relative">
163
+ <h4 style="margin-bottom: 3px;">Selectors :</h4>
164
+ <ol id="node-input-locateValues-container"></ol>
165
+ </div>
90
166
  </script>
91
167
 
92
168
  <script type="text/x-red" data-help-name="element-action">
@@ -130,4 +206,4 @@
130
206
  </ul>
131
207
  </p>
132
208
  <p><b>Text to Send</b> if the Send Keys action is selected, this value would send the text to the selected web element.<br></p>
133
- </script>
209
+ </script>
@@ -1,105 +1,134 @@
1
- const common = require('./wdio-common')
1
+ const common = require("./wdio-common");
2
2
 
3
- module.exports = function(RED) {
3
+ module.exports = function (RED) {
4
4
  function elementAction(config) {
5
- RED.nodes.createNode(this, config)
6
- const node = this
7
- const context = node.context()
8
- common.clearStatus(node)
5
+ RED.nodes.createNode(this, config);
6
+ const node = this;
7
+ const context = node.context();
8
+ common.clearStatus(node);
9
9
 
10
10
  var getTypeInputValue = async (msg, type, value) => {
11
- var r = ''
11
+ var r = "";
12
12
  switch (type) {
13
- case 'msg':
14
- r = RED.util.getMessageProperty(msg, value)
15
- break
16
- case 'flow':
17
- r = context.flow.get(value)
18
- break
19
- case 'global':
20
- r = context.global.get(value)
21
- break
22
- case 'str':
13
+ case "msg":
14
+ r = RED.util.getMessageProperty(msg, value);
15
+ break;
16
+ case "flow":
17
+ r = context.flow.get(value);
18
+ break;
19
+ case "global":
20
+ r = context.global.get(value);
21
+ break;
22
+ case "str":
23
23
  try {
24
- r = unescape(JSON.parse('"' + value + '"'))
24
+ r = unescape(JSON.parse('"' + value + '"'));
25
25
  } catch (e) {
26
- r = value
26
+ r = value;
27
27
  }
28
- break
29
- case 'num':
30
- r = parseFloat(value)
31
- break
32
- case 'json':
33
- if (value !== '') {
34
- r = JSON.parse(value)
28
+ break;
29
+ case "num":
30
+ r = parseFloat(value);
31
+ break;
32
+ case "json":
33
+ if (value !== "") {
34
+ r = JSON.parse(value);
35
35
  } else {
36
- r = undefined
36
+ r = undefined;
37
37
  }
38
38
  }
39
- return r
40
- }
39
+ return r;
40
+ };
41
41
 
42
- node.on('input', async (msg) => {
42
+ node.on("input", async (msg) => {
43
43
  try {
44
- let locateUsing = config.locateUsing || msg.locateUsing
45
- let locateValue = config.locateValue || msg.locateValue
44
+ let multiple = config.locateType || msg.locateType;
45
+ let locateValues = config.locateValues || msg.locateValues;
46
+ let locateUsing = config.locateUsing || msg.locateUsing;
47
+ let locateValue = config.locateValue || msg.locateValue;
46
48
 
47
- let browser = await common.getBrowser(context)
48
- let capabilities = browser.capabilities
49
- let elementId = await common.getElementId(
50
- browser,
51
- locateUsing,
52
- locateValue
53
- )
49
+ let browser = await common.getBrowser(context);
50
+ let capabilities = browser.capabilities;
51
+ let elementId = null;
52
+ node.log = "";
54
53
 
55
- let attribute = config.attribute || msg.attribute
54
+ if (!multiple) {
55
+ elementId = await common.getElementId(
56
+ browser,
57
+ locateUsing,
58
+ locateValue
59
+ );
60
+ if (!elementId) {
61
+ throw new Error(`Element not found using ${locateUsing}: ${locateValue}`);
62
+ }
63
+ } else {
64
+ for (let i = 0; i < locateValues.length; i++) {
65
+ const { using, value } = locateValues[i];
66
+ elementId = await common.getElementId(browser, using, value);
67
+ if (elementId) {
68
+ node.log += `Attempt ${i + 1}: Element found using ${using}: ${value}\n`;
69
+ locateUsing = using
70
+ locateValue = value
71
+ break;
72
+ }
73
+ else {
74
+ node.log += `Attempt ${i + 1}: Element not found using ${using}: ${value}\n`;
75
+ node.warn(`Element not found using ${using}: ${value}`);
76
+ }
77
+ }
78
+ if (!elementId) {
79
+ throw new Error(`Element not found using all selector values`);
80
+ }
81
+ }
82
+
83
+ let attribute = config.attribute || msg.attribute;
56
84
 
57
- if (config.action === 'click') {
58
- node.log = `Click on the webelement identified using ${locateUsing}: "${locateValue}".`
59
- await browser.elementClick(elementId)
60
- } else if (config.action === 'clear') {
61
- node.log = `Clear the Value of the webelement identified using ${locateUsing}: "${locateValue}".`
62
- await browser.elementClear(elementId)
63
- } else if (config.action === 'sendKeys') {
85
+ if (config.action === "click") {
86
+ node.log += `Click on the webelement identified using ${locateUsing}: "${locateValue}".`;
87
+ await browser.elementClick(elementId);
88
+ } else if (config.action === "clear") {
89
+ node.log += `Clear the Value of the webelement identified using ${locateUsing}: "${locateValue}".`;
90
+ await browser.elementClear(elementId);
91
+ } else if (config.action === "sendKeys") {
64
92
  let value = await getTypeInputValue(
65
93
  msg,
66
94
  config.object,
67
95
  config.sendKeys
68
- )
69
- node.log = `Enter the Value: "${value}" to the webelement identified using ${locateUsing}: "${locateValue}".`
96
+ );
97
+ node.log += `Enter the Value: "${value}" to the webelement identified using ${locateUsing}: "${locateValue}".`;
70
98
  await browser.elementSendKeys(
71
99
  elementId,
72
100
  capabilities.version ? Array.from(value) : value
73
- )
74
- } else if (config.action === 'getValue') {
75
- node.log = `Get the Value of webelement identified using ${locateUsing}: "${locateValue}".`
76
- msg.payload = await browser.getElementAttribute(elementId, 'value')
77
- } else if (config.action === 'getText') {
78
- node.log = `Get the Text of webelement identified using ${locateUsing}: "${locateValue}".`
79
- msg.payload = await browser.getElementText(elementId)
80
- } else if (config.action === 'getAttribute') {
81
- node.log = `Get the Attribute: "${attribute}" of webelement identified using ${locateUsing}: "${locateValue}".`
82
- msg.payload = await browser.getElementAttribute(elementId, attribute)
83
- } else if (config.action === 'takeScreenShot') {
84
- node.log = 'Take the screenshot of the webelement.'
85
- msg.payload = await browser.takeElementScreenshot(elementId)
86
- } else if (config.action === 'hover') {
101
+ );
102
+ } else if (config.action === "getValue") {
103
+ node.log += `Get the Value of webelement identified using ${locateUsing}: "${locateValue}".`;
104
+ msg.payload = await browser.getElementAttribute(elementId, "value");
105
+ } else if (config.action === "getText") {
106
+ node.log += `Get the Text of webelement identified using ${locateUsing}: "${locateValue}".`;
107
+ msg.payload = await browser.getElementText(elementId);
108
+ } else if (config.action === "getAttribute") {
109
+ node.log += `Get the Attribute: "${attribute}" of webelement identified using ${locateUsing}: "${locateValue}".`;
110
+ msg.payload = await browser.getElementAttribute(elementId, attribute);
111
+ } else if (config.action === "takeScreenShot") {
112
+ node.log += "Take the screenshot of the webelement.";
113
+ msg.payload = await browser.takeElementScreenshot(elementId);
114
+ } else if (config.action === "hover") {
87
115
  let element = await common.getElement(
88
116
  browser,
89
117
  locateUsing,
90
118
  locateValue
91
- )
92
- node.log = `Hover on the webelement identified using ${locateUsing}: "${locateValue}".`
93
- msg.payload = await element.moveTo()
119
+ );
120
+ node.log += `Hover on the webelement identified using ${locateUsing}: "${locateValue}".`;
121
+ msg.payload = await element.moveTo();
94
122
  }
95
- await common.log(node)
96
- common.successStatus(node)
97
- node.send(msg)
123
+ await common.log(node);
124
+ common.successStatus(node);
125
+ node.send(msg);
98
126
  } catch (e) {
99
- await common.log(node)
100
- common.handleError(e, node, msg)
127
+ node.log = `Error: ${e.message}`;
128
+ await common.log(node);
129
+ common.handleError(e, node, msg);
101
130
  }
102
- })
131
+ });
103
132
  }
104
- RED.nodes.registerType('element-action', elementAction)
105
- }
133
+ RED.nodes.registerType("element-action", elementAction);
134
+ };
@@ -1,13 +1,13 @@
1
- const wdio = require('webdriverio')
2
- let newSessionNode
1
+ const wdio = require("webdriverio");
2
+ let newSessionNode;
3
3
 
4
4
  module.exports.getBrowser = (context) => {
5
- let browser = context.flow.get('wdio_browser')
5
+ let browser = context.flow.get("wdio_browser");
6
6
  if (!browser || !browser.sessionId)
7
- throw new Error('No session defined - call newSession first')
7
+ throw new Error("No session defined - call newSession first");
8
8
 
9
- return browser
10
- }
9
+ return browser;
10
+ };
11
11
 
12
12
  /*
13
13
  config = {
@@ -25,139 +25,131 @@ config = {
25
25
  }
26
26
  */
27
27
  module.exports.newSession = async (config, node, context) => {
28
- let browser
28
+ let browser;
29
29
  try {
30
- browser = await wdio.remote(config)
31
- context.flow.set('wdio_browser', browser)
32
- newSessionNode = node
30
+ browser = await wdio.remote(config);
31
+ context.flow.set("wdio_browser", browser);
32
+ newSessionNode = node;
33
33
  } catch (e) {
34
- throw e
34
+ throw e;
35
35
  }
36
- return browser
37
- }
36
+ return browser;
37
+ };
38
38
 
39
39
  module.exports.deleteSession = async (context) => {
40
- let b
41
- let browser = context.flow.get('wdio_browser')
40
+ let b;
41
+ let browser = context.flow.get("wdio_browser");
42
42
  try {
43
- b = { sessionId: browser.sessionId }
44
- await browser.closeWindow()
45
- await browser.deleteSession()
46
- context.flow.set('wdio_browser', null)
47
- if (newSessionNode) module.exports.disconnected(newSessionNode)
43
+ b = { sessionId: browser.sessionId };
44
+ await browser.closeWindow();
45
+ await browser.deleteSession();
46
+ context.flow.set("wdio_browser", null);
47
+ if (newSessionNode) module.exports.disconnected(newSessionNode);
48
48
  } catch (e) {}
49
- return b
50
- }
49
+ return b;
50
+ };
51
51
 
52
52
  module.exports.getElementId = async (browser, using, value) => {
53
- let elementId
54
- try {
55
- const element = await browser.findElement(using, value)
56
- if (element && Object.keys(element)) {
57
- elementId = element[Object.keys(element)[0]]
58
- } else {
59
- let e
60
- if (element && element.message) {
61
- e = element.message
62
- } else {
63
- e = 'Element not found'
64
- }
65
- throw new Error(e)
66
- }
67
- } catch (e) {
68
- throw e
53
+ const element = await browser.findElement(using, value);
54
+ return element.ELEMENT ?? "";
55
+ };
56
+
57
+ module.exports.getLocator = async (using, value) => {
58
+ let locator = "";
59
+ switch (using) {
60
+ case "id":
61
+ locator = `#${value}`;
62
+ break;
63
+ case "name":
64
+ locator = `[name='${value}']`;
65
+ break;
66
+ case "className":
67
+ locator = `.${value}`;
68
+ break;
69
+ case "tagName":
70
+ locator = value;
71
+ break;
72
+ case "cssSelector":
73
+ locator = value;
74
+ break;
75
+ case "text":
76
+ locator = `=${value}`;
77
+ break;
78
+ case "partialText":
79
+ locator = `*=${value}`;
80
+ break;
81
+ case "xPath":
82
+ locator = value;
83
+ break;
84
+ default:
85
+ locator = value;
69
86
  }
70
- return elementId
71
- }
72
87
 
73
- module.exports.getLocator = async (browser, using, value) => {
74
- let locator = ''
75
- switch (using) {
76
- case 'id':
77
- locator = `#${value}`
78
- break
79
- case 'name':
80
- locator = `[name='${value}']`
81
- break
82
- case 'className':
83
- locator = `.${value}`
84
- break
85
- case 'tagName':
86
- locator = value
87
- break
88
- case 'cssSelector':
89
- locator = value
90
- break
91
- case 'text':
92
- locator = `=${value}`
93
- break
94
- case 'partialText':
95
- locator = `*=${value}`
96
- break
97
- case 'xPath':
98
- locator = value
99
- break
100
- default:
101
- locator = value
102
- }
103
-
104
- return locator
105
- }
88
+ return locator;
89
+ };
106
90
 
107
91
  module.exports.handleError = (e, node, msg) => {
108
- console.log(e)
109
- module.exports.errorStatus(node)
110
- node.error(e, msg)
111
- }
92
+ console.log(e);
93
+ module.exports.errorStatus(node);
94
+ node.error(e, msg);
95
+ };
112
96
 
113
97
  module.exports.clearStatus = (node) => {
114
- node.status({})
115
- }
98
+ node.status({});
99
+ };
116
100
 
117
101
  module.exports.connectedStatus = (node) => {
118
102
  node.status({
119
- fill: 'green',
120
- shape: 'dot',
121
- text: 'connected'
122
- })
123
- }
103
+ fill: "green",
104
+ shape: "dot",
105
+ text: "connected",
106
+ });
107
+ };
124
108
 
125
109
  module.exports.disconnectedStatus = (node) => {
126
110
  node.status({
127
- fill: 'green',
128
- shape: 'ring',
129
- text: 'disconnected'
130
- })
131
- }
111
+ fill: "green",
112
+ shape: "ring",
113
+ text: "disconnected",
114
+ });
115
+ };
132
116
 
133
117
  module.exports.successStatus = (node) => {
134
118
  node.status({
135
- fill: 'green',
136
- shape: 'ring',
137
- text: 'done'
138
- })
139
- }
119
+ fill: "green",
120
+ shape: "ring",
121
+ text: "done",
122
+ });
123
+ };
140
124
 
141
125
  module.exports.errorStatus = (node) => {
142
126
  node.status({
143
- fill: 'red',
144
- shape: 'ring',
145
- text: 'error'
146
- })
147
- }
127
+ fill: "red",
128
+ shape: "ring",
129
+ text: "error",
130
+ });
131
+ };
148
132
 
149
133
  module.exports.log = async (node) => {
150
- let context = node.context()
151
- let stepCount = await (context.global.get('stepCount') || 0) + 1
152
- let document = await context.global.get('document') || ''
153
- await context.global.set('document', `${document}${stepCount}. Node: ${node.name} - ${node.log}\n`)
154
- await context.global.set('stepCount', stepCount)
155
- }
134
+ let context = node.context();
135
+ let stepCount = (await (context.global.get("stepCount") || 0)) + 1;
136
+ let document = (await context.global.get("document")) || "";
137
+ await context.global.set(
138
+ "document",
139
+ `${document}${stepCount}. Node (${node.id}): ${node.name} - ${node.log}\n`
140
+ );
141
+ await context.global.set("stepCount", stepCount);
142
+ };
156
143
 
157
144
  module.exports.document = async (node) => {
158
- let context = node.context()
159
- let document = await context.global.get('document') || ''
160
- document = node.line? `${document}\n${node.name}${node.refUrl? `\nRef: ${node.refUrl}`:''}\n\n` :
161
- `${document}********************\n${node.name}${node.refUrl? `\nRef: ${node.refUrl}`:''}\n********************\n`
162
- await context.global.set('document', document.replaceAll('\\n','\n'))
163
- }
145
+ let context = node.context();
146
+ let document = (await context.global.get("document")) || "";
147
+ document = node.line
148
+ ? `${document}\n${node.name}${
149
+ node.refUrl ? `\nRef: ${node.refUrl}` : ""
150
+ }\n\n`
151
+ : `${document}********************\n${node.name}${
152
+ node.refUrl ? `\nRef: ${node.refUrl}` : ""
153
+ }\n********************\n`;
154
+ await context.global.set("document", document.replaceAll("\\n", "\n"));
155
+ };