system-testing 1.0.27 → 1.0.30

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.27",
3
+ "version": "1.0.30",
4
4
  "description": "System testing with Selenium and browsers.",
5
5
  "keywords": [
6
6
  "system",
@@ -22,10 +22,11 @@
22
22
  "main": "src/index.js",
23
23
  "scripts": {
24
24
  "lint": "eslint",
25
- "test": "echo \"Error: no test specified\" && exit 1"
25
+ "test": "echo \"Error: no test specified\" && exit 1",
26
+ "typecheck": "tsc --noEmit"
26
27
  },
27
28
  "dependencies": {
28
- "awaitery": "^1.0.1",
29
+ "awaitery": "^1.0.2",
29
30
  "diggerize": "^1.0.9",
30
31
  "htmlfy": "^1.0.0",
31
32
  "mime": "^4.0.7",
@@ -34,12 +35,16 @@
34
35
  "ws": "^8.18.3"
35
36
  },
36
37
  "peerDependencies": {
38
+ "@kaspernj/api-maker": "^1.0.2053",
39
+ "expo-router": "^6.0.19",
37
40
  "selenium-webdriver": "^4.34.0"
38
41
  },
39
42
  "devDependencies": {
40
43
  "@eslint/js": "^9.39.1",
44
+ "@types/node": "^25.0.2",
41
45
  "eslint": "^9.39.1",
42
46
  "eslint-plugin-jsdoc": "^61.4.1",
43
- "globals": "^16.5.0"
47
+ "globals": "^16.5.0",
48
+ "typescript": "^5.9.3"
44
49
  }
45
50
  }
package/peak_flow.yml CHANGED
@@ -2,3 +2,4 @@ before_script:
2
2
  - npm install
3
3
  script:
4
4
  - npm run lint
5
+ - npm run typecheck
@@ -1,3 +1,5 @@
1
+ // @ts-check
2
+
1
3
  import Client from "scoundrel-remote-eval/src/client/index.js"
2
4
  import ClientWebSocket from "scoundrel-remote-eval/src/client/connections/web-socket/index.js"
3
5
  import {digg} from "diggerize"
@@ -5,7 +7,10 @@ import EventEmitter from "events"
5
7
 
6
8
  import SystemTestCommunicator from "./system-test-communicator.js"
7
9
 
8
- const shared = {}
10
+ /** @type {{systemTestBrowserHelper: SystemTestBrowserHelper | null}} */
11
+ const shared = {
12
+ systemTestBrowserHelper: null
13
+ }
9
14
 
10
15
  export default class SystemTestBrowserHelper {
11
16
  static current() {
@@ -39,10 +44,10 @@ export default class SystemTestBrowserHelper {
39
44
  waitForScoundrelStarted() {
40
45
  return new Promise((resolve) => {
41
46
  if (this.scoundrelClient) {
42
- resolve()
47
+ resolve(undefined)
43
48
  } else {
44
49
  this.events.once("scoundrelStarted", () => {
45
- resolve()
50
+ resolve(undefined)
46
51
  })
47
52
  }
48
53
  })
@@ -76,14 +81,24 @@ export default class SystemTestBrowserHelper {
76
81
  type: "unhandledrejection",
77
82
  error: event.reason,
78
83
  errorClass: "UnhandledRejection",
79
- file: null,
80
- line: null,
81
84
  message: event.reason.message || event.reason || "Unhandled promise rejection without a message",
82
85
  url: window.location.href
83
86
  })
84
87
  })
85
88
  }
86
89
 
90
+ /**
91
+ * @param {object} data
92
+ * @param {string} [data.backtrace]
93
+ * @param {Error} [data.error]
94
+ * @param {string} [data.errorClass]
95
+ * @param {string} [data.file]
96
+ * @param {number} [data.line]
97
+ * @param {string} [data.message]
98
+ * @param {string} [data.type]
99
+ * @param {string} [data.url]
100
+ * @returns {void}
101
+ */
87
102
  handleError(data) {
88
103
  let backtrace
89
104
 
@@ -92,7 +107,7 @@ export default class SystemTestBrowserHelper {
92
107
  backtrace.shift()
93
108
  backtrace = backtrace.join("\n")
94
109
  } else if (data.file) {
95
- backtrace = [`${data.file}:${data.line}`]
110
+ backtrace = `${data.file}:${data.line}`
96
111
  }
97
112
 
98
113
  data.backtrace = backtrace
@@ -132,18 +147,35 @@ export default class SystemTestBrowserHelper {
132
147
  */
133
148
  getEvents() { return this.events }
134
149
 
150
+ /**
151
+ * @param {any[]} args
152
+ * @returns {void}
153
+ */
135
154
  fakeConsoleError = (...args) => {
136
155
  this.communicator.sendCommand({type: "console.error", value: this.consoleLogMessage(args)})
137
156
 
138
- return this.originalConsoleError(...args)
157
+ if (this.originalConsoleError) {
158
+ return this.originalConsoleError(...args)
159
+ }
139
160
  }
140
161
 
162
+ /**
163
+ * @param {any[]} args
164
+ * @returns {void}
165
+ */
141
166
  fakeConsoleLog = (...args) => {
142
167
  this.communicator.sendCommand({type: "console.log", value: this.consoleLogMessage(args)})
143
168
 
144
- return this.originalConsoleLog(...args)
169
+ if (this.originalConsoleLog) {
170
+ return this.originalConsoleLog(...args)
171
+ }
145
172
  }
146
173
 
174
+ /**
175
+ * @param {any} arg
176
+ * @param {any[]} [scannedObjects]
177
+ * @returns {any}
178
+ */
147
179
  consoleLogMessage(arg, scannedObjects = []) {
148
180
  if (Array.isArray(arg)) {
149
181
  if (scannedObjects.includes(arg)) {
@@ -166,6 +198,7 @@ export default class SystemTestBrowserHelper {
166
198
  scannedObjects.push(arg)
167
199
  }
168
200
 
201
+ /** @type {Record<string, any>} */
169
202
  const result = {}
170
203
 
171
204
  for (const key in arg) {
@@ -180,6 +213,10 @@ export default class SystemTestBrowserHelper {
180
213
  }
181
214
  }
182
215
 
216
+ /**
217
+ * @param {{data: {path: string, type: string}}} args
218
+ * @returns {Promise<{result: string} | void>}
219
+ */
183
220
  onCommand = async ({data}) => {
184
221
  if (data.type == "initialize") {
185
222
  this.events.emit("initialize")
@@ -226,6 +263,7 @@ export default class SystemTestBrowserHelper {
226
263
  * @returns {Promise<Array<Record<string, any>>>}
227
264
  */
228
265
  async sendQuery(sql) {
266
+ // @ts-expect-error
229
267
  return await this.communicator.sendCommand({type: "query", sql})
230
268
  }
231
269
  }
@@ -1,9 +1,23 @@
1
+ // @ts-check
2
+
1
3
  export default class SystemTestCommunicator {
4
+ /** @type {WebSocket | null} */
5
+ ws = null
6
+
7
+ /**
8
+ * @param {object} args
9
+ * @param {(args: Record<string, any>) => Promise<{result: string} | void>} args.onCommand
10
+ * @param {object} [args.parent]
11
+ */
2
12
  constructor({onCommand, parent}) {
3
13
  this.onCommand = onCommand
4
14
  this.parent = parent
5
15
  this._sendQueueCount = 0
16
+
17
+ /** @type {Record<string, any>} */
6
18
  this._sendQueue = []
19
+
20
+ /** @type {Record<string, {resolve: (data: any) => void, reject: (data: any) => void}>} */
7
21
  this._responses = {}
8
22
  }
9
23
 
@@ -11,15 +25,22 @@ export default class SystemTestCommunicator {
11
25
  while (this._sendQueue.length !== 0) {
12
26
  const data = this._sendQueue.shift()
13
27
 
28
+ if (!this.ws || this.ws.readyState !== 1) {
29
+ throw new Error("WebSocket is not open")
30
+ }
31
+
14
32
  this.ws.send(JSON.stringify(data))
15
33
  }
16
34
  }
17
35
 
36
+ /** @param {Error} error */
18
37
  onError = (error) => {
19
38
  console.error("onWebSocketClientError", error)
20
39
  }
21
40
 
41
+ /** @param {string} rawData */
22
42
  onMessage = async (rawData) => {
43
+ /** @type {{data: any, id: number, type: string, isTrusted?: boolean}} */
23
44
  const data = JSON.parse(rawData)
24
45
 
25
46
  if (data.isTrusted) {
@@ -30,7 +51,11 @@ export default class SystemTestCommunicator {
30
51
 
31
52
  this.respond(data.id, {result})
32
53
  } catch (error) {
33
- this.respond(data.id, {error: error.message})
54
+ if (error instanceof Error) {
55
+ this.respond(data.id, {error: error.message})
56
+ } else {
57
+ this.respond(data.id, {error: error})
58
+ }
34
59
  }
35
60
  } else if (data.type == "response") {
36
61
  const response = this._responses[data.id]
@@ -42,7 +67,7 @@ export default class SystemTestCommunicator {
42
67
  delete this._responses[data.id]
43
68
 
44
69
  if (data.data.error) {
45
- response.error(data.data.error)
70
+ response.reject(data.data.error)
46
71
  } else {
47
72
  response.resolve(data.data.result)
48
73
  }
@@ -55,6 +80,10 @@ export default class SystemTestCommunicator {
55
80
  this.flushSendQueue()
56
81
  }
57
82
 
83
+ /**
84
+ * @param {object} data
85
+ * @returns {void}
86
+ */
58
87
  send(data) {
59
88
  this._sendQueue.push(data)
60
89
 
@@ -69,15 +98,20 @@ export default class SystemTestCommunicator {
69
98
  * @returns {Promise<void>} A promise that resolves with the response data.
70
99
  */
71
100
  sendCommand(data) {
72
- return new Promise((resolve, error) => {
101
+ return new Promise((resolve, reject) => {
73
102
  const id = this._sendQueueCount
74
103
 
75
104
  this._sendQueueCount += 1
76
- this._responses[id] = {resolve, error}
105
+ this._responses[id] = {resolve, reject}
77
106
  this.send({type: "command", id, data})
78
107
  })
79
108
  }
80
109
 
110
+ /**
111
+ * @param {number} id
112
+ * @param {object} data
113
+ * @returns {void}
114
+ */
81
115
  respond(id, data) {
82
116
  this.send({type: "response", id, data})
83
117
  }
@@ -1,3 +1,5 @@
1
+ // @ts-check
2
+
1
3
  import fs from "node:fs/promises"
2
4
  import http from "node:http"
3
5
  import mime from "mime"
@@ -6,10 +8,21 @@ import url from "url"
6
8
  export default class SystemTestHttpServer {
7
9
  /** @returns {void} */
8
10
  close() {
9
- this.httpServer.close()
11
+ this.httpServer?.close()
10
12
  }
11
13
 
14
+ /**
15
+ * @param {http.IncomingMessage} request
16
+ * @param {http.ServerResponse} response
17
+ * @returns {Promise<void>}
18
+ */
12
19
  onHttpServerRequest = async (request, response) => {
20
+ if (!request.url) {
21
+ response.statusCode = 400
22
+ response.end("Bad Request")
23
+ return
24
+ }
25
+
13
26
  const parsedUrl = url.parse(request.url)
14
27
  let filePath = `${process.cwd()}/dist${parsedUrl.pathname}`
15
28
 
@@ -34,7 +47,11 @@ export default class SystemTestHttpServer {
34
47
  const mimeType = mime.getType(filePath)
35
48
 
36
49
  response.statusCode = 200
37
- response.setHeader("Content-Type", mimeType)
50
+
51
+ if (mimeType) {
52
+ response.setHeader("Content-Type", mimeType)
53
+ }
54
+
38
55
  response.end(fileContent)
39
56
  }
40
57
 
@@ -1,6 +1,7 @@
1
+ // @ts-check
2
+
1
3
  import {Builder, By, until} from "selenium-webdriver"
2
4
  import chrome from "selenium-webdriver/chrome.js"
3
- import {digg} from "diggerize"
4
5
  import fs from "node:fs/promises"
5
6
  import logging from "selenium-webdriver/lib/logging.js"
6
7
  import moment from "moment"
@@ -14,20 +15,40 @@ import {WebSocketServer} from "ws"
14
15
 
15
16
  class ElementNotFoundError extends Error { }
16
17
 
18
+ /** @type {{systemTest: SystemTest | null}} */
19
+ const shared = {
20
+ systemTest: null
21
+ }
22
+
17
23
  export default class SystemTest {
18
24
  static rootPath = "/blank?systemTest=true"
19
25
 
26
+ /** @type {SystemTestCommunicator | undefined} */
27
+ communicator = undefined
28
+ _started = false
29
+ _timeouts = 5000
30
+
20
31
  /**
21
32
  * Gets the current system test instance
22
- * @param {object} args
33
+ * @param {object} [args]
34
+ * @param {string} [args.host]
35
+ * @param {number} [args.port]
23
36
  * @returns {SystemTest}
24
37
  */
25
38
  static current(args) {
26
- if (!globalThis.systemTest) {
27
- globalThis.systemTest = new SystemTest(args)
39
+ if (!shared.systemTest) {
40
+ shared.systemTest = new SystemTest(args)
41
+ }
42
+
43
+ return shared.systemTest
44
+ }
45
+
46
+ getCommunicator() {
47
+ if (!this.communicator) {
48
+ throw new Error("Communicator hasn't been initialized yet")
28
49
  }
29
50
 
30
- return globalThis.systemTest
51
+ return this.communicator
31
52
  }
32
53
 
33
54
  /**
@@ -38,7 +59,7 @@ export default class SystemTest {
38
59
  static async run(callback) {
39
60
  const systemTest = this.current()
40
61
 
41
- await systemTest.communicator.sendCommand({type: "initialize"})
62
+ await systemTest.getCommunicator().sendCommand({type: "initialize"})
42
63
  await systemTest.dismissTo(SystemTest.rootPath)
43
64
 
44
65
  try {
@@ -53,11 +74,11 @@ export default class SystemTest {
53
74
 
54
75
  /**
55
76
  * Creates a new SystemTest instance
56
- * @param {object} args
57
- * @param {string} args.host
58
- * @param {number} args.port
77
+ * @param {object} [args]
78
+ * @param {string} [args.host]
79
+ * @param {number} [args.port]
59
80
  */
60
- constructor({host = "localhost", port = 8081, ...restArgs} = {}) {
81
+ constructor({host = "localhost", port = 8081, ...restArgs} = {host: "localhost", port: 8081}) {
61
82
  const restArgsKeys = Object.keys(restArgs)
62
83
 
63
84
  if (restArgsKeys.length > 0) {
@@ -74,10 +95,16 @@ export default class SystemTest {
74
95
 
75
96
  /**
76
97
  * Gets the base selector for scoping element searches
77
- * @returns {string}
98
+ * @returns {string | undefined}
78
99
  */
79
100
  getBaseSelector() { return this._baseSelector }
80
101
 
102
+ getDriver() {
103
+ if (!this.driver) throw new Error("Driver hasn't been initialized yet")
104
+
105
+ return this.driver
106
+ }
107
+
81
108
  /**
82
109
  * Sets the base selector for scoping element searches
83
110
  * @param {string} baseSelector
@@ -115,18 +142,18 @@ export default class SystemTest {
115
142
  * Finds all elements by CSS selector
116
143
  * @param {string} selector
117
144
  * @param {object} args
118
- * @returns {import("selenium-webdriver").WebElement[]}
145
+ * @param {boolean} [args.visible]
146
+ * @param {boolean} [args.useBaseSelector]
147
+ * @returns {Promise<import("selenium-webdriver").WebElement[]>}
119
148
  */
120
149
  async all(selector, args = {}) {
121
150
  const {visible = true, useBaseSelector = true, ...restArgs} = args
122
151
  const restArgsKeys = Object.keys(restArgs)
123
152
 
124
- if (restArgsKeys.length > 0) {
125
- throw new Error(`Unknown arguments: ${restArgsKeys.join(", ")}`)
126
- }
153
+ if (restArgsKeys.length > 0) throw new Error(`Unknown arguments: ${restArgsKeys.join(", ")}`)
127
154
 
128
155
  const actualSelector = useBaseSelector ? this.getSelector(selector) : selector
129
- const elements = await this.driver.findElements(By.css(actualSelector))
156
+ const elements = await this.getDriver().findElements(By.css(actualSelector))
130
157
  const activeElements = []
131
158
 
132
159
  for (const element of elements) {
@@ -158,20 +185,24 @@ export default class SystemTest {
158
185
 
159
186
  try {
160
187
  const element = await this._findElement(elementOrIdentifier)
161
- const actions = this.driver.actions({async: true})
188
+ const actions = this.getDriver().actions({async: true})
162
189
 
163
190
  await actions.move({origin: element}).click().perform()
164
191
  break
165
192
  } catch (error) {
166
- if (error.constructor.name === "ElementNotInteractableError") {
167
- if (tries >= 3) {
168
- throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed after ${tries} tries - ${error.constructor.name}: ${error.message}`)
193
+ if (error instanceof Error) {
194
+ if (error.constructor.name === "ElementNotInteractableError") {
195
+ if (tries >= 3) {
196
+ throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed after ${tries} tries - ${error.constructor.name}: ${error.message}`)
197
+ } else {
198
+ await wait(50)
199
+ }
169
200
  } else {
170
- await wait(50)
201
+ // Re-throw with un-corrupted stack trace
202
+ throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed - ${error.constructor.name}: ${error.message}`)
171
203
  }
172
204
  } else {
173
- // Re-throw with un-corrupted stack trace
174
- throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed - ${error.constructor.name}: ${error.message}`)
205
+ throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed - ${typeof error}: ${error}`)
175
206
  }
176
207
  }
177
208
  }
@@ -181,7 +212,7 @@ export default class SystemTest {
181
212
  * Finds a single element by CSS selector
182
213
  * @param {string} selector
183
214
  * @param {object} args
184
- * @returns {import("selenium-webdriver").WebElement}
215
+ * @returns {Promise<import("selenium-webdriver").WebElement>}
185
216
  */
186
217
  async find(selector, args = {}) {
187
218
  let elements
@@ -190,7 +221,11 @@ export default class SystemTest {
190
221
  elements = await this.all(selector, args)
191
222
  } catch (error) {
192
223
  // Re-throw to recover stack trace
193
- throw new Error(`${error.message} (selector: ${this.getSelector(selector)})`)
224
+ if (error instanceof Error) {
225
+ throw new Error(`${error.message} (selector: ${this.getSelector(selector)})`)
226
+ } else {
227
+ throw new Error(`${error} (selector: ${this.getSelector(selector)})`)
228
+ }
194
229
  }
195
230
 
196
231
  if (elements.length > 1) {
@@ -231,7 +266,7 @@ export default class SystemTest {
231
266
  /**
232
267
  * Finds a single element by CSS selector without waiting
233
268
  * @param {string} selector
234
- * @param {object} args
269
+ * @param {object} [args]
235
270
  * @returns {Promise<import("selenium-webdriver").WebElement>}
236
271
  */
237
272
  async findNoWait(selector, args) {
@@ -249,7 +284,7 @@ export default class SystemTest {
249
284
  * @returns {Promise<string[]>}
250
285
  */
251
286
  async getBrowserLogs() {
252
- const entries = await this.driver.manage().logs().get(logging.Type.BROWSER)
287
+ const entries = await this.getDriver().manage().logs().get(logging.Type.BROWSER)
253
288
  const browserLogs = []
254
289
 
255
290
  for (const entry of entries) {
@@ -272,7 +307,7 @@ export default class SystemTest {
272
307
  * @returns {Promise<string>}
273
308
  */
274
309
  async getCurrentUrl() {
275
- return await this.driver.getCurrentUrl()
310
+ return await this.getDriver().getCurrentUrl()
276
311
  }
277
312
 
278
313
  /**
@@ -295,34 +330,39 @@ export default class SystemTest {
295
330
  tries++
296
331
 
297
332
  const element = await this._findElement(elementOrIdentifier)
333
+ const candidate = element[methodName]
298
334
 
299
- if (!element[methodName]) {
335
+ if (!candidate) {
300
336
  throw new Error(`${element.constructor.name} hasn't an attribute named: ${methodName}`)
301
- } else if (typeof element[methodName] != "function") {
337
+ } else if (typeof candidate != "function") {
302
338
  throw new Error(`${element.constructor.name}#${methodName} is not a function`)
303
339
  }
304
340
 
305
341
  try {
306
- return await element[methodName](...args)
342
+ return await candidate(...args)
307
343
  } catch (error) {
308
- if (error.constructor.name === "ElementNotInteractableError") {
309
- // Retry finding the element and interacting with it
310
- if (tries >= 3) {
311
- let elementDescription
312
-
313
- if (typeof elementOrIdentifier == "string") {
314
- elementDescription = `CSS selector ${elementOrIdentifier}`
344
+ if (error instanceof Error) {
345
+ if (error.constructor.name === "ElementNotInteractableError") {
346
+ // Retry finding the element and interacting with it
347
+ if (tries >= 3) {
348
+ let elementDescription
349
+
350
+ if (typeof elementOrIdentifier == "string") {
351
+ elementDescription = `CSS selector ${elementOrIdentifier}`
352
+ } else {
353
+ elementDescription = `${element.constructor.name}`
354
+ }
355
+
356
+ throw new Error(`${elementDescription} ${methodName} failed after ${tries} tries - ${error.constructor.name}: ${error.message}`)
315
357
  } else {
316
- elementDescription = `${element.constructor.name}`
358
+ await wait(50)
317
359
  }
318
-
319
- throw new Error(`${elementDescription} ${methodName} failed after ${tries} tries - ${error.constructor.name}: ${error.message}`)
320
360
  } else {
321
- await wait(50)
361
+ // Re-throw with un-corrupted stack trace
362
+ throw new Error(`${element.constructor.name} ${methodName} failed - ${error.constructor.name}: ${error.message}`)
322
363
  }
323
364
  } else {
324
- // Re-throw with un-corrupted stack trace
325
- throw new Error(`${element.constructor.name} ${methodName} failed - ${error.constructor.name}: ${error.message}`)
365
+ throw new Error(`${element.constructor.name} ${methodName} failed - ${typeof error}: ${error}`)
326
366
  }
327
367
  }
328
368
  }
@@ -340,7 +380,9 @@ export default class SystemTest {
340
380
  await this.findNoWait(selector)
341
381
  found = true
342
382
  } catch (error) {
343
- if (!error.message.startsWith("Element couldn't be found after ")) {
383
+ if (error instanceof Error && error.message.startsWith("Element couldn't be found after ")) {
384
+ // Ignore
385
+ } else {
344
386
  throw error
345
387
  }
346
388
  }
@@ -353,6 +395,7 @@ export default class SystemTest {
353
395
  /**
354
396
  * @param {string} selector
355
397
  * @param {object} args
398
+ * @param {boolean} [args.useBaseSelector]
356
399
  * @returns {Promise<void>}
357
400
  */
358
401
  async waitForNoSelector(selector, args) {
@@ -376,7 +419,7 @@ export default class SystemTest {
376
419
  throw new Error(`Element still found after ${timeout}ms: ${selector}`)
377
420
  }
378
421
  } catch (error) {
379
- if (error.message.startsWith("Element couldn't be found after ")) {
422
+ if (error instanceof Error && error.message.startsWith("Element couldn't be found after ")) {
380
423
  break
381
424
  }
382
425
  }
@@ -406,6 +449,7 @@ export default class SystemTest {
406
449
  * @returns {Promise<void>}
407
450
  */
408
451
  async expectNotificationMessage(expectedNotificationMessage) {
452
+ /** @type {string[]} */
409
453
  const allDetectedNotificationMessages = []
410
454
  let foundNotificationMessageElement
411
455
 
@@ -456,7 +500,7 @@ export default class SystemTest {
456
500
  * Gets the HTML of the current page
457
501
  * @returns {Promise<string>}
458
502
  */
459
- async getHTML() { return await this.driver.getPageSource() }
503
+ async getHTML() { return await this.getDriver().getPageSource() }
460
504
 
461
505
  /**
462
506
  * Starts the system test
@@ -482,6 +526,7 @@ export default class SystemTest {
482
526
  options.addArguments("--no-sandbox")
483
527
  options.addArguments("--window-size=1920,1080")
484
528
 
529
+ /** @type {import("selenium-webdriver").WebDriver} */
485
530
  this.driver = new Builder()
486
531
  .forBrowser("chrome")
487
532
  .setChromeOptions(options)
@@ -529,7 +574,7 @@ export default class SystemTest {
529
574
  * @returns {Promise<void>}
530
575
  */
531
576
  async driverSetTimeouts(newTimeout) {
532
- await this.driver.manage().setTimeouts({implicit: newTimeout})
577
+ await this.getDriver().manage().setTimeouts({implicit: newTimeout})
533
578
  }
534
579
 
535
580
  /**
@@ -568,7 +613,7 @@ export default class SystemTest {
568
613
 
569
614
  /**
570
615
  * Sets the on command callback
571
- * @param {function(object) : void} callback
616
+ * @param {function({type: string, data: Record<string, any>}): Promise<void>} callback
572
617
  * @returns {void}
573
618
  */
574
619
  onCommand(callback) {
@@ -577,8 +622,7 @@ export default class SystemTest {
577
622
 
578
623
  /**
579
624
  * Handles a command received from the browser
580
- * @param {object} data
581
- * @param {object} data.data
625
+ * @param {{data: {message: string, backtrace: string, type: string, value: any[]}}} args
582
626
  * @returns {Promise<any>}
583
627
  */
584
628
  onCommandReceived = async ({data}) => {
@@ -612,14 +656,18 @@ export default class SystemTest {
612
656
  /**
613
657
  * Handles a new web socket connection
614
658
  * @param {WebSocket} ws
615
- * @returns {void}
659
+ * @returns {Promise<void>}
616
660
  */
617
661
  onWebSocketConnection = async (ws) => {
618
662
  this.ws = ws
619
- this.communicator.ws = ws
620
- this.communicator.onOpen()
621
- this.ws.on("error", digg(this, "communicator", "onError"))
622
- this.ws.on("message", digg(this, "communicator", "onMessage"))
663
+ this.getCommunicator().ws = ws
664
+ this.getCommunicator().onOpen()
665
+
666
+ // @ts-expect-error
667
+ this.ws.on("error", this.communicator.onError)
668
+
669
+ // @ts-expect-error
670
+ this.ws.on("message", this.communicator.onMessage)
623
671
 
624
672
  if (this.waitForClientWebSocketPromiseResolve) {
625
673
  this.waitForClientWebSocketPromiseResolve()
@@ -632,12 +680,14 @@ export default class SystemTest {
632
680
  */
633
681
  onWebSocketClose = () => {
634
682
  this.ws = null
635
- this.communicator.ws = null
683
+ this.getCommunicator().ws = null
636
684
  }
637
685
 
638
686
  /**
639
687
  * Handles an error reported from the browser
640
688
  * @param {object} data
689
+ * @param {string} data.message
690
+ * @param {string} [data.backtrace]
641
691
  * @returns {void}
642
692
  */
643
693
  handleError(data) {
@@ -663,7 +713,7 @@ export default class SystemTest {
663
713
  this.stopScoundrel()
664
714
  this.systemTestHttpServer?.close()
665
715
  this.wss?.close()
666
- await this.driver.quit()
716
+ await this.driver?.quit()
667
717
  }
668
718
 
669
719
  /**
@@ -674,7 +724,7 @@ export default class SystemTest {
674
724
  async driverVisit(path) {
675
725
  const url = `${this.currentUrl}${path}`
676
726
 
677
- await this.driver.get(url)
727
+ await this.getDriver().get(url)
678
728
  }
679
729
 
680
730
  /**
@@ -686,7 +736,7 @@ export default class SystemTest {
686
736
 
687
737
  await fs.mkdir(path, {recursive: true})
688
738
 
689
- const imageContent = await this.driver.takeScreenshot()
739
+ const imageContent = await this.getDriver().takeScreenshot()
690
740
  const now = new Date()
691
741
  const screenshotPath = `${path}/${moment(now).format("YYYY-MM-DD-HH-MM-SS")}.png`
692
742
  const htmlPath = `${path}/${moment(now).format("YYYY-MM-DD-HH-MM-SS")}.html`
@@ -711,7 +761,7 @@ export default class SystemTest {
711
761
  * @returns {Promise<void>}
712
762
  */
713
763
  async visit(path) {
714
- await this.communicator.sendCommand({type: "visit", path})
764
+ await this.getCommunicator().sendCommand({type: "visit", path})
715
765
  }
716
766
 
717
767
  /**
@@ -720,6 +770,6 @@ export default class SystemTest {
720
770
  * @returns {Promise<void>}
721
771
  */
722
772
  async dismissTo(path) {
723
- await this.communicator.sendCommand({type: "dismissTo", path})
773
+ await this.getCommunicator().sendCommand({type: "dismissTo", path})
724
774
  }
725
775
  }
@@ -1,5 +1,5 @@
1
1
  import qs from "qs"
2
- import SystemTestBrowserHelper from "./system-test-browser-helper"
2
+ import SystemTestBrowserHelper from "./system-test-browser-helper.js"
3
3
  import {useCallback, useMemo} from "react"
4
4
  import useEventEmitter from "@kaspernj/api-maker/build/use-event-emitter.js"
5
5
  import {useRouter} from "expo-router"
@@ -46,7 +46,7 @@ function getSystemTestBrowserHelper() {
46
46
  * @param {function() : void} options.onInitialize - A callback function that is called when the system test browser helper is initialized.
47
47
  * @returns {{enabled: boolean, systemTestBrowserHelper: SystemTestBrowserHelper}}
48
48
  */
49
- export default function useSystemTest({onInitialize, ...restArgs} = {}) {
49
+ export default function useSystemTest({onInitialize, ...restArgs} = {onInitialize: undefined}) {
50
50
  const router = useRouter()
51
51
  const enabled = useMemo(() => isSystemTestEnabled(), [])
52
52
  const systemTestBrowserHelper = enabled ? getSystemTestBrowserHelper() : null
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "checkJs": true,
4
+ "noEmit": true,
5
+ "target": "ES2020",
6
+
7
+ "moduleResolution": "nodenext",
8
+ "module": "nodenext",
9
+
10
+ "lib": ["dom", "ES2020"],
11
+ "types": ["node"],
12
+ "skipLibCheck": true
13
+ }
14
+ }