system-testing 1.0.25 → 1.0.27
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/eslint.config.js +23 -0
- package/package.json +8 -1
- package/peak_flow.yml +4 -0
- package/src/system-test-browser-helper.js +25 -2
- package/src/system-test-communicator.js +2 -3
- package/src/system-test-http-server.js +5 -3
- package/src/system-test.js +107 -44
- package/src/use-system-test.js +11 -9
package/eslint.config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from "@eslint/js"
|
|
2
|
+
import {jsdoc} from "eslint-plugin-jsdoc"
|
|
3
|
+
import globals from "globals"
|
|
4
|
+
import { defineConfig } from "eslint/config"
|
|
5
|
+
|
|
6
|
+
export default defineConfig([
|
|
7
|
+
{
|
|
8
|
+
files: ["**/*.{js,mjs,cjs}"],
|
|
9
|
+
plugins: {js},
|
|
10
|
+
extends: ["js/recommended"],
|
|
11
|
+
languageOptions: {
|
|
12
|
+
globals: {...globals.browser, ...globals.node}
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
jsdoc({
|
|
16
|
+
config: "flat/recommended",
|
|
17
|
+
rules: {
|
|
18
|
+
"jsdoc/reject-any-type": "off",
|
|
19
|
+
"jsdoc/require-param-description": "off",
|
|
20
|
+
"jsdoc/require-returns-description": "off"
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
])
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "system-testing",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.27",
|
|
4
4
|
"description": "System testing with Selenium and browsers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"system",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"type": "module",
|
|
22
22
|
"main": "src/index.js",
|
|
23
23
|
"scripts": {
|
|
24
|
+
"lint": "eslint",
|
|
24
25
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
@@ -34,5 +35,11 @@
|
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
|
36
37
|
"selenium-webdriver": "^4.34.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@eslint/js": "^9.39.1",
|
|
41
|
+
"eslint": "^9.39.1",
|
|
42
|
+
"eslint-plugin-jsdoc": "^61.4.1",
|
|
43
|
+
"globals": "^16.5.0"
|
|
37
44
|
}
|
|
38
45
|
}
|
package/peak_flow.yml
ADDED
|
@@ -100,15 +100,20 @@ export default class SystemTestBrowserHelper {
|
|
|
100
100
|
this.communicator.sendCommand(data)
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* @returns {void}
|
|
105
|
+
*/
|
|
103
106
|
connectWebSocket() {
|
|
104
|
-
this.ws = new WebSocket("ws://localhost:1985")
|
|
105
|
-
|
|
107
|
+
this.ws = new WebSocket("ws://localhost:1985")
|
|
106
108
|
this.communicator.ws = this.ws
|
|
107
109
|
this.ws.addEventListener("error", digg(this, "communicator", "onError"))
|
|
108
110
|
this.ws.addEventListener("open", digg(this, "communicator", "onOpen"))
|
|
109
111
|
this.ws.addEventListener("message", (event) => this.communicator.onMessage(event.data))
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
/**
|
|
115
|
+
* @returns {void}
|
|
116
|
+
*/
|
|
112
117
|
enableOnBrowser() {
|
|
113
118
|
this._enabled = true
|
|
114
119
|
this.connectWebSocket()
|
|
@@ -117,7 +122,14 @@ export default class SystemTestBrowserHelper {
|
|
|
117
122
|
this.overrideConsoleLog()
|
|
118
123
|
}
|
|
119
124
|
|
|
125
|
+
/**
|
|
126
|
+
* @returns {boolean}
|
|
127
|
+
*/
|
|
120
128
|
getEnabled() { return this._enabled }
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @returns {EventEmitter}
|
|
132
|
+
*/
|
|
121
133
|
getEvents() { return this.events }
|
|
122
134
|
|
|
123
135
|
fakeConsoleError = (...args) => {
|
|
@@ -186,10 +198,17 @@ export default class SystemTestBrowserHelper {
|
|
|
186
198
|
}
|
|
187
199
|
}
|
|
188
200
|
|
|
201
|
+
/**
|
|
202
|
+
* @param {function() : void} callback
|
|
203
|
+
* @returns {void}
|
|
204
|
+
*/
|
|
189
205
|
onInitialize(callback) {
|
|
190
206
|
this._onInitializeCallback = callback
|
|
191
207
|
}
|
|
192
208
|
|
|
209
|
+
/**
|
|
210
|
+
* @returns {void}
|
|
211
|
+
*/
|
|
193
212
|
overrideConsoleLog() {
|
|
194
213
|
if (this.originalConsoleError || this.originalConsoleLog) {
|
|
195
214
|
throw new Error("Console methods has already been overridden!")
|
|
@@ -202,6 +221,10 @@ export default class SystemTestBrowserHelper {
|
|
|
202
221
|
console.log = this.fakeConsoleLog
|
|
203
222
|
}
|
|
204
223
|
|
|
224
|
+
/**
|
|
225
|
+
* @param {string} sql
|
|
226
|
+
* @returns {Promise<Array<Record<string, any>>>}
|
|
227
|
+
*/
|
|
205
228
|
async sendQuery(sql) {
|
|
206
229
|
return await this.communicator.sendCommand({type: "query", sql})
|
|
207
230
|
}
|
|
@@ -65,9 +65,8 @@ export default class SystemTestCommunicator {
|
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Sends a command and returns a promise that resolves with the response.
|
|
68
|
-
*
|
|
69
|
-
* @
|
|
70
|
-
* @returns {Promise} A promise that resolves with the response data.
|
|
68
|
+
* @param {object} data - The command data to send.
|
|
69
|
+
* @returns {Promise<void>} A promise that resolves with the response data.
|
|
71
70
|
*/
|
|
72
71
|
sendCommand(data) {
|
|
73
72
|
return new Promise((resolve, error) => {
|
|
@@ -4,6 +4,7 @@ import mime from "mime"
|
|
|
4
4
|
import url from "url"
|
|
5
5
|
|
|
6
6
|
export default class SystemTestHttpServer {
|
|
7
|
+
/** @returns {void} */
|
|
7
8
|
close() {
|
|
8
9
|
this.httpServer.close()
|
|
9
10
|
}
|
|
@@ -21,7 +22,7 @@ export default class SystemTestHttpServer {
|
|
|
21
22
|
try {
|
|
22
23
|
await fs.stat(filePath)
|
|
23
24
|
fileExists = true
|
|
24
|
-
} catch (_error) {
|
|
25
|
+
} catch (_error) { // eslint-disable-line no-unused-vars
|
|
25
26
|
fileExists = false
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -37,12 +38,13 @@ export default class SystemTestHttpServer {
|
|
|
37
38
|
response.end(fileContent)
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/** @returns {Promise<void>} */
|
|
40
42
|
async start() {
|
|
41
|
-
this.basePath = await fs.realpath(`${__dirname}/../..`)
|
|
42
|
-
|
|
43
|
+
this.basePath = await fs.realpath(`${__dirname}/../..`)
|
|
43
44
|
await this.startHttpServer()
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/** @returns {Promise<void>} */
|
|
46
48
|
startHttpServer() {
|
|
47
49
|
return new Promise((resolve) => {
|
|
48
50
|
this.httpServer = http.createServer(this.onHttpServerRequest)
|
package/src/system-test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {Builder, By} from "selenium-webdriver"
|
|
1
|
+
import {Builder, By, until} from "selenium-webdriver"
|
|
2
2
|
import chrome from "selenium-webdriver/chrome.js"
|
|
3
3
|
import {digg} from "diggerize"
|
|
4
4
|
import fs from "node:fs/promises"
|
|
@@ -19,7 +19,6 @@ export default class SystemTest {
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Gets the current system test instance
|
|
22
|
-
*
|
|
23
22
|
* @param {object} args
|
|
24
23
|
* @returns {SystemTest}
|
|
25
24
|
*/
|
|
@@ -33,8 +32,8 @@ export default class SystemTest {
|
|
|
33
32
|
|
|
34
33
|
/**
|
|
35
34
|
* Runs a system test
|
|
36
|
-
*
|
|
37
35
|
* @param {function(SystemTest): Promise<void>} callback
|
|
36
|
+
* @returns {Promise<void>}
|
|
38
37
|
*/
|
|
39
38
|
static async run(callback) {
|
|
40
39
|
const systemTest = this.current()
|
|
@@ -54,7 +53,6 @@ export default class SystemTest {
|
|
|
54
53
|
|
|
55
54
|
/**
|
|
56
55
|
* Creates a new SystemTest instance
|
|
57
|
-
*
|
|
58
56
|
* @param {object} args
|
|
59
57
|
* @param {string} args.host
|
|
60
58
|
* @param {number} args.port
|
|
@@ -76,21 +74,18 @@ export default class SystemTest {
|
|
|
76
74
|
|
|
77
75
|
/**
|
|
78
76
|
* Gets the base selector for scoping element searches
|
|
79
|
-
*
|
|
80
77
|
* @returns {string}
|
|
81
78
|
*/
|
|
82
79
|
getBaseSelector() { return this._baseSelector }
|
|
83
80
|
|
|
84
81
|
/**
|
|
85
82
|
* Sets the base selector for scoping element searches
|
|
86
|
-
*
|
|
87
83
|
* @param {string} baseSelector
|
|
88
84
|
*/
|
|
89
85
|
setBaseSelector(baseSelector) { this._baseSelector = baseSelector }
|
|
90
86
|
|
|
91
87
|
/**
|
|
92
88
|
* Gets a selector scoped to the base selector
|
|
93
|
-
*
|
|
94
89
|
* @param {string} selector
|
|
95
90
|
* @returns {string}
|
|
96
91
|
*/
|
|
@@ -98,13 +93,19 @@ export default class SystemTest {
|
|
|
98
93
|
return this.getBaseSelector() ? `${this.getBaseSelector()} ${selector}` : selector
|
|
99
94
|
}
|
|
100
95
|
|
|
101
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* Starts Scoundrel server which the browser connects to for remote evaluation in the browser
|
|
98
|
+
* @returns {void}
|
|
99
|
+
*/
|
|
102
100
|
startScoundrel() {
|
|
103
101
|
this.wss = new WebSocketServer({port: 8090})
|
|
104
102
|
this.serverWebSocket = new ServerWebSocket(this.wss)
|
|
105
103
|
this.server = new Server(this.serverWebSocket)
|
|
106
104
|
}
|
|
107
105
|
|
|
106
|
+
/**
|
|
107
|
+
* @returns {void}
|
|
108
|
+
*/
|
|
108
109
|
stopScoundrel() {
|
|
109
110
|
this.server?.close()
|
|
110
111
|
this.wss?.close()
|
|
@@ -112,10 +113,8 @@ export default class SystemTest {
|
|
|
112
113
|
|
|
113
114
|
/**
|
|
114
115
|
* Finds all elements by CSS selector
|
|
115
|
-
*
|
|
116
116
|
* @param {string} selector
|
|
117
117
|
* @param {object} args
|
|
118
|
-
*
|
|
119
118
|
* @returns {import("selenium-webdriver").WebElement[]}
|
|
120
119
|
*/
|
|
121
120
|
async all(selector, args = {}) {
|
|
@@ -148,9 +147,9 @@ export default class SystemTest {
|
|
|
148
147
|
|
|
149
148
|
/**
|
|
150
149
|
* Clicks an element that has children which fills out the element and would otherwise have caused a ElementClickInterceptedError
|
|
151
|
-
*
|
|
152
|
-
* @
|
|
153
|
-
|
|
150
|
+
* @param {string|import("selenium-webdriver").WebElement} elementOrIdentifier
|
|
151
|
+
* @returns {Promise<void>}
|
|
152
|
+
*/
|
|
154
153
|
async click(elementOrIdentifier) {
|
|
155
154
|
let tries = 0
|
|
156
155
|
|
|
@@ -166,13 +165,13 @@ export default class SystemTest {
|
|
|
166
165
|
} catch (error) {
|
|
167
166
|
if (error.constructor.name === "ElementNotInteractableError") {
|
|
168
167
|
if (tries >= 3) {
|
|
169
|
-
throw new Error(`Element ${
|
|
168
|
+
throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed after ${tries} tries - ${error.constructor.name}: ${error.message}`)
|
|
170
169
|
} else {
|
|
171
170
|
await wait(50)
|
|
172
171
|
}
|
|
173
172
|
} else {
|
|
174
173
|
// Re-throw with un-corrupted stack trace
|
|
175
|
-
throw new Error(`Element ${
|
|
174
|
+
throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed - ${error.constructor.name}: ${error.message}`)
|
|
176
175
|
}
|
|
177
176
|
}
|
|
178
177
|
}
|
|
@@ -180,7 +179,6 @@ export default class SystemTest {
|
|
|
180
179
|
|
|
181
180
|
/**
|
|
182
181
|
* Finds a single element by CSS selector
|
|
183
|
-
*
|
|
184
182
|
* @param {string} selector
|
|
185
183
|
* @param {object} args
|
|
186
184
|
* @returns {import("selenium-webdriver").WebElement}
|
|
@@ -208,13 +206,16 @@ export default class SystemTest {
|
|
|
208
206
|
|
|
209
207
|
/**
|
|
210
208
|
* Finds a single element by test ID
|
|
211
|
-
*
|
|
212
209
|
* @param {string} testID
|
|
213
210
|
* @param {object} args
|
|
214
|
-
* @returns {import("selenium-webdriver").WebElement}
|
|
211
|
+
* @returns {Promise<import("selenium-webdriver").WebElement>}
|
|
215
212
|
*/
|
|
216
213
|
async findByTestID(testID, args) { return await this.find(`[data-testid='${testID}']`, args) }
|
|
217
214
|
|
|
215
|
+
/**
|
|
216
|
+
* @param {string|import("selenium-webdriver").WebElement} elementOrIdentifier
|
|
217
|
+
* @returns {Promise<import("selenium-webdriver").WebElement>}
|
|
218
|
+
*/
|
|
218
219
|
async _findElement(elementOrIdentifier) {
|
|
219
220
|
let element
|
|
220
221
|
|
|
@@ -229,9 +230,9 @@ export default class SystemTest {
|
|
|
229
230
|
|
|
230
231
|
/**
|
|
231
232
|
* Finds a single element by CSS selector without waiting
|
|
232
|
-
*
|
|
233
233
|
* @param {string} selector
|
|
234
|
-
* @
|
|
234
|
+
* @param {object} args
|
|
235
|
+
* @returns {Promise<import("selenium-webdriver").WebElement>}
|
|
235
236
|
*/
|
|
236
237
|
async findNoWait(selector, args) {
|
|
237
238
|
await this.driverSetTimeouts(0)
|
|
@@ -245,7 +246,6 @@ export default class SystemTest {
|
|
|
245
246
|
|
|
246
247
|
/**
|
|
247
248
|
* Gets browser logs
|
|
248
|
-
*
|
|
249
249
|
* @returns {Promise<string[]>}
|
|
250
250
|
*/
|
|
251
251
|
async getBrowserLogs() {
|
|
@@ -268,20 +268,24 @@ export default class SystemTest {
|
|
|
268
268
|
return browserLogs
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
/**
|
|
272
|
+
* @returns {Promise<string>}
|
|
273
|
+
*/
|
|
271
274
|
async getCurrentUrl() {
|
|
272
275
|
return await this.driver.getCurrentUrl()
|
|
273
276
|
}
|
|
274
277
|
|
|
278
|
+
/**
|
|
279
|
+
* @returns {number}
|
|
280
|
+
*/
|
|
275
281
|
getTimeouts() { return this._timeouts }
|
|
276
282
|
|
|
277
283
|
/**
|
|
278
284
|
* Interacts with an element by calling a method on it with the given arguments.
|
|
279
285
|
* Retrying on ElementNotInteractableError.
|
|
280
|
-
*
|
|
281
286
|
* @param {import("selenium-webdriver").WebElement|string} elementOrIdentifier - The element or a CSS selector to find the element.
|
|
282
287
|
* @param {string} methodName - The method name to call on the element.
|
|
283
288
|
* @param {...any} args - Arguments to pass to the method.
|
|
284
|
-
*
|
|
285
289
|
* @returns {Promise<any>}
|
|
286
290
|
*/
|
|
287
291
|
async interact(elementOrIdentifier, methodName, ...args) {
|
|
@@ -326,8 +330,8 @@ export default class SystemTest {
|
|
|
326
330
|
|
|
327
331
|
/**
|
|
328
332
|
* Expects no element to be found by CSS selector
|
|
329
|
-
*
|
|
330
333
|
* @param {string} selector
|
|
334
|
+
* @returns {Promise<void>}
|
|
331
335
|
*/
|
|
332
336
|
async expectNoElement(selector) {
|
|
333
337
|
let found = false
|
|
@@ -346,9 +350,41 @@ export default class SystemTest {
|
|
|
346
350
|
}
|
|
347
351
|
}
|
|
348
352
|
|
|
353
|
+
/**
|
|
354
|
+
* @param {string} selector
|
|
355
|
+
* @param {object} args
|
|
356
|
+
* @returns {Promise<void>}
|
|
357
|
+
*/
|
|
358
|
+
async waitForNoSelector(selector, args) {
|
|
359
|
+
const timeStart = new Date().getTime()
|
|
360
|
+
const timeout = this.getTimeouts()
|
|
361
|
+
const {useBaseSelector, ...restArgs} = args
|
|
362
|
+
|
|
363
|
+
if (Object.keys(restArgs).length > 0) {
|
|
364
|
+
throw new Error(`Unexpected args: ${Object.keys(restArgs).join(", ")}`)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
while (true) {
|
|
368
|
+
try {
|
|
369
|
+
const actualSelector = useBaseSelector ? this.getSelector(selector) : selector
|
|
370
|
+
|
|
371
|
+
await this.driver.wait(until.elementIsNotVisible(By.css(actualSelector)), 0)
|
|
372
|
+
|
|
373
|
+
const timeElapsed = new Date().getTime() - timeStart
|
|
374
|
+
|
|
375
|
+
if (timeElapsed > timeout) {
|
|
376
|
+
throw new Error(`Element still found after ${timeout}ms: ${selector}`)
|
|
377
|
+
}
|
|
378
|
+
} catch (error) {
|
|
379
|
+
if (error.message.startsWith("Element couldn't be found after ")) {
|
|
380
|
+
break
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
349
386
|
/**
|
|
350
387
|
* Gets notification messages
|
|
351
|
-
*
|
|
352
388
|
* @returns {Promise<string[]>}
|
|
353
389
|
*/
|
|
354
390
|
async notificationMessages() {
|
|
@@ -366,45 +402,65 @@ export default class SystemTest {
|
|
|
366
402
|
|
|
367
403
|
/**
|
|
368
404
|
* Expects a notification message to appear and waits for it if necessary.
|
|
369
|
-
*
|
|
370
405
|
* @param {string} expectedNotificationMessage
|
|
406
|
+
* @returns {Promise<void>}
|
|
371
407
|
*/
|
|
372
408
|
async expectNotificationMessage(expectedNotificationMessage) {
|
|
373
409
|
const allDetectedNotificationMessages = []
|
|
410
|
+
let foundNotificationMessageElement
|
|
374
411
|
|
|
375
412
|
await waitFor(async () => {
|
|
376
|
-
const
|
|
413
|
+
const notificationMessageElements = await this.all("[data-class='notification-message']", {useBaseSelector: false})
|
|
414
|
+
|
|
415
|
+
for (const notificationMessageElement of notificationMessageElements) {
|
|
416
|
+
const notificationMessage = await notificationMessageElement.getText()
|
|
377
417
|
|
|
378
|
-
for (const notificationMessage of notificationMessages) {
|
|
379
418
|
if (!allDetectedNotificationMessages.includes(notificationMessage)) {
|
|
380
419
|
allDetectedNotificationMessages.push(notificationMessage)
|
|
381
420
|
}
|
|
382
421
|
|
|
383
422
|
if (notificationMessage == expectedNotificationMessage) {
|
|
423
|
+
foundNotificationMessageElement = notificationMessageElement
|
|
384
424
|
return
|
|
385
425
|
}
|
|
386
426
|
}
|
|
387
427
|
|
|
388
428
|
throw new Error(`Notification message ${expectedNotificationMessage} wasn't included in: ${allDetectedNotificationMessages.join(", ")}`)
|
|
389
429
|
})
|
|
430
|
+
|
|
431
|
+
if (foundNotificationMessageElement) {
|
|
432
|
+
await this.interact(foundNotificationMessageElement, "click") // Dismiss the notification message
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* @returns {Promise<void>}
|
|
438
|
+
*/
|
|
439
|
+
async dismissNotificationMessages() {
|
|
440
|
+
const notificationMessageElements = await this.all("[data-class='notification-message']", {useBaseSelector: false})
|
|
441
|
+
|
|
442
|
+
for (const notificationMessageElement of notificationMessageElements) {
|
|
443
|
+
await this.interact(notificationMessageElement, "click")
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
await this.waitForNoSelector("[data-class='notification-message']", {useBaseSelector: false})
|
|
390
447
|
}
|
|
391
448
|
|
|
392
449
|
/**
|
|
393
450
|
* Indicates whether the system test has been started
|
|
394
|
-
*
|
|
395
451
|
* @returns {boolean}
|
|
396
452
|
*/
|
|
397
453
|
isStarted() { return this._started }
|
|
398
454
|
|
|
399
455
|
/**
|
|
400
456
|
* Gets the HTML of the current page
|
|
401
|
-
*
|
|
402
457
|
* @returns {Promise<string>}
|
|
403
458
|
*/
|
|
404
459
|
async getHTML() { return await this.driver.getPageSource() }
|
|
405
460
|
|
|
406
461
|
/**
|
|
407
462
|
* Starts the system test
|
|
463
|
+
* @returns {Promise<void>}
|
|
408
464
|
*/
|
|
409
465
|
async start() {
|
|
410
466
|
if (process.env.SYSTEM_TEST_HOST == "expo-dev-server") {
|
|
@@ -444,8 +500,7 @@ export default class SystemTest {
|
|
|
444
500
|
await this.find("body > #root", {useBaseSelector: false})
|
|
445
501
|
await this.find("[data-testid='systemTestingComponent']", {visible: null, useBaseSelector: false})
|
|
446
502
|
} catch (error) {
|
|
447
|
-
await
|
|
448
|
-
|
|
503
|
+
await this.takeScreenshot()
|
|
449
504
|
throw error
|
|
450
505
|
}
|
|
451
506
|
|
|
@@ -453,11 +508,12 @@ export default class SystemTest {
|
|
|
453
508
|
await this.waitForClientWebSocket()
|
|
454
509
|
|
|
455
510
|
this._started = true
|
|
456
|
-
|
|
511
|
+
this.setBaseSelector("[data-testid='systemTestingComponent'][data-focussed='true']")
|
|
457
512
|
}
|
|
458
513
|
|
|
459
514
|
/**
|
|
460
515
|
* Restores previously set timeouts
|
|
516
|
+
* @returns {Promise<void>}
|
|
461
517
|
*/
|
|
462
518
|
async restoreTimeouts() {
|
|
463
519
|
if (!this.getTimeouts()) {
|
|
@@ -469,8 +525,8 @@ export default class SystemTest {
|
|
|
469
525
|
|
|
470
526
|
/**
|
|
471
527
|
* Sets driver timeouts
|
|
472
|
-
*
|
|
473
528
|
* @param {number} newTimeout
|
|
529
|
+
* @returns {Promise<void>}
|
|
474
530
|
*/
|
|
475
531
|
async driverSetTimeouts(newTimeout) {
|
|
476
532
|
await this.driver.manage().setTimeouts({implicit: newTimeout})
|
|
@@ -478,8 +534,8 @@ export default class SystemTest {
|
|
|
478
534
|
|
|
479
535
|
/**
|
|
480
536
|
* Sets timeouts and stores the previous timeouts
|
|
481
|
-
*
|
|
482
537
|
* @param {number} newTimeout
|
|
538
|
+
* @returns {Promise<void>}
|
|
483
539
|
*/
|
|
484
540
|
async setTimeouts(newTimeout) {
|
|
485
541
|
this._timeouts = newTimeout
|
|
@@ -488,7 +544,6 @@ export default class SystemTest {
|
|
|
488
544
|
|
|
489
545
|
/**
|
|
490
546
|
* Waits for the client web socket to connect
|
|
491
|
-
*
|
|
492
547
|
* @returns {Promise<void>}
|
|
493
548
|
*/
|
|
494
549
|
waitForClientWebSocket() {
|
|
@@ -503,6 +558,7 @@ export default class SystemTest {
|
|
|
503
558
|
|
|
504
559
|
/**
|
|
505
560
|
* Starts the web socket server
|
|
561
|
+
* @returns {void}
|
|
506
562
|
*/
|
|
507
563
|
startWebSocketServer() {
|
|
508
564
|
this.wss = new WebSocketServer({port: 1985})
|
|
@@ -512,6 +568,8 @@ export default class SystemTest {
|
|
|
512
568
|
|
|
513
569
|
/**
|
|
514
570
|
* Sets the on command callback
|
|
571
|
+
* @param {function(object) : void} callback
|
|
572
|
+
* @returns {void}
|
|
515
573
|
*/
|
|
516
574
|
onCommand(callback) {
|
|
517
575
|
this._onCommandCallback = callback
|
|
@@ -519,8 +577,8 @@ export default class SystemTest {
|
|
|
519
577
|
|
|
520
578
|
/**
|
|
521
579
|
* Handles a command received from the browser
|
|
522
|
-
*
|
|
523
|
-
* @param {
|
|
580
|
+
* @param {object} data
|
|
581
|
+
* @param {object} data.data
|
|
524
582
|
* @returns {Promise<any>}
|
|
525
583
|
*/
|
|
526
584
|
onCommandReceived = async ({data}) => {
|
|
@@ -553,8 +611,8 @@ export default class SystemTest {
|
|
|
553
611
|
|
|
554
612
|
/**
|
|
555
613
|
* Handles a new web socket connection
|
|
556
|
-
*
|
|
557
614
|
* @param {WebSocket} ws
|
|
615
|
+
* @returns {void}
|
|
558
616
|
*/
|
|
559
617
|
onWebSocketConnection = async (ws) => {
|
|
560
618
|
this.ws = ws
|
|
@@ -569,6 +627,9 @@ export default class SystemTest {
|
|
|
569
627
|
}
|
|
570
628
|
}
|
|
571
629
|
|
|
630
|
+
/**
|
|
631
|
+
* @returns {void}
|
|
632
|
+
*/
|
|
572
633
|
onWebSocketClose = () => {
|
|
573
634
|
this.ws = null
|
|
574
635
|
this.communicator.ws = null
|
|
@@ -576,8 +637,8 @@ export default class SystemTest {
|
|
|
576
637
|
|
|
577
638
|
/**
|
|
578
639
|
* Handles an error reported from the browser
|
|
579
|
-
*
|
|
580
|
-
* @
|
|
640
|
+
* @param {object} data
|
|
641
|
+
* @returns {void}
|
|
581
642
|
*/
|
|
582
643
|
handleError(data) {
|
|
583
644
|
if (data.message.includes("Minified React error #419")) {
|
|
@@ -596,6 +657,7 @@ export default class SystemTest {
|
|
|
596
657
|
|
|
597
658
|
/**
|
|
598
659
|
* Stops the system test
|
|
660
|
+
* @returns {Promise<void>}
|
|
599
661
|
*/
|
|
600
662
|
async stop() {
|
|
601
663
|
this.stopScoundrel()
|
|
@@ -606,8 +668,8 @@ export default class SystemTest {
|
|
|
606
668
|
|
|
607
669
|
/**
|
|
608
670
|
* Visits a path in the browser
|
|
609
|
-
*
|
|
610
671
|
* @param {string} path
|
|
672
|
+
* @returns {Promise<void>}
|
|
611
673
|
*/
|
|
612
674
|
async driverVisit(path) {
|
|
613
675
|
const url = `${this.currentUrl}${path}`
|
|
@@ -617,6 +679,7 @@ export default class SystemTest {
|
|
|
617
679
|
|
|
618
680
|
/**
|
|
619
681
|
* Takes a screenshot, saves HTML and browser logs
|
|
682
|
+
* @returns {Promise<void>}
|
|
620
683
|
*/
|
|
621
684
|
async takeScreenshot() {
|
|
622
685
|
const path = `${process.cwd()}/tmp/screenshots`
|
|
@@ -644,8 +707,8 @@ export default class SystemTest {
|
|
|
644
707
|
|
|
645
708
|
/**
|
|
646
709
|
* Visits a path in the browser
|
|
647
|
-
*
|
|
648
710
|
* @param {string} path
|
|
711
|
+
* @returns {Promise<void>}
|
|
649
712
|
*/
|
|
650
713
|
async visit(path) {
|
|
651
714
|
await this.communicator.sendCommand({type: "visit", path})
|
|
@@ -653,8 +716,8 @@ export default class SystemTest {
|
|
|
653
716
|
|
|
654
717
|
/**
|
|
655
718
|
* Dismisses to a path in the browser
|
|
656
|
-
*
|
|
657
719
|
* @param {string} path
|
|
720
|
+
* @returns {Promise<void>}
|
|
658
721
|
*/
|
|
659
722
|
async dismissTo(path) {
|
|
660
723
|
await this.communicator.sendCommand({type: "dismissTo", path})
|
package/src/use-system-test.js
CHANGED
|
@@ -9,7 +9,10 @@ const shared = {
|
|
|
9
9
|
systemTestBrowserHelper: null
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @returns {boolean}
|
|
14
|
+
*/
|
|
15
|
+
function isSystemTestEnabled() {
|
|
13
16
|
let enabled = false
|
|
14
17
|
const initialUrl = globalThis.location?.href
|
|
15
18
|
|
|
@@ -25,7 +28,10 @@ const isSystemTestEnabled = () => {
|
|
|
25
28
|
return enabled
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
/**
|
|
32
|
+
* @returns {SystemTestBrowserHelper}
|
|
33
|
+
*/
|
|
34
|
+
function getSystemTestBrowserHelper() {
|
|
29
35
|
if (!shared.systemTestBrowserHelper) {
|
|
30
36
|
shared.systemTestBrowserHelper = new SystemTestBrowserHelper()
|
|
31
37
|
shared.systemTestBrowserHelper.enableOnBrowser()
|
|
@@ -36,13 +42,9 @@ const getSystemTestBrowserHelper = () => {
|
|
|
36
42
|
|
|
37
43
|
/**
|
|
38
44
|
* A hook that provides system test capabilities.
|
|
39
|
-
*
|
|
40
|
-
* @param {
|
|
41
|
-
* @
|
|
42
|
-
*
|
|
43
|
-
* @returns {Object} An object containing:
|
|
44
|
-
* - enabled: A boolean indicating if system test mode is enabled.
|
|
45
|
-
* - systemTestBrowserHelper: An instance of SystemTestBrowserHelper if enabled, otherwise null.
|
|
45
|
+
* @param {object} options - Options for the hook.
|
|
46
|
+
* @param {function() : void} options.onInitialize - A callback function that is called when the system test browser helper is initialized.
|
|
47
|
+
* @returns {{enabled: boolean, systemTestBrowserHelper: SystemTestBrowserHelper}}
|
|
46
48
|
*/
|
|
47
49
|
export default function useSystemTest({onInitialize, ...restArgs} = {}) {
|
|
48
50
|
const router = useRouter()
|