@cuppet/core 1.1.5 → 1.2.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/features/app/hooks.js +24 -4
- package/features/app/mqttManager.js +291 -0
- package/features/app/stepDefinitions/helperSteps.js +5 -0
- package/features/app/stepDefinitions/mqttSteps.js +168 -0
- package/features/tests/example-lighthouse.feature +7 -0
- package/features/tests/example-mobile.feature +9 -0
- package/features/tests/example-pa11y.feature +6 -0
- package/features/tests/example-visual-test.feature +5 -0
- package/features/tests/mqttExample.feature +48 -0
- package/index.js +4 -0
- package/package.json +14 -3
- package/src/apiFunctions.js +3 -20
- package/src/helperFunctions.js +53 -0
- package/src/mqttFunctions.js +285 -0
- package/stepDefinitions.js +1 -0
package/features/app/hooks.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { BeforeAll, AfterAll, Before, After, AfterStep, Status } = require('@cucumber/cucumber');
|
|
2
2
|
const BrowserManager = require('./browserManager');
|
|
3
3
|
const AppiumManager = require('./appiumManager');
|
|
4
|
+
const MqttManager = require('./mqttManager');
|
|
4
5
|
const fs = require('fs');
|
|
5
6
|
const config = require('config');
|
|
6
7
|
const dataStore = require('../../src/dataStorage');
|
|
@@ -62,10 +63,21 @@ Before(async function (testCase) {
|
|
|
62
63
|
// Get Appium capabilities from config
|
|
63
64
|
const appiumCapabilities = config.get('appiumCapabilities');
|
|
64
65
|
|
|
65
|
-
// Check if the test is tagged with @appium
|
|
66
|
+
// Check if the test is tagged with @appium, @mqtt or neither
|
|
66
67
|
const arrayTags = testCase.pickle.tags;
|
|
67
|
-
const
|
|
68
|
-
|
|
68
|
+
const appiumTag = arrayTags.find((item) => item.name === '@appium');
|
|
69
|
+
const mqttTag = arrayTags.find((item) => item.name === '@mqtt');
|
|
70
|
+
const apiTag = arrayTags.find((item) => item.name === '@api');
|
|
71
|
+
|
|
72
|
+
// Initialize MQTT Manager if @mqtt tag is present
|
|
73
|
+
if (mqttTag) {
|
|
74
|
+
const mqttManager = new MqttManager();
|
|
75
|
+
await mqttManager.initialize();
|
|
76
|
+
this.mqttManager = mqttManager;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Initialize browser unless it's API-only or MQTT-only test
|
|
80
|
+
if (!appiumTag && !apiTag && !mqttTag) {
|
|
69
81
|
const browserManager = new BrowserManager(browserViewport, browserArgs, credentials);
|
|
70
82
|
await browserManager.initialize();
|
|
71
83
|
|
|
@@ -74,7 +86,7 @@ Before(async function (testCase) {
|
|
|
74
86
|
this.browser = browserManager.browser;
|
|
75
87
|
this.page = browserManager.page;
|
|
76
88
|
this.scenarioName = testCase.pickle.name;
|
|
77
|
-
} else {
|
|
89
|
+
} else if (appiumTag) {
|
|
78
90
|
const appiumManager = new AppiumManager(appiumCapabilities);
|
|
79
91
|
await appiumManager.initialize();
|
|
80
92
|
this.appiumManager = appiumManager;
|
|
@@ -87,6 +99,14 @@ After(async function (testCase) {
|
|
|
87
99
|
if (testCase.result.status === Status.FAILED) {
|
|
88
100
|
console.log(`Scenario: '${testCase.pickle.name}' - has failed...\r\n`);
|
|
89
101
|
}
|
|
102
|
+
|
|
103
|
+
// Cleanup MQTT connection if present
|
|
104
|
+
if (this.mqttManager) {
|
|
105
|
+
await this.mqttManager.stop();
|
|
106
|
+
this.mqttManager = null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Cleanup browser/appium connections
|
|
90
110
|
if (this.browser) {
|
|
91
111
|
await this.browserManager.stop();
|
|
92
112
|
} else if (this.appiumDriver) {
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
const mqtt = require('mqtt');
|
|
2
|
+
const config = require('config');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MqttManager class for managing MQTT client lifecycle
|
|
6
|
+
* Follows the same pattern as BrowserManager and AppiumManager
|
|
7
|
+
*/
|
|
8
|
+
class MqttManager {
|
|
9
|
+
constructor(brokerUrl = null, options = {}) {
|
|
10
|
+
this.client = null;
|
|
11
|
+
this.brokerUrl =
|
|
12
|
+
brokerUrl || (config.has('mqtt.brokerUrl') ? config.get('mqtt.brokerUrl') : 'mqtt://localhost:1883');
|
|
13
|
+
this.options = this.prepareOptions(options);
|
|
14
|
+
this.messageBuffer = new Map(); // Store messages by topic
|
|
15
|
+
this.subscriptions = new Set(); // Track active subscriptions
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Prepare MQTT connection options from config or provided options
|
|
20
|
+
* @param {Object} customOptions - Custom options to override config
|
|
21
|
+
* @returns {Object} - MQTT connection options
|
|
22
|
+
*/
|
|
23
|
+
prepareOptions(customOptions) {
|
|
24
|
+
const defaultOptions = {
|
|
25
|
+
clientId: config.has('mqtt.clientId')
|
|
26
|
+
? config.get('mqtt.clientId')
|
|
27
|
+
: `cuppet-test-${Math.random().toString(16).slice(2, 8)}`,
|
|
28
|
+
clean: config.has('mqtt.cleanSession') ? config.get('mqtt.cleanSession') : true,
|
|
29
|
+
connectTimeout: config.has('mqtt.connectTimeout') ? config.get('mqtt.connectTimeout') : 5000,
|
|
30
|
+
keepalive: config.has('mqtt.keepalive') ? config.get('mqtt.keepalive') : 60,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Add username and password if provided in config
|
|
34
|
+
if (config.has('mqtt.username')) {
|
|
35
|
+
defaultOptions.username = config.get('mqtt.username');
|
|
36
|
+
}
|
|
37
|
+
if (config.has('mqtt.password')) {
|
|
38
|
+
defaultOptions.password = config.get('mqtt.password');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Merge with custom options
|
|
42
|
+
return { ...defaultOptions, ...customOptions };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initialize MQTT client and connect to broker
|
|
47
|
+
* @returns {Promise<void>}
|
|
48
|
+
*/
|
|
49
|
+
async initialize() {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
try {
|
|
52
|
+
console.log(`Connecting to MQTT broker: ${this.brokerUrl}`);
|
|
53
|
+
this.client = mqtt.connect(this.brokerUrl, this.options);
|
|
54
|
+
|
|
55
|
+
let settled = false; // Guard against multiple resolve/reject calls
|
|
56
|
+
|
|
57
|
+
// Centralized cleanup function
|
|
58
|
+
const cleanup = () => {
|
|
59
|
+
clearTimeout(timeoutId);
|
|
60
|
+
this.client.removeListener('connect', onConnect);
|
|
61
|
+
this.client.removeListener('error', onError);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Declare timeout first
|
|
65
|
+
const timeoutId = setTimeout(() => {
|
|
66
|
+
if (!settled) {
|
|
67
|
+
settled = true;
|
|
68
|
+
cleanup();
|
|
69
|
+
this.client.end(true);
|
|
70
|
+
reject(new Error(`MQTT connection timeout after ${this.options.connectTimeout}ms`));
|
|
71
|
+
}
|
|
72
|
+
}, this.options.connectTimeout);
|
|
73
|
+
|
|
74
|
+
const onConnect = () => {
|
|
75
|
+
if (!settled) {
|
|
76
|
+
settled = true;
|
|
77
|
+
cleanup();
|
|
78
|
+
console.log(`Successfully connected to MQTT broker: ${this.brokerUrl}`);
|
|
79
|
+
this.setupMessageHandler();
|
|
80
|
+
resolve();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const onError = (error) => {
|
|
85
|
+
if (!settled) {
|
|
86
|
+
settled = true;
|
|
87
|
+
cleanup();
|
|
88
|
+
console.error(`MQTT connection error: ${error.message}`);
|
|
89
|
+
this.client.end(true);
|
|
90
|
+
reject(error);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
this.client.once('connect', onConnect);
|
|
95
|
+
this.client.once('error', onError);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (this.client) {
|
|
98
|
+
this.client.end(true);
|
|
99
|
+
}
|
|
100
|
+
reject(error);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Setup message handler to buffer incoming messages
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
109
|
+
setupMessageHandler() {
|
|
110
|
+
this.client.on('message', (topic, message) => {
|
|
111
|
+
console.log(`Received message on topic: ${topic}`);
|
|
112
|
+
|
|
113
|
+
// Store message in buffer for the specific topic
|
|
114
|
+
if (!this.messageBuffer.has(topic)) {
|
|
115
|
+
this.messageBuffer.set(topic, []);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const messageData = {
|
|
119
|
+
topic: topic,
|
|
120
|
+
message: message.toString(),
|
|
121
|
+
timestamp: Date.now(),
|
|
122
|
+
raw: message,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
this.messageBuffer.get(topic).push(messageData);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Subscribe to a topic or topics
|
|
131
|
+
* @param {string|string[]} topic - Topic or array of topics to subscribe to
|
|
132
|
+
* @param {Object} options - Subscription options (qos, etc.)
|
|
133
|
+
* @returns {Promise<void>}
|
|
134
|
+
*/
|
|
135
|
+
async subscribe(topic, options = { qos: 0 }) {
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
this.client.subscribe(topic, options, (error, granted) => {
|
|
138
|
+
if (error) {
|
|
139
|
+
console.error(`Failed to subscribe to ${topic}: ${error.message}`);
|
|
140
|
+
reject(error);
|
|
141
|
+
} else {
|
|
142
|
+
const topics = Array.isArray(topic) ? topic : [topic];
|
|
143
|
+
topics.forEach((t) => this.subscriptions.add(t));
|
|
144
|
+
console.log(`Successfully subscribed to: ${JSON.stringify(granted)}`);
|
|
145
|
+
resolve(granted);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Unsubscribe from a topic or topics
|
|
153
|
+
* @param {string|string[]} topic - Topic or array of topics to unsubscribe from
|
|
154
|
+
* @returns {Promise<void>}
|
|
155
|
+
*/
|
|
156
|
+
async unsubscribe(topic) {
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
this.client.unsubscribe(topic, (error) => {
|
|
159
|
+
if (error) {
|
|
160
|
+
console.error(`Failed to unsubscribe from ${topic}: ${error.message}`);
|
|
161
|
+
reject(error);
|
|
162
|
+
} else {
|
|
163
|
+
const topics = Array.isArray(topic) ? topic : [topic];
|
|
164
|
+
topics.forEach((t) => this.subscriptions.delete(t));
|
|
165
|
+
console.log(`Successfully unsubscribed from: ${topic}`);
|
|
166
|
+
resolve();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Publish a message to a topic
|
|
174
|
+
* @param {string} topic - Topic to publish to
|
|
175
|
+
* @param {string|Buffer} message - Message to publish
|
|
176
|
+
* @param {Object} options - Publish options (qos, retain, etc.)
|
|
177
|
+
* @returns {Promise<void>}
|
|
178
|
+
*/
|
|
179
|
+
async publish(topic, message, options = { qos: 0, retain: false }) {
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
this.client.publish(topic, message, options, (error) => {
|
|
182
|
+
if (error) {
|
|
183
|
+
console.error(`Failed to publish to ${topic}: ${error.message}`);
|
|
184
|
+
reject(error);
|
|
185
|
+
} else {
|
|
186
|
+
console.log(`Successfully published to: ${topic}`);
|
|
187
|
+
resolve();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get messages from buffer for a specific topic
|
|
195
|
+
* @param {string} topic - Topic to get messages for
|
|
196
|
+
* @returns {Array} - Array of messages
|
|
197
|
+
*/
|
|
198
|
+
getMessages(topic) {
|
|
199
|
+
return this.messageBuffer.get(topic) || [];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get the latest message from buffer for a specific topic
|
|
204
|
+
* @param {string} topic - Topic to get latest message for
|
|
205
|
+
* @returns {Object|null} - Latest message or null
|
|
206
|
+
*/
|
|
207
|
+
getLatestMessage(topic) {
|
|
208
|
+
const messages = this.getMessages(topic);
|
|
209
|
+
return messages.length > 0 ? messages[messages.length - 1] : null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Clear message buffer for a specific topic or all topics
|
|
214
|
+
* @param {string|null} topic - Topic to clear or null to clear all
|
|
215
|
+
* @returns {<void>}
|
|
216
|
+
*/
|
|
217
|
+
clearMessageBuffer(topic = null) {
|
|
218
|
+
if (topic) {
|
|
219
|
+
this.messageBuffer.delete(topic);
|
|
220
|
+
console.log(`Cleared message buffer for topic: ${topic}`);
|
|
221
|
+
} else {
|
|
222
|
+
this.messageBuffer.clear();
|
|
223
|
+
console.log('Cleared all message buffers');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Wait for a message on a specific topic with timeout
|
|
229
|
+
* @param {string} topic - Topic to wait for message on
|
|
230
|
+
* @param {number} timeoutSeconds - Timeout in seconds
|
|
231
|
+
* @returns {Promise<Object>} - Resolves with message when received
|
|
232
|
+
*/
|
|
233
|
+
async waitForMessage(topic, timeoutSeconds = 10) {
|
|
234
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
235
|
+
const startTime = Date.now();
|
|
236
|
+
|
|
237
|
+
return new Promise((resolve, reject) => {
|
|
238
|
+
const checkInterval = setInterval(() => {
|
|
239
|
+
const latestMessage = this.getLatestMessage(topic);
|
|
240
|
+
|
|
241
|
+
if (latestMessage && latestMessage.timestamp >= startTime) {
|
|
242
|
+
clearInterval(checkInterval);
|
|
243
|
+
resolve(latestMessage);
|
|
244
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
245
|
+
clearInterval(checkInterval);
|
|
246
|
+
reject(new Error(`Timeout waiting for message on topic: ${topic} after ${timeoutSeconds} seconds`));
|
|
247
|
+
}
|
|
248
|
+
}, 100); // Check every 100ms
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Check if client is connected
|
|
254
|
+
* @returns {boolean}
|
|
255
|
+
*/
|
|
256
|
+
isConnected() {
|
|
257
|
+
return this.client && this.client.connected;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Stop the MQTT client and cleanup
|
|
262
|
+
* @returns {Promise<void>}
|
|
263
|
+
*/
|
|
264
|
+
async stop() {
|
|
265
|
+
return new Promise((resolve) => {
|
|
266
|
+
if (this.client && this.client.connected) {
|
|
267
|
+
console.log('Disconnecting from MQTT broker...');
|
|
268
|
+
|
|
269
|
+
// Unsubscribe from all topics
|
|
270
|
+
if (this.subscriptions.size > 0) {
|
|
271
|
+
const topicsArray = Array.from(this.subscriptions);
|
|
272
|
+
this.client.unsubscribe(topicsArray);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Clear buffers
|
|
276
|
+
this.messageBuffer.clear();
|
|
277
|
+
this.subscriptions.clear();
|
|
278
|
+
|
|
279
|
+
// End connection
|
|
280
|
+
this.client.end(false, () => {
|
|
281
|
+
console.log('MQTT client disconnected');
|
|
282
|
+
resolve();
|
|
283
|
+
});
|
|
284
|
+
} else {
|
|
285
|
+
resolve();
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
module.exports = MqttManager;
|
|
@@ -77,3 +77,8 @@ Given('I switch back to original window', async function () {
|
|
|
77
77
|
Given('I switch to {string} tab', async function (tabNumber) {
|
|
78
78
|
this.page = await helper.switchToTab(this.browser, tabNumber);
|
|
79
79
|
});
|
|
80
|
+
|
|
81
|
+
Then('I generate UUID and store it in {string} variable', async function (variable) {
|
|
82
|
+
const uuid = helper.generateRandomUuid();
|
|
83
|
+
await dataStorage.iStoreVariableWithValueToTheJsonFile(uuid, variable);
|
|
84
|
+
});
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
const { Given, When, Then } = require('@cucumber/cucumber');
|
|
2
|
+
const mqttFunctions = require('../../../src/mqttFunctions');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MQTT Step Definitions
|
|
6
|
+
* These steps integrate with the hook-managed MQTT connection
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Subscribe to an MQTT topic
|
|
11
|
+
* @example When I subscribe to MQTT topic "devices/sensor1/telemetry"
|
|
12
|
+
* @example When I subscribe to MQTT topic "devices/+/data" with QoS 1
|
|
13
|
+
*/
|
|
14
|
+
When('I subscribe to MQTT topic {string}', async function (topic) {
|
|
15
|
+
await mqttFunctions.subscribeToTopic(this.mqttManager, topic, 0);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
When('I subscribe to MQTT topic {string} with QoS {int}', async function (topic, qos) {
|
|
19
|
+
await mqttFunctions.subscribeToTopic(this.mqttManager, topic, qos);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Unsubscribe from an MQTT topic
|
|
24
|
+
* @example When I unsubscribe from MQTT topic "devices/sensor1/telemetry"
|
|
25
|
+
*/
|
|
26
|
+
When('I unsubscribe from MQTT topic {string}', async function (topic) {
|
|
27
|
+
await mqttFunctions.unsubscribeFromTopic(this.mqttManager, topic);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Publish a message to an MQTT topic
|
|
32
|
+
* @example When I publish "Hello World" to MQTT topic "test/message"
|
|
33
|
+
* @example When I publish "%savedVariable%" to MQTT topic "devices/sensor1/command"
|
|
34
|
+
*/
|
|
35
|
+
When('I publish {string} to MQTT topic {string}', async function (message, topic) {
|
|
36
|
+
await mqttFunctions.publishMessage(this.mqttManager, message, topic, 0, false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Publish a message with QoS and retain options
|
|
41
|
+
* @example When I publish "Alert" to MQTT topic "alerts/critical" with QoS 1 and retain true
|
|
42
|
+
*/
|
|
43
|
+
When(
|
|
44
|
+
'I publish {string} to MQTT topic {string} with QoS {int} and retain {word}',
|
|
45
|
+
async function (message, topic, qos, retain) {
|
|
46
|
+
const retainFlag = retain === 'true';
|
|
47
|
+
await mqttFunctions.publishMessage(this.mqttManager, message, topic, qos, retainFlag);
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Publish a JSON message to an MQTT topic
|
|
53
|
+
* @example When I publish JSON '{"temperature": 25, "humidity": 60}' to MQTT topic "sensors/room1"
|
|
54
|
+
*/
|
|
55
|
+
When('I publish JSON {string} to MQTT topic {string}', async function (jsonMessage, topic) {
|
|
56
|
+
await mqttFunctions.publishJsonMessage(this.mqttManager, jsonMessage, topic, 0, false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Wait for and validate message reception on a topic
|
|
61
|
+
* @example Then I should receive a message on MQTT topic "devices/sensor1/response" within 5 seconds
|
|
62
|
+
* @example Then I should receive a message on MQTT topic "alerts/+/critical" within 10 seconds
|
|
63
|
+
*/
|
|
64
|
+
Then('I should receive a message on MQTT topic {string} within {int} seconds', async function (topic, timeout) {
|
|
65
|
+
await mqttFunctions.validateMessageReceived(this.mqttManager, topic, timeout);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
Then('I should receive a message on MQTT topic {string}', async function (topic) {
|
|
69
|
+
await mqttFunctions.validateMessageReceived(this.mqttManager, topic, 10);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate the content of the latest message on a topic
|
|
74
|
+
* @example Then the MQTT message on topic "test/echo" should equal "Hello World"
|
|
75
|
+
*/
|
|
76
|
+
Then('the MQTT message on topic {string} should equal {string}', async function (topic, expectedContent) {
|
|
77
|
+
await mqttFunctions.validateMessageContent(this.mqttManager, topic, expectedContent);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validate that the message contains a substring
|
|
82
|
+
* @example Then the MQTT message on topic "logs/app" should contain "ERROR"
|
|
83
|
+
*/
|
|
84
|
+
Then('the MQTT message on topic {string} should contain {string}', async function (topic, substring) {
|
|
85
|
+
await mqttFunctions.validateMessageContains(this.mqttManager, topic, substring);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate JSON message property value
|
|
90
|
+
* @example Then the MQTT message on topic "sensors/temp" should have property "temperature" with value "25"
|
|
91
|
+
* @example Then the MQTT message on topic "data/user" should have property "user.name" with value "John"
|
|
92
|
+
*/
|
|
93
|
+
Then(
|
|
94
|
+
'the MQTT message on topic {string} should have property {string} with value {string}',
|
|
95
|
+
async function (topic, property, expectedValue) {
|
|
96
|
+
await mqttFunctions.validateJsonProperty(this.mqttManager, topic, property, expectedValue);
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Validate JSON message property type
|
|
102
|
+
* @example Then the MQTT message on topic "sensors/data" property "temperature" should be a "number"
|
|
103
|
+
*/
|
|
104
|
+
Then(
|
|
105
|
+
'the MQTT message on topic {string} property {string} should be a {string}',
|
|
106
|
+
async function (topic, property, type) {
|
|
107
|
+
await mqttFunctions.validateJsonPropertyType(this.mqttManager, topic, property, type);
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Remember a JSON property value from MQTT message
|
|
113
|
+
* @example Then I remember the MQTT message property "id" from topic "devices/sensor1/response" as "deviceId"
|
|
114
|
+
* @example Then I remember the MQTT message property "data.temperature" from topic "sensors/temp" as "currentTemp"
|
|
115
|
+
*/
|
|
116
|
+
Then(
|
|
117
|
+
'I remember the MQTT message property {string} from topic {string} as {string}',
|
|
118
|
+
async function (property, topic, variableName) {
|
|
119
|
+
await mqttFunctions.rememberJsonProperty(this.mqttManager, topic, property, variableName);
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Remember the entire message content
|
|
125
|
+
* @example Then I remember the MQTT message from topic "test/data" as "lastMessage"
|
|
126
|
+
*/
|
|
127
|
+
Then('I remember the MQTT message from topic {string} as {string}', async function (topic, variableName) {
|
|
128
|
+
await mqttFunctions.rememberMessage(this.mqttManager, topic, variableName);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Clear message buffer for a topic
|
|
133
|
+
* @example When I clear the MQTT message buffer for topic "test/messages"
|
|
134
|
+
* @example When I clear all MQTT message buffers
|
|
135
|
+
*/
|
|
136
|
+
When('I clear the MQTT message buffer for topic {string}', async function (topic) {
|
|
137
|
+
await mqttFunctions.clearMessageBuffer(this.mqttManager, topic);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
When('I clear all MQTT message buffers', async function () {
|
|
141
|
+
await mqttFunctions.clearMessageBuffer(this.mqttManager, null);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Validate message count on a topic
|
|
146
|
+
* @example Then I should have received 3 messages on MQTT topic "test/counter"
|
|
147
|
+
*/
|
|
148
|
+
Then('I should have received {int} messages on MQTT topic {string}', async function (expectedCount, topic) {
|
|
149
|
+
await mqttFunctions.validateMessageCount(this.mqttManager, topic, expectedCount);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Optional: Explicit connect/disconnect steps for advanced scenarios
|
|
154
|
+
* These are not needed if using the @mqtt tag (connection is automatic)
|
|
155
|
+
*/
|
|
156
|
+
Given('I connect to MQTT broker {string}', async function (brokerUrl) {
|
|
157
|
+
const MqttManager = require('../mqttManager');
|
|
158
|
+
const mqttManager = new MqttManager(brokerUrl);
|
|
159
|
+
await mqttManager.initialize();
|
|
160
|
+
this.mqttManager = mqttManager;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
Given('I disconnect from MQTT broker', async function () {
|
|
164
|
+
if (this.mqttManager) {
|
|
165
|
+
await this.mqttManager.stop();
|
|
166
|
+
this.mqttManager = null;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
@appium
|
|
2
|
+
Feature: Click elements on mobile device
|
|
3
|
+
Scenario: Open settings page and click on a specific setting
|
|
4
|
+
Given I go to "com.google.android.youtube" app package and ".HomeActivity" activity
|
|
5
|
+
And I click on the element "id=com.android.permissioncontroller:id/permission_deny_button" on mobile
|
|
6
|
+
And I wait for "2" seconds
|
|
7
|
+
Then I scroll to the element 'text("Reject all")' on mobile
|
|
8
|
+
Then I click on the element '.text("Reject all")' on mobile
|
|
9
|
+
Then I click on the element '.description("Search")' on mobile
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
@mqtt
|
|
2
|
+
Feature: MQTT Testing Examples
|
|
3
|
+
Test MQTT messaging functionality using Cucumber and mqtt.js
|
|
4
|
+
|
|
5
|
+
Background:
|
|
6
|
+
Given I subscribe to MQTT topic "test/echo"
|
|
7
|
+
And I subscribe to MQTT topic "sensors/temperature"
|
|
8
|
+
|
|
9
|
+
Scenario: Simple message publish and receive
|
|
10
|
+
When I publish "Hello MQTT World" to MQTT topic "test/echo"
|
|
11
|
+
Then I should receive a message on MQTT topic "test/echo" within 5 seconds
|
|
12
|
+
And the MQTT message on topic "test/echo" should equal "Hello MQTT World"
|
|
13
|
+
|
|
14
|
+
Scenario: JSON message with property validation
|
|
15
|
+
When I publish JSON '{"temperature": 25.5, "humidity": 60, "unit": "celsius"}' to MQTT topic "sensors/temperature"
|
|
16
|
+
Then I should receive a message on MQTT topic "sensors/temperature" within 5 seconds
|
|
17
|
+
And the MQTT message on topic "sensors/temperature" should have property "temperature" with value "25.5"
|
|
18
|
+
And the MQTT message on topic "sensors/temperature" should have property "unit" with value "celsius"
|
|
19
|
+
And the MQTT message on topic "sensors/temperature" property "temperature" should be a "number"
|
|
20
|
+
And the MQTT message on topic "sensors/temperature" property "humidity" should be a "number"
|
|
21
|
+
|
|
22
|
+
Scenario: Store and reuse MQTT message data
|
|
23
|
+
Given I subscribe to MQTT topic "devices/sensor-001/status"
|
|
24
|
+
When I publish JSON '{"deviceId": "sensor-001", "status": "active"}' to MQTT topic "devices/sensor-001/status"
|
|
25
|
+
Then I should receive a message on MQTT topic "devices/sensor-001/status" within 5 seconds
|
|
26
|
+
And I remember the MQTT message property "deviceId" from topic "devices/sensor-001/status" as "savedDeviceId"
|
|
27
|
+
And I publish "Command for %savedDeviceId%" to MQTT topic "devices/commands"
|
|
28
|
+
|
|
29
|
+
Scenario: Test message with QoS and retain
|
|
30
|
+
When I publish "Critical Alert" to MQTT topic "alerts/critical" with QoS 1 and retain true
|
|
31
|
+
And I subscribe to MQTT topic "alerts/critical" with QoS 1
|
|
32
|
+
Then I should receive a message on MQTT topic "alerts/critical" within 5 seconds
|
|
33
|
+
And the MQTT message on topic "alerts/critical" should contain "Critical"
|
|
34
|
+
|
|
35
|
+
Scenario: Validate message count
|
|
36
|
+
When I clear the MQTT message buffer for topic "test/counter"
|
|
37
|
+
And I subscribe to MQTT topic "test/counter"
|
|
38
|
+
And I publish "Message 1" to MQTT topic "test/counter"
|
|
39
|
+
And I publish "Message 2" to MQTT topic "test/counter"
|
|
40
|
+
And I publish "Message 3" to MQTT topic "test/counter"
|
|
41
|
+
And I wait for "1" seconds
|
|
42
|
+
Then I should have received 3 messages on MQTT topic "test/counter"
|
|
43
|
+
|
|
44
|
+
Scenario: Wildcard topic subscription
|
|
45
|
+
When I subscribe to MQTT topic "devices/+/telemetry"
|
|
46
|
+
And I publish JSON '{"device": "sensor1", "value": 100}' to MQTT topic "devices/sensor1/telemetry"
|
|
47
|
+
Then I should receive a message on MQTT topic "devices/sensor1/telemetry" within 5 seconds
|
|
48
|
+
And the MQTT message on topic "devices/sensor1/telemetry" should have property "device" with value "sensor1"
|
package/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const dataStorage = require('./src/dataStorage');
|
|
|
7
7
|
const mainFunctions = require('./src/mainFunctions');
|
|
8
8
|
const helperFunctions = require('./src/helperFunctions');
|
|
9
9
|
const apiFunctions = require('./src/apiFunctions');
|
|
10
|
+
const mqttFunctions = require('./src/mqttFunctions');
|
|
10
11
|
const appiumTesting = require('./src/appiumTesting');
|
|
11
12
|
const accessibilityTesting = require('./src/accessibilityTesting');
|
|
12
13
|
const lighthouse = require('./src/lighthouse');
|
|
@@ -15,6 +16,7 @@ const visualRegression = require('./src/visualRegression');
|
|
|
15
16
|
// Export managers
|
|
16
17
|
const BrowserManager = require('./features/app/browserManager');
|
|
17
18
|
const AppiumManager = require('./features/app/appiumManager');
|
|
19
|
+
const MqttManager = require('./features/app/mqttManager');
|
|
18
20
|
|
|
19
21
|
// Export step definitions
|
|
20
22
|
const stepDefinitions = require('./stepDefinitions');
|
|
@@ -26,6 +28,7 @@ module.exports = {
|
|
|
26
28
|
mainFunctions,
|
|
27
29
|
helperFunctions,
|
|
28
30
|
apiFunctions,
|
|
31
|
+
mqttFunctions,
|
|
29
32
|
appiumTesting,
|
|
30
33
|
accessibilityTesting,
|
|
31
34
|
lighthouse,
|
|
@@ -34,6 +37,7 @@ module.exports = {
|
|
|
34
37
|
// Managers
|
|
35
38
|
BrowserManager,
|
|
36
39
|
AppiumManager,
|
|
40
|
+
MqttManager,
|
|
37
41
|
|
|
38
42
|
// Step definitions
|
|
39
43
|
stepDefinitions,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cuppet/core",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Core testing framework components for Cuppet - BDD framework based on Cucumber and Puppeteer",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
"cucumber",
|
|
18
18
|
"puppeteer",
|
|
19
19
|
"appium",
|
|
20
|
+
"mqtt",
|
|
21
|
+
"iot",
|
|
20
22
|
"automation",
|
|
21
23
|
"e2e"
|
|
22
24
|
],
|
|
@@ -32,15 +34,17 @@
|
|
|
32
34
|
"appium": "3.0.1",
|
|
33
35
|
"appium-uiautomator2-driver": "5.0",
|
|
34
36
|
"axios": "^1.11.0",
|
|
35
|
-
"backstopjs": "^6.3.
|
|
37
|
+
"backstopjs": "^6.3.25",
|
|
36
38
|
"chai": "6.0.1",
|
|
37
39
|
"lighthouse": "^12.8.0",
|
|
38
40
|
"mime": "^3.0.0",
|
|
39
41
|
"mime-types": "^3.0.1",
|
|
40
42
|
"moment": "^2.30.1",
|
|
43
|
+
"mqtt": "^5.11.3",
|
|
41
44
|
"pa11y": "^9.0.0",
|
|
42
45
|
"pa11y-reporter-html": "^2.0.0",
|
|
43
46
|
"puppeteer": "^24.0.1",
|
|
47
|
+
"uuid": "^13.0.0",
|
|
44
48
|
"webdriverio": "9.12.7",
|
|
45
49
|
"xml2js": "^0.6.2"
|
|
46
50
|
},
|
|
@@ -60,7 +64,14 @@
|
|
|
60
64
|
"semantic-release": "^24.2.5"
|
|
61
65
|
},
|
|
62
66
|
"scripts": {
|
|
63
|
-
"test": "cucumber-js features/tests",
|
|
67
|
+
"test": "cucumber-js features/tests/example.feature",
|
|
68
|
+
"run:visual": "cucumber-js features/tests/example-visual-test.feature",
|
|
69
|
+
"run:pa11y": "cucumber-js features/tests/example-pa11y.feature",
|
|
70
|
+
"run:lighthouse": "cucumber-js features/tests/example-lighthouse.feature",
|
|
71
|
+
"run:mqtt": "cucumber-js features/tests/mqttExample.feature",
|
|
72
|
+
"run:mobile": "cucumber-js features/tests/example-mobile.feature",
|
|
73
|
+
"run:accessibility": "cucumber-js features/tests/example-accessibility.feature",
|
|
74
|
+
"run:performance": "cucumber-js features/tests/example-performance.feature",
|
|
64
75
|
"postinstall": "node postinstall.js",
|
|
65
76
|
"lint:check": "eslint .",
|
|
66
77
|
"format:check": "prettier --check .",
|
package/src/apiFunctions.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
2
|
const config = require('config');
|
|
3
3
|
const storage = require('./dataStorage');
|
|
4
|
+
const helper = require('./helperFunctions');
|
|
4
5
|
const xml2js = require('xml2js');
|
|
5
6
|
const assert = require('chai').assert;
|
|
6
7
|
const fs = require('fs');
|
|
@@ -201,7 +202,7 @@ module.exports = {
|
|
|
201
202
|
* @returns {Promise<void>}
|
|
202
203
|
*/
|
|
203
204
|
propertyHasValue: async function (property, expectedValue) {
|
|
204
|
-
const actualValue = await
|
|
205
|
+
const actualValue = await helper.getPropertyValue(this.response.data, property);
|
|
205
206
|
assert.strictEqual(actualValue, expectedValue, `Property "${property}" does not have the expected value`);
|
|
206
207
|
},
|
|
207
208
|
|
|
@@ -214,28 +215,10 @@ module.exports = {
|
|
|
214
215
|
* @returns {Promise<void>}
|
|
215
216
|
*/
|
|
216
217
|
iRememberVariable: async function (property, variable) {
|
|
217
|
-
const propValue = await
|
|
218
|
+
const propValue = await helper.getPropertyValue(this.response.data, property);
|
|
218
219
|
await storage.iStoreVariableWithValueToTheJsonFile(propValue, variable);
|
|
219
220
|
},
|
|
220
221
|
|
|
221
|
-
/**
|
|
222
|
-
* Go through the response object and return the value of specific property
|
|
223
|
-
* @param property - name of the property. For nested structure use -> parent.child1.child2 etc.
|
|
224
|
-
* @returns {Promise<*>}
|
|
225
|
-
*/
|
|
226
|
-
getPropertyValue: async function (property) {
|
|
227
|
-
const response = this.response.data;
|
|
228
|
-
const keys = property.split('.');
|
|
229
|
-
let value = response;
|
|
230
|
-
for (let key of keys) {
|
|
231
|
-
value = value[key];
|
|
232
|
-
}
|
|
233
|
-
if (!value) {
|
|
234
|
-
throw new Error(`Value with property: ${property} is not found!`);
|
|
235
|
-
}
|
|
236
|
-
return value;
|
|
237
|
-
},
|
|
238
|
-
|
|
239
222
|
/**
|
|
240
223
|
* Load custom json file and make a request body from it
|
|
241
224
|
* @param path
|
package/src/helperFunctions.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* @typedef {import('puppeteer').Browser} Browser
|
|
5
5
|
*/
|
|
6
6
|
const config = require('config');
|
|
7
|
+
const { v4: uuidv4 } = require('uuid');
|
|
8
|
+
|
|
7
9
|
module.exports = {
|
|
8
10
|
/**
|
|
9
11
|
* Waits for a keypress event to continue the test execution.
|
|
@@ -32,6 +34,33 @@ module.exports = {
|
|
|
32
34
|
.substring(2, length + 2);
|
|
33
35
|
},
|
|
34
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Generate random v4 UUID
|
|
39
|
+
* @example 123e4567-e89b-12d3-a456-426614174000
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
generateRandomUuid: function () {
|
|
43
|
+
return uuidv4();
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Iterate through json object and return the value of specific property
|
|
48
|
+
* @param object - the json object to iterate through
|
|
49
|
+
* @param property - name of the property. For nested structure use -> parent.child1.child2 etc.
|
|
50
|
+
* @returns {*}
|
|
51
|
+
*/
|
|
52
|
+
getPropertyValue: function (object, property) {
|
|
53
|
+
const keys = property.split('.');
|
|
54
|
+
let value = object;
|
|
55
|
+
for (let key of keys) {
|
|
56
|
+
if (value === undefined || value === null) {
|
|
57
|
+
throw new Error(`Property path "${property}" not found in object`);
|
|
58
|
+
}
|
|
59
|
+
value = value[key];
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
},
|
|
63
|
+
|
|
35
64
|
/**
|
|
36
65
|
* Wait until AJAX request is completed
|
|
37
66
|
* @param {Page} page
|
|
@@ -162,4 +191,28 @@ module.exports = {
|
|
|
162
191
|
let pathName = newUrl.pathname;
|
|
163
192
|
return pathName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
|
164
193
|
},
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Convert string representations of primitive types to their actual types
|
|
197
|
+
* Handles: "null" -> null, "true" -> true, "false" -> false, "undefined" -> undefined, numeric strings -> numbers
|
|
198
|
+
* @param {*} value - The value to convert
|
|
199
|
+
* @returns {*} - The converted value or original value if no conversion needed
|
|
200
|
+
*/
|
|
201
|
+
castPrimitiveType: function (value) {
|
|
202
|
+
if (typeof value === 'string') {
|
|
203
|
+
if (value === 'null') {
|
|
204
|
+
return null;
|
|
205
|
+
} else if (value === 'true') {
|
|
206
|
+
return true;
|
|
207
|
+
} else if (value === 'false') {
|
|
208
|
+
return false;
|
|
209
|
+
} else if (value === 'undefined') {
|
|
210
|
+
return undefined;
|
|
211
|
+
} else if (!isNaN(value) && value.trim() !== '') {
|
|
212
|
+
// Convert numeric strings to numbers
|
|
213
|
+
return Number(value);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return value;
|
|
217
|
+
},
|
|
165
218
|
};
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
const assert = require('chai').assert;
|
|
2
|
+
const storage = require('./dataStorage');
|
|
3
|
+
const helper = require('./helperFunctions');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* MQTT Functions Module
|
|
7
|
+
* Provides core MQTT testing operations following the same pattern as Puppeteer and Appium functions
|
|
8
|
+
*/
|
|
9
|
+
module.exports = {
|
|
10
|
+
/**
|
|
11
|
+
* Prepare topic by replacing variables
|
|
12
|
+
* @param {string} topic - Topic with potential variables
|
|
13
|
+
* @returns {Promise<string>} - Resolved topic
|
|
14
|
+
*/
|
|
15
|
+
prepareTopic: async function (topic) {
|
|
16
|
+
return await storage.checkForMultipleVariables(topic);
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Prepare message by replacing variables
|
|
21
|
+
* @param {string} message - Message with potential variables
|
|
22
|
+
* @returns {Promise<string>} - Resolved message
|
|
23
|
+
*/
|
|
24
|
+
prepareMessage: async function (message) {
|
|
25
|
+
return await storage.checkForMultipleVariables(message);
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Subscribe to a topic
|
|
30
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
31
|
+
* @param {string} topic - Topic to subscribe to
|
|
32
|
+
* @param {number} qos - Quality of Service level (0, 1, or 2)
|
|
33
|
+
* @returns {Promise<void>}
|
|
34
|
+
*/
|
|
35
|
+
subscribeToTopic: async function (mqttManager, topic, qos = 0) {
|
|
36
|
+
const resolvedTopic = await this.prepareTopic(topic);
|
|
37
|
+
await mqttManager.subscribe(resolvedTopic, { qos });
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Unsubscribe from a topic
|
|
42
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
43
|
+
* @param {string} topic - Topic to unsubscribe from
|
|
44
|
+
* @returns {Promise<void>}
|
|
45
|
+
*/
|
|
46
|
+
unsubscribeFromTopic: async function (mqttManager, topic) {
|
|
47
|
+
const resolvedTopic = await this.prepareTopic(topic);
|
|
48
|
+
await mqttManager.unsubscribe(resolvedTopic);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Publish a message to a topic
|
|
53
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
54
|
+
* @param {string} message - Message to publish
|
|
55
|
+
* @param {string} topic - Topic to publish to
|
|
56
|
+
* @param {number} qos - Quality of Service level (0, 1, or 2)
|
|
57
|
+
* @param {boolean} retain - Whether to retain the message
|
|
58
|
+
* @returns {Promise<void>}
|
|
59
|
+
*/
|
|
60
|
+
publishMessage: async function (mqttManager, message, topic, qos = 0, retain = false) {
|
|
61
|
+
const resolvedTopic = await this.prepareTopic(topic);
|
|
62
|
+
const resolvedMessage = await this.prepareMessage(message);
|
|
63
|
+
await mqttManager.publish(resolvedTopic, resolvedMessage, { qos, retain });
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Publish a JSON message to a topic
|
|
68
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
69
|
+
* @param {string} jsonString - JSON string to publish
|
|
70
|
+
* @param {string} topic - Topic to publish to
|
|
71
|
+
* @param {number} qos - Quality of Service level
|
|
72
|
+
* @param {boolean} retain - Whether to retain the message
|
|
73
|
+
* @returns {Promise<void>}
|
|
74
|
+
*/
|
|
75
|
+
publishJsonMessage: async function (mqttManager, jsonString, topic, qos = 0, retain = false) {
|
|
76
|
+
const resolvedTopic = await this.prepareTopic(topic);
|
|
77
|
+
const resolvedJson = await this.prepareMessage(jsonString);
|
|
78
|
+
|
|
79
|
+
// Validate JSON
|
|
80
|
+
try {
|
|
81
|
+
JSON.parse(resolvedJson);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
throw new Error(`Invalid JSON message: ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await mqttManager.publish(resolvedTopic, resolvedJson, { qos, retain });
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Wait for a message on a specific topic
|
|
91
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
92
|
+
* @param {string} topic - Topic to wait for message on
|
|
93
|
+
* @param {number} timeoutSeconds - Timeout in seconds
|
|
94
|
+
* @returns {Promise<Object>} - Message object
|
|
95
|
+
*/
|
|
96
|
+
waitForMessage: async function (mqttManager, topic, timeoutSeconds = 10) {
|
|
97
|
+
const resolvedTopic = await this.prepareTopic(topic);
|
|
98
|
+
return await mqttManager.waitForMessage(resolvedTopic, timeoutSeconds);
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the latest message from a topic
|
|
103
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
104
|
+
* @param {string} topic - Topic to get message from
|
|
105
|
+
* @returns {Promise<Object>} - Latest message object
|
|
106
|
+
*/
|
|
107
|
+
getLatestMessage: async function (mqttManager, topic) {
|
|
108
|
+
const resolvedTopic = await this.prepareTopic(topic);
|
|
109
|
+
const message = mqttManager.getLatestMessage(resolvedTopic);
|
|
110
|
+
|
|
111
|
+
if (!message) {
|
|
112
|
+
throw new Error(`No messages received on topic: ${resolvedTopic}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return message;
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parse message as JSON
|
|
120
|
+
* @param {Object} messageObj - Message object from buffer
|
|
121
|
+
* @returns {Object} - Parsed JSON object
|
|
122
|
+
*/
|
|
123
|
+
parseMessageAsJson: function (messageObj) {
|
|
124
|
+
try {
|
|
125
|
+
return JSON.parse(messageObj.message);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
throw new Error(`Failed to parse message as JSON: ${error.message}`);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Validate that a message was received on a topic
|
|
133
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
134
|
+
* @param {string} topic - Topic to check
|
|
135
|
+
* @param {number} timeoutSeconds - Timeout in seconds
|
|
136
|
+
* @returns {Promise<Object>} - Message object
|
|
137
|
+
*/
|
|
138
|
+
validateMessageReceived: async function (mqttManager, topic, timeoutSeconds = 10) {
|
|
139
|
+
const message = await this.waitForMessage(mqttManager, topic, timeoutSeconds);
|
|
140
|
+
assert.isDefined(message, `No message received on topic: ${topic}`);
|
|
141
|
+
return message;
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Validate message content equals expected value
|
|
146
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
147
|
+
* @param {string} topic - Topic to check message on
|
|
148
|
+
* @param {string} expectedContent - Expected message content
|
|
149
|
+
* @returns {Promise<void>}
|
|
150
|
+
*/
|
|
151
|
+
validateMessageContent: async function (mqttManager, topic, expectedContent) {
|
|
152
|
+
const resolvedExpected = await storage.checkForSavedVariable(expectedContent);
|
|
153
|
+
const message = await this.getLatestMessage(mqttManager, topic);
|
|
154
|
+
assert.strictEqual(
|
|
155
|
+
message.message,
|
|
156
|
+
resolvedExpected,
|
|
157
|
+
`Message content does not match. Expected: ${resolvedExpected}, Actual: ${message.message}`
|
|
158
|
+
);
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Validate message contains a substring
|
|
163
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
164
|
+
* @param {string} topic - Topic to check message on
|
|
165
|
+
* @param {string} substring - Substring to search for
|
|
166
|
+
* @returns {Promise<void>}
|
|
167
|
+
*/
|
|
168
|
+
validateMessageContains: async function (mqttManager, topic, substring) {
|
|
169
|
+
const resolvedSubstring = await storage.checkForSavedVariable(substring);
|
|
170
|
+
const message = await this.getLatestMessage(mqttManager, topic);
|
|
171
|
+
assert.include(
|
|
172
|
+
message.message,
|
|
173
|
+
resolvedSubstring,
|
|
174
|
+
`Message does not contain expected substring: ${resolvedSubstring}`
|
|
175
|
+
);
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Validate JSON message property has expected value
|
|
180
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
181
|
+
* @param {string} topic - Topic to get message from
|
|
182
|
+
* @param {string} property - Property path (e.g., "data.temperature")
|
|
183
|
+
* @param {string} expectedValue - Expected value
|
|
184
|
+
* @returns {Promise<void>}
|
|
185
|
+
*/
|
|
186
|
+
validateJsonProperty: async function (mqttManager, topic, property, expectedValue) {
|
|
187
|
+
let resolvedExpected = await storage.checkForSavedVariable(expectedValue);
|
|
188
|
+
resolvedExpected = helper.castPrimitiveType(resolvedExpected);
|
|
189
|
+
|
|
190
|
+
const message = await this.getLatestMessage(mqttManager, topic);
|
|
191
|
+
const jsonData = this.parseMessageAsJson(message);
|
|
192
|
+
const value = helper.getPropertyValue(jsonData, property);
|
|
193
|
+
|
|
194
|
+
assert.strictEqual(
|
|
195
|
+
value,
|
|
196
|
+
resolvedExpected,
|
|
197
|
+
`The value of the property "${property}" does not match: Expected: ${resolvedExpected}, Actual: ${value}`
|
|
198
|
+
);
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Validate JSON message property type
|
|
203
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
204
|
+
* @param {string} topic - Topic to get message from
|
|
205
|
+
* @param {string} property - Property path
|
|
206
|
+
* @param {string} type - Expected type (string, number, boolean, object, array)
|
|
207
|
+
* @returns {Promise<void>}
|
|
208
|
+
*/
|
|
209
|
+
validateJsonPropertyType: async function (mqttManager, topic, property, type) {
|
|
210
|
+
const message = await this.getLatestMessage(mqttManager, topic);
|
|
211
|
+
const jsonData = this.parseMessageAsJson(message);
|
|
212
|
+
const value = helper.getPropertyValue(jsonData, property);
|
|
213
|
+
assert.typeOf(value, type, `Property "${property}" is not of type ${type}`);
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Remember a property value from JSON message
|
|
218
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
219
|
+
* @param {string} topic - Topic to get message from
|
|
220
|
+
* @param {string} property - Property path
|
|
221
|
+
* @param {string} variableName - Variable name to store value
|
|
222
|
+
* @returns {Promise<void>}
|
|
223
|
+
*/
|
|
224
|
+
rememberJsonProperty: async function (mqttManager, topic, property, variableName) {
|
|
225
|
+
const message = await this.getLatestMessage(mqttManager, topic);
|
|
226
|
+
const jsonData = this.parseMessageAsJson(message);
|
|
227
|
+
const value = helper.getPropertyValue(jsonData, property);
|
|
228
|
+
await storage.iStoreVariableWithValueToTheJsonFile(value, variableName);
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Remember the entire message content
|
|
233
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
234
|
+
* @param {string} topic - Topic to get message from
|
|
235
|
+
* @param {string} variableName - Variable name to store message
|
|
236
|
+
* @returns {Promise<void>}
|
|
237
|
+
*/
|
|
238
|
+
rememberMessage: async function (mqttManager, topic, variableName) {
|
|
239
|
+
const message = await this.getLatestMessage(mqttManager, topic);
|
|
240
|
+
await storage.iStoreVariableWithValueToTheJsonFile(message.message, variableName);
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Clear message buffer for a topic
|
|
245
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
246
|
+
* @param {string} topic - Topic to clear buffer for (or null for all)
|
|
247
|
+
* @returns {Promise<void>}
|
|
248
|
+
*/
|
|
249
|
+
clearMessageBuffer: async function (mqttManager, topic = null) {
|
|
250
|
+
if (topic) {
|
|
251
|
+
const resolvedTopic = await this.prepareTopic(topic);
|
|
252
|
+
mqttManager.clearMessageBuffer(resolvedTopic);
|
|
253
|
+
} else {
|
|
254
|
+
mqttManager.clearMessageBuffer();
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get message count for a topic
|
|
260
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
261
|
+
* @param {string} topic - Topic to get message count for
|
|
262
|
+
* @returns {Promise<number>} - Number of messages
|
|
263
|
+
*/
|
|
264
|
+
getMessageCount: async function (mqttManager, topic) {
|
|
265
|
+
const resolvedTopic = await this.prepareTopic(topic);
|
|
266
|
+
return (await mqttManager.getMessages(resolvedTopic)).length;
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Validate message count on a topic
|
|
271
|
+
* @param {object} mqttManager - MQTT manager instance
|
|
272
|
+
* @param {string} topic - Topic to check
|
|
273
|
+
* @param {number} expectedCount - Expected number of messages
|
|
274
|
+
* @returns {Promise<void>}
|
|
275
|
+
*/
|
|
276
|
+
validateMessageCount: async function (mqttManager, topic, expectedCount) {
|
|
277
|
+
const resolvedTopic = await this.prepareTopic(topic);
|
|
278
|
+
const actualCount = await this.getMessageCount(mqttManager, resolvedTopic);
|
|
279
|
+
assert.strictEqual(
|
|
280
|
+
actualCount,
|
|
281
|
+
expectedCount,
|
|
282
|
+
`Expected ${expectedCount} messages on topic "${topic}", but found ${actualCount}`
|
|
283
|
+
);
|
|
284
|
+
},
|
|
285
|
+
};
|
package/stepDefinitions.js
CHANGED
|
@@ -10,6 +10,7 @@ module.exports = {
|
|
|
10
10
|
iframeSteps: require('./features/app/stepDefinitions/iframeSteps'),
|
|
11
11
|
ifVisibleSteps: require('./features/app/stepDefinitions/ifVisibleSteps'),
|
|
12
12
|
lighthouseSteps: require('./features/app/stepDefinitions/lighthouseSteps'),
|
|
13
|
+
mqttSteps: require('./features/app/stepDefinitions/mqttSteps'),
|
|
13
14
|
pageElements: require('./features/app/stepDefinitions/pageElements'),
|
|
14
15
|
pageElementsConfig: require('./features/app/stepDefinitions/pageElementsConfig'),
|
|
15
16
|
pageElementsJson: require('./features/app/stepDefinitions/pageElementsJson'),
|