ui5-test-runner 1.1.4 → 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 (92) hide show
  1. package/README.md +32 -188
  2. package/index.js +47 -16
  3. package/package.json +30 -12
  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 -2
  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/report.html +0 -202
  92. /package/{defaults → src/defaults}/nyc.json +0 -0
package/src/endpoints.js CHANGED
@@ -2,86 +2,72 @@
2
2
 
3
3
  const { join } = require('path')
4
4
  const { body } = require('reserve')
5
- const { screenshot, stop } = require('./browsers')
6
- const { writeFile } = require('fs').promises
7
- const { extractUrl, filename } = require('./tools')
5
+ const { extractPageUrl } = require('./tools')
8
6
  const { Request, Response } = require('reserve')
9
- const output = require('./output')
7
+ const { getOutput } = require('./output')
8
+ const { begin, testStart, log, testDone, done } = require('./qunit-hooks')
9
+ const { addTestPages } = require('./add-test-pages')
10
+ const { getJobProgress } = require('./get-job-progress')
11
+ const { readFile } = require('fs/promises')
12
+ const { TextEncoder } = require('util')
10
13
 
11
14
  module.exports = job => {
12
- async function endpointImpl (implementation, request) {
13
- const url = extractUrl(request.headers)
15
+ async function endpointImpl (api, implementation, request) {
16
+ const url = extractPageUrl(request.headers)
14
17
  const data = JSON.parse(await body(request))
15
- if (job.parallel === -1) {
16
- output.endpoint(url, data)
17
- }
18
18
  try {
19
19
  await implementation.call(this, url, data)
20
- } catch (e) {
21
- output.endpointError(url, data, e)
20
+ } catch (error) {
21
+ getOutput(job).endpointError({ api, url, data, error })
22
22
  }
23
23
  }
24
24
 
25
- function synchronousEndpoint (implementation) {
25
+ function synchronousEndpoint (api, implementation) {
26
26
  return async function (request, response) {
27
- await endpointImpl(implementation, request)
27
+ await endpointImpl(api, implementation, request)
28
28
  response.writeHead(200)
29
29
  response.end()
30
30
  }
31
31
  }
32
32
 
33
- function endpoint (implementation) {
33
+ function endpoint (api, implementation) {
34
34
  return async function (request, response) {
35
35
  response.writeHead(200)
36
36
  response.end()
37
- await endpointImpl(implementation, request)
37
+ await endpointImpl(api, implementation, request)
38
38
  }
39
39
  }
40
40
 
41
- function getPageTest (page, testId) {
42
- const { tests, order } = page
43
- if (!tests[testId]) {
44
- tests[testId] = {
45
- timestamps: []
46
- }
47
- order.push(testId)
48
- }
49
- return tests[testId]
41
+ async function getInjects (...names) {
42
+ return '\n;\n' + (await Promise.all(names.map(name => readFile(join(__dirname, 'inject', `${name}.js`)))))
43
+ .map(buffer => buffer.toString())
44
+ .join('\n;\n')
45
+ }
46
+
47
+ function contentLength (content) {
48
+ return new TextEncoder().encode(content).length
49
+ }
50
+
51
+ function sendScript (response, content) {
52
+ response.writeHead(200, {
53
+ 'content-type': 'text/javascript',
54
+ 'content-length': contentLength(content),
55
+ 'cache-control': 'no-store'
56
+ })
57
+ response.end(content)
50
58
  }
51
59
 
52
60
  return job.parallel
53
61
  ? [{
54
62
  // Substitute qunit-redirect to extract test pages
55
63
  match: '/resources/sap/ui/qunit/qunit-redirect.js',
56
- file: join(__dirname, './inject/qunit-redirect.js')
64
+ custom: async (request, response) => {
65
+ sendScript(response, await getInjects('post', 'qunit-redirect'))
66
+ }
57
67
  }, {
58
68
  // Endpoint to receive test pages
59
69
  match: '^/_/addTestPages',
60
- custom: endpoint(async (url, data) => {
61
- let testPageUrls
62
- if (job.pageFilter) {
63
- const filter = new RegExp(job.pageFilter)
64
- testPageUrls = data.filter(name => name.match(filter))
65
- } else {
66
- testPageUrls = data
67
- }
68
- if (job.pageParams) {
69
- testPageUrls = testPageUrls.map(url => {
70
- if (url.includes('?')) {
71
- return url + '&' + job.pageParams
72
- }
73
- return url + '?' + job.pageParams
74
- })
75
- }
76
- const pages = testPageUrls.reduce((mapping, page) => {
77
- mapping[page] = filename(page)
78
- return mapping
79
- }, {})
80
- const pagesFileName = join(job.tstReportDir, 'pages.json')
81
- await writeFile(pagesFileName, JSON.stringify(pages))
82
- job.testPageUrls = Object.keys(pages) // filter out duplicates
83
- stop(job, url)
84
- })
70
+ custom: endpoint('addTestPages', (url, pages) => addTestPages(job, url, pages))
85
71
  }, {
86
72
  // QUnit hooks
87
73
  match: '^/_/qunit-hooks.js',
@@ -96,13 +82,11 @@ module.exports = job => {
96
82
  const ui5Request = new Request('GET', request.url)
97
83
  ui5Request.internal = true
98
84
  const ui5Response = new Response()
99
- const hooksRequest = new Request('GET', '/_/qunit-hooks.js')
100
- const hooksResponse = new Response()
101
- await Promise.all([
102
- this.configuration.dispatch(ui5Request, ui5Response),
103
- this.configuration.dispatch(hooksRequest, hooksResponse)
85
+ const [inject] = await Promise.all([
86
+ getInjects('post', 'qunit-hooks'),
87
+ this.configuration.dispatch(ui5Request, ui5Response)
104
88
  ])
105
- const hooksLength = parseInt(hooksResponse.headers['content-length'], 10)
89
+ const hooksLength = contentLength(inject)
106
90
  const ui5Length = parseInt(ui5Response.headers['content-length'], 10)
107
91
  response.writeHead(ui5Response.statusCode, {
108
92
  ...ui5Response.headers,
@@ -110,94 +94,56 @@ module.exports = job => {
110
94
  'cache-control': 'no-store' // for debugging purpose
111
95
  })
112
96
  response.write(ui5Response.toString())
113
- response.end(hooksResponse.toString())
97
+ response.end(inject)
114
98
  }
115
99
  }, {
116
100
  // Endpoint to receive QUnit.begin
117
101
  match: '^/_/QUnit/begin',
118
- custom: endpoint((url, details) => {
119
- const page = {
120
- isOpa: details.isOpa,
121
- total: details.totalTests,
122
- failed: 0,
123
- passed: 0,
124
- tests: {},
125
- order: []
126
- }
127
- details.modules.forEach(module => {
128
- module.tests.forEach(test => getPageTest(page, test.testId))
129
- })
130
- job.testPages[url] = page
131
- })
102
+ custom: endpoint('QUnit/begin', (url, details) => begin(job, url, details))
103
+ }, {
104
+ // Endpoint to receive QUnit.testStart
105
+ match: '^/_/QUnit/testStart',
106
+ custom: endpoint('QUnit/testStart', (url, details) => testStart(job, url, details))
132
107
  }, {
133
108
  // Endpoint to receive QUnit.log
134
109
  match: '^/_/QUnit/log',
135
- custom: synchronousEndpoint(async (url, report) => {
136
- const page = job.testPages[url]
137
- if (page.isOpa) {
138
- const { testId, runtime } = report
139
- getPageTest(page, testId).timestamps.push(runtime)
140
- await screenshot(job, url, `${testId}-${runtime}.png`)
141
- }
142
- })
110
+ custom: synchronousEndpoint('QUnit/log', async (url, report) => log(job, url, report))
143
111
  }, {
144
112
  // Endpoint to receive QUnit.testDone
145
113
  match: '^/_/QUnit/testDone',
146
- custom: synchronousEndpoint(async (url, report) => {
147
- const page = job.testPages[url]
148
- const { testId } = report
149
- if (report.failed) {
150
- await screenshot(job, url, `${testId}.png`)
151
- job.failed = true
152
- ++page.failed
153
- } else {
154
- ++page.passed
155
- }
156
- getPageTest(page, testId).report = report
157
- })
114
+ custom: synchronousEndpoint('QUnit/testDone', async (url, report) => testDone(job, url, report))
158
115
  }, {
159
116
  // Endpoint to receive QUnit.done
160
117
  match: '^/_/QUnit/done',
161
- custom: endpoint(async (url, report) => {
162
- const page = job.testPages[url]
163
- if (page) {
164
- await screenshot(job, url, 'screenshot.png')
165
- if (report.__coverage__) {
166
- const coverageFileName = join(job.covTempDir, `${filename(url)}.json`)
167
- await writeFile(coverageFileName, JSON.stringify(report.__coverage__))
168
- delete report.__coverage__
169
- }
170
- page.report = report
171
- }
172
- stop(job, url)
173
- })
118
+ custom: endpoint('QUnit/done', async (url, report) => done(job, url, report))
174
119
  }, {
175
120
  // UI to follow progress
176
121
  match: '^/_/progress.html',
177
- file: join(__dirname, 'progress.html')
122
+ file: job.progressPage
123
+ }, {
124
+ // Report 'main' substituted for progress
125
+ match: '^/_/report/main.js',
126
+ file: join(__dirname, 'defaults/report/progress.js')
127
+ }, {
128
+ // Other report resources
129
+ match: '^/_/report/(.*)',
130
+ file: join(__dirname, 'defaults/report/$1')
131
+ }, {
132
+ // punybind
133
+ match: '^/_/punybind.js',
134
+ file: join(__dirname, '../node_modules/punybind/dist/punybind.js')
135
+ }, {
136
+ // punyexpr
137
+ match: '^/_/punyexpr.js',
138
+ file: join(__dirname, '../node_modules/punyexpr/dist/punyexpr.js')
178
139
  }, {
179
140
  // Endpoint to follow progress
180
- match: '^/_/progress',
181
- custom: async (request, response) => {
182
- const json = JSON.stringify(job, (key, value) => {
183
- if (((key === 'tests' || key === 'browsers') && typeof value === 'object') ||
184
- (key === 'order' && Array.isArray(value))
185
- ) {
186
- return undefined // Filter out
187
- }
188
- return value
189
- })
190
- const length = (new TextEncoder().encode(json)).length
191
- response.writeHead(200, {
192
- 'Content-Type': 'application/json',
193
- 'Content-Length': length
194
- })
195
- response.end(json)
196
- }
141
+ match: '^/_/progress(?:\\?page=([^&]*)(?:&test=([^&]*))?)?',
142
+ custom: (request, response, pageId, testId) => getJobProgress(job, request, response, pageId, testId)
197
143
  }, {
198
144
  // Endpoint to coverage files
199
145
  match: '^/_/coverage/(.*)',
200
- file: join(job.covReportDir, '$1')
146
+ file: join(job.coverageReportDir, '$1')
201
147
  }, {
202
148
  // Endpoint to report
203
149
  match: '^/_/report.html',
@@ -205,7 +151,7 @@ module.exports = job => {
205
151
  }, {
206
152
  // Endpoint to report files
207
153
  match: '^/_/(.*)',
208
- file: join(job.tstReportDir, '$1')
154
+ file: join(job.reportDir, '$1')
209
155
  }]
210
156
  : []
211
157
  }
package/src/error.js ADDED
@@ -0,0 +1,52 @@
1
+ class UTRError extends Error {
2
+ get code () { return this._code }
3
+
4
+ constructor (error, details) {
5
+ super()
6
+ this.name = `UTRError:${error.name}`
7
+ this._code = error.code
8
+ if (details) {
9
+ this.message = details
10
+ } else {
11
+ this.message = error.name
12
+ }
13
+ }
14
+ }
15
+
16
+ const errors = [{
17
+ name: 'GENERIC'
18
+ }, {
19
+ name: 'NPM_FAILED'
20
+ }, {
21
+ name: 'MISSING_OR_INVALID_BROWSER_CAPABILITIES'
22
+ }, {
23
+ name: 'BROWSER_PROBE_FAILED'
24
+ }, {
25
+ name: 'BROWSER_FAILED'
26
+ }, {
27
+ name: 'BROWSER_SCREENSHOT_FAILED'
28
+ }, {
29
+ name: 'BROWSER_SCREENSHOT_TIMEOUT'
30
+ }, {
31
+ name: 'BROWSER_SCREENSHOT_NOT_SUPPORTED'
32
+ }, {
33
+ name: 'QUNIT_ERROR'
34
+ }, {
35
+ name: 'MODE_INCOMPATIBLE_OPTION'
36
+ }, {
37
+ name: 'BROWSER_MISS_SCRIPTS_CAPABILITY'
38
+ }, {
39
+ name: 'NPM_DEPENDENCY_NOT_FOUND'
40
+ }]
41
+
42
+ errors.forEach((error, index) => {
43
+ error.code = -1 - index
44
+ UTRError[`${error.name}_CODE`] = error.code
45
+ UTRError[error.name] = function (details = '') {
46
+ return new UTRError(error, details)
47
+ }
48
+ })
49
+
50
+ module.exports = {
51
+ UTRError
52
+ }
@@ -0,0 +1,17 @@
1
+ const { UTRError } = require('./error')
2
+
3
+ describe('src/errors', () => {
4
+ it('generates a fully documented error', () => {
5
+ const error = UTRError.NPM_FAILED('test')
6
+ expect(error.name).toStrictEqual('UTRError:NPM_FAILED')
7
+ expect(error.code).toBeLessThan(0)
8
+ expect(error.message).toStrictEqual('test')
9
+ })
10
+
11
+ it('generates a generic error', () => {
12
+ const error = UTRError.NPM_FAILED()
13
+ expect(error.name).toStrictEqual('UTRError:NPM_FAILED')
14
+ expect(error.code).toBeLessThan(0)
15
+ expect(error.message).toStrictEqual('NPM_FAILED')
16
+ })
17
+ })
@@ -0,0 +1,69 @@
1
+ 'use strict'
2
+
3
+ const send = (response, obj) => {
4
+ let json
5
+ if (typeof obj !== 'string') {
6
+ json = JSON.stringify(obj, undefined, 2)
7
+ } else {
8
+ json = obj
9
+ }
10
+ const length = (new TextEncoder().encode(json)).length
11
+ response.writeHead(200, {
12
+ 'Content-Type': 'application/json',
13
+ 'Content-Length': length
14
+ })
15
+ response.end(json)
16
+ }
17
+
18
+ const notFound = response => {
19
+ response.writeHead(404)
20
+ response.end()
21
+ }
22
+
23
+ module.exports = {
24
+ async getJobProgress (job, request, response, pageId, testId) {
25
+ if (pageId) {
26
+ const url = Object.keys(job.qunitPages).find(pageUrl => job.qunitPages[pageUrl].id === pageId)
27
+ if (!url) {
28
+ return notFound(response)
29
+ }
30
+ const qunitPage = { url, ...job.qunitPages[url] }
31
+ if (!testId) {
32
+ return send(response, JSON.stringify(qunitPage, (key, value) => {
33
+ if (key === 'logs') {
34
+ return undefined
35
+ }
36
+ return value
37
+ }, 2))
38
+ }
39
+ let test
40
+ let moduleName
41
+ qunitPage.modules.every(module => module.tests.every(candidate => {
42
+ if (candidate.testId === testId) {
43
+ moduleName = module.name
44
+ test = candidate
45
+ return false
46
+ }
47
+ return true
48
+ }))
49
+ if (!test) {
50
+ return notFound(response)
51
+ }
52
+ return send(response, {
53
+ url,
54
+ pageId,
55
+ module: moduleName,
56
+ ...test
57
+ })
58
+ }
59
+ send(response, JSON.stringify({
60
+ ...job,
61
+ status: job.status
62
+ }, (key, value) => {
63
+ if (key === 'modules') {
64
+ return undefined
65
+ }
66
+ return value
67
+ }, 2))
68
+ }
69
+ }
@@ -0,0 +1,175 @@
1
+ const { getJobProgress } = require('./get-job-progress')
2
+ const { Response } = require('reserve')
3
+
4
+ describe('src/get-job-progress', () => {
5
+ const url = 'https://localhost:8080/tests/unitTests.qunit.html'
6
+ const id = 'abcdef'
7
+ const firstTestId = '8ef4ee8b'
8
+
9
+ const job = {
10
+ cwd: '.',
11
+ port: 8080,
12
+ ui5: 'https://ui5.sap.com',
13
+ testPageUrls: [url],
14
+ qunitPages: {
15
+ [url]: {
16
+ id,
17
+ start: '2023-01-09T12:49:34.255Z',
18
+ failed: 0,
19
+ passed: 2,
20
+ count: 3,
21
+ modules: [{
22
+ name: 'module',
23
+ tests: [{
24
+ name: 'First test',
25
+ testId: firstTestId,
26
+ logs: ['whatever it contains'],
27
+ report: {
28
+ skipped: false,
29
+ todo: false,
30
+ failed: 0,
31
+ passed: 1,
32
+ total: 1,
33
+ runtime: 3
34
+ }
35
+ }, {
36
+ name: 'Second test',
37
+ testId: '35d36b9d',
38
+ logs: [],
39
+ report: {
40
+ skipped: false,
41
+ todo: false,
42
+ failed: 0,
43
+ passed: 1,
44
+ total: 1,
45
+ runtime: 5
46
+ }
47
+ }, {
48
+ name: 'Third test',
49
+ testId: 'e32c4c98',
50
+ logs: [],
51
+ report: {
52
+ skipped: false,
53
+ todo: false,
54
+ failed: 1,
55
+ passed: 0,
56
+ total: 1,
57
+ runtime: 3
58
+ }
59
+ }]
60
+ }]
61
+ }
62
+ }
63
+ }
64
+
65
+ it('returns job without modules', async () => {
66
+ const response = new Response()
67
+ await getJobProgress(job, {}, response)
68
+ await response.waitForFinish()
69
+ const json = JSON.parse(response.toString())
70
+ expect(json).toStrictEqual({
71
+ cwd: '.',
72
+ port: 8080,
73
+ ui5: 'https://ui5.sap.com',
74
+ testPageUrls: [url],
75
+ qunitPages: {
76
+ [url]: {
77
+ id,
78
+ start: '2023-01-09T12:49:34.255Z',
79
+ failed: 0,
80
+ passed: 2,
81
+ count: 3
82
+ }
83
+ }
84
+ })
85
+ })
86
+
87
+ it('returns page without logs from page id', async () => {
88
+ const response = new Response()
89
+ await getJobProgress(job, {}, response, id)
90
+ await response.waitForFinish()
91
+ const json = JSON.parse(response.toString())
92
+ expect(json).toStrictEqual({
93
+ url,
94
+ id,
95
+ start: '2023-01-09T12:49:34.255Z',
96
+ failed: 0,
97
+ passed: 2,
98
+ count: 3,
99
+ modules: [{
100
+ name: 'module',
101
+ tests: [{
102
+ name: 'First test',
103
+ testId: '8ef4ee8b',
104
+ report: {
105
+ skipped: false,
106
+ todo: false,
107
+ failed: 0,
108
+ passed: 1,
109
+ total: 1,
110
+ runtime: 3
111
+ }
112
+ }, {
113
+ name: 'Second test',
114
+ testId: '35d36b9d',
115
+ report: {
116
+ skipped: false,
117
+ todo: false,
118
+ failed: 0,
119
+ passed: 1,
120
+ total: 1,
121
+ runtime: 5
122
+ }
123
+ }, {
124
+ name: 'Third test',
125
+ testId: 'e32c4c98',
126
+ report: {
127
+ skipped: false,
128
+ todo: false,
129
+ failed: 1,
130
+ passed: 0,
131
+ total: 1,
132
+ runtime: 3
133
+ }
134
+ }]
135
+ }]
136
+ })
137
+ })
138
+
139
+ it('returns 404 if page id is unknown', async () => {
140
+ const response = new Response()
141
+ await getJobProgress(job, {}, response, 'unknown')
142
+ await response.waitForFinish()
143
+ expect(response.statusCode).toBe(404)
144
+ })
145
+
146
+ it('returns test page and test id', async () => {
147
+ const response = new Response()
148
+ await getJobProgress(job, {}, response, id, firstTestId)
149
+ await response.waitForFinish()
150
+ const json = JSON.parse(response.toString())
151
+ expect(json).toStrictEqual({
152
+ url,
153
+ pageId: id,
154
+ module: 'module',
155
+ name: 'First test',
156
+ testId: firstTestId,
157
+ logs: ['whatever it contains'],
158
+ report: {
159
+ skipped: false,
160
+ todo: false,
161
+ failed: 0,
162
+ passed: 1,
163
+ total: 1,
164
+ runtime: 3
165
+ }
166
+ })
167
+ })
168
+
169
+ it('returns 404 if test id is unknown', async () => {
170
+ const response = new Response()
171
+ await getJobProgress(job, {}, response, id, 'unknown')
172
+ await response.waitForFinish()
173
+ expect(response.statusCode).toBe(404)
174
+ })
175
+ })
@@ -0,0 +1,96 @@
1
+ (function () {
2
+ 'use strict'
3
+
4
+ if (window['ui5-test-runner/post']) {
5
+ return
6
+ }
7
+
8
+ const base = window['ui5-test-runner/base-host'] || ''
9
+
10
+ let lastPost = Promise.resolve()
11
+ let UI5Object
12
+
13
+ function stringify (data) {
14
+ const objects = []
15
+ const referenced = []
16
+ if (!UI5Object && window.sap && window.sap.ui && window.sap.ui.base) {
17
+ UI5Object = window.sap.ui.base.Object
18
+ }
19
+ const ui5Summary = obj => {
20
+ const id = obj.getId && obj.getId()
21
+ const className = obj.getMetadata().getName()
22
+ return {
23
+ 'ui5:class': className,
24
+ 'ui5:id': id
25
+ }
26
+ }
27
+ const simple = JSON.stringify(data, function (key, value) {
28
+ if (typeof value === 'object' && value) {
29
+ if (UI5Object && value instanceof UI5Object) {
30
+ return ui5Summary(value)
31
+ }
32
+ const id = objects.indexOf(value)
33
+ if (id !== -1) {
34
+ referenced[id] = true
35
+ return null // Skip error and check all references
36
+ }
37
+ objects.push(value)
38
+ }
39
+ return value
40
+ })
41
+ if (referenced.length === 0) {
42
+ return simple
43
+ }
44
+ const stringified = []
45
+ return JSON.stringify(data, function (key, value) {
46
+ if (typeof value === 'object' && value) {
47
+ if (UI5Object && value instanceof UI5Object) {
48
+ return ui5Summary(value)
49
+ }
50
+ const id = objects.indexOf(value)
51
+ if (referenced[id]) {
52
+ if (stringified[id]) {
53
+ return { 'circular:ref': id }
54
+ }
55
+ stringified[id] = true
56
+ if (Array.isArray(value)) {
57
+ return {
58
+ 'circular:id': id,
59
+ 'circular:array': [].concat(value) // 'new' object
60
+ }
61
+ }
62
+ return Object.assign({
63
+ 'circular:id': id
64
+ }, value)
65
+ }
66
+ }
67
+ return value
68
+ })
69
+ }
70
+
71
+ window['ui5-test-runner/stringify'] = stringify
72
+
73
+ window['ui5-test-runner/post'] = function post (url, data) {
74
+ function request () {
75
+ return new Promise(function (resolve, reject) {
76
+ const xhr = new XMLHttpRequest()
77
+ xhr.addEventListener('load', () => {
78
+ resolve(xhr.responseText)
79
+ })
80
+ xhr.addEventListener('error', () => {
81
+ reject(xhr.statusText)
82
+ })
83
+ xhr.open('POST', base + '/_/' + url)
84
+ xhr.setRequestHeader('x-page-url', location)
85
+ xhr.send(stringify(data))
86
+ })
87
+ }
88
+ lastPost = lastPost
89
+ .then(undefined, function (reason) {
90
+ console.error('Failed to POST to ' + url + '\nreason: ' + reason.toString())
91
+ throw new Error('failed')
92
+ })
93
+ .then(request)
94
+ return lastPost
95
+ }
96
+ }())