system-testing 1.0.16 → 1.0.17

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.16",
3
+ "version": "1.0.17",
4
4
  "description": "System testing with Selenium and browsers.",
5
5
  "keywords": [
6
6
  "system",
@@ -7,11 +7,18 @@ import moment from "moment"
7
7
  import {prettify} from "htmlfy"
8
8
  import SystemTestCommunicator from "./system-test-communicator.js"
9
9
  import SystemTestHttpServer from "./system-test-http-server.js"
10
+ import {waitFor} from "awaitery"
10
11
  import {WebSocketServer} from "ws"
11
12
 
12
13
  class ElementNotFoundError extends Error { }
13
14
 
14
15
  export default class SystemTest {
16
+ /**
17
+ * Gets the current system test instance
18
+ *
19
+ * @param {object} args
20
+ * @returns {SystemTest}
21
+ */
15
22
  static current(args) {
16
23
  if (!globalThis.systemTest) {
17
24
  globalThis.systemTest = new SystemTest(args)
@@ -20,6 +27,11 @@ export default class SystemTest {
20
27
  return globalThis.systemTest
21
28
  }
22
29
 
30
+ /**
31
+ * Runs a system test
32
+ *
33
+ * @param {function(SystemTest): Promise<void>} callback
34
+ */
23
35
  static async run(callback) {
24
36
  const systemTest = this.current()
25
37
 
@@ -36,6 +48,13 @@ export default class SystemTest {
36
48
  }
37
49
  }
38
50
 
51
+ /**
52
+ * Creates a new SystemTest instance
53
+ *
54
+ * @param {object} args
55
+ * @param {string} args.host
56
+ * @param {number} args.port
57
+ */
39
58
  constructor({host = "localhost", port = 8081, ...restArgs} = {}) {
40
59
  const restArgsKeys = Object.keys(restArgs)
41
60
 
@@ -50,6 +69,14 @@ export default class SystemTest {
50
69
  this._sendCount = 0
51
70
  }
52
71
 
72
+ /**
73
+ * Finds all elements by CSS selector
74
+ *
75
+ * @param {string} selector
76
+ * @param {object} args
77
+ *
78
+ * @returns {import("selenium-webdriver").WebElement[]}
79
+ */
53
80
  async all(selector, args = {}) {
54
81
  const {visible = true} = args
55
82
  const elements = await this.driver.findElements(By.css(selector))
@@ -71,13 +98,24 @@ export default class SystemTest {
71
98
  return activeElements
72
99
  }
73
100
 
74
- // Clicks an element that has children which fills out the element and would otherwise have caused a ElementClickInterceptedError
101
+ /**
102
+ * Clicks an element that has children which fills out the element and would otherwise have caused a ElementClickInterceptedError
103
+ *
104
+ * @param {import("selenium-webdriver").WebElement} element
105
+ **/
75
106
  async click(element) {
76
107
  const actions = this.driver.actions({async: true})
77
108
 
78
109
  await actions.move({origin: element}).click().perform()
79
110
  }
80
111
 
112
+ /**
113
+ * Finds a single element by CSS selector
114
+ *
115
+ * @param {string} selector
116
+ * @param {object} args
117
+ * @returns {import("selenium-webdriver").WebElement}
118
+ */
81
119
  async find(selector, args = {}) {
82
120
  let elements
83
121
 
@@ -99,8 +137,21 @@ export default class SystemTest {
99
137
  return elements[0]
100
138
  }
101
139
 
140
+ /**
141
+ * Finds a single element by test ID
142
+ *
143
+ * @param {string} testID
144
+ * @param {object} args
145
+ * @returns {import("selenium-webdriver").WebElement}
146
+ */
102
147
  async findByTestID(testID, args) { return await this.find(`[data-testid='${testID}']`, args) }
103
148
 
149
+ /**
150
+ * Finds a single element by CSS selector without waiting
151
+ *
152
+ * @param {string} selector
153
+ * @returns {import("selenium-webdriver").WebElement}
154
+ */
104
155
  async findNoWait(selector) {
105
156
  await this.driverSetTimeouts(0)
106
157
 
@@ -111,6 +162,11 @@ export default class SystemTest {
111
162
  }
112
163
  }
113
164
 
165
+ /**
166
+ * Gets browser logs
167
+ *
168
+ * @returns {Promise<string[]>}
169
+ */
114
170
  async getBrowserLogs() {
115
171
  const entries = await this.driver.manage().logs().get(logging.Type.BROWSER)
116
172
  const browserLogs = []
@@ -131,6 +187,11 @@ export default class SystemTest {
131
187
  return browserLogs
132
188
  }
133
189
 
190
+ /**
191
+ * Expects no element to be found by CSS selector
192
+ *
193
+ * @param {string} selector
194
+ */
134
195
  async expectNoElement(selector) {
135
196
  let found = false
136
197
 
@@ -148,6 +209,11 @@ export default class SystemTest {
148
209
  }
149
210
  }
150
211
 
212
+ /**
213
+ * Gets notification messages
214
+ *
215
+ * @returns {Promise<string[]>}
216
+ */
151
217
  async notificationMessages() {
152
218
  const notificationMessageElements = await this.all("[data-class='notification-message']")
153
219
  const notificationMessageTexts = []
@@ -161,9 +227,48 @@ export default class SystemTest {
161
227
  return notificationMessageTexts
162
228
  }
163
229
 
230
+ /**
231
+ * Expects a notification message to appear and waits for it if necessary.
232
+ *
233
+ * @param {string} expectedNotificationMessage
234
+ */
235
+ async expectNotificationMessage(expectedNotificationMessage) {
236
+ const allDetectedNotificationMessages = []
237
+
238
+ await waitFor(async () => {
239
+ const notificationMessages = await this.notificationMessages()
240
+
241
+ for (const notificationMessage of notificationMessages) {
242
+ if (!allDetectedNotificationMessages.includes(notificationMessage)) {
243
+ allDetectedNotificationMessages.push(notificationMessage)
244
+ }
245
+
246
+ if (notificationMessage == expectedNotificationMessage) {
247
+ return
248
+ }
249
+ }
250
+
251
+ throw new Error(`Notification message ${expectedNotificationMessage} wasn't included in: ${allDetectedNotificationMessages.join(", ")}`)
252
+ })
253
+ }
254
+
255
+ /**
256
+ * Indicates whether the system test has been started
257
+ *
258
+ * @returns {boolean}
259
+ */
164
260
  isStarted() { return this._started }
261
+
262
+ /**
263
+ * Gets the HTML of the current page
264
+ *
265
+ * @returns {Promise<string>}
266
+ */
165
267
  async getHTML() { return await this.driver.getPageSource() }
166
268
 
269
+ /**
270
+ * Starts the system test
271
+ */
167
272
  async start() {
168
273
  if (process.env.SYSTEM_TEST_HOST == "expo-dev-server") {
169
274
  this.currentUrl = `http://${this._host}:${this._port}`
@@ -213,6 +318,9 @@ export default class SystemTest {
213
318
  this._started = true
214
319
  }
215
320
 
321
+ /**
322
+ * Restores previously set timeouts
323
+ */
216
324
  async restoreTimeouts() {
217
325
  if (!this._timeouts) {
218
326
  throw new Error("Timeouts haven't previously been set")
@@ -221,15 +329,30 @@ export default class SystemTest {
221
329
  await this.driverSetTimeouts(this._timeouts)
222
330
  }
223
331
 
332
+ /**
333
+ * Sets driver timeouts
334
+ *
335
+ * @param {number} newTimeout
336
+ */
224
337
  async driverSetTimeouts(newTimeout) {
225
338
  await this.driver.manage().setTimeouts({implicit: newTimeout})
226
339
  }
227
340
 
341
+ /**
342
+ * Sets timeouts and stores the previous timeouts
343
+ *
344
+ * @param {number} newTimeout
345
+ */
228
346
  async setTimeouts(newTimeout) {
229
347
  this._timeouts = newTimeout
230
348
  await this.restoreTimeouts()
231
349
  }
232
350
 
351
+ /**
352
+ * Waits for the client web socket to connect
353
+ *
354
+ * @returns {Promise<void>}
355
+ */
233
356
  waitForClientWebSocket() {
234
357
  return new Promise((resolve) => {
235
358
  if (this.ws) {
@@ -240,6 +363,9 @@ export default class SystemTest {
240
363
  })
241
364
  }
242
365
 
366
+ /**
367
+ * Starts the web socket server
368
+ */
243
369
  startWebSocketServer() {
244
370
  this.wss = new WebSocketServer({port: 1985})
245
371
  this.wss.on("connection", this.onWebSocketConnection)
@@ -311,18 +437,29 @@ export default class SystemTest {
311
437
  console.error(error)
312
438
  }
313
439
 
440
+ /**
441
+ * Stops the system test
442
+ */
314
443
  async stop() {
315
444
  this.systemTestHttpServer?.close()
316
445
  this.wss?.close()
317
446
  await this.driver.quit()
318
447
  }
319
448
 
449
+ /**
450
+ * Visits a path in the browser
451
+ *
452
+ * @param {string} path
453
+ */
320
454
  async driverVisit(path) {
321
455
  const url = `${this.currentUrl}${path}`
322
456
 
323
457
  await this.driver.get(url)
324
458
  }
325
459
 
460
+ /**
461
+ * Takes a screenshot, saves HTML and browser logs
462
+ */
326
463
  async takeScreenshot() {
327
464
  const path = `${process.cwd()}/tmp/screenshots`
328
465
 
@@ -346,6 +483,11 @@ export default class SystemTest {
346
483
  console.log("HTML:", htmlPath)
347
484
  }
348
485
 
486
+ /**
487
+ * Visits a path in the browser
488
+ *
489
+ * @param {string} path
490
+ */
349
491
  async visit(path) {
350
492
  await this.communicator.sendCommand({type: "visit", path})
351
493
  }
@@ -34,6 +34,16 @@ const getSystemTestBrowserHelper = () => {
34
34
  return shared.systemTestBrowserHelper
35
35
  }
36
36
 
37
+ /**
38
+ * A hook that provides system test capabilities.
39
+ *
40
+ * @param {Object} options - Options for the hook.
41
+ * @param {Function} options.onInitialize - A callback function that is called when the system test browser helper is initialized.
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.
46
+ */
37
47
  export default function useSystemTest({onInitialize, ...restArgs} = {}) {
38
48
  const router = useRouter()
39
49
  const enabled = useMemo(() => isSystemTestEnabled(), [])