system-testing 1.0.21 → 1.0.23
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-browser-helper.js +2 -0
- package/src/system-test.js +79 -15
- package/src/use-system-test.js +22 -3
package/package.json
CHANGED
|
@@ -179,6 +179,8 @@ export default class SystemTestBrowserHelper {
|
|
|
179
179
|
return {result: "initialized"}
|
|
180
180
|
} else if (data.type == "visit") {
|
|
181
181
|
this.events.emit("navigate", {path: data.path})
|
|
182
|
+
} else if (data.type == "dismissTo") {
|
|
183
|
+
this.events.emit("dismissTo", {path: data.path})
|
|
182
184
|
} else {
|
|
183
185
|
throw new Error(`Unknown command type for SystemTestBrowserHelper: ${data.type}`)
|
|
184
186
|
}
|
package/src/system-test.js
CHANGED
|
@@ -15,6 +15,8 @@ import {WebSocketServer} from "ws"
|
|
|
15
15
|
class ElementNotFoundError extends Error { }
|
|
16
16
|
|
|
17
17
|
export default class SystemTest {
|
|
18
|
+
static rootPath = "/blank?systemTest=true"
|
|
19
|
+
|
|
18
20
|
/**
|
|
19
21
|
* Gets the current system test instance
|
|
20
22
|
*
|
|
@@ -38,10 +40,10 @@ export default class SystemTest {
|
|
|
38
40
|
const systemTest = this.current()
|
|
39
41
|
|
|
40
42
|
await systemTest.communicator.sendCommand({type: "initialize"})
|
|
41
|
-
await systemTest.
|
|
43
|
+
await systemTest.dismissTo(SystemTest.rootPath)
|
|
42
44
|
|
|
43
45
|
try {
|
|
44
|
-
await systemTest.findByTestID("blankText")
|
|
46
|
+
await systemTest.findByTestID("blankText", {useBaseSelector: false})
|
|
45
47
|
await callback(systemTest)
|
|
46
48
|
} catch (error) {
|
|
47
49
|
await systemTest.takeScreenshot()
|
|
@@ -72,6 +74,30 @@ export default class SystemTest {
|
|
|
72
74
|
this.communicator = new SystemTestCommunicator({onCommand: this.onCommandReceived})
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Gets the base selector for scoping element searches
|
|
79
|
+
*
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
getBaseSelector() { return this._baseSelector }
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Sets the base selector for scoping element searches
|
|
86
|
+
*
|
|
87
|
+
* @param {string} baseSelector
|
|
88
|
+
*/
|
|
89
|
+
setBaseSelector(baseSelector) { this._baseSelector = baseSelector }
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Gets a selector scoped to the base selector
|
|
93
|
+
*
|
|
94
|
+
* @param {string} selector
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
getSelector(selector) {
|
|
98
|
+
return this.getBaseSelector() ? `${this.getBaseSelector()} ${selector}` : selector
|
|
99
|
+
}
|
|
100
|
+
|
|
75
101
|
/** Starts Scoundrel server which the browser connects to for remote evaluation in the browser */
|
|
76
102
|
startScoundrel() {
|
|
77
103
|
this.wss = new WebSocketServer({port: 8090})
|
|
@@ -93,8 +119,15 @@ export default class SystemTest {
|
|
|
93
119
|
* @returns {import("selenium-webdriver").WebElement[]}
|
|
94
120
|
*/
|
|
95
121
|
async all(selector, args = {}) {
|
|
96
|
-
const {visible = true} = args
|
|
97
|
-
const
|
|
122
|
+
const {visible = true, useBaseSelector = true, ...restArgs} = args
|
|
123
|
+
const restArgsKeys = Object.keys(restArgs)
|
|
124
|
+
|
|
125
|
+
if (restArgsKeys.length > 0) {
|
|
126
|
+
throw new Error(`Unknown arguments: ${restArgsKeys.join(", ")}`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const actualSelector = useBaseSelector ? this.getSelector(selector) : selector
|
|
130
|
+
const elements = await this.driver.findElements(By.css(actualSelector))
|
|
98
131
|
const activeElements = []
|
|
99
132
|
|
|
100
133
|
for (const element of elements) {
|
|
@@ -142,15 +175,15 @@ export default class SystemTest {
|
|
|
142
175
|
elements = await this.all(selector, args)
|
|
143
176
|
} catch (error) {
|
|
144
177
|
// Re-throw to recover stack trace
|
|
145
|
-
throw new Error(`${error.message} (selector: ${selector})`)
|
|
178
|
+
throw new Error(`${error.message} (selector: ${this.getSelector(selector)})`)
|
|
146
179
|
}
|
|
147
180
|
|
|
148
181
|
if (elements.length > 1) {
|
|
149
|
-
throw new Error(`More than 1 elements (${elements.length}) was found by CSS: ${selector}`)
|
|
182
|
+
throw new Error(`More than 1 elements (${elements.length}) was found by CSS: ${this.getSelector(selector)}`)
|
|
150
183
|
}
|
|
151
184
|
|
|
152
185
|
if (!elements[0]) {
|
|
153
|
-
throw new ElementNotFoundError(`Element couldn't be found by CSS: ${selector}`)
|
|
186
|
+
throw new ElementNotFoundError(`Element couldn't be found after ${(this.getTimeouts() / 1000).toFixed(2)}s by CSS: ${this.getSelector(selector)}`)
|
|
154
187
|
}
|
|
155
188
|
|
|
156
189
|
return elements[0]
|
|
@@ -171,11 +204,11 @@ export default class SystemTest {
|
|
|
171
204
|
* @param {string} selector
|
|
172
205
|
* @returns {import("selenium-webdriver").WebElement}
|
|
173
206
|
*/
|
|
174
|
-
async findNoWait(selector) {
|
|
207
|
+
async findNoWait(selector, args) {
|
|
175
208
|
await this.driverSetTimeouts(0)
|
|
176
209
|
|
|
177
210
|
try {
|
|
178
|
-
return await this.find(selector)
|
|
211
|
+
return await this.find(selector, args)
|
|
179
212
|
} finally {
|
|
180
213
|
await this.restoreTimeouts()
|
|
181
214
|
}
|
|
@@ -210,6 +243,8 @@ export default class SystemTest {
|
|
|
210
243
|
return await this.driver.getCurrentUrl()
|
|
211
244
|
}
|
|
212
245
|
|
|
246
|
+
getTimeouts() { return this._timeouts }
|
|
247
|
+
|
|
213
248
|
/**
|
|
214
249
|
* Interacts with an element by calling a method on it with the given arguments.
|
|
215
250
|
* Retrying on ElementNotInteractableError.
|
|
@@ -283,7 +318,7 @@ export default class SystemTest {
|
|
|
283
318
|
* @returns {Promise<string[]>}
|
|
284
319
|
*/
|
|
285
320
|
async notificationMessages() {
|
|
286
|
-
const notificationMessageElements = await this.all("[data-class='notification-message']")
|
|
321
|
+
const notificationMessageElements = await this.all("[data-class='notification-message']", {useBaseSelector: false})
|
|
287
322
|
const notificationMessageTexts = []
|
|
288
323
|
|
|
289
324
|
for (const notificationMessageElement of notificationMessageElements) {
|
|
@@ -369,11 +404,11 @@ export default class SystemTest {
|
|
|
369
404
|
await this.startWebSocketServer()
|
|
370
405
|
|
|
371
406
|
// Visit the root page and wait for Expo to be loaded and the app to appear
|
|
372
|
-
await this.driverVisit(
|
|
407
|
+
await this.driverVisit(SystemTest.rootPath)
|
|
373
408
|
|
|
374
409
|
try {
|
|
375
|
-
await this.find("body > #root")
|
|
376
|
-
await this.find("[data-testid='systemTestingComponent']", {visible: null})
|
|
410
|
+
await this.find("body > #root", {useBaseSelector: false})
|
|
411
|
+
await this.find("[data-testid='systemTestingComponent']", {visible: null, useBaseSelector: false})
|
|
377
412
|
} catch (error) {
|
|
378
413
|
await systemTest.takeScreenshot()
|
|
379
414
|
|
|
@@ -384,17 +419,18 @@ export default class SystemTest {
|
|
|
384
419
|
await this.waitForClientWebSocket()
|
|
385
420
|
|
|
386
421
|
this._started = true
|
|
422
|
+
systemTest.setBaseSelector("[data-testid='systemTestingComponent'][data-focussed='true']")
|
|
387
423
|
}
|
|
388
424
|
|
|
389
425
|
/**
|
|
390
426
|
* Restores previously set timeouts
|
|
391
427
|
*/
|
|
392
428
|
async restoreTimeouts() {
|
|
393
|
-
if (!this.
|
|
429
|
+
if (!this.getTimeouts()) {
|
|
394
430
|
throw new Error("Timeouts haven't previously been set")
|
|
395
431
|
}
|
|
396
432
|
|
|
397
|
-
await this.driverSetTimeouts(this.
|
|
433
|
+
await this.driverSetTimeouts(this.getTimeouts())
|
|
398
434
|
}
|
|
399
435
|
|
|
400
436
|
/**
|
|
@@ -440,10 +476,19 @@ export default class SystemTest {
|
|
|
440
476
|
this.wss.on("close", this.onWebSocketClose)
|
|
441
477
|
}
|
|
442
478
|
|
|
479
|
+
/**
|
|
480
|
+
* Sets the on command callback
|
|
481
|
+
*/
|
|
443
482
|
onCommand(callback) {
|
|
444
483
|
this._onCommandCallback = callback
|
|
445
484
|
}
|
|
446
485
|
|
|
486
|
+
/**
|
|
487
|
+
* Handles a command received from the browser
|
|
488
|
+
*
|
|
489
|
+
* @param {Object} data
|
|
490
|
+
* @returns {Promise<any>}
|
|
491
|
+
*/
|
|
447
492
|
onCommandReceived = async ({data}) => {
|
|
448
493
|
const type = data.type
|
|
449
494
|
let result
|
|
@@ -472,6 +517,11 @@ export default class SystemTest {
|
|
|
472
517
|
return result
|
|
473
518
|
}
|
|
474
519
|
|
|
520
|
+
/**
|
|
521
|
+
* Handles a new web socket connection
|
|
522
|
+
*
|
|
523
|
+
* @param {WebSocket} ws
|
|
524
|
+
*/
|
|
475
525
|
onWebSocketConnection = async (ws) => {
|
|
476
526
|
this.ws = ws
|
|
477
527
|
this.communicator.ws = ws
|
|
@@ -490,6 +540,11 @@ export default class SystemTest {
|
|
|
490
540
|
this.communicator.ws = null
|
|
491
541
|
}
|
|
492
542
|
|
|
543
|
+
/**
|
|
544
|
+
* Handles an error reported from the browser
|
|
545
|
+
*
|
|
546
|
+
* @param {Object} data
|
|
547
|
+
*/
|
|
493
548
|
handleError(data) {
|
|
494
549
|
if (data.message.includes("Minified React error #419")) {
|
|
495
550
|
// Ignore this error message
|
|
@@ -561,4 +616,13 @@ export default class SystemTest {
|
|
|
561
616
|
async visit(path) {
|
|
562
617
|
await this.communicator.sendCommand({type: "visit", path})
|
|
563
618
|
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Dismisses to a path in the browser
|
|
622
|
+
*
|
|
623
|
+
* @param {string} path
|
|
624
|
+
*/
|
|
625
|
+
async dismissTo(path) {
|
|
626
|
+
await this.communicator.sendCommand({type: "dismissTo", path})
|
|
627
|
+
}
|
|
564
628
|
}
|
package/src/use-system-test.js
CHANGED
|
@@ -49,12 +49,31 @@ export default function useSystemTest({onInitialize, ...restArgs} = {}) {
|
|
|
49
49
|
const enabled = useMemo(() => isSystemTestEnabled(), [])
|
|
50
50
|
const systemTestBrowserHelper = enabled ? getSystemTestBrowserHelper() : null
|
|
51
51
|
const result = useMemo(() => ({enabled, systemTestBrowserHelper}), [enabled, systemTestBrowserHelper])
|
|
52
|
+
const instanceShared = useMemo(() => ({}), [])
|
|
52
53
|
|
|
54
|
+
instanceShared.enabled = enabled
|
|
55
|
+
instanceShared.router = router
|
|
56
|
+
|
|
57
|
+
// Resets navigation when instructed by the system test browser helper
|
|
58
|
+
const onSystemTestBrowserHelperDismissTo = useCallback(({path}) => {
|
|
59
|
+
if (instanceShared.enabled) {
|
|
60
|
+
try {
|
|
61
|
+
instanceShared.router.dismissTo(path)
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(`Failed to dismiss to path "${path}": ${error.message}`)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}, [])
|
|
67
|
+
|
|
68
|
+
useEventEmitter(shared.systemTestBrowserHelper?.getEvents(), "dismissTo", onSystemTestBrowserHelperDismissTo)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
// Navigates when instructed by the system test browser helper and keeping history of screens
|
|
53
72
|
const onSystemTestBrowserHelperNavigate = useCallback(({path}) => {
|
|
54
|
-
if (enabled) {
|
|
55
|
-
router.navigate(path)
|
|
73
|
+
if (instanceShared.enabled) {
|
|
74
|
+
instanceShared.router.navigate(path)
|
|
56
75
|
}
|
|
57
|
-
}, [
|
|
76
|
+
}, [])
|
|
58
77
|
|
|
59
78
|
useEventEmitter(shared.systemTestBrowserHelper?.getEvents(), "navigate", onSystemTestBrowserHelperNavigate)
|
|
60
79
|
|