system-testing 1.0.31 → 1.0.33
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 +2 -1
- package/src/system-test-communicator.js +13 -11
- package/src/system-test-http-server.js +5 -1
- package/src/system-test.js +69 -35
- 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.33",
|
|
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 {
|
|
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,17 +16,17 @@ 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
|
+
_driverTimeouts = 5000
|
|
29
30
|
_timeouts = 5000
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -36,11 +37,11 @@ export default class SystemTest {
|
|
|
36
37
|
* @returns {SystemTest}
|
|
37
38
|
*/
|
|
38
39
|
static current(args) {
|
|
39
|
-
if (!
|
|
40
|
-
|
|
40
|
+
if (!globalThis.systemTest) {
|
|
41
|
+
globalThis.systemTest = new SystemTest(args)
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
return
|
|
44
|
+
return globalThis.systemTest
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
getCommunicator() {
|
|
@@ -87,7 +88,10 @@ export default class SystemTest {
|
|
|
87
88
|
|
|
88
89
|
this._host = host
|
|
89
90
|
this._port = port
|
|
91
|
+
|
|
92
|
+
/** @type {Record<number, object>} */
|
|
90
93
|
this._responses = {}
|
|
94
|
+
|
|
91
95
|
this._sendCount = 0
|
|
92
96
|
this.startScoundrel()
|
|
93
97
|
this.communicator = new SystemTestCommunicator({onCommand: this.onCommandReceived})
|
|
@@ -99,7 +103,11 @@ export default class SystemTest {
|
|
|
99
103
|
*/
|
|
100
104
|
getBaseSelector() { return this._baseSelector }
|
|
101
105
|
|
|
106
|
+
/**
|
|
107
|
+
* @returns {import("selenium-webdriver").WebDriver}
|
|
108
|
+
*/
|
|
102
109
|
getDriver() {
|
|
110
|
+
if (!this) throw new Error("No this?")
|
|
103
111
|
if (!this.driver) throw new Error("Driver hasn't been initialized yet")
|
|
104
112
|
|
|
105
113
|
return this.driver
|
|
@@ -125,6 +133,8 @@ export default class SystemTest {
|
|
|
125
133
|
* @returns {void}
|
|
126
134
|
*/
|
|
127
135
|
startScoundrel() {
|
|
136
|
+
if (this.wss) throw new Error("Scoundrel server already started")
|
|
137
|
+
|
|
128
138
|
this.wss = new WebSocketServer({port: 8090})
|
|
129
139
|
this.serverWebSocket = new ServerWebSocket(this.wss)
|
|
130
140
|
this.server = new Server(this.serverWebSocket)
|
|
@@ -142,18 +152,38 @@ export default class SystemTest {
|
|
|
142
152
|
* Finds all elements by CSS selector
|
|
143
153
|
* @param {string} selector
|
|
144
154
|
* @param {object} args
|
|
155
|
+
* @param {number} [args.timeout]
|
|
145
156
|
* @param {boolean} [args.visible]
|
|
146
157
|
* @param {boolean} [args.useBaseSelector]
|
|
147
158
|
* @returns {Promise<import("selenium-webdriver").WebElement[]>}
|
|
148
159
|
*/
|
|
149
160
|
async all(selector, args = {}) {
|
|
150
|
-
const {visible = true, useBaseSelector = true, ...restArgs} = args
|
|
161
|
+
const {visible = true, timeout, useBaseSelector = true, ...restArgs} = args
|
|
151
162
|
const restArgsKeys = Object.keys(restArgs)
|
|
163
|
+
let actualTimeout
|
|
164
|
+
|
|
165
|
+
if (timeout === undefined) {
|
|
166
|
+
actualTimeout = this._driverTimeouts
|
|
167
|
+
} else {
|
|
168
|
+
actualTimeout = timeout
|
|
169
|
+
}
|
|
152
170
|
|
|
153
171
|
if (restArgsKeys.length > 0) throw new Error(`Unknown arguments: ${restArgsKeys.join(", ")}`)
|
|
154
172
|
|
|
155
173
|
const actualSelector = useBaseSelector ? this.getSelector(selector) : selector
|
|
156
|
-
const
|
|
174
|
+
const getElements = async () => await this.getDriver().findElements(By.css(actualSelector))
|
|
175
|
+
let elements = []
|
|
176
|
+
|
|
177
|
+
if (actualTimeout == 0) {
|
|
178
|
+
elements = await getElements()
|
|
179
|
+
} else {
|
|
180
|
+
await this.getDriver().wait(async () => {
|
|
181
|
+
elements = await getElements()
|
|
182
|
+
|
|
183
|
+
return elements.length > 0
|
|
184
|
+
}, actualTimeout)
|
|
185
|
+
}
|
|
186
|
+
|
|
157
187
|
const activeElements = []
|
|
158
188
|
|
|
159
189
|
for (const element of elements) {
|
|
@@ -215,13 +245,17 @@ export default class SystemTest {
|
|
|
215
245
|
* @returns {Promise<import("selenium-webdriver").WebElement>}
|
|
216
246
|
*/
|
|
217
247
|
async find(selector, args = {}) {
|
|
218
|
-
let elements
|
|
248
|
+
let elements = []
|
|
219
249
|
|
|
220
250
|
try {
|
|
221
251
|
elements = await this.all(selector, args)
|
|
222
252
|
} catch (error) {
|
|
223
253
|
// Re-throw to recover stack trace
|
|
224
254
|
if (error instanceof Error) {
|
|
255
|
+
if (error.message.startsWith("Wait timed out after")) {
|
|
256
|
+
elements = []
|
|
257
|
+
}
|
|
258
|
+
|
|
225
259
|
throw new Error(`${error.message} (selector: ${this.getSelector(selector)})`)
|
|
226
260
|
} else {
|
|
227
261
|
throw new Error(`${error} (selector: ${this.getSelector(selector)})`)
|
|
@@ -330,16 +364,16 @@ export default class SystemTest {
|
|
|
330
364
|
tries++
|
|
331
365
|
|
|
332
366
|
const element = await this._findElement(elementOrIdentifier)
|
|
333
|
-
const candidate = element[methodName]
|
|
334
367
|
|
|
335
|
-
if (!
|
|
368
|
+
if (!element[methodName]) {
|
|
336
369
|
throw new Error(`${element.constructor.name} hasn't an attribute named: ${methodName}`)
|
|
337
|
-
} else if (typeof
|
|
370
|
+
} else if (typeof element[methodName] != "function") {
|
|
338
371
|
throw new Error(`${element.constructor.name}#${methodName} is not a function`)
|
|
339
372
|
}
|
|
340
373
|
|
|
341
374
|
try {
|
|
342
|
-
|
|
375
|
+
// Dont call with candidate, because that will bind the function wrong.
|
|
376
|
+
return await element[methodName](...args)
|
|
343
377
|
} catch (error) {
|
|
344
378
|
if (error instanceof Error) {
|
|
345
379
|
if (error.constructor.name === "ElementNotInteractableError") {
|
|
@@ -399,31 +433,30 @@ export default class SystemTest {
|
|
|
399
433
|
* @returns {Promise<void>}
|
|
400
434
|
*/
|
|
401
435
|
async waitForNoSelector(selector, args) {
|
|
402
|
-
const timeStart = new Date().getTime()
|
|
403
|
-
const timeout = this.getTimeouts()
|
|
404
436
|
const {useBaseSelector, ...restArgs} = args
|
|
405
437
|
|
|
406
438
|
if (Object.keys(restArgs).length > 0) {
|
|
407
439
|
throw new Error(`Unexpected args: ${Object.keys(restArgs).join(", ")}`)
|
|
408
440
|
}
|
|
409
441
|
|
|
410
|
-
|
|
411
|
-
try {
|
|
412
|
-
const actualSelector = useBaseSelector ? this.getSelector(selector) : selector
|
|
413
|
-
|
|
414
|
-
await this.driver.wait(until.elementIsNotVisible(By.css(actualSelector)), 0)
|
|
442
|
+
const actualSelector = useBaseSelector ? this.getSelector(selector) : selector
|
|
415
443
|
|
|
416
|
-
|
|
444
|
+
await this.getDriver().wait(
|
|
445
|
+
async () => {
|
|
446
|
+
const elements = await this.getDriver().findElements(By.css(actualSelector))
|
|
417
447
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
} catch (error) {
|
|
422
|
-
if (error instanceof Error && error.message.startsWith("Element couldn't be found after ")) {
|
|
423
|
-
break
|
|
448
|
+
// Not found at all
|
|
449
|
+
if (elements.length === 0) {
|
|
450
|
+
return true
|
|
424
451
|
}
|
|
425
|
-
|
|
426
|
-
|
|
452
|
+
|
|
453
|
+
// Found but not visible
|
|
454
|
+
const isDisplayed = await elements[0].isDisplayed()
|
|
455
|
+
|
|
456
|
+
return !isDisplayed
|
|
457
|
+
},
|
|
458
|
+
this.getTimeouts()
|
|
459
|
+
)
|
|
427
460
|
}
|
|
428
461
|
|
|
429
462
|
/**
|
|
@@ -526,10 +559,10 @@ export default class SystemTest {
|
|
|
526
559
|
options.addArguments("--no-sandbox")
|
|
527
560
|
options.addArguments("--window-size=1920,1080")
|
|
528
561
|
|
|
529
|
-
/** @type {import("selenium-webdriver").WebDriver} */
|
|
530
562
|
this.driver = new Builder()
|
|
531
563
|
.forBrowser("chrome")
|
|
532
564
|
.setChromeOptions(options)
|
|
565
|
+
// @ts-expect-error
|
|
533
566
|
.setCapability("goog:loggingPrefs", {browser: "ALL"})
|
|
534
567
|
.build()
|
|
535
568
|
|
|
@@ -574,6 +607,7 @@ export default class SystemTest {
|
|
|
574
607
|
* @returns {Promise<void>}
|
|
575
608
|
*/
|
|
576
609
|
async driverSetTimeouts(newTimeout) {
|
|
610
|
+
this._driverTimeouts = newTimeout
|
|
577
611
|
await this.getDriver().manage().setTimeouts({implicit: newTimeout})
|
|
578
612
|
}
|
|
579
613
|
|
|
@@ -664,10 +698,10 @@ export default class SystemTest {
|
|
|
664
698
|
this.getCommunicator().onOpen()
|
|
665
699
|
|
|
666
700
|
// @ts-expect-error
|
|
667
|
-
this.ws.on("error", this
|
|
701
|
+
this.ws.on("error", digg(this, "communicator", "onError"))
|
|
668
702
|
|
|
669
703
|
// @ts-expect-error
|
|
670
|
-
this.ws.on("message", this
|
|
704
|
+
this.ws.on("message", digg(this, "communicator", "onMessage"))
|
|
671
705
|
|
|
672
706
|
if (this.waitForClientWebSocketPromiseResolve) {
|
|
673
707
|
this.waitForClientWebSocketPromiseResolve()
|