system-testing 1.0.31 → 1.0.32

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.31",
3
+ "version": "1.0.32",
4
4
  "description": "System testing with Selenium and browsers.",
5
5
  "keywords": [
6
6
  "system",
@@ -26,6 +26,7 @@
26
26
  "typecheck": "tsc --noEmit"
27
27
  },
28
28
  "dependencies": {
29
+ "@types/selenium-webdriver": "^4.35.4",
29
30
  "awaitery": "^1.0.2",
30
31
  "diggerize": "^1.0.9",
31
32
  "htmlfy": "^1.0.0",
@@ -1,6 +1,14 @@
1
1
  // @ts-check
2
2
 
3
3
  export default class SystemTestCommunicator {
4
+ /** @type {Record<string, {resolve: (data: any) => void, reject: (data: any) => void}>} */
5
+ _responses = {}
6
+
7
+ /** @type {Record<string, any>} */
8
+ _sendQueue = []
9
+
10
+ _sendQueueCount = 0
11
+
4
12
  /** @type {WebSocket | null} */
5
13
  ws = null
6
14
 
@@ -12,13 +20,6 @@ export default class SystemTestCommunicator {
12
20
  constructor({onCommand, parent}) {
13
21
  this.onCommand = onCommand
14
22
  this.parent = parent
15
- this._sendQueueCount = 0
16
-
17
- /** @type {Record<string, any>} */
18
- this._sendQueue = []
19
-
20
- /** @type {Record<string, {resolve: (data: any) => void, reject: (data: any) => void}>} */
21
- this._responses = {}
22
23
  }
23
24
 
24
25
  flushSendQueue() {
@@ -81,20 +82,20 @@ export default class SystemTestCommunicator {
81
82
  }
82
83
 
83
84
  /**
84
- * @param {object} data
85
+ * @param {Record<string, any>} data
85
86
  * @returns {void}
86
87
  */
87
88
  send(data) {
88
89
  this._sendQueue.push(data)
89
90
 
90
- if (this.ws?.readyState == 1) {
91
+ if (this.ws.readyState == 1) {
91
92
  this.flushSendQueue()
92
93
  }
93
94
  }
94
95
 
95
96
  /**
96
97
  * Sends a command and returns a promise that resolves with the response.
97
- * @param {object} data - The command data to send.
98
+ * @param {Record<string, any>} data - The command data to send.
98
99
  * @returns {Promise<void>} A promise that resolves with the response data.
99
100
  */
100
101
  sendCommand(data) {
@@ -103,13 +104,14 @@ export default class SystemTestCommunicator {
103
104
 
104
105
  this._sendQueueCount += 1
105
106
  this._responses[id] = {resolve, reject}
107
+
106
108
  this.send({type: "command", id, data})
107
109
  })
108
110
  }
109
111
 
110
112
  /**
111
113
  * @param {number} id
112
- * @param {object} data
114
+ * @param {Record<string, any>} data
113
115
  * @returns {void}
114
116
  */
115
117
  respond(id, data) {
@@ -8,7 +8,11 @@ import url from "url"
8
8
  export default class SystemTestHttpServer {
9
9
  /** @returns {void} */
10
10
  close() {
11
- this.httpServer?.close()
11
+ if (!this.httpServer) {
12
+ throw new Error("HTTP server is not initialized")
13
+ }
14
+
15
+ this.httpServer.close()
12
16
  }
13
17
 
14
18
  /**
@@ -1,7 +1,8 @@
1
1
  // @ts-check
2
2
 
3
- import {Builder, By, until} from "selenium-webdriver"
3
+ import {Builder, By} from "selenium-webdriver"
4
4
  import chrome from "selenium-webdriver/chrome.js"
5
+ import {digg} from "diggerize"
5
6
  import fs from "node:fs/promises"
6
7
  import logging from "selenium-webdriver/lib/logging.js"
7
8
  import moment from "moment"
@@ -15,16 +16,15 @@ import {WebSocketServer} from "ws"
15
16
 
16
17
  class ElementNotFoundError extends Error { }
17
18
 
18
- /** @type {{systemTest: SystemTest | null}} */
19
- const shared = {
20
- systemTest: null
21
- }
22
-
23
19
  export default class SystemTest {
24
20
  static rootPath = "/blank?systemTest=true"
25
21
 
26
22
  /** @type {SystemTestCommunicator | undefined} */
27
23
  communicator = undefined
24
+
25
+ /** @type {import("selenium-webdriver").WebDriver | undefined} */
26
+ driver = undefined
27
+
28
28
  _started = false
29
29
  _timeouts = 5000
30
30
 
@@ -36,11 +36,11 @@ export default class SystemTest {
36
36
  * @returns {SystemTest}
37
37
  */
38
38
  static current(args) {
39
- if (!shared.systemTest) {
40
- shared.systemTest = new SystemTest(args)
39
+ if (!globalThis.systemTest) {
40
+ globalThis.systemTest = new SystemTest(args)
41
41
  }
42
42
 
43
- return shared.systemTest
43
+ return globalThis.systemTest
44
44
  }
45
45
 
46
46
  getCommunicator() {
@@ -87,7 +87,10 @@ export default class SystemTest {
87
87
 
88
88
  this._host = host
89
89
  this._port = port
90
+
91
+ /** @type {Record<number, object>} */
90
92
  this._responses = {}
93
+
91
94
  this._sendCount = 0
92
95
  this.startScoundrel()
93
96
  this.communicator = new SystemTestCommunicator({onCommand: this.onCommandReceived})
@@ -99,7 +102,11 @@ export default class SystemTest {
99
102
  */
100
103
  getBaseSelector() { return this._baseSelector }
101
104
 
105
+ /**
106
+ * @returns {import("selenium-webdriver").WebDriver}
107
+ */
102
108
  getDriver() {
109
+ if (!this) throw new Error("No this?")
103
110
  if (!this.driver) throw new Error("Driver hasn't been initialized yet")
104
111
 
105
112
  return this.driver
@@ -125,6 +132,8 @@ export default class SystemTest {
125
132
  * @returns {void}
126
133
  */
127
134
  startScoundrel() {
135
+ if (this.wss) throw new Error("Scoundrel server already started")
136
+
128
137
  this.wss = new WebSocketServer({port: 8090})
129
138
  this.serverWebSocket = new ServerWebSocket(this.wss)
130
139
  this.server = new Server(this.serverWebSocket)
@@ -153,7 +162,15 @@ export default class SystemTest {
153
162
  if (restArgsKeys.length > 0) throw new Error(`Unknown arguments: ${restArgsKeys.join(", ")}`)
154
163
 
155
164
  const actualSelector = useBaseSelector ? this.getSelector(selector) : selector
156
- const elements = await this.getDriver().findElements(By.css(actualSelector))
165
+
166
+ let elements = []
167
+
168
+ await this.getDriver().wait(async () => {
169
+ elements = await this.getDriver().findElements(By.css(actualSelector))
170
+
171
+ return elements.length > 0
172
+ }, this.getTimeouts())
173
+
157
174
  const activeElements = []
158
175
 
159
176
  for (const element of elements) {
@@ -330,16 +347,16 @@ export default class SystemTest {
330
347
  tries++
331
348
 
332
349
  const element = await this._findElement(elementOrIdentifier)
333
- const candidate = element[methodName]
334
350
 
335
- if (!candidate) {
351
+ if (!element[methodName]) {
336
352
  throw new Error(`${element.constructor.name} hasn't an attribute named: ${methodName}`)
337
- } else if (typeof candidate != "function") {
353
+ } else if (typeof element[methodName] != "function") {
338
354
  throw new Error(`${element.constructor.name}#${methodName} is not a function`)
339
355
  }
340
356
 
341
357
  try {
342
- return await candidate(...args)
358
+ // Dont call with candidate, because that will bind the function wrong.
359
+ return await element[methodName](...args)
343
360
  } catch (error) {
344
361
  if (error instanceof Error) {
345
362
  if (error.constructor.name === "ElementNotInteractableError") {
@@ -399,31 +416,29 @@ export default class SystemTest {
399
416
  * @returns {Promise<void>}
400
417
  */
401
418
  async waitForNoSelector(selector, args) {
402
- const timeStart = new Date().getTime()
403
- const timeout = this.getTimeouts()
404
419
  const {useBaseSelector, ...restArgs} = args
405
420
 
406
421
  if (Object.keys(restArgs).length > 0) {
407
422
  throw new Error(`Unexpected args: ${Object.keys(restArgs).join(", ")}`)
408
423
  }
409
424
 
410
- while (true) {
411
- try {
412
- const actualSelector = useBaseSelector ? this.getSelector(selector) : selector
413
-
414
- await this.driver.wait(until.elementIsNotVisible(By.css(actualSelector)), 0)
425
+ const actualSelector = useBaseSelector ? this.getSelector(selector) : selector
415
426
 
416
- const timeElapsed = new Date().getTime() - timeStart
427
+ await this.getDriver().wait(
428
+ async () => {
429
+ const elements = await this.getDriver().findElements(By.css(actualSelector))
417
430
 
418
- if (timeElapsed > timeout) {
419
- throw new Error(`Element still found after ${timeout}ms: ${selector}`)
431
+ // Not found at all
432
+ if (elements.length === 0) {
433
+ return true;
420
434
  }
421
- } catch (error) {
422
- if (error instanceof Error && error.message.startsWith("Element couldn't be found after ")) {
423
- break
424
- }
425
- }
426
- }
435
+
436
+ // Found but not visible
437
+ const isDisplayed = await elements[0].isDisplayed()
438
+ return !isDisplayed
439
+ },
440
+ this.getTimeouts()
441
+ )
427
442
  }
428
443
 
429
444
  /**
@@ -526,10 +541,10 @@ export default class SystemTest {
526
541
  options.addArguments("--no-sandbox")
527
542
  options.addArguments("--window-size=1920,1080")
528
543
 
529
- /** @type {import("selenium-webdriver").WebDriver} */
530
544
  this.driver = new Builder()
531
545
  .forBrowser("chrome")
532
546
  .setChromeOptions(options)
547
+ // @ts-expect-error
533
548
  .setCapability("goog:loggingPrefs", {browser: "ALL"})
534
549
  .build()
535
550
 
@@ -664,10 +679,10 @@ export default class SystemTest {
664
679
  this.getCommunicator().onOpen()
665
680
 
666
681
  // @ts-expect-error
667
- this.ws.on("error", this.communicator.onError)
682
+ this.ws.on("error", digg(this, "communicator", "onError"))
668
683
 
669
684
  // @ts-expect-error
670
- this.ws.on("message", this.communicator.onMessage)
685
+ this.ws.on("message", digg(this, "communicator", "onMessage"))
671
686
 
672
687
  if (this.waitForClientWebSocketPromiseResolve) {
673
688
  this.waitForClientWebSocketPromiseResolve()
package/tsconfig.json CHANGED
@@ -8,6 +8,7 @@
8
8
  "module": "nodenext",
9
9
 
10
10
  "lib": ["dom", "ES2020"],
11
+ "sourceMap": true,
11
12
  "types": ["node"],
12
13
  "skipLibCheck": true
13
14
  }