ui5-test-runner 1.1.5 → 2.0.0

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.
Files changed (93) hide show
  1. package/README.md +32 -188
  2. package/index.js +47 -16
  3. package/package.json +28 -10
  4. package/src/add-test-pages.js +35 -0
  5. package/src/add-test-pages.spec.js +95 -0
  6. package/src/browser.spec.js +724 -0
  7. package/src/browsers.js +220 -59
  8. package/src/capabilities/index.js +194 -0
  9. package/src/capabilities/tests/basic/iframe.html +8 -0
  10. package/src/capabilities/tests/basic/index.html +12 -0
  11. package/src/capabilities/tests/basic/index.js +20 -0
  12. package/src/capabilities/tests/basic/ui5.html +24 -0
  13. package/src/capabilities/tests/dynamic-include/index.js +21 -0
  14. package/src/capabilities/tests/dynamic-include/mix.html +11 -0
  15. package/src/capabilities/tests/dynamic-include/one.html +11 -0
  16. package/src/capabilities/tests/dynamic-include/post.js +3 -0
  17. package/src/capabilities/tests/dynamic-include/test.js +1 -0
  18. package/src/capabilities/tests/dynamic-include/two.html +11 -0
  19. package/src/capabilities/tests/index.js +16 -0
  20. package/src/capabilities/tests/local-storage/index.html +16 -0
  21. package/src/capabilities/tests/local-storage/index.js +21 -0
  22. package/src/capabilities/tests/screenshot/index.html +13 -0
  23. package/src/capabilities/tests/screenshot/index.js +18 -0
  24. package/src/capabilities/tests/scripts/index.js +50 -0
  25. package/src/capabilities/tests/scripts/qunit.html +22 -0
  26. package/src/capabilities/tests/scripts/testsuite.html +10 -0
  27. package/src/capabilities/tests/scripts/testsuite.js +8 -0
  28. package/src/capabilities/tests/timeout/index.html +21 -0
  29. package/src/capabilities/tests/timeout/index.js +19 -0
  30. package/src/capabilities/tests/traces/index.html +18 -0
  31. package/src/capabilities/tests/traces/index.js +81 -0
  32. package/src/cors.js +1 -1
  33. package/src/cors.spec.js +41 -0
  34. package/src/coverage.js +30 -18
  35. package/src/coverage.spec.js +79 -0
  36. package/src/csv-reader.js +36 -0
  37. package/src/csv-reader.spec.js +42 -0
  38. package/src/csv-writer.js +52 -0
  39. package/src/csv-writer.spec.js +77 -0
  40. package/src/defaults/browser.js +144 -0
  41. package/src/defaults/jsdom/compatibility.js +95 -0
  42. package/src/defaults/jsdom/debug.js +23 -0
  43. package/src/defaults/jsdom/resource-loader.js +43 -0
  44. package/src/defaults/jsdom/sap.ui.test.matchers.visible.js +39 -0
  45. package/src/defaults/jsdom.js +64 -0
  46. package/src/defaults/junit-xml-report.js +64 -0
  47. package/src/defaults/puppeteer.js +111 -0
  48. package/src/defaults/report/common.js +38 -0
  49. package/src/defaults/report/default.html +84 -0
  50. package/src/defaults/report/main.js +44 -0
  51. package/src/defaults/report/progress.js +49 -0
  52. package/src/defaults/report/styles.css +66 -0
  53. package/src/defaults/report.js +69 -0
  54. package/src/defaults/selenium-webdriver/chrome.js +38 -0
  55. package/src/defaults/selenium-webdriver/edge.js +25 -0
  56. package/src/defaults/selenium-webdriver/firefox.js +31 -0
  57. package/src/defaults/selenium-webdriver.js +138 -0
  58. package/src/endpoints.js +70 -124
  59. package/src/error.js +52 -0
  60. package/src/error.spec.js +17 -0
  61. package/src/get-job-progress.js +69 -0
  62. package/src/get-job-progress.spec.js +175 -0
  63. package/src/inject/post.js +96 -0
  64. package/src/inject/post.spec.js +147 -0
  65. package/src/inject/qunit-hooks.js +6 -21
  66. package/src/inject/qunit-intercept.js +30 -0
  67. package/src/inject/qunit-redirect.js +15 -7
  68. package/src/job-mode.js +45 -0
  69. package/src/job.js +254 -108
  70. package/src/job.spec.js +413 -0
  71. package/src/npm.js +73 -0
  72. package/src/npm.spec.js +98 -0
  73. package/src/options.js +73 -0
  74. package/src/options.spec.js +125 -0
  75. package/src/output.js +450 -131
  76. package/src/qunit-hooks.js +116 -0
  77. package/src/qunit-hooks.spec.js +687 -0
  78. package/src/report.js +42 -0
  79. package/src/reserve.js +3 -4
  80. package/src/simulate.spec.js +437 -0
  81. package/src/symbols.js +8 -0
  82. package/src/tests.js +127 -84
  83. package/src/timeout.spec.js +39 -0
  84. package/src/tools.js +111 -4
  85. package/src/tools.spec.js +90 -0
  86. package/src/ui5.js +3 -3
  87. package/src/unhandled.js +6 -6
  88. package/src/unhandled.spec.js +63 -0
  89. package/defaults/chromium.js +0 -62
  90. package/src/progress.html +0 -71
  91. package/src/proxies.js +0 -8
  92. package/src/report.html +0 -202
  93. /package/{defaults → src/defaults}/nyc.json +0 -0
@@ -0,0 +1,77 @@
1
+ const { buildCsvWriter } = require('./csv-writer')
2
+ const { writeFile } = require('fs/promises')
3
+
4
+ jest.mock('fs/promises')
5
+
6
+ describe('src/csv-writer', () => {
7
+ const now = Date.now()
8
+
9
+ beforeAll(() => {
10
+ jest.spyOn(Date, 'now').mockImplementation(() => now)
11
+ })
12
+
13
+ beforeEach(() => {
14
+ writeFile.mockReset()
15
+ })
16
+
17
+ it('allocates an object', () => {
18
+ const writer = buildCsvWriter('test.csv')
19
+ expect(writer).not.toBeUndefined()
20
+ expect(writeFile).not.toHaveBeenCalled()
21
+ })
22
+
23
+ it('starts writing the file on first record', async () => {
24
+ const writer = buildCsvWriter('test.csv')
25
+ writer.append({ text: 'test' })
26
+ await writer.ready
27
+ expect(writeFile.mock.calls).toEqual([
28
+ ['test.csv', 'timestamp\ttext\n', { flag: 'a+' }],
29
+ ['test.csv', `${now}\ttest\n`, { flag: 'a+' }]
30
+ ])
31
+ })
32
+
33
+ it('ignores any additional field not used on the first append', async () => {
34
+ const writer = buildCsvWriter('test.csv')
35
+ writer.append({ text: 'test 1' })
36
+ writer.append({ text: 'test 2', ignored: true })
37
+ await writer.ready
38
+ expect(writeFile.mock.calls).toEqual([
39
+ ['test.csv', 'timestamp\ttext\n', { flag: 'a+' }],
40
+ ['test.csv', `${now}\ttest 1\n`, { flag: 'a+' }],
41
+ ['test.csv', `${now}\ttest 2\n`, { flag: 'a+' }]
42
+ ])
43
+ })
44
+
45
+ it('escapes automatically complex values', async () => {
46
+ const writer = buildCsvWriter('test.csv')
47
+ writer.append({ number: 1, text: 'test\t1\n' })
48
+ await writer.ready
49
+ expect(writeFile.mock.calls).toEqual([
50
+ ['test.csv', 'timestamp\tnumber\ttext\n', { flag: 'a+' }],
51
+ ['test.csv', `${now}\t1\t"test\\t1\\n"\n`, { flag: 'a+' }]
52
+ ])
53
+ })
54
+
55
+ it('handles provided timestamp', async () => {
56
+ const writer = buildCsvWriter('test.csv')
57
+ writer.append({ timestamp: 456, number: 1, text: 'test\t1\n' })
58
+ await writer.ready
59
+ expect(writeFile.mock.calls).toEqual([
60
+ ['test.csv', 'timestamp\tnumber\ttext\n', { flag: 'a+' }],
61
+ ['test.csv', '456\t1\t"test\\t1\\n"\n', { flag: 'a+' }]
62
+ ])
63
+ })
64
+
65
+ it('handles record array', async () => {
66
+ const writer = buildCsvWriter('test.csv')
67
+ await writer.append([
68
+ { number: 1, text: 'test\t1\n' },
69
+ { number: 2, text: 'test\t2\n' }
70
+ ])
71
+ await writer.ready
72
+ expect(writeFile.mock.calls).toEqual([
73
+ ['test.csv', 'timestamp\tnumber\ttext\n', { flag: 'a+' }],
74
+ ['test.csv', `${now}\t1\t"test\\t1\\n"\n${now}\t2\t"test\\t2\\n"\n`, { flag: 'a+' }]
75
+ ])
76
+ })
77
+ })
@@ -0,0 +1,144 @@
1
+ const { readFile, writeFile } = require('fs/promises')
2
+ const { join } = require('path')
3
+ const { Command } = require('commander')
4
+ const { buildCsvWriter } = require('../csv-writer')
5
+ const { any, boolean, integer } = require('../options')
6
+
7
+ const noop = () => { }
8
+
9
+ module.exports = ({
10
+ metadata,
11
+ flush = noop,
12
+ beforeExit = noop,
13
+ screenshot,
14
+ capabilities: computeCapabilities,
15
+ run,
16
+ error = noop
17
+ }) => {
18
+ const command = new Command()
19
+ command
20
+ .name(`ui5-test-runner/@/${metadata.name}`)
21
+ .description(`Browser instantiation command for ${metadata.name}`)
22
+ .helpOption(false)
23
+ metadata.options.forEach(([label, description, parser, defaultValue]) => {
24
+ if (defaultValue === undefined && typeof parser !== 'function') {
25
+ defaultValue = parser
26
+ if (typeof defaultValue === 'number') {
27
+ parser = integer
28
+ } else if (typeof defaultValue === 'boolean') {
29
+ parser = boolean
30
+ } else {
31
+ parser = any
32
+ }
33
+ }
34
+ command.option(label, description, parser, defaultValue)
35
+ })
36
+
37
+ const append = () => { }
38
+ let consoleWriter = { ready: Promise.resolve(), append }
39
+ let networkWriter = { ready: Promise.resolve(), append }
40
+
41
+ let stopping = false
42
+ let settings
43
+ let options
44
+
45
+ async function exit (code) {
46
+ if (stopping) {
47
+ return
48
+ }
49
+ stopping = true
50
+ if (settings && typeof settings.capabilities !== 'string' && settings.capabilities.traces.length) {
51
+ try {
52
+ await flush({
53
+ settings,
54
+ options,
55
+ consoleWriter,
56
+ networkWriter
57
+ })
58
+ } catch (e) {
59
+ console.error('[exit:flush]', e)
60
+ code = -3
61
+ }
62
+ await Promise.all([consoleWriter.ready, networkWriter.ready])
63
+ }
64
+ try {
65
+ await beforeExit({
66
+ settings,
67
+ options
68
+ })
69
+ } catch (e) {
70
+ console.error('[exit:beforeExit]', e)
71
+ // but ignore
72
+ }
73
+ process.exit(code)
74
+ }
75
+
76
+ process.on('message', async message => {
77
+ const { command } = message
78
+ try {
79
+ if (command === 'stop') {
80
+ await exit(0)
81
+ } else if (command === 'screenshot') {
82
+ if (settings.capabilities.screenshot && screenshot && await screenshot({
83
+ settings,
84
+ options,
85
+ filename: message.filename
86
+ })) {
87
+ process.send(message)
88
+ } else {
89
+ throw new Error('screenshot command failed')
90
+ }
91
+ }
92
+ } catch (e) {
93
+ console.error(e)
94
+ exit(-2)
95
+ }
96
+ })
97
+
98
+ if (process.argv.length !== 3) {
99
+ command.outputHelp()
100
+ return exit(0)
101
+ }
102
+
103
+ async function main () {
104
+ settings = JSON.parse((await readFile(process.argv[2])).toString())
105
+ command.parse(settings.args, { from: 'user' })
106
+ options = command.opts()
107
+
108
+ if (typeof settings.capabilities === 'string') {
109
+ let capabilities
110
+ if (computeCapabilities !== undefined) {
111
+ capabilities = await computeCapabilities({ settings, options })
112
+ } else {
113
+ capabilities = metadata.capabilities
114
+ }
115
+ await writeFile(settings.capabilities, JSON.stringify(capabilities, undefined, 2))
116
+ return exit(0)
117
+ }
118
+
119
+ consoleWriter = buildCsvWriter(join(settings.dir, 'console.csv'))
120
+ networkWriter = buildCsvWriter(join(settings.dir, 'network.csv'))
121
+
122
+ await run({
123
+ settings,
124
+ options,
125
+ consoleWriter,
126
+ networkWriter,
127
+ exit
128
+ })
129
+ }
130
+
131
+ main()
132
+ .catch(async e => {
133
+ if (!stopping) {
134
+ await error({
135
+ settings,
136
+ options,
137
+ error: e,
138
+ exit
139
+ })
140
+ console.error(e)
141
+ }
142
+ exit(-1)
143
+ })
144
+ }
@@ -0,0 +1,95 @@
1
+ const noop = () => {}
2
+
3
+ function fakeMatchMedia () {
4
+ return {
5
+ matches: false,
6
+ addListener: noop,
7
+ removeListener: noop
8
+ }
9
+ }
10
+
11
+ function wrapXHR (window, networkWriter) {
12
+ const { XMLHttpRequest } = window
13
+ const { open } = XMLHttpRequest.prototype
14
+ XMLHttpRequest.prototype.open = function (...args) {
15
+ const [method, url] = args
16
+ const log = () => {
17
+ const { status } = this
18
+ networkWriter.append({
19
+ method,
20
+ url,
21
+ status
22
+ })
23
+ }
24
+ this.addEventListener('load', log)
25
+ this.addEventListener('error', log)
26
+ return open.call(this, ...args)
27
+ }
28
+ }
29
+
30
+ function adjustXPathResult (window) {
31
+ /* https://ui5.sap.com/resources/sap/ui/model/odata/AnnotationParser-dbg.js
32
+ getXPath: function() {
33
+ xmlNodes.length = xmlNodes.snapshotLength;
34
+ */
35
+ const { Document } = window
36
+ const evaluate = Document.prototype.evaluate
37
+ Document.prototype.evaluate = function () {
38
+ const result = evaluate.apply(this, arguments)
39
+ let length = result.length
40
+ Object.defineProperty(result, 'length', {
41
+ get: () => length,
42
+ set: (value) => {
43
+ length = value
44
+ return true
45
+ }
46
+ })
47
+ return result
48
+ }
49
+ }
50
+
51
+ function fixMatchesDontThrow (window) {
52
+ // https://github.com/jsdom/jsdom/issues/3057
53
+ // Fix _nwsapiDontThrow which throws :-(
54
+ const { document } = window
55
+ const [impl] = Object.getOwnPropertySymbols(document)
56
+ const documentImpl = document[impl]
57
+ let _nwsapiDontThrow
58
+ Object.defineProperty(documentImpl, '_nwsapiDontThrow', {
59
+ get () {
60
+ return _nwsapiDontThrow
61
+ },
62
+ set (nwsapiDontThrow) {
63
+ _nwsapiDontThrow = nwsapiDontThrow
64
+ const { match } = nwsapiDontThrow
65
+ _nwsapiDontThrow.match = function () {
66
+ try {
67
+ return match.apply(this, arguments)
68
+ } catch (e) {
69
+ return false
70
+ }
71
+ }
72
+ return true
73
+ }
74
+ })
75
+ }
76
+
77
+ module.exports = ({
78
+ window,
79
+ networkWriter
80
+ }) => {
81
+ window.addEventListener('error', event => {
82
+ const { message, filename, lineno, colno } = event
83
+ window.console.error(`${filename}@${lineno}:${colno}: ${message}`)
84
+ })
85
+
86
+ window.performance.timing = {
87
+ navigationStart: new Date().getTime(),
88
+ fetchStart: new Date().getTime()
89
+ }
90
+ window.matchMedia = window.matchMedia || fakeMatchMedia
91
+
92
+ wrapXHR(window, networkWriter)
93
+ adjustXPathResult(window)
94
+ fixMatchesDontThrow(window)
95
+ }
@@ -0,0 +1,23 @@
1
+ module.exports = window => {
2
+ // Proxify sap.ui to hook the loader and enable traces
3
+ window.sap = {
4
+ ui: new Proxy({}, {
5
+ get (obj, prop) {
6
+ return obj[prop]
7
+ },
8
+ set (obj, prop, value) {
9
+ obj[prop] = value
10
+ if (prop === 'loader') {
11
+ value._.logger = {
12
+ debug: (...args) => window.console.log('LOADER', ...args),
13
+ info: (...args) => window.console.info('LOADER', ...args),
14
+ warning: (...args) => window.console.warn('LOADER', ...args),
15
+ error: (...args) => window.console.error('LOADER', ...args),
16
+ isLoggable: () => true
17
+ }
18
+ }
19
+ return true
20
+ }
21
+ })
22
+ }
23
+ }
@@ -0,0 +1,43 @@
1
+ module.exports = ({
2
+ jsdom,
3
+ networkWriter,
4
+ consoleWriter
5
+ }) => {
6
+ const { ResourceLoader: JSDOMResourceLoader } = jsdom
7
+
8
+ const { readFile } = require('fs/promises')
9
+ const { join } = require('path')
10
+
11
+ class ResourceLoader extends JSDOMResourceLoader {
12
+ fetch (url, options) {
13
+ const request = super.fetch(url, options)
14
+ const log = reason => {
15
+ const { response } = request
16
+ let status
17
+ if (response === undefined) {
18
+ consoleWriter.append({
19
+ type: 'error',
20
+ message: 'NETWORK ERROR : ' + (reason ? reason.toString() : 'unknown reason')
21
+ })
22
+ status = 599
23
+ } else {
24
+ status = response.statusCode
25
+ }
26
+ networkWriter.append({
27
+ method: 'GET',
28
+ url,
29
+ status
30
+ })
31
+ }
32
+ request.then(log, log)
33
+ if (url.match(/sap\/ui\/test\/matchers\/Visible(-dbg)?.js/)) {
34
+ return request.then(() => {
35
+ return readFile(join(__dirname, 'sap.ui.test.matchers.visible.js'))
36
+ })
37
+ }
38
+ return request
39
+ }
40
+ }
41
+
42
+ return new ResourceLoader()
43
+ }
@@ -0,0 +1,39 @@
1
+ /* Modified version of https://ui5.sap.com/resources/sap/ui/test/matchers/Visible-dbg.js */
2
+ /* see https://github.com/jsdom/jsdom/issues/1048 */
3
+ /* global sap, jQuery */
4
+ sap.ui.define(['sap/ui/test/matchers/Matcher'], function (Matcher) {
5
+ 'use strict'
6
+
7
+ /**
8
+ * @class Checks if a controls domref is visible.
9
+ * @private
10
+ * @extends sap.ui.test.matchers.Matcher
11
+ * @name sap.ui.test.matchers.Visible
12
+ * @author SAP SE
13
+ * @since 1.34
14
+ */
15
+ return Matcher.extend('sap.ui.test.matchers.Visible', /** @lends sap.ui.test.matchers.Visible.prototype */ {
16
+ isMatching: function (oControl) {
17
+ const oDomRef = oControl.$()
18
+
19
+ if (oDomRef.length) {
20
+ const isVisible = ref => ref.css('display') !== 'none' && ref.css('visibility') !== 'hidden'
21
+ if (!isVisible(oDomRef)) {
22
+ this._oLogger.debug("Control '" + oControl + "' is not visible")
23
+ return false
24
+ }
25
+ const parents = oDomRef.parents()
26
+ for (const parent of parents) {
27
+ if (!isVisible(jQuery(parent))) {
28
+ this._oLogger.debug("Control '" + oControl + "' is not visible (because of hidden parent)")
29
+ return false
30
+ }
31
+ }
32
+ return true
33
+ }
34
+
35
+ this._oLogger.debug("Control '" + oControl + "'' is not rendered")
36
+ return false
37
+ }
38
+ })
39
+ })
@@ -0,0 +1,64 @@
1
+ 'use strict'
2
+
3
+ const { join } = require('path')
4
+
5
+ require('./browser')({
6
+ metadata: {
7
+ name: 'jsdom',
8
+ options: [
9
+ ['--debug [flag]', 'Enable more traces', false]
10
+ ],
11
+ capabilities: {
12
+ modules: ['jsdom'],
13
+ scripts: true,
14
+ traces: ['console', 'network']
15
+ }
16
+ },
17
+
18
+ async run ({
19
+ settings: { url, scripts, modules },
20
+ options,
21
+ consoleWriter,
22
+ networkWriter
23
+ }) {
24
+ const jsdom = require(modules.jsdom)
25
+ const { JSDOM, VirtualConsole } = jsdom
26
+
27
+ const virtualConsole = new VirtualConsole()
28
+ virtualConsole.on('error', (...args) => consoleWriter.append({ type: 'error', text: args.join(' ') }))
29
+ virtualConsole.on('warn', (...args) => consoleWriter.append({ type: 'warning', text: args.join(' ') }))
30
+ virtualConsole.on('info', (...args) => consoleWriter.append({ type: 'info', text: args.join(' ') }))
31
+ virtualConsole.on('log', (...args) => consoleWriter.append({ type: 'log', text: args.join(' ') }))
32
+
33
+ const beforeParse = (window) => {
34
+ require('./jsdom/compatibility')({ window, networkWriter })
35
+ if (options.debug) {
36
+ require('./jsdom/debug')(window)
37
+ }
38
+ scripts.forEach(script => window.eval(script))
39
+ }
40
+
41
+ // https://github.com/jsdom/jsdom/issues/2920
42
+ const Window = require(join(modules.jsdom, 'lib/jsdom/browser/Window.js'))
43
+ const origCreate = Window.createWindow.bind(Window)
44
+ Window.createWindow = (...args) => {
45
+ const window = origCreate(...args)
46
+ beforeParse(window)
47
+ return window
48
+ }
49
+
50
+ JSDOM.fromURL(url, {
51
+ includeNodeLocations: true,
52
+ storageQuota: 10000000,
53
+ runScripts: 'dangerously',
54
+ pretendToBeVisual: true,
55
+ virtualConsole,
56
+ resources: require('./jsdom/resource-loader')({
57
+ jsdom,
58
+ networkWriter,
59
+ consoleWriter
60
+ }),
61
+ beforeParse
62
+ })
63
+ }
64
+ })
@@ -0,0 +1,64 @@
1
+ 'use strict'
2
+
3
+ const { join } = require('path')
4
+ const { writeFile } = require('fs').promises
5
+ const [,, reportDir] = process.argv
6
+
7
+ const output = []
8
+ function o (text) {
9
+ output.push(text)
10
+ }
11
+
12
+ function xmlEscape (text) {
13
+ return text.replace(/<|>|&|"/g, match => ({
14
+ '<': '&lt;',
15
+ '>': '&gt;',
16
+ '&': '&amp;',
17
+ '"': '&quot;'
18
+ }[match]))
19
+ }
20
+
21
+ async function main () {
22
+ const job = require(join(reportDir, 'job.js'))
23
+ o('<?xml version="1.0" encoding="UTF-8"?>')
24
+ o('<testsuites>')
25
+ const urls = Object.keys(job.qunitPages)
26
+ for (const url of urls) {
27
+ const qunitPage = job.qunitPages[url]
28
+ for (const module of qunitPage.modules) {
29
+ o(` <testsuite
30
+ name="${xmlEscape(url)}"
31
+ package="${xmlEscape(module.name)}"
32
+ tests="${module.tests.length}"
33
+ >`)
34
+ for (const test of module.tests) {
35
+ o(` <testcase
36
+ name="${xmlEscape(test.name)}"
37
+ >`)
38
+ if (test.skip) {
39
+ o(' <skipped></skipped>')
40
+ } else if (test.report.failed) {
41
+ const log = test.logs.filter(({ result }) => !result)[0]
42
+ o(` <failure
43
+ message="${xmlEscape(log.message)}"
44
+ >`)
45
+ o(xmlEscape(log.source))
46
+ o(' </failure>')
47
+ }
48
+ o(' </testcase>')
49
+ }
50
+ o(' </testsuite>')
51
+ }
52
+ }
53
+ o('</testsuites>')
54
+ await writeFile(join(reportDir, 'junit.xml'), output.join('\n'))
55
+ }
56
+
57
+ main()
58
+ .catch(reason => {
59
+ console.error(reason)
60
+ return -1
61
+ })
62
+ .then((code = 0) => {
63
+ process.exit(code)
64
+ })
@@ -0,0 +1,111 @@
1
+ 'use strict'
2
+
3
+ let browser
4
+ let page
5
+
6
+ require('./browser')({
7
+ metadata: {
8
+ name: 'puppeteer',
9
+ options: [
10
+ ['--visible [flag]', 'Show the browser', false],
11
+ ['-w, --viewport-width <width>', 'Viewport width', 1920],
12
+ ['-h, --viewport-height <height>', 'Viewport height', 1080],
13
+ ['-l, --language <lang...>', 'Language(s)', ['en-US']],
14
+ ['-u, --unsecure', 'Disable security features', false]
15
+ ],
16
+ capabilities: {
17
+ modules: ['puppeteer'],
18
+ screenshot: '.png',
19
+ scripts: true,
20
+ traces: ['console', 'network']
21
+ }
22
+ },
23
+
24
+ async screenshot ({ filename }) {
25
+ if (page) {
26
+ await page.screenshot({
27
+ path: filename,
28
+ fullPage: true
29
+ })
30
+ return true
31
+ }
32
+ },
33
+
34
+ async beforeExit () {
35
+ if (page) {
36
+ await page.close()
37
+ }
38
+ if (browser) {
39
+ await browser.close()
40
+ }
41
+ },
42
+
43
+ async run ({
44
+ settings: { url, scripts, modules },
45
+ options,
46
+ consoleWriter,
47
+ networkWriter
48
+ }) {
49
+ const puppeteer = require(modules.puppeteer)
50
+
51
+ const args = [
52
+ '--start-maximized',
53
+ '--no-sandbox',
54
+ '--disable-gpu',
55
+ '--disable-extensions',
56
+ `--window-size=${options.viewportWidth},${options.viewportHeight}`,
57
+ `--lang=${options.language.join(',')}`
58
+ ]
59
+
60
+ if (options.unsecure) {
61
+ args.push(
62
+ '--disable-web-security',
63
+ '--disable-features=IsolateOrigins',
64
+ '--disable-features=BlockInsecurePrivateNetworkRequests',
65
+ '--disable-site-isolation-trials'
66
+ )
67
+ }
68
+
69
+ browser = await puppeteer.launch({
70
+ headless: !options.visible,
71
+ defaultViewport: null,
72
+ args
73
+ })
74
+
75
+ page = (await browser.pages())[0]
76
+
77
+ if (options.unsecure) {
78
+ await page.setBypassCSP(true)
79
+ }
80
+
81
+ page
82
+ .on('console', message => consoleWriter.append({
83
+ type: message.type(),
84
+ text: message.text()
85
+ }))
86
+ .on('response', response => {
87
+ const request = response.request()
88
+ networkWriter.append({
89
+ method: request.method(),
90
+ url: response.url(),
91
+ status: response.status()
92
+ })
93
+ })
94
+
95
+ if (scripts && scripts.length) {
96
+ for await (const script of scripts) {
97
+ await page.evaluateOnNewDocument(script)
98
+ }
99
+ }
100
+
101
+ await page.setDefaultNavigationTimeout(0)
102
+ await page.goto(url)
103
+ },
104
+
105
+ async error ({ error: e, exit }) {
106
+ // Lots of threads on this message but no clear solution
107
+ if (e.message === 'Navigation failed because browser has disconnected!') {
108
+ await exit(0)
109
+ }
110
+ }
111
+ })
@@ -0,0 +1,38 @@
1
+ (function () {
2
+ /* global punybind, punyexpr */
3
+
4
+ const report = {}
5
+
6
+ report.elapsed = function (start, end = Date.now()) {
7
+ if (typeof end === 'string') {
8
+ end = new Date(end).getTime()
9
+ }
10
+ if (typeof start === 'string') {
11
+ start = new Date(start).getTime()
12
+ }
13
+ const ms = end - start
14
+ if (isNaN(ms)) {
15
+ return '-'
16
+ }
17
+ if (ms > 5000) {
18
+ const mins = Math.floor(ms / 60000)
19
+ const secs = Math.floor((ms % 60000) / 1000)
20
+ return `${mins.toString().padStart(2, 0)}:${secs.toString().padStart(2, 0)}`
21
+ }
22
+ return `${ms} ms`
23
+ }
24
+
25
+ let setReady
26
+ report.ready = new Promise(resolve => {
27
+ setReady = resolve
28
+ })
29
+
30
+ window.addEventListener('load', () => {
31
+ const safebind = punybind.use({
32
+ compiler: punyexpr
33
+ })
34
+ safebind(document.body).then(setReady)
35
+ })
36
+
37
+ window.report = report
38
+ }())