system-testing 1.0.33 → 1.0.35

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.
@@ -1,809 +0,0 @@
1
- // @ts-check
2
-
3
- import {Builder, By} from "selenium-webdriver"
4
- import chrome from "selenium-webdriver/chrome.js"
5
- import {digg} from "diggerize"
6
- import fs from "node:fs/promises"
7
- import logging from "selenium-webdriver/lib/logging.js"
8
- import moment from "moment"
9
- import {prettify} from "htmlfy"
10
- import Server from "scoundrel-remote-eval/src/server/index.js"
11
- import ServerWebSocket from "scoundrel-remote-eval/src/server/connections/web-socket/index.js"
12
- import SystemTestCommunicator from "./system-test-communicator.js"
13
- import SystemTestHttpServer from "./system-test-http-server.js"
14
- import {wait, waitFor} from "awaitery"
15
- import {WebSocketServer} from "ws"
16
-
17
- class ElementNotFoundError extends Error { }
18
-
19
- export default class SystemTest {
20
- static rootPath = "/blank?systemTest=true"
21
-
22
- /** @type {SystemTestCommunicator | undefined} */
23
- communicator = undefined
24
-
25
- /** @type {import("selenium-webdriver").WebDriver | undefined} */
26
- driver = undefined
27
-
28
- _started = false
29
- _driverTimeouts = 5000
30
- _timeouts = 5000
31
-
32
- /**
33
- * Gets the current system test instance
34
- * @param {object} [args]
35
- * @param {string} [args.host]
36
- * @param {number} [args.port]
37
- * @returns {SystemTest}
38
- */
39
- static current(args) {
40
- if (!globalThis.systemTest) {
41
- globalThis.systemTest = new SystemTest(args)
42
- }
43
-
44
- return globalThis.systemTest
45
- }
46
-
47
- getCommunicator() {
48
- if (!this.communicator) {
49
- throw new Error("Communicator hasn't been initialized yet")
50
- }
51
-
52
- return this.communicator
53
- }
54
-
55
- /**
56
- * Runs a system test
57
- * @param {function(SystemTest): Promise<void>} callback
58
- * @returns {Promise<void>}
59
- */
60
- static async run(callback) {
61
- const systemTest = this.current()
62
-
63
- await systemTest.getCommunicator().sendCommand({type: "initialize"})
64
- await systemTest.dismissTo(SystemTest.rootPath)
65
-
66
- try {
67
- await systemTest.findByTestID("blankText", {useBaseSelector: false})
68
- await callback(systemTest)
69
- } catch (error) {
70
- await systemTest.takeScreenshot()
71
-
72
- throw error
73
- }
74
- }
75
-
76
- /**
77
- * Creates a new SystemTest instance
78
- * @param {object} [args]
79
- * @param {string} [args.host]
80
- * @param {number} [args.port]
81
- */
82
- constructor({host = "localhost", port = 8081, ...restArgs} = {host: "localhost", port: 8081}) {
83
- const restArgsKeys = Object.keys(restArgs)
84
-
85
- if (restArgsKeys.length > 0) {
86
- throw new Error(`Unknown arguments: ${restArgsKeys.join(", ")}`)
87
- }
88
-
89
- this._host = host
90
- this._port = port
91
-
92
- /** @type {Record<number, object>} */
93
- this._responses = {}
94
-
95
- this._sendCount = 0
96
- this.startScoundrel()
97
- this.communicator = new SystemTestCommunicator({onCommand: this.onCommandReceived})
98
- }
99
-
100
- /**
101
- * Gets the base selector for scoping element searches
102
- * @returns {string | undefined}
103
- */
104
- getBaseSelector() { return this._baseSelector }
105
-
106
- /**
107
- * @returns {import("selenium-webdriver").WebDriver}
108
- */
109
- getDriver() {
110
- if (!this) throw new Error("No this?")
111
- if (!this.driver) throw new Error("Driver hasn't been initialized yet")
112
-
113
- return this.driver
114
- }
115
-
116
- /**
117
- * Sets the base selector for scoping element searches
118
- * @param {string} baseSelector
119
- */
120
- setBaseSelector(baseSelector) { this._baseSelector = baseSelector }
121
-
122
- /**
123
- * Gets a selector scoped to the base selector
124
- * @param {string} selector
125
- * @returns {string}
126
- */
127
- getSelector(selector) {
128
- return this.getBaseSelector() ? `${this.getBaseSelector()} ${selector}` : selector
129
- }
130
-
131
- /**
132
- * Starts Scoundrel server which the browser connects to for remote evaluation in the browser
133
- * @returns {void}
134
- */
135
- startScoundrel() {
136
- if (this.wss) throw new Error("Scoundrel server already started")
137
-
138
- this.wss = new WebSocketServer({port: 8090})
139
- this.serverWebSocket = new ServerWebSocket(this.wss)
140
- this.server = new Server(this.serverWebSocket)
141
- }
142
-
143
- /**
144
- * @returns {void}
145
- */
146
- stopScoundrel() {
147
- this.server?.close()
148
- this.wss?.close()
149
- }
150
-
151
- /**
152
- * Finds all elements by CSS selector
153
- * @param {string} selector
154
- * @param {object} args
155
- * @param {number} [args.timeout]
156
- * @param {boolean} [args.visible]
157
- * @param {boolean} [args.useBaseSelector]
158
- * @returns {Promise<import("selenium-webdriver").WebElement[]>}
159
- */
160
- async all(selector, args = {}) {
161
- const {visible = true, timeout, useBaseSelector = true, ...restArgs} = args
162
- const restArgsKeys = Object.keys(restArgs)
163
- let actualTimeout
164
-
165
- if (timeout === undefined) {
166
- actualTimeout = this._driverTimeouts
167
- } else {
168
- actualTimeout = timeout
169
- }
170
-
171
- if (restArgsKeys.length > 0) throw new Error(`Unknown arguments: ${restArgsKeys.join(", ")}`)
172
-
173
- const actualSelector = useBaseSelector ? this.getSelector(selector) : selector
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
-
187
- const activeElements = []
188
-
189
- for (const element of elements) {
190
- let keep = true
191
-
192
- if (visible === true || visible === false) {
193
- const isDisplayed = await element.isDisplayed()
194
-
195
- if (visible && !isDisplayed) keep = false
196
- if (!visible && isDisplayed) keep = false
197
- }
198
-
199
- if (keep) activeElements.push(element)
200
- }
201
-
202
- return activeElements
203
- }
204
-
205
- /**
206
- * Clicks an element that has children which fills out the element and would otherwise have caused a ElementClickInterceptedError
207
- * @param {string|import("selenium-webdriver").WebElement} elementOrIdentifier
208
- * @returns {Promise<void>}
209
- */
210
- async click(elementOrIdentifier) {
211
- let tries = 0
212
-
213
- while (true) {
214
- tries++
215
-
216
- try {
217
- const element = await this._findElement(elementOrIdentifier)
218
- const actions = this.getDriver().actions({async: true})
219
-
220
- await actions.move({origin: element}).click().perform()
221
- break
222
- } catch (error) {
223
- if (error instanceof Error) {
224
- if (error.constructor.name === "ElementNotInteractableError") {
225
- if (tries >= 3) {
226
- throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed after ${tries} tries - ${error.constructor.name}: ${error.message}`)
227
- } else {
228
- await wait(50)
229
- }
230
- } else {
231
- // Re-throw with un-corrupted stack trace
232
- throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed - ${error.constructor.name}: ${error.message}`)
233
- }
234
- } else {
235
- throw new Error(`Element ${elementOrIdentifier.constructor.name} click failed - ${typeof error}: ${error}`)
236
- }
237
- }
238
- }
239
- }
240
-
241
- /**
242
- * Finds a single element by CSS selector
243
- * @param {string} selector
244
- * @param {object} args
245
- * @returns {Promise<import("selenium-webdriver").WebElement>}
246
- */
247
- async find(selector, args = {}) {
248
- let elements = []
249
-
250
- try {
251
- elements = await this.all(selector, args)
252
- } catch (error) {
253
- // Re-throw to recover stack trace
254
- if (error instanceof Error) {
255
- if (error.message.startsWith("Wait timed out after")) {
256
- elements = []
257
- }
258
-
259
- throw new Error(`${error.message} (selector: ${this.getSelector(selector)})`)
260
- } else {
261
- throw new Error(`${error} (selector: ${this.getSelector(selector)})`)
262
- }
263
- }
264
-
265
- if (elements.length > 1) {
266
- throw new Error(`More than 1 elements (${elements.length}) was found by CSS: ${this.getSelector(selector)}`)
267
- }
268
-
269
- if (!elements[0]) {
270
- throw new ElementNotFoundError(`Element couldn't be found after ${(this.getTimeouts() / 1000).toFixed(2)}s by CSS: ${this.getSelector(selector)}`)
271
- }
272
-
273
- return elements[0]
274
- }
275
-
276
- /**
277
- * Finds a single element by test ID
278
- * @param {string} testID
279
- * @param {object} args
280
- * @returns {Promise<import("selenium-webdriver").WebElement>}
281
- */
282
- async findByTestID(testID, args) { return await this.find(`[data-testid='${testID}']`, args) }
283
-
284
- /**
285
- * @param {string|import("selenium-webdriver").WebElement} elementOrIdentifier
286
- * @returns {Promise<import("selenium-webdriver").WebElement>}
287
- */
288
- async _findElement(elementOrIdentifier) {
289
- let element
290
-
291
- if (typeof elementOrIdentifier == "string") {
292
- element = await this.find(elementOrIdentifier)
293
- } else {
294
- element = elementOrIdentifier
295
- }
296
-
297
- return element
298
- }
299
-
300
- /**
301
- * Finds a single element by CSS selector without waiting
302
- * @param {string} selector
303
- * @param {object} [args]
304
- * @returns {Promise<import("selenium-webdriver").WebElement>}
305
- */
306
- async findNoWait(selector, args) {
307
- await this.driverSetTimeouts(0)
308
-
309
- try {
310
- return await this.find(selector, args)
311
- } finally {
312
- await this.restoreTimeouts()
313
- }
314
- }
315
-
316
- /**
317
- * Gets browser logs
318
- * @returns {Promise<string[]>}
319
- */
320
- async getBrowserLogs() {
321
- const entries = await this.getDriver().manage().logs().get(logging.Type.BROWSER)
322
- const browserLogs = []
323
-
324
- for (const entry of entries) {
325
- const messageMatch = entry.message.match(/^(.+) (\d+):(\d+) (.+)$/)
326
- let message
327
-
328
- if (messageMatch) {
329
- message = messageMatch[4]
330
- } else {
331
- message = entry.message
332
- }
333
-
334
- browserLogs.push(`${entry.level.name}: ${message}`)
335
- }
336
-
337
- return browserLogs
338
- }
339
-
340
- /**
341
- * @returns {Promise<string>}
342
- */
343
- async getCurrentUrl() {
344
- return await this.getDriver().getCurrentUrl()
345
- }
346
-
347
- /**
348
- * @returns {number}
349
- */
350
- getTimeouts() { return this._timeouts }
351
-
352
- /**
353
- * Interacts with an element by calling a method on it with the given arguments.
354
- * Retrying on ElementNotInteractableError.
355
- * @param {import("selenium-webdriver").WebElement|string} elementOrIdentifier - The element or a CSS selector to find the element.
356
- * @param {string} methodName - The method name to call on the element.
357
- * @param {...any} args - Arguments to pass to the method.
358
- * @returns {Promise<any>}
359
- */
360
- async interact(elementOrIdentifier, methodName, ...args) {
361
- let tries = 0
362
-
363
- while (true) {
364
- tries++
365
-
366
- const element = await this._findElement(elementOrIdentifier)
367
-
368
- if (!element[methodName]) {
369
- throw new Error(`${element.constructor.name} hasn't an attribute named: ${methodName}`)
370
- } else if (typeof element[methodName] != "function") {
371
- throw new Error(`${element.constructor.name}#${methodName} is not a function`)
372
- }
373
-
374
- try {
375
- // Dont call with candidate, because that will bind the function wrong.
376
- return await element[methodName](...args)
377
- } catch (error) {
378
- if (error instanceof Error) {
379
- if (error.constructor.name === "ElementNotInteractableError") {
380
- // Retry finding the element and interacting with it
381
- if (tries >= 3) {
382
- let elementDescription
383
-
384
- if (typeof elementOrIdentifier == "string") {
385
- elementDescription = `CSS selector ${elementOrIdentifier}`
386
- } else {
387
- elementDescription = `${element.constructor.name}`
388
- }
389
-
390
- throw new Error(`${elementDescription} ${methodName} failed after ${tries} tries - ${error.constructor.name}: ${error.message}`)
391
- } else {
392
- await wait(50)
393
- }
394
- } else {
395
- // Re-throw with un-corrupted stack trace
396
- throw new Error(`${element.constructor.name} ${methodName} failed - ${error.constructor.name}: ${error.message}`)
397
- }
398
- } else {
399
- throw new Error(`${element.constructor.name} ${methodName} failed - ${typeof error}: ${error}`)
400
- }
401
- }
402
- }
403
- }
404
-
405
- /**
406
- * Expects no element to be found by CSS selector
407
- * @param {string} selector
408
- * @returns {Promise<void>}
409
- */
410
- async expectNoElement(selector) {
411
- let found = false
412
-
413
- try {
414
- await this.findNoWait(selector)
415
- found = true
416
- } catch (error) {
417
- if (error instanceof Error && error.message.startsWith("Element couldn't be found after ")) {
418
- // Ignore
419
- } else {
420
- throw error
421
- }
422
- }
423
-
424
- if (found) {
425
- throw new Error(`Expected not to find: ${selector}`)
426
- }
427
- }
428
-
429
- /**
430
- * @param {string} selector
431
- * @param {object} args
432
- * @param {boolean} [args.useBaseSelector]
433
- * @returns {Promise<void>}
434
- */
435
- async waitForNoSelector(selector, args) {
436
- const {useBaseSelector, ...restArgs} = args
437
-
438
- if (Object.keys(restArgs).length > 0) {
439
- throw new Error(`Unexpected args: ${Object.keys(restArgs).join(", ")}`)
440
- }
441
-
442
- const actualSelector = useBaseSelector ? this.getSelector(selector) : selector
443
-
444
- await this.getDriver().wait(
445
- async () => {
446
- const elements = await this.getDriver().findElements(By.css(actualSelector))
447
-
448
- // Not found at all
449
- if (elements.length === 0) {
450
- return true
451
- }
452
-
453
- // Found but not visible
454
- const isDisplayed = await elements[0].isDisplayed()
455
-
456
- return !isDisplayed
457
- },
458
- this.getTimeouts()
459
- )
460
- }
461
-
462
- /**
463
- * Gets notification messages
464
- * @returns {Promise<string[]>}
465
- */
466
- async notificationMessages() {
467
- const notificationMessageElements = await this.all("[data-class='notification-message']", {useBaseSelector: false})
468
- const notificationMessageTexts = []
469
-
470
- for (const notificationMessageElement of notificationMessageElements) {
471
- const text = await notificationMessageElement.getText()
472
-
473
- notificationMessageTexts.push(text)
474
- }
475
-
476
- return notificationMessageTexts
477
- }
478
-
479
- /**
480
- * Expects a notification message to appear and waits for it if necessary.
481
- * @param {string} expectedNotificationMessage
482
- * @returns {Promise<void>}
483
- */
484
- async expectNotificationMessage(expectedNotificationMessage) {
485
- /** @type {string[]} */
486
- const allDetectedNotificationMessages = []
487
- let foundNotificationMessageElement
488
-
489
- await waitFor(async () => {
490
- const notificationMessageElements = await this.all("[data-class='notification-message']", {useBaseSelector: false})
491
-
492
- for (const notificationMessageElement of notificationMessageElements) {
493
- const notificationMessage = await notificationMessageElement.getText()
494
-
495
- if (!allDetectedNotificationMessages.includes(notificationMessage)) {
496
- allDetectedNotificationMessages.push(notificationMessage)
497
- }
498
-
499
- if (notificationMessage == expectedNotificationMessage) {
500
- foundNotificationMessageElement = notificationMessageElement
501
- return
502
- }
503
- }
504
-
505
- throw new Error(`Notification message ${expectedNotificationMessage} wasn't included in: ${allDetectedNotificationMessages.join(", ")}`)
506
- })
507
-
508
- if (foundNotificationMessageElement) {
509
- await this.interact(foundNotificationMessageElement, "click") // Dismiss the notification message
510
- }
511
- }
512
-
513
- /**
514
- * @returns {Promise<void>}
515
- */
516
- async dismissNotificationMessages() {
517
- const notificationMessageElements = await this.all("[data-class='notification-message']", {useBaseSelector: false})
518
-
519
- for (const notificationMessageElement of notificationMessageElements) {
520
- await this.interact(notificationMessageElement, "click")
521
- }
522
-
523
- await this.waitForNoSelector("[data-class='notification-message']", {useBaseSelector: false})
524
- }
525
-
526
- /**
527
- * Indicates whether the system test has been started
528
- * @returns {boolean}
529
- */
530
- isStarted() { return this._started }
531
-
532
- /**
533
- * Gets the HTML of the current page
534
- * @returns {Promise<string>}
535
- */
536
- async getHTML() { return await this.getDriver().getPageSource() }
537
-
538
- /**
539
- * Starts the system test
540
- * @returns {Promise<void>}
541
- */
542
- async start() {
543
- if (process.env.SYSTEM_TEST_HOST == "expo-dev-server") {
544
- this.currentUrl = `http://${this._host}:${this._port}`
545
- } else if (process.env.SYSTEM_TEST_HOST == "dist") {
546
- this.currentUrl = `http://${this._host}:1984`
547
- this.systemTestHttpServer = new SystemTestHttpServer()
548
-
549
- await this.systemTestHttpServer.start()
550
- } else {
551
- throw new Error("Please set SYSTEM_TEST_HOST to 'expo-dev-server' or 'dist'")
552
- }
553
-
554
- const options = new chrome.Options()
555
-
556
- options.addArguments("--disable-dev-shm-usage")
557
- options.addArguments("--disable-gpu")
558
- options.addArguments("--headless=new")
559
- options.addArguments("--no-sandbox")
560
- options.addArguments("--window-size=1920,1080")
561
-
562
- this.driver = new Builder()
563
- .forBrowser("chrome")
564
- .setChromeOptions(options)
565
- // @ts-expect-error
566
- .setCapability("goog:loggingPrefs", {browser: "ALL"})
567
- .build()
568
-
569
- await this.setTimeouts(5000)
570
-
571
- // Web socket server to communicate with browser
572
- await this.startWebSocketServer()
573
-
574
- // Visit the root page and wait for Expo to be loaded and the app to appear
575
- await this.driverVisit(SystemTest.rootPath)
576
-
577
- try {
578
- await this.find("body > #root", {useBaseSelector: false})
579
- await this.find("[data-testid='systemTestingComponent']", {visible: null, useBaseSelector: false})
580
- } catch (error) {
581
- await this.takeScreenshot()
582
- throw error
583
- }
584
-
585
- // Wait for client to connect
586
- await this.waitForClientWebSocket()
587
-
588
- this._started = true
589
- this.setBaseSelector("[data-testid='systemTestingComponent'][data-focussed='true']")
590
- }
591
-
592
- /**
593
- * Restores previously set timeouts
594
- * @returns {Promise<void>}
595
- */
596
- async restoreTimeouts() {
597
- if (!this.getTimeouts()) {
598
- throw new Error("Timeouts haven't previously been set")
599
- }
600
-
601
- await this.driverSetTimeouts(this.getTimeouts())
602
- }
603
-
604
- /**
605
- * Sets driver timeouts
606
- * @param {number} newTimeout
607
- * @returns {Promise<void>}
608
- */
609
- async driverSetTimeouts(newTimeout) {
610
- this._driverTimeouts = newTimeout
611
- await this.getDriver().manage().setTimeouts({implicit: newTimeout})
612
- }
613
-
614
- /**
615
- * Sets timeouts and stores the previous timeouts
616
- * @param {number} newTimeout
617
- * @returns {Promise<void>}
618
- */
619
- async setTimeouts(newTimeout) {
620
- this._timeouts = newTimeout
621
- await this.restoreTimeouts()
622
- }
623
-
624
- /**
625
- * Waits for the client web socket to connect
626
- * @returns {Promise<void>}
627
- */
628
- waitForClientWebSocket() {
629
- return new Promise((resolve) => {
630
- if (this.ws) {
631
- resolve()
632
- }
633
-
634
- this.waitForClientWebSocketPromiseResolve = resolve
635
- })
636
- }
637
-
638
- /**
639
- * Starts the web socket server
640
- * @returns {void}
641
- */
642
- startWebSocketServer() {
643
- this.wss = new WebSocketServer({port: 1985})
644
- this.wss.on("connection", this.onWebSocketConnection)
645
- this.wss.on("close", this.onWebSocketClose)
646
- }
647
-
648
- /**
649
- * Sets the on command callback
650
- * @param {function({type: string, data: Record<string, any>}): Promise<void>} callback
651
- * @returns {void}
652
- */
653
- onCommand(callback) {
654
- this._onCommandCallback = callback
655
- }
656
-
657
- /**
658
- * Handles a command received from the browser
659
- * @param {{data: {message: string, backtrace: string, type: string, value: any[]}}} args
660
- * @returns {Promise<any>}
661
- */
662
- onCommandReceived = async ({data}) => {
663
- const type = data.type
664
- let result
665
-
666
- if (type == "console.error") {
667
- const errorMessage = data.value[0]
668
- let showMessage = true
669
-
670
- if (errorMessage.includes("Minified React error #419")) {
671
- showMessage = false
672
- }
673
-
674
- if (showMessage) {
675
- console.error("Browser error", ...data.value)
676
- }
677
- } else if (type == "console.log") {
678
- console.log("Browser log", ...data.value)
679
- } else if (type == "error" || data.type == "unhandledrejection") {
680
- this.handleError(data)
681
- } else if (this._onCommandCallback) {
682
- result = await this._onCommandCallback({data, type})
683
- } else {
684
- console.error(`onWebSocketClientMessage unknown data (type ${type})`, data)
685
- }
686
-
687
- return result
688
- }
689
-
690
- /**
691
- * Handles a new web socket connection
692
- * @param {WebSocket} ws
693
- * @returns {Promise<void>}
694
- */
695
- onWebSocketConnection = async (ws) => {
696
- this.ws = ws
697
- this.getCommunicator().ws = ws
698
- this.getCommunicator().onOpen()
699
-
700
- // @ts-expect-error
701
- this.ws.on("error", digg(this, "communicator", "onError"))
702
-
703
- // @ts-expect-error
704
- this.ws.on("message", digg(this, "communicator", "onMessage"))
705
-
706
- if (this.waitForClientWebSocketPromiseResolve) {
707
- this.waitForClientWebSocketPromiseResolve()
708
- delete this.waitForClientWebSocketPromiseResolve
709
- }
710
- }
711
-
712
- /**
713
- * @returns {void}
714
- */
715
- onWebSocketClose = () => {
716
- this.ws = null
717
- this.getCommunicator().ws = null
718
- }
719
-
720
- /**
721
- * Handles an error reported from the browser
722
- * @param {object} data
723
- * @param {string} data.message
724
- * @param {string} [data.backtrace]
725
- * @returns {void}
726
- */
727
- handleError(data) {
728
- if (data.message.includes("Minified React error #419")) {
729
- // Ignore this error message
730
- return
731
- }
732
-
733
- const error = new Error(`Browser error: ${data.message}`)
734
-
735
- if (data.backtrace) {
736
- error.stack = `${error.message}\n${data.backtrace}`
737
- }
738
-
739
- console.error(error)
740
- }
741
-
742
- /**
743
- * Stops the system test
744
- * @returns {Promise<void>}
745
- */
746
- async stop() {
747
- this.stopScoundrel()
748
- this.systemTestHttpServer?.close()
749
- this.wss?.close()
750
- await this.driver?.quit()
751
- }
752
-
753
- /**
754
- * Visits a path in the browser
755
- * @param {string} path
756
- * @returns {Promise<void>}
757
- */
758
- async driverVisit(path) {
759
- const url = `${this.currentUrl}${path}`
760
-
761
- await this.getDriver().get(url)
762
- }
763
-
764
- /**
765
- * Takes a screenshot, saves HTML and browser logs
766
- * @returns {Promise<void>}
767
- */
768
- async takeScreenshot() {
769
- const path = `${process.cwd()}/tmp/screenshots`
770
-
771
- await fs.mkdir(path, {recursive: true})
772
-
773
- const imageContent = await this.getDriver().takeScreenshot()
774
- const now = new Date()
775
- const screenshotPath = `${path}/${moment(now).format("YYYY-MM-DD-HH-MM-SS")}.png`
776
- const htmlPath = `${path}/${moment(now).format("YYYY-MM-DD-HH-MM-SS")}.html`
777
- const logsPath = `${path}/${moment(now).format("YYYY-MM-DD-HH-MM-SS")}.logs.txt`
778
- const logsText = await this.getBrowserLogs()
779
- const html = await this.getHTML()
780
- const htmlPretty = prettify(html)
781
-
782
- await fs.writeFile(htmlPath, htmlPretty)
783
- await fs.writeFile(logsPath, logsText.join("\n"))
784
- await fs.writeFile(screenshotPath, imageContent, "base64")
785
-
786
- console.log("Current URL:", await this.getCurrentUrl())
787
- console.log("Logs:", logsPath)
788
- console.log("Screenshot:", screenshotPath)
789
- console.log("HTML:", htmlPath)
790
- }
791
-
792
- /**
793
- * Visits a path in the browser
794
- * @param {string} path
795
- * @returns {Promise<void>}
796
- */
797
- async visit(path) {
798
- await this.getCommunicator().sendCommand({type: "visit", path})
799
- }
800
-
801
- /**
802
- * Dismisses to a path in the browser
803
- * @param {string} path
804
- * @returns {Promise<void>}
805
- */
806
- async dismissTo(path) {
807
- await this.getCommunicator().sendCommand({type: "dismissTo", path})
808
- }
809
- }