meross-cli 0.1.0

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +110 -0
  4. package/cli/commands/control/execute.js +23 -0
  5. package/cli/commands/control/index.js +12 -0
  6. package/cli/commands/control/menu.js +193 -0
  7. package/cli/commands/control/params/generic.js +229 -0
  8. package/cli/commands/control/params/index.js +56 -0
  9. package/cli/commands/control/params/light.js +188 -0
  10. package/cli/commands/control/params/thermostat.js +166 -0
  11. package/cli/commands/control/params/timer.js +242 -0
  12. package/cli/commands/control/params/trigger.js +206 -0
  13. package/cli/commands/dump.js +35 -0
  14. package/cli/commands/index.js +34 -0
  15. package/cli/commands/info.js +221 -0
  16. package/cli/commands/list.js +112 -0
  17. package/cli/commands/mqtt.js +187 -0
  18. package/cli/commands/sniffer/device-sniffer.js +217 -0
  19. package/cli/commands/sniffer/fake-app.js +233 -0
  20. package/cli/commands/sniffer/index.js +7 -0
  21. package/cli/commands/sniffer/message-queue.js +65 -0
  22. package/cli/commands/sniffer/sniffer-menu.js +676 -0
  23. package/cli/commands/stats.js +90 -0
  24. package/cli/commands/status/device-status.js +1403 -0
  25. package/cli/commands/status/hub-status.js +72 -0
  26. package/cli/commands/status/index.js +50 -0
  27. package/cli/commands/status/subdevices/hub-smoke-detector.js +82 -0
  28. package/cli/commands/status/subdevices/hub-temp-hum-sensor.js +43 -0
  29. package/cli/commands/status/subdevices/hub-thermostat-valve.js +83 -0
  30. package/cli/commands/status/subdevices/hub-water-leak-sensor.js +27 -0
  31. package/cli/commands/status/subdevices/index.js +23 -0
  32. package/cli/commands/test/index.js +185 -0
  33. package/cli/config/users.js +108 -0
  34. package/cli/control-registry.js +875 -0
  35. package/cli/helpers/client.js +89 -0
  36. package/cli/helpers/meross.js +106 -0
  37. package/cli/menu/index.js +10 -0
  38. package/cli/menu/main.js +648 -0
  39. package/cli/menu/settings.js +789 -0
  40. package/cli/meross-cli.js +547 -0
  41. package/cli/tests/README.md +365 -0
  42. package/cli/tests/test-alarm.js +144 -0
  43. package/cli/tests/test-child-lock.js +248 -0
  44. package/cli/tests/test-config.js +133 -0
  45. package/cli/tests/test-control.js +189 -0
  46. package/cli/tests/test-diffuser.js +505 -0
  47. package/cli/tests/test-dnd.js +246 -0
  48. package/cli/tests/test-electricity.js +209 -0
  49. package/cli/tests/test-encryption.js +281 -0
  50. package/cli/tests/test-garage.js +259 -0
  51. package/cli/tests/test-helper.js +313 -0
  52. package/cli/tests/test-hub-mts100.js +355 -0
  53. package/cli/tests/test-hub-sensors.js +489 -0
  54. package/cli/tests/test-light.js +253 -0
  55. package/cli/tests/test-presence.js +497 -0
  56. package/cli/tests/test-registry.js +419 -0
  57. package/cli/tests/test-roller-shutter.js +628 -0
  58. package/cli/tests/test-runner.js +415 -0
  59. package/cli/tests/test-runtime.js +234 -0
  60. package/cli/tests/test-screen.js +133 -0
  61. package/cli/tests/test-sensor-history.js +146 -0
  62. package/cli/tests/test-smoke-config.js +138 -0
  63. package/cli/tests/test-spray.js +131 -0
  64. package/cli/tests/test-temp-unit.js +133 -0
  65. package/cli/tests/test-template.js +238 -0
  66. package/cli/tests/test-thermostat.js +919 -0
  67. package/cli/tests/test-timer.js +372 -0
  68. package/cli/tests/test-toggle.js +342 -0
  69. package/cli/tests/test-trigger.js +279 -0
  70. package/cli/utils/display.js +86 -0
  71. package/cli/utils/terminal.js +137 -0
  72. package/package.json +53 -0
@@ -0,0 +1,342 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Toggle/Switch Device Tests
5
+ * Tests on/off control for switches and smart plugs
6
+ */
7
+
8
+ const { findDevicesByAbility, waitForDeviceConnection, getDeviceName, OnlineStatus } = require('./test-helper');
9
+
10
+ /**
11
+ * Test metadata
12
+ */
13
+ const metadata = {
14
+ name: 'switch',
15
+ description: 'Tests on/off control for switches and smart plugs',
16
+ requiredAbilities: ['Appliance.Control.ToggleX', 'Appliance.Control.Toggle'],
17
+ minDevices: 1
18
+ };
19
+
20
+ /**
21
+ * Helper function to find all toggle-capable devices
22
+ * @param {Object} manager - MerossManager instance
23
+ * @returns {Promise<Array>} Array of toggle devices
24
+ */
25
+ async function findAllToggleDevices(manager) {
26
+ const toggleXDevices = await findDevicesByAbility(manager, 'Appliance.Control.ToggleX', OnlineStatus.ONLINE);
27
+ const toggleDevices = await findDevicesByAbility(manager, 'Appliance.Control.Toggle', OnlineStatus.ONLINE);
28
+
29
+ // Combine and deduplicate
30
+ const allToggleDevices = [...toggleXDevices];
31
+ for (const device of toggleDevices) {
32
+ const uuid = device.dev?.uuid || device.uuid;
33
+ if (!allToggleDevices.find(d => (d.dev?.uuid || d.uuid) === uuid)) {
34
+ allToggleDevices.push(device);
35
+ }
36
+ }
37
+ return allToggleDevices;
38
+ }
39
+
40
+ /**
41
+ * Runs all tests for toggle/switch devices
42
+ * @param {Object} context - Test context object
43
+ * @param {Object} context.manager - MerossManager instance (already connected)
44
+ * @param {Array<Object>} context.devices - Pre-filtered devices (from CLI selection or auto-discovery)
45
+ * @param {Object} context.options - Test options (timeout, verbose, etc.)
46
+ * @returns {Promise<Array>} Array of test result objects
47
+ */
48
+ async function runTests(context) {
49
+ const { manager, devices, options = {} } = context;
50
+ const timeout = options.timeout || 30000;
51
+ const results = [];
52
+
53
+ // If no devices provided, discover them
54
+ let toggleDevices = devices || [];
55
+ if (toggleDevices.length === 0) {
56
+ toggleDevices = await findAllToggleDevices(manager);
57
+ }
58
+
59
+ // Wait for devices to be connected
60
+ for (const device of toggleDevices) {
61
+ await waitForDeviceConnection(device, timeout);
62
+ await new Promise(resolve => setTimeout(resolve, 1000));
63
+ }
64
+
65
+ // Test 1: Check if devices were found
66
+ if (toggleDevices.length === 0) {
67
+ results.push({
68
+ name: 'should find devices with toggle capability',
69
+ passed: false,
70
+ skipped: true,
71
+ error: 'No devices with toggle capability found',
72
+ device: null
73
+ });
74
+ return results; // Early return if no devices
75
+ }
76
+
77
+ results.push({
78
+ name: 'should find devices with toggle capability',
79
+ passed: true,
80
+ skipped: false,
81
+ error: null,
82
+ device: null
83
+ });
84
+
85
+ const testDevice = toggleDevices[0];
86
+ const deviceName = getDeviceName(testDevice);
87
+
88
+ // Test 2: Get toggle state
89
+ try {
90
+ if (typeof testDevice.getToggleState === 'function') {
91
+ const toggleState = await testDevice.getToggleState(0);
92
+
93
+ if (!toggleState) {
94
+ results.push({
95
+ name: 'should get toggle state',
96
+ passed: false,
97
+ skipped: false,
98
+ error: 'getToggleState returned null or undefined',
99
+ device: deviceName
100
+ });
101
+ } else {
102
+ // Check cached toggle state
103
+ const cachedState = testDevice.getCachedToggleState(0);
104
+ const isOn = testDevice.isOn(0);
105
+
106
+ results.push({
107
+ name: 'should get toggle state',
108
+ passed: true,
109
+ skipped: false,
110
+ error: null,
111
+ device: deviceName,
112
+ details: {
113
+ toggleState: toggleState,
114
+ cachedState: cachedState,
115
+ isOn: isOn
116
+ }
117
+ });
118
+ }
119
+ } else {
120
+ results.push({
121
+ name: 'should get toggle state',
122
+ passed: false,
123
+ skipped: true,
124
+ error: 'Device does not support getToggleState',
125
+ device: deviceName
126
+ });
127
+ }
128
+ } catch (error) {
129
+ results.push({
130
+ name: 'should get toggle state',
131
+ passed: false,
132
+ skipped: false,
133
+ error: error.message,
134
+ device: deviceName
135
+ });
136
+ }
137
+
138
+ // Test 3: Control toggle state (turn on/off)
139
+ try {
140
+ // Get initial state
141
+ let initialState = undefined;
142
+ if (typeof testDevice.getToggleState === 'function') {
143
+ const stateResponse = await testDevice.getToggleState(0);
144
+ if (stateResponse && stateResponse.togglex) {
145
+ initialState = stateResponse.togglex[0]?.onoff || stateResponse.togglex.onoff;
146
+ }
147
+ } else if (typeof testDevice.isOn === 'function') {
148
+ initialState = testDevice.isOn(0);
149
+ }
150
+
151
+ // Test turnOn if available
152
+ if (typeof testDevice.turnOn === 'function') {
153
+ const turnOnResult = await testDevice.turnOn(0);
154
+
155
+ if (!turnOnResult) {
156
+ results.push({
157
+ name: 'should control toggle state (turn on)',
158
+ passed: false,
159
+ skipped: false,
160
+ error: 'turnOn() returned null or undefined',
161
+ device: deviceName
162
+ });
163
+ } else {
164
+ // Wait a bit for state to update
165
+ await new Promise(resolve => setTimeout(resolve, 2000));
166
+
167
+ // Verify state
168
+ if (typeof testDevice.isOn === 'function') {
169
+ const isOnAfter = testDevice.isOn(0);
170
+ if (!isOnAfter) {
171
+ results.push({
172
+ name: 'should control toggle state (turn on)',
173
+ passed: false,
174
+ skipped: false,
175
+ error: 'Device did not turn on after turnOn() call',
176
+ device: deviceName
177
+ });
178
+ } else {
179
+ results.push({
180
+ name: 'should control toggle state (turn on)',
181
+ passed: true,
182
+ skipped: false,
183
+ error: null,
184
+ device: deviceName
185
+ });
186
+ }
187
+ } else {
188
+ // Can't verify, but command succeeded
189
+ results.push({
190
+ name: 'should control toggle state (turn on)',
191
+ passed: true,
192
+ skipped: false,
193
+ error: null,
194
+ device: deviceName,
195
+ details: { note: 'turnOn succeeded but cannot verify state (isOn not available)' }
196
+ });
197
+ }
198
+ }
199
+ } else {
200
+ results.push({
201
+ name: 'should control toggle state (turn on)',
202
+ passed: false,
203
+ skipped: true,
204
+ error: 'Device does not support turnOn',
205
+ device: deviceName
206
+ });
207
+ }
208
+
209
+ // Test turnOff if available
210
+ if (typeof testDevice.turnOff === 'function') {
211
+ const turnOffResult = await testDevice.turnOff(0);
212
+
213
+ if (!turnOffResult) {
214
+ results.push({
215
+ name: 'should control toggle state (turn off)',
216
+ passed: false,
217
+ skipped: false,
218
+ error: 'turnOff() returned null or undefined',
219
+ device: deviceName
220
+ });
221
+ } else {
222
+ // Wait a bit for state to update
223
+ await new Promise(resolve => setTimeout(resolve, 2000));
224
+
225
+ // Verify state
226
+ if (typeof testDevice.isOn === 'function') {
227
+ const isOnAfter = testDevice.isOn(0);
228
+ if (isOnAfter) {
229
+ results.push({
230
+ name: 'should control toggle state (turn off)',
231
+ passed: false,
232
+ skipped: false,
233
+ error: 'Device did not turn off after turnOff() call',
234
+ device: deviceName
235
+ });
236
+ } else {
237
+ results.push({
238
+ name: 'should control toggle state (turn off)',
239
+ passed: true,
240
+ skipped: false,
241
+ error: null,
242
+ device: deviceName
243
+ });
244
+ }
245
+ } else {
246
+ // Can't verify, but command succeeded
247
+ results.push({
248
+ name: 'should control toggle state (turn off)',
249
+ passed: true,
250
+ skipped: false,
251
+ error: null,
252
+ device: deviceName,
253
+ details: { note: 'turnOff succeeded but cannot verify state (isOn not available)' }
254
+ });
255
+ }
256
+ }
257
+ } else {
258
+ results.push({
259
+ name: 'should control toggle state (turn off)',
260
+ passed: false,
261
+ skipped: true,
262
+ error: 'Device does not support turnOff',
263
+ device: deviceName
264
+ });
265
+ }
266
+
267
+ // Restore initial state if we changed it
268
+ if (initialState !== undefined && typeof testDevice.turnOn === 'function' && typeof testDevice.turnOff === 'function') {
269
+ if (initialState === 1 || initialState === true) {
270
+ await testDevice.turnOn(0);
271
+ } else {
272
+ await testDevice.turnOff(0);
273
+ }
274
+ await new Promise(resolve => setTimeout(resolve, 2000));
275
+ }
276
+
277
+ } catch (error) {
278
+ results.push({
279
+ name: 'should control toggle state',
280
+ passed: false,
281
+ skipped: false,
282
+ error: error.message,
283
+ device: deviceName
284
+ });
285
+ }
286
+
287
+ // Test 4: Handle toggle push notifications
288
+ try {
289
+ const testDeviceForPush = toggleDevices[0];
290
+
291
+ // Set up listener for toggle push notifications
292
+ let receivedNotification = false;
293
+ const notificationHandler = (notification) => {
294
+ if (notification.namespace === 'Appliance.Control.ToggleX' ||
295
+ notification.namespace === 'Appliance.Control.Toggle') {
296
+ receivedNotification = true;
297
+ }
298
+ };
299
+
300
+ testDeviceForPush.on('pushNotification', notificationHandler);
301
+
302
+ // Get toggle state (may trigger a push notification)
303
+ if (typeof testDeviceForPush.getToggleState === 'function') {
304
+ await testDeviceForPush.getToggleState(0);
305
+ }
306
+
307
+ // Wait a bit for potential push notifications
308
+ await new Promise(resolve => setTimeout(resolve, 5000));
309
+
310
+ // Remove listener
311
+ testDeviceForPush.removeListener('pushNotification', notificationHandler);
312
+
313
+ // Note: We don't assert on receivedNotification since push notifications
314
+ // are device-initiated and may not occur during testing
315
+ // This test just verifies the listener mechanism works
316
+ results.push({
317
+ name: 'should handle toggle push notifications',
318
+ passed: true,
319
+ skipped: false,
320
+ error: null,
321
+ device: getDeviceName(testDeviceForPush),
322
+ details: { notificationReceived: receivedNotification }
323
+ });
324
+
325
+ } catch (error) {
326
+ results.push({
327
+ name: 'should handle toggle push notifications',
328
+ passed: false,
329
+ skipped: false,
330
+ error: error.message,
331
+ device: toggleDevices.length > 0 ? getDeviceName(toggleDevices[0]) : null
332
+ });
333
+ }
334
+
335
+ return results;
336
+ }
337
+
338
+ // Export metadata and runTests function
339
+ module.exports = {
340
+ metadata,
341
+ runTests
342
+ };
@@ -0,0 +1,279 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Trigger Tests
5
+ * Tests trigger creation, management, and push notifications
6
+ */
7
+
8
+ const { findDevicesByAbility, getDeviceName, OnlineStatus } = require('./test-helper');
9
+ const { TriggerType, TriggerUtils } = require('meross-iot');
10
+ const { createTrigger } = TriggerUtils;
11
+
12
+ const metadata = {
13
+ name: 'trigger',
14
+ description: 'Tests trigger creation, management, and push notifications',
15
+ requiredAbilities: ['Appliance.Control.TriggerX'],
16
+ minDevices: 1
17
+ };
18
+
19
+ async function runTests(context) {
20
+ const { manager, devices, options = {} } = context;
21
+ const timeout = options.timeout || 30000;
22
+ const results = [];
23
+
24
+ // If no devices provided, discover them
25
+ let testDevices = devices || [];
26
+ if (testDevices.length === 0) {
27
+ testDevices = await findDevicesByAbility(manager, 'Appliance.Control.TriggerX', OnlineStatus.ONLINE);
28
+ }
29
+
30
+ if (testDevices.length === 0) {
31
+ results.push({
32
+ name: 'should find devices with trigger capability',
33
+ passed: false,
34
+ skipped: true,
35
+ error: 'No devices with trigger capability found',
36
+ device: null
37
+ });
38
+ return results;
39
+ }
40
+
41
+ results.push({
42
+ name: 'should find devices with trigger capability',
43
+ passed: true,
44
+ skipped: false,
45
+ error: null,
46
+ device: null
47
+ });
48
+
49
+ const testDevice = testDevices[0];
50
+ const deviceName = getDeviceName(testDevice);
51
+
52
+ let createdTriggerId = null;
53
+
54
+ // Test 1: Create a trigger and verify it exists
55
+ try {
56
+ // Create a test trigger using the utility function
57
+ const testTrigger = createTrigger({
58
+ alias: 'Test Trigger - CLI Test',
59
+ duration: '30m', // 30 minutes
60
+ days: ['weekday'],
61
+ type: TriggerType.SINGLE_POINT_WEEKLY_CYCLE,
62
+ channel: 0,
63
+ enabled: true
64
+ });
65
+
66
+ // Wait for push notification after SET (as per API spec: "PUSH after SET")
67
+ let triggerIdFromPush = null;
68
+ const pushNotificationPromise = new Promise((resolve) => {
69
+ const handler = (notification) => {
70
+ if (notification.namespace === 'Appliance.Control.TriggerX' && notification.triggerxData) {
71
+ const triggerData = Array.isArray(notification.triggerxData) ? notification.triggerxData : [notification.triggerxData];
72
+ const createdTrigger = triggerData.find(t =>
73
+ t.channel === 0 &&
74
+ t.alias === 'Test Trigger - CLI Test' &&
75
+ t.id
76
+ );
77
+ if (createdTrigger && createdTrigger.id) {
78
+ triggerIdFromPush = createdTrigger.id;
79
+ testDevice.removeListener('pushNotification', handler);
80
+ resolve();
81
+ }
82
+ }
83
+ };
84
+ testDevice.on('pushNotification', handler);
85
+
86
+ // Timeout after 5 seconds if no push notification arrives
87
+ setTimeout(() => {
88
+ testDevice.removeListener('pushNotification', handler);
89
+ resolve();
90
+ }, 5000);
91
+ });
92
+
93
+ const createResult = await testDevice.setTriggerX(testTrigger);
94
+
95
+ if (!createResult) {
96
+ results.push({
97
+ name: 'should create trigger',
98
+ passed: false,
99
+ skipped: false,
100
+ error: 'setTriggerX returned null or undefined',
101
+ device: deviceName
102
+ });
103
+ return results;
104
+ }
105
+
106
+ // Wait for push notification (contains trigger with ID)
107
+ await pushNotificationPromise;
108
+
109
+ // Get the trigger ID from push notification, or fallback to generated ID
110
+ if (triggerIdFromPush) {
111
+ createdTriggerId = triggerIdFromPush;
112
+ } else if (testTrigger.id) {
113
+ createdTriggerId = testTrigger.id;
114
+ } else {
115
+ // Wait a bit and query by alias to find it
116
+ await new Promise(resolve => setTimeout(resolve, 2000));
117
+ const triggers = await testDevice.getTriggerX(0);
118
+ const foundTrigger = triggers?.triggerx?.find(t => t.alias === 'Test Trigger - CLI Test');
119
+ if (foundTrigger && foundTrigger.id) {
120
+ createdTriggerId = foundTrigger.id;
121
+ }
122
+ }
123
+
124
+ if (!createdTriggerId) {
125
+ results.push({
126
+ name: 'should create trigger',
127
+ passed: false,
128
+ skipped: false,
129
+ error: 'Could not get trigger ID after creation',
130
+ device: deviceName
131
+ });
132
+ return results;
133
+ }
134
+
135
+ // Verify trigger exists by querying it
136
+ const triggerInfo = await testDevice.getTriggerX(0);
137
+ const triggerExists = triggerInfo?.triggerx?.some(t => t.id === createdTriggerId);
138
+
139
+ if (!triggerExists) {
140
+ results.push({
141
+ name: 'should create trigger',
142
+ passed: false,
143
+ skipped: false,
144
+ error: 'Created trigger not found when querying',
145
+ device: deviceName
146
+ });
147
+ return results;
148
+ }
149
+
150
+ // Verify cached trigger state
151
+ const cachedTriggers = testDevice.getCachedTriggerX(0);
152
+
153
+ results.push({
154
+ name: 'should create trigger',
155
+ passed: true,
156
+ skipped: false,
157
+ error: null,
158
+ device: deviceName,
159
+ details: {
160
+ triggerId: createdTriggerId,
161
+ cachedTriggerCount: cachedTriggers ? cachedTriggers.length : 0
162
+ }
163
+ });
164
+ } catch (error) {
165
+ results.push({
166
+ name: 'should create trigger',
167
+ passed: false,
168
+ skipped: false,
169
+ error: error.message,
170
+ device: deviceName
171
+ });
172
+ return results;
173
+ }
174
+
175
+ // Test 2: Delete the trigger
176
+ if (createdTriggerId) {
177
+ try {
178
+ const deleteResponse = await testDevice.deleteTriggerX(createdTriggerId, 0);
179
+
180
+ // Check what the DELETE response contains
181
+ const deleteResponseError = deleteResponse?.error;
182
+ const hasError = deleteResponseError !== null && deleteResponseError !== undefined;
183
+ const isIdNotFound = deleteResponseError?.code === 5050 ||
184
+ deleteResponseError?.detail === 'id not found';
185
+
186
+ // If DELETE returned an error other than "id not found", this is a real failure
187
+ if (hasError && !isIdNotFound) {
188
+ results.push({
189
+ name: 'should delete trigger',
190
+ passed: false,
191
+ skipped: false,
192
+ error: `DELETE command failed: ${JSON.stringify(deleteResponseError)}`,
193
+ device: deviceName,
194
+ details: {
195
+ triggerId: createdTriggerId,
196
+ deleteResponse: deleteResponse
197
+ }
198
+ });
199
+ } else {
200
+ // DELETE succeeded (no error, or error 5050)
201
+ // Error 5050 means "id not found" which indicates the trigger is deleted (success)
202
+ // Empty DELETEACK also means deletion was accepted
203
+ results.push({
204
+ name: 'should delete trigger',
205
+ passed: true,
206
+ skipped: false,
207
+ error: null,
208
+ device: deviceName,
209
+ details: {
210
+ triggerId: createdTriggerId,
211
+ deleteResponse: isIdNotFound ? 'Trigger deleted (error 5050 - id not found)' : 'DELETE command accepted (empty DELETEACK)'
212
+ }
213
+ });
214
+ }
215
+ } catch (error) {
216
+ results.push({
217
+ name: 'should delete trigger',
218
+ passed: false,
219
+ skipped: false,
220
+ error: error.message,
221
+ device: deviceName,
222
+ details: {
223
+ triggerId: createdTriggerId
224
+ }
225
+ });
226
+ }
227
+ }
228
+
229
+ // Test 3: Handle trigger push notifications
230
+ try {
231
+ // Set up listener for trigger push notifications
232
+ let receivedNotification = false;
233
+ const notificationHandler = (notification) => {
234
+ if (notification.namespace === 'Appliance.Control.TriggerX') {
235
+ receivedNotification = true;
236
+ }
237
+ };
238
+
239
+ testDevice.on('pushNotification', notificationHandler);
240
+
241
+ // Get trigger info (may trigger a push notification)
242
+ await testDevice.getTriggerX(0);
243
+
244
+ // Wait a bit for potential push notifications
245
+ await new Promise(resolve => setTimeout(resolve, 2000));
246
+
247
+ // Remove listener
248
+ testDevice.removeListener('pushNotification', notificationHandler);
249
+
250
+ // Note: We don't assert on receivedNotification since push notifications
251
+ // are device-initiated and may not occur during testing
252
+ results.push({
253
+ name: 'should handle trigger push notifications',
254
+ passed: true,
255
+ skipped: false,
256
+ error: null,
257
+ device: deviceName,
258
+ details: {
259
+ notificationReceived: receivedNotification,
260
+ note: 'Push notifications are device-initiated and may not occur during testing'
261
+ }
262
+ });
263
+ } catch (error) {
264
+ results.push({
265
+ name: 'should handle trigger push notifications',
266
+ passed: false,
267
+ skipped: false,
268
+ error: error.message,
269
+ device: deviceName
270
+ });
271
+ }
272
+
273
+ return results;
274
+ }
275
+
276
+ module.exports = {
277
+ metadata,
278
+ runTests
279
+ };