system-testing 1.0.30 → 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 +5 -4
- package/src/system-test-communicator.js +13 -11
- package/src/system-test-http-server.js +5 -1
- package/src/system-test.js +48 -33
- package/tsconfig.json +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "system-testing",
|
|
3
|
-
"version": "1.0.
|
|
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",
|
|
@@ -35,9 +36,9 @@
|
|
|
35
36
|
"ws": "^8.18.3"
|
|
36
37
|
},
|
|
37
38
|
"peerDependencies": {
|
|
38
|
-
"@kaspernj/api-maker": "
|
|
39
|
-
"expo-router": "
|
|
40
|
-
"selenium-webdriver": "
|
|
39
|
+
"@kaspernj/api-maker": "*",
|
|
40
|
+
"expo-router": "*",
|
|
41
|
+
"selenium-webdriver": ">= 4.34.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@eslint/js": "^9.39.1",
|
|
@@ -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 {
|
|
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
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
/**
|
package/src/system-test.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import {Builder, By
|
|
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 (!
|
|
40
|
-
|
|
39
|
+
if (!globalThis.systemTest) {
|
|
40
|
+
globalThis.systemTest = new SystemTest(args)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
return
|
|
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
|
-
|
|
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 (!
|
|
351
|
+
if (!element[methodName]) {
|
|
336
352
|
throw new Error(`${element.constructor.name} hasn't an attribute named: ${methodName}`)
|
|
337
|
-
} else if (typeof
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
427
|
+
await this.getDriver().wait(
|
|
428
|
+
async () => {
|
|
429
|
+
const elements = await this.getDriver().findElements(By.css(actualSelector))
|
|
417
430
|
|
|
418
|
-
|
|
419
|
-
|
|
431
|
+
// Not found at all
|
|
432
|
+
if (elements.length === 0) {
|
|
433
|
+
return true;
|
|
420
434
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
|
682
|
+
this.ws.on("error", digg(this, "communicator", "onError"))
|
|
668
683
|
|
|
669
684
|
// @ts-expect-error
|
|
670
|
-
this.ws.on("message", this
|
|
685
|
+
this.ws.on("message", digg(this, "communicator", "onMessage"))
|
|
671
686
|
|
|
672
687
|
if (this.waitForClientWebSocketPromiseResolve) {
|
|
673
688
|
this.waitForClientWebSocketPromiseResolve()
|