system-testing 1.0.15 → 1.0.17
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 +1 -1
- package/src/system-test.js +144 -2
- package/src/use-system-test.js +10 -0
package/package.json
CHANGED
package/src/system-test.js
CHANGED
|
@@ -7,11 +7,18 @@ import moment from "moment"
|
|
|
7
7
|
import {prettify} from "htmlfy"
|
|
8
8
|
import SystemTestCommunicator from "./system-test-communicator.js"
|
|
9
9
|
import SystemTestHttpServer from "./system-test-http-server.js"
|
|
10
|
+
import {waitFor} from "awaitery"
|
|
10
11
|
import {WebSocketServer} from "ws"
|
|
11
12
|
|
|
12
13
|
class ElementNotFoundError extends Error { }
|
|
13
14
|
|
|
14
15
|
export default class SystemTest {
|
|
16
|
+
/**
|
|
17
|
+
* Gets the current system test instance
|
|
18
|
+
*
|
|
19
|
+
* @param {object} args
|
|
20
|
+
* @returns {SystemTest}
|
|
21
|
+
*/
|
|
15
22
|
static current(args) {
|
|
16
23
|
if (!globalThis.systemTest) {
|
|
17
24
|
globalThis.systemTest = new SystemTest(args)
|
|
@@ -20,6 +27,11 @@ export default class SystemTest {
|
|
|
20
27
|
return globalThis.systemTest
|
|
21
28
|
}
|
|
22
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Runs a system test
|
|
32
|
+
*
|
|
33
|
+
* @param {function(SystemTest): Promise<void>} callback
|
|
34
|
+
*/
|
|
23
35
|
static async run(callback) {
|
|
24
36
|
const systemTest = this.current()
|
|
25
37
|
|
|
@@ -36,7 +48,14 @@ export default class SystemTest {
|
|
|
36
48
|
}
|
|
37
49
|
}
|
|
38
50
|
|
|
39
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Creates a new SystemTest instance
|
|
53
|
+
*
|
|
54
|
+
* @param {object} args
|
|
55
|
+
* @param {string} args.host
|
|
56
|
+
* @param {number} args.port
|
|
57
|
+
*/
|
|
58
|
+
constructor({host = "localhost", port = 8081, ...restArgs} = {}) {
|
|
40
59
|
const restArgsKeys = Object.keys(restArgs)
|
|
41
60
|
|
|
42
61
|
if (restArgsKeys.length > 0) {
|
|
@@ -50,6 +69,14 @@ export default class SystemTest {
|
|
|
50
69
|
this._sendCount = 0
|
|
51
70
|
}
|
|
52
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Finds all elements by CSS selector
|
|
74
|
+
*
|
|
75
|
+
* @param {string} selector
|
|
76
|
+
* @param {object} args
|
|
77
|
+
*
|
|
78
|
+
* @returns {import("selenium-webdriver").WebElement[]}
|
|
79
|
+
*/
|
|
53
80
|
async all(selector, args = {}) {
|
|
54
81
|
const {visible = true} = args
|
|
55
82
|
const elements = await this.driver.findElements(By.css(selector))
|
|
@@ -71,13 +98,24 @@ export default class SystemTest {
|
|
|
71
98
|
return activeElements
|
|
72
99
|
}
|
|
73
100
|
|
|
74
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Clicks an element that has children which fills out the element and would otherwise have caused a ElementClickInterceptedError
|
|
103
|
+
*
|
|
104
|
+
* @param {import("selenium-webdriver").WebElement} element
|
|
105
|
+
**/
|
|
75
106
|
async click(element) {
|
|
76
107
|
const actions = this.driver.actions({async: true})
|
|
77
108
|
|
|
78
109
|
await actions.move({origin: element}).click().perform()
|
|
79
110
|
}
|
|
80
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Finds a single element by CSS selector
|
|
114
|
+
*
|
|
115
|
+
* @param {string} selector
|
|
116
|
+
* @param {object} args
|
|
117
|
+
* @returns {import("selenium-webdriver").WebElement}
|
|
118
|
+
*/
|
|
81
119
|
async find(selector, args = {}) {
|
|
82
120
|
let elements
|
|
83
121
|
|
|
@@ -99,8 +137,21 @@ export default class SystemTest {
|
|
|
99
137
|
return elements[0]
|
|
100
138
|
}
|
|
101
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Finds a single element by test ID
|
|
142
|
+
*
|
|
143
|
+
* @param {string} testID
|
|
144
|
+
* @param {object} args
|
|
145
|
+
* @returns {import("selenium-webdriver").WebElement}
|
|
146
|
+
*/
|
|
102
147
|
async findByTestID(testID, args) { return await this.find(`[data-testid='${testID}']`, args) }
|
|
103
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Finds a single element by CSS selector without waiting
|
|
151
|
+
*
|
|
152
|
+
* @param {string} selector
|
|
153
|
+
* @returns {import("selenium-webdriver").WebElement}
|
|
154
|
+
*/
|
|
104
155
|
async findNoWait(selector) {
|
|
105
156
|
await this.driverSetTimeouts(0)
|
|
106
157
|
|
|
@@ -111,6 +162,11 @@ export default class SystemTest {
|
|
|
111
162
|
}
|
|
112
163
|
}
|
|
113
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Gets browser logs
|
|
167
|
+
*
|
|
168
|
+
* @returns {Promise<string[]>}
|
|
169
|
+
*/
|
|
114
170
|
async getBrowserLogs() {
|
|
115
171
|
const entries = await this.driver.manage().logs().get(logging.Type.BROWSER)
|
|
116
172
|
const browserLogs = []
|
|
@@ -131,6 +187,11 @@ export default class SystemTest {
|
|
|
131
187
|
return browserLogs
|
|
132
188
|
}
|
|
133
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Expects no element to be found by CSS selector
|
|
192
|
+
*
|
|
193
|
+
* @param {string} selector
|
|
194
|
+
*/
|
|
134
195
|
async expectNoElement(selector) {
|
|
135
196
|
let found = false
|
|
136
197
|
|
|
@@ -148,6 +209,11 @@ export default class SystemTest {
|
|
|
148
209
|
}
|
|
149
210
|
}
|
|
150
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Gets notification messages
|
|
214
|
+
*
|
|
215
|
+
* @returns {Promise<string[]>}
|
|
216
|
+
*/
|
|
151
217
|
async notificationMessages() {
|
|
152
218
|
const notificationMessageElements = await this.all("[data-class='notification-message']")
|
|
153
219
|
const notificationMessageTexts = []
|
|
@@ -161,9 +227,48 @@ export default class SystemTest {
|
|
|
161
227
|
return notificationMessageTexts
|
|
162
228
|
}
|
|
163
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Expects a notification message to appear and waits for it if necessary.
|
|
232
|
+
*
|
|
233
|
+
* @param {string} expectedNotificationMessage
|
|
234
|
+
*/
|
|
235
|
+
async expectNotificationMessage(expectedNotificationMessage) {
|
|
236
|
+
const allDetectedNotificationMessages = []
|
|
237
|
+
|
|
238
|
+
await waitFor(async () => {
|
|
239
|
+
const notificationMessages = await this.notificationMessages()
|
|
240
|
+
|
|
241
|
+
for (const notificationMessage of notificationMessages) {
|
|
242
|
+
if (!allDetectedNotificationMessages.includes(notificationMessage)) {
|
|
243
|
+
allDetectedNotificationMessages.push(notificationMessage)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (notificationMessage == expectedNotificationMessage) {
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
throw new Error(`Notification message ${expectedNotificationMessage} wasn't included in: ${allDetectedNotificationMessages.join(", ")}`)
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Indicates whether the system test has been started
|
|
257
|
+
*
|
|
258
|
+
* @returns {boolean}
|
|
259
|
+
*/
|
|
164
260
|
isStarted() { return this._started }
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Gets the HTML of the current page
|
|
264
|
+
*
|
|
265
|
+
* @returns {Promise<string>}
|
|
266
|
+
*/
|
|
165
267
|
async getHTML() { return await this.driver.getPageSource() }
|
|
166
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Starts the system test
|
|
271
|
+
*/
|
|
167
272
|
async start() {
|
|
168
273
|
if (process.env.SYSTEM_TEST_HOST == "expo-dev-server") {
|
|
169
274
|
this.currentUrl = `http://${this._host}:${this._port}`
|
|
@@ -213,6 +318,9 @@ export default class SystemTest {
|
|
|
213
318
|
this._started = true
|
|
214
319
|
}
|
|
215
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Restores previously set timeouts
|
|
323
|
+
*/
|
|
216
324
|
async restoreTimeouts() {
|
|
217
325
|
if (!this._timeouts) {
|
|
218
326
|
throw new Error("Timeouts haven't previously been set")
|
|
@@ -221,15 +329,30 @@ export default class SystemTest {
|
|
|
221
329
|
await this.driverSetTimeouts(this._timeouts)
|
|
222
330
|
}
|
|
223
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Sets driver timeouts
|
|
334
|
+
*
|
|
335
|
+
* @param {number} newTimeout
|
|
336
|
+
*/
|
|
224
337
|
async driverSetTimeouts(newTimeout) {
|
|
225
338
|
await this.driver.manage().setTimeouts({implicit: newTimeout})
|
|
226
339
|
}
|
|
227
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Sets timeouts and stores the previous timeouts
|
|
343
|
+
*
|
|
344
|
+
* @param {number} newTimeout
|
|
345
|
+
*/
|
|
228
346
|
async setTimeouts(newTimeout) {
|
|
229
347
|
this._timeouts = newTimeout
|
|
230
348
|
await this.restoreTimeouts()
|
|
231
349
|
}
|
|
232
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Waits for the client web socket to connect
|
|
353
|
+
*
|
|
354
|
+
* @returns {Promise<void>}
|
|
355
|
+
*/
|
|
233
356
|
waitForClientWebSocket() {
|
|
234
357
|
return new Promise((resolve) => {
|
|
235
358
|
if (this.ws) {
|
|
@@ -240,6 +363,9 @@ export default class SystemTest {
|
|
|
240
363
|
})
|
|
241
364
|
}
|
|
242
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Starts the web socket server
|
|
368
|
+
*/
|
|
243
369
|
startWebSocketServer() {
|
|
244
370
|
this.wss = new WebSocketServer({port: 1985})
|
|
245
371
|
this.wss.on("connection", this.onWebSocketConnection)
|
|
@@ -311,18 +437,29 @@ export default class SystemTest {
|
|
|
311
437
|
console.error(error)
|
|
312
438
|
}
|
|
313
439
|
|
|
440
|
+
/**
|
|
441
|
+
* Stops the system test
|
|
442
|
+
*/
|
|
314
443
|
async stop() {
|
|
315
444
|
this.systemTestHttpServer?.close()
|
|
316
445
|
this.wss?.close()
|
|
317
446
|
await this.driver.quit()
|
|
318
447
|
}
|
|
319
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Visits a path in the browser
|
|
451
|
+
*
|
|
452
|
+
* @param {string} path
|
|
453
|
+
*/
|
|
320
454
|
async driverVisit(path) {
|
|
321
455
|
const url = `${this.currentUrl}${path}`
|
|
322
456
|
|
|
323
457
|
await this.driver.get(url)
|
|
324
458
|
}
|
|
325
459
|
|
|
460
|
+
/**
|
|
461
|
+
* Takes a screenshot, saves HTML and browser logs
|
|
462
|
+
*/
|
|
326
463
|
async takeScreenshot() {
|
|
327
464
|
const path = `${process.cwd()}/tmp/screenshots`
|
|
328
465
|
|
|
@@ -346,6 +483,11 @@ export default class SystemTest {
|
|
|
346
483
|
console.log("HTML:", htmlPath)
|
|
347
484
|
}
|
|
348
485
|
|
|
486
|
+
/**
|
|
487
|
+
* Visits a path in the browser
|
|
488
|
+
*
|
|
489
|
+
* @param {string} path
|
|
490
|
+
*/
|
|
349
491
|
async visit(path) {
|
|
350
492
|
await this.communicator.sendCommand({type: "visit", path})
|
|
351
493
|
}
|
package/src/use-system-test.js
CHANGED
|
@@ -34,6 +34,16 @@ const getSystemTestBrowserHelper = () => {
|
|
|
34
34
|
return shared.systemTestBrowserHelper
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* A hook that provides system test capabilities.
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} options - Options for the hook.
|
|
41
|
+
* @param {Function} options.onInitialize - A callback function that is called when the system test browser helper is initialized.
|
|
42
|
+
*
|
|
43
|
+
* @returns {Object} An object containing:
|
|
44
|
+
* - enabled: A boolean indicating if system test mode is enabled.
|
|
45
|
+
* - systemTestBrowserHelper: An instance of SystemTestBrowserHelper if enabled, otherwise null.
|
|
46
|
+
*/
|
|
37
47
|
export default function useSystemTest({onInitialize, ...restArgs} = {}) {
|
|
38
48
|
const router = useRouter()
|
|
39
49
|
const enabled = useMemo(() => isSystemTestEnabled(), [])
|