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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "system-testing",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "System testing with Selenium and browsers.",
5
5
  "keywords": [
6
6
  "system",
@@ -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
  }
@@ -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.visit("/blank")
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 elements = await this.driver.findElements(By.css(selector))
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("/?systemTest=true")
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._timeouts) {
429
+ if (!this.getTimeouts()) {
394
430
  throw new Error("Timeouts haven't previously been set")
395
431
  }
396
432
 
397
- await this.driverSetTimeouts(this._timeouts)
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
  }
@@ -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
- }, [enabled, router])
76
+ }, [])
58
77
 
59
78
  useEventEmitter(shared.systemTestBrowserHelper?.getEvents(), "navigate", onSystemTestBrowserHelperNavigate)
60
79