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.
- package/README.md +32 -188
- package/index.js +47 -16
- package/package.json +28 -10
- package/src/add-test-pages.js +35 -0
- package/src/add-test-pages.spec.js +95 -0
- package/src/browser.spec.js +724 -0
- package/src/browsers.js +220 -59
- package/src/capabilities/index.js +194 -0
- package/src/capabilities/tests/basic/iframe.html +8 -0
- package/src/capabilities/tests/basic/index.html +12 -0
- package/src/capabilities/tests/basic/index.js +20 -0
- package/src/capabilities/tests/basic/ui5.html +24 -0
- package/src/capabilities/tests/dynamic-include/index.js +21 -0
- package/src/capabilities/tests/dynamic-include/mix.html +11 -0
- package/src/capabilities/tests/dynamic-include/one.html +11 -0
- package/src/capabilities/tests/dynamic-include/post.js +3 -0
- package/src/capabilities/tests/dynamic-include/test.js +1 -0
- package/src/capabilities/tests/dynamic-include/two.html +11 -0
- package/src/capabilities/tests/index.js +16 -0
- package/src/capabilities/tests/local-storage/index.html +16 -0
- package/src/capabilities/tests/local-storage/index.js +21 -0
- package/src/capabilities/tests/screenshot/index.html +13 -0
- package/src/capabilities/tests/screenshot/index.js +18 -0
- package/src/capabilities/tests/scripts/index.js +50 -0
- package/src/capabilities/tests/scripts/qunit.html +22 -0
- package/src/capabilities/tests/scripts/testsuite.html +10 -0
- package/src/capabilities/tests/scripts/testsuite.js +8 -0
- package/src/capabilities/tests/timeout/index.html +21 -0
- package/src/capabilities/tests/timeout/index.js +19 -0
- package/src/capabilities/tests/traces/index.html +18 -0
- package/src/capabilities/tests/traces/index.js +81 -0
- package/src/cors.js +1 -1
- package/src/cors.spec.js +41 -0
- package/src/coverage.js +30 -18
- package/src/coverage.spec.js +79 -0
- package/src/csv-reader.js +36 -0
- package/src/csv-reader.spec.js +42 -0
- package/src/csv-writer.js +52 -0
- package/src/csv-writer.spec.js +77 -0
- package/src/defaults/browser.js +144 -0
- package/src/defaults/jsdom/compatibility.js +95 -0
- package/src/defaults/jsdom/debug.js +23 -0
- package/src/defaults/jsdom/resource-loader.js +43 -0
- package/src/defaults/jsdom/sap.ui.test.matchers.visible.js +39 -0
- package/src/defaults/jsdom.js +64 -0
- package/src/defaults/junit-xml-report.js +64 -0
- package/src/defaults/puppeteer.js +111 -0
- package/src/defaults/report/common.js +38 -0
- package/src/defaults/report/default.html +84 -0
- package/src/defaults/report/main.js +44 -0
- package/src/defaults/report/progress.js +49 -0
- package/src/defaults/report/styles.css +66 -0
- package/src/defaults/report.js +69 -0
- package/src/defaults/selenium-webdriver/chrome.js +38 -0
- package/src/defaults/selenium-webdriver/edge.js +25 -0
- package/src/defaults/selenium-webdriver/firefox.js +31 -0
- package/src/defaults/selenium-webdriver.js +138 -0
- package/src/endpoints.js +70 -124
- package/src/error.js +52 -0
- package/src/error.spec.js +17 -0
- package/src/get-job-progress.js +69 -0
- package/src/get-job-progress.spec.js +175 -0
- package/src/inject/post.js +96 -0
- package/src/inject/post.spec.js +147 -0
- package/src/inject/qunit-hooks.js +6 -21
- package/src/inject/qunit-intercept.js +30 -0
- package/src/inject/qunit-redirect.js +15 -7
- package/src/job-mode.js +45 -0
- package/src/job.js +254 -108
- package/src/job.spec.js +413 -0
- package/src/npm.js +73 -0
- package/src/npm.spec.js +98 -0
- package/src/options.js +73 -0
- package/src/options.spec.js +125 -0
- package/src/output.js +450 -131
- package/src/qunit-hooks.js +116 -0
- package/src/qunit-hooks.spec.js +687 -0
- package/src/report.js +42 -0
- package/src/reserve.js +3 -4
- package/src/simulate.spec.js +437 -0
- package/src/symbols.js +8 -0
- package/src/tests.js +127 -84
- package/src/timeout.spec.js +39 -0
- package/src/tools.js +111 -4
- package/src/tools.spec.js +90 -0
- package/src/ui5.js +3 -3
- package/src/unhandled.js +6 -6
- package/src/unhandled.spec.js +63 -0
- package/defaults/chromium.js +0 -62
- package/src/progress.html +0 -71
- package/src/proxies.js +0 -8
- package/src/report.html +0 -202
- /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
|
+
'<': '<',
|
|
15
|
+
'>': '>',
|
|
16
|
+
'&': '&',
|
|
17
|
+
'"': '"'
|
|
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
|
+
}())
|