system-testing 1.0.20 → 1.0.22

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.20",
3
+ "version": "1.0.22",
4
4
  "description": "System testing with Selenium and browsers.",
5
5
  "keywords": [
6
6
  "system",
@@ -29,6 +29,7 @@
29
29
  "htmlfy": "^1.0.0",
30
30
  "mime": "^4.0.7",
31
31
  "moment": "^2.30.1",
32
+ "scoundrel-remote-eval": "^1.0.6",
32
33
  "ws": "^8.18.3"
33
34
  },
34
35
  "peerDependencies": {
@@ -1,3 +1,5 @@
1
+ import Client from "scoundrel-remote-eval/src/client/index.js"
2
+ import ClientWebSocket from "scoundrel-remote-eval/src/client/connections/web-socket/index.js"
1
3
  import {digg} from "diggerize"
2
4
  import EventEmitter from "events"
3
5
 
@@ -20,6 +22,38 @@ export default class SystemTestBrowserHelper {
20
22
  this.events = new EventEmitter()
21
23
 
22
24
  shared.systemTestBrowserHelper = this
25
+
26
+ this.startScoundrel()
27
+ }
28
+
29
+ async startScoundrel() {
30
+ this.scoundrelWs = new WebSocket("http://localhost:8090")
31
+ this.scoundrelClientWebSocket = new ClientWebSocket(this.scoundrelWs)
32
+
33
+ await this.scoundrelClientWebSocket.waitForOpened()
34
+
35
+ this.scoundrelClient = new Client(this.scoundrelClientWebSocket)
36
+ this.events.emit("scoundrelStarted")
37
+ }
38
+
39
+ waitForScoundrelStarted() {
40
+ return new Promise((resolve) => {
41
+ if (this.scoundrelClient) {
42
+ resolve()
43
+ } else {
44
+ this.events.once("scoundrelStarted", () => {
45
+ resolve()
46
+ })
47
+ }
48
+ })
49
+ }
50
+
51
+ getScoundrel() {
52
+ if (!this.scoundrelClient) {
53
+ throw new Error("Scoundrel client is not started yet")
54
+ }
55
+
56
+ return this.scoundrelClient
23
57
  }
24
58
 
25
59
  connectOnError() {
@@ -83,8 +117,8 @@ export default class SystemTestBrowserHelper {
83
117
  this.overrideConsoleLog()
84
118
  }
85
119
 
86
- getEnabled = () => this._enabled
87
- getEvents = () => this.events
120
+ getEnabled() { return this._enabled }
121
+ getEvents() { return this.events }
88
122
 
89
123
  fakeConsoleError = (...args) => {
90
124
  this.communicator.sendCommand({type: "console.error", value: this.consoleLogMessage(args)})
@@ -63,6 +63,12 @@ export default class SystemTestCommunicator {
63
63
  }
64
64
  }
65
65
 
66
+ /**
67
+ * Sends a command and returns a promise that resolves with the response.
68
+ *
69
+ * @param {Object} data - The command data to send.
70
+ * @returns {Promise} A promise that resolves with the response data.
71
+ */
66
72
  sendCommand(data) {
67
73
  return new Promise((resolve, error) => {
68
74
  const id = this._sendQueueCount
@@ -5,6 +5,8 @@ import fs from "node:fs/promises"
5
5
  import logging from "selenium-webdriver/lib/logging.js"
6
6
  import moment from "moment"
7
7
  import {prettify} from "htmlfy"
8
+ import Server from "scoundrel-remote-eval/src/server/index.js"
9
+ import ServerWebSocket from "scoundrel-remote-eval/src/server/connections/web-socket/index.js"
8
10
  import SystemTestCommunicator from "./system-test-communicator.js"
9
11
  import SystemTestHttpServer from "./system-test-http-server.js"
10
12
  import {wait, waitFor} from "awaitery"
@@ -39,7 +41,7 @@ export default class SystemTest {
39
41
  await systemTest.visit("/blank")
40
42
 
41
43
  try {
42
- await systemTest.findByTestID("blankText")
44
+ await systemTest.findByTestID("blankText", {useBaseSelector: false})
43
45
  await callback(systemTest)
44
46
  } catch (error) {
45
47
  await systemTest.takeScreenshot()
@@ -62,11 +64,48 @@ export default class SystemTest {
62
64
  throw new Error(`Unknown arguments: ${restArgsKeys.join(", ")}`)
63
65
  }
64
66
 
65
- this.communicator = new SystemTestCommunicator({onCommand: this.onCommandReceived})
66
67
  this._host = host
67
68
  this._port = port
68
69
  this._responses = {}
69
70
  this._sendCount = 0
71
+ this.startScoundrel()
72
+ this.communicator = new SystemTestCommunicator({onCommand: this.onCommandReceived})
73
+ }
74
+
75
+ /**
76
+ * Gets the base selector for scoping element searches
77
+ *
78
+ * @returns {string}
79
+ */
80
+ getBaseSelector() { return this._baseSelector }
81
+
82
+ /**
83
+ * Sets the base selector for scoping element searches
84
+ *
85
+ * @param {string} baseSelector
86
+ */
87
+ setBaseSelector(baseSelector) { this._baseSelector = baseSelector }
88
+
89
+ /**
90
+ * Gets a selector scoped to the base selector
91
+ *
92
+ * @param {string} selector
93
+ * @returns {string}
94
+ */
95
+ getSelector(selector) {
96
+ return this.getBaseSelector() ? `${this.getBaseSelector()} ${selector}` : selector
97
+ }
98
+
99
+ /** Starts Scoundrel server which the browser connects to for remote evaluation in the browser */
100
+ startScoundrel() {
101
+ this.wss = new WebSocketServer({port: 8090})
102
+ this.serverWebSocket = new ServerWebSocket(this.wss)
103
+ this.server = new Server(this.serverWebSocket)
104
+ }
105
+
106
+ stopScoundrel() {
107
+ this.server?.close()
108
+ this.wss?.close()
70
109
  }
71
110
 
72
111
  /**
@@ -78,8 +117,15 @@ export default class SystemTest {
78
117
  * @returns {import("selenium-webdriver").WebElement[]}
79
118
  */
80
119
  async all(selector, args = {}) {
81
- const {visible = true} = args
82
- const elements = await this.driver.findElements(By.css(selector))
120
+ const {visible = true, useBaseSelector = true, ...restArgs} = args
121
+ const restArgsKeys = Object.keys(restArgs)
122
+
123
+ if (restArgsKeys.length > 0) {
124
+ throw new Error(`Unknown arguments: ${restArgsKeys.join(", ")}`)
125
+ }
126
+
127
+ const actualSelector = useBaseSelector ? this.getSelector(selector) : selector
128
+ const elements = await this.driver.findElements(By.css(actualSelector))
83
129
  const activeElements = []
84
130
 
85
131
  for (const element of elements) {
@@ -127,15 +173,15 @@ export default class SystemTest {
127
173
  elements = await this.all(selector, args)
128
174
  } catch (error) {
129
175
  // Re-throw to recover stack trace
130
- throw new Error(`${error.message} (selector: ${selector})`)
176
+ throw new Error(`${error.message} (selector: ${this.getSelector(selector)})`)
131
177
  }
132
178
 
133
179
  if (elements.length > 1) {
134
- throw new Error(`More than 1 elements (${elements.length}) was found by CSS: ${selector}`)
180
+ throw new Error(`More than 1 elements (${elements.length}) was found by CSS: ${this.getSelector(selector)}`)
135
181
  }
136
182
 
137
183
  if (!elements[0]) {
138
- throw new ElementNotFoundError(`Element couldn't be found by CSS: ${selector}`)
184
+ throw new ElementNotFoundError(`Element couldn't be found after ${(this.getTimeouts() / 1000).toFixed(2)}s by CSS: ${this.getSelector(selector)}`)
139
185
  }
140
186
 
141
187
  return elements[0]
@@ -156,11 +202,11 @@ export default class SystemTest {
156
202
  * @param {string} selector
157
203
  * @returns {import("selenium-webdriver").WebElement}
158
204
  */
159
- async findNoWait(selector) {
205
+ async findNoWait(selector, args) {
160
206
  await this.driverSetTimeouts(0)
161
207
 
162
208
  try {
163
- return await this.find(selector)
209
+ return await this.find(selector, args)
164
210
  } finally {
165
211
  await this.restoreTimeouts()
166
212
  }
@@ -195,6 +241,8 @@ export default class SystemTest {
195
241
  return await this.driver.getCurrentUrl()
196
242
  }
197
243
 
244
+ getTimeouts() { return this._timeouts }
245
+
198
246
  /**
199
247
  * Interacts with an element by calling a method on it with the given arguments.
200
248
  * Retrying on ElementNotInteractableError.
@@ -268,7 +316,7 @@ export default class SystemTest {
268
316
  * @returns {Promise<string[]>}
269
317
  */
270
318
  async notificationMessages() {
271
- const notificationMessageElements = await this.all("[data-class='notification-message']")
319
+ const notificationMessageElements = await this.all("[data-class='notification-message']", {useBaseSelector: false})
272
320
  const notificationMessageTexts = []
273
321
 
274
322
  for (const notificationMessageElement of notificationMessageElements) {
@@ -357,8 +405,8 @@ export default class SystemTest {
357
405
  await this.driverVisit("/?systemTest=true")
358
406
 
359
407
  try {
360
- await this.find("body > #root")
361
- await this.find("[data-testid='systemTestingComponent']", {visible: null})
408
+ await this.find("body > #root", {useBaseSelector: false})
409
+ await this.find("[data-testid='systemTestingComponent']", {visible: null, useBaseSelector: false})
362
410
  } catch (error) {
363
411
  await systemTest.takeScreenshot()
364
412
 
@@ -369,17 +417,18 @@ export default class SystemTest {
369
417
  await this.waitForClientWebSocket()
370
418
 
371
419
  this._started = true
420
+ systemTest.setBaseSelector("[data-testid='systemTestingComponent'][data-focussed='true']")
372
421
  }
373
422
 
374
423
  /**
375
424
  * Restores previously set timeouts
376
425
  */
377
426
  async restoreTimeouts() {
378
- if (!this._timeouts) {
427
+ if (!this.getTimeouts()) {
379
428
  throw new Error("Timeouts haven't previously been set")
380
429
  }
381
430
 
382
- await this.driverSetTimeouts(this._timeouts)
431
+ await this.driverSetTimeouts(this.getTimeouts())
383
432
  }
384
433
 
385
434
  /**
@@ -425,10 +474,19 @@ export default class SystemTest {
425
474
  this.wss.on("close", this.onWebSocketClose)
426
475
  }
427
476
 
477
+ /**
478
+ * Sets the on command callback
479
+ */
428
480
  onCommand(callback) {
429
481
  this._onCommandCallback = callback
430
482
  }
431
483
 
484
+ /**
485
+ * Handles a command received from the browser
486
+ *
487
+ * @param {Object} data
488
+ * @returns {Promise<any>}
489
+ */
432
490
  onCommandReceived = async ({data}) => {
433
491
  const type = data.type
434
492
  let result
@@ -457,6 +515,11 @@ export default class SystemTest {
457
515
  return result
458
516
  }
459
517
 
518
+ /**
519
+ * Handles a new web socket connection
520
+ *
521
+ * @param {WebSocket} ws
522
+ */
460
523
  onWebSocketConnection = async (ws) => {
461
524
  this.ws = ws
462
525
  this.communicator.ws = ws
@@ -475,6 +538,11 @@ export default class SystemTest {
475
538
  this.communicator.ws = null
476
539
  }
477
540
 
541
+ /**
542
+ * Handles an error reported from the browser
543
+ *
544
+ * @param {Object} data
545
+ */
478
546
  handleError(data) {
479
547
  if (data.message.includes("Minified React error #419")) {
480
548
  // Ignore this error message
@@ -494,6 +562,7 @@ export default class SystemTest {
494
562
  * Stops the system test
495
563
  */
496
564
  async stop() {
565
+ this.stopScoundrel()
497
566
  this.systemTestHttpServer?.close()
498
567
  this.wss?.close()
499
568
  await this.driver.quit()