ui5-test-runner 5.7.1 → 5.7.2

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/index.js CHANGED
@@ -16,6 +16,8 @@ const { start } = require('./src/start')
16
16
  const { executeIf } = require('./src/if')
17
17
  const { batch } = require('./src/batch')
18
18
  const { end } = require('./src/end')
19
+ const { checkLatest } = require('./src/npm')
20
+ const { cleanHandles } = require('./src/clean')
19
21
 
20
22
  function send (message) {
21
23
  if (process.send) {
@@ -50,11 +52,12 @@ async function main () {
50
52
  job = fromCmdLine(process.cwd(), process.argv.slice(2))
51
53
  output = getOutput(job)
52
54
  await recreateDir(job.reportDir)
53
- output.version()
55
+ const { name, version } = output.version()
54
56
  if (job.batchMode) {
55
57
  output.batchMode()
56
58
  }
57
59
  output.reportOnJobProgress()
60
+ checkLatest(job, name, version)
58
61
  if (job.mode === 'capabilities') {
59
62
  return capabilities(job)
60
63
  }
@@ -149,6 +152,8 @@ async function main () {
149
152
  if (startedCommand) {
150
153
  await startedCommand.stop()
151
154
  }
155
+ output.debug('main', 'terminated')
156
+ cleanHandles(job)
152
157
  }
153
158
 
154
159
  main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui5-test-runner",
3
- "version": "5.7.1",
3
+ "version": "5.7.2",
4
4
  "description": "Standalone test runner for UI5",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -22,7 +22,8 @@
22
22
  "test:report": "node ./src/defaults/report.js ./test/report && reserve --config ./test/report/reserve.json",
23
23
  "test:text-report": "node ./src/defaults/text-report.js ./test/report",
24
24
  "build:doc": "node build/doc",
25
- "clean": "npm uninstall -g ui5-test-runner puppeteer nyc selenium-webdriver playwright webdriverio jsdom"
25
+ "clean": "npm uninstall -g ui5-test-runner puppeteer nyc selenium-webdriver playwright webdriverio jsdom",
26
+ "update": "ncu -i --format group"
26
27
  },
27
28
  "repository": {
28
29
  "type": "git",
@@ -47,21 +48,22 @@
47
48
  "commander": "^12.1.0",
48
49
  "ps-tree": "^1.2.0",
49
50
  "punybind": "^1.2.1",
50
- "punyexpr": "1.0.4",
51
+ "punyexpr": "1.1.1",
51
52
  "reserve": "2.2.0"
52
53
  },
53
54
  "devDependencies": {
54
- "@openui5/types": "^1.135.0",
55
- "@ui5/cli": "^4.0.14",
55
+ "@openui5/types": "^1.136.0",
56
+ "@ui5/cli": "^4.0.18",
56
57
  "@ui5/middleware-code-coverage": "^2.0.1",
57
58
  "dotenv": "^16.5.0",
58
59
  "jest": "^29.7.0",
59
- "nock": "^14.0.3",
60
+ "nock": "^14.0.5",
61
+ "npm-check-updates": "^18.0.1",
60
62
  "nyc": "^17.1.0",
61
63
  "rimraf": "^6.0.1",
62
64
  "standard": "^17.1.2",
63
65
  "typescript": "^5.8.3",
64
- "ui5-tooling-transpile": "^3.7.5"
66
+ "ui5-tooling-transpile": "^3.8.0"
65
67
  },
66
68
  "optionalDependencies": {
67
69
  "fsevents": "^2.3.3"
@@ -83,6 +83,9 @@ async function capabilities (job) {
83
83
  if (job.logServer) {
84
84
  server.on('redirected', output.redirected)
85
85
  }
86
+ server.on('error', (error) => {
87
+ output.error('REserve error:', error)
88
+ })
86
89
  await new Promise(resolve => server
87
90
  .on('ready', ({ port }) => {
88
91
  job.port = port
@@ -7,14 +7,12 @@ module.exports = [{
7
7
  url: 'basic/index.html'
8
8
  }, {
9
9
  label: 'Loads a UI5 example',
10
- for: capabilities => !capabilities.modules.includes('jsdom'), // does not work anymore on JSDOM
11
10
  url: 'basic/ui5.html',
12
11
  endpoint: ({ body }) => {
13
12
  assert.strictEqual(body['sap.m.Button'], true)
14
13
  }
15
14
  }, {
16
15
  label: 'Loads a UI5 inside an iframe',
17
- for: capabilities => !capabilities.modules.includes('jsdom'), // does not work anymore on JSDOM
18
16
  url: 'basic/iframe.html',
19
17
  endpoint: ({ body }) => {
20
18
  assert.strictEqual(body['sap.m.Button'], true)
@@ -14,7 +14,7 @@
14
14
  sap.ui.getCore().attachInit(function() {
15
15
  const xhr = new XMLHttpRequest()
16
16
  xhr.open('POST', '/_/log')
17
- if (parent !== window) {
17
+ if (parent !== window && parent.document.location.toString() !== 'about:blank') {
18
18
  xhr.setRequestHeader('x-page-url', parent.document.location.toString())
19
19
  }
20
20
  xhr.send('{"sap.m.Button":' + !!sap.m.Button + '}')
package/src/clean.js ADDED
@@ -0,0 +1,29 @@
1
+ const { getOutput } = require('./output')
2
+
3
+ module.exports = {
4
+ cleanHandles (job) {
5
+ const output = getOutput(job)
6
+ const activeHandles = process._getActiveHandles ? process._getActiveHandles() : []
7
+ let displayWarning = true
8
+ for (const handle of activeHandles) {
9
+ const className = handle && handle.constructor && handle.constructor.name
10
+ output.debug('handle', 'active handle', className)
11
+ if (className === 'TLSSocket') {
12
+ if (displayWarning) {
13
+ displayWarning = false
14
+ output.detectedLeakOfHandles()
15
+ }
16
+ let info
17
+ if (handle._httpMessage) {
18
+ const { path, method, host, protocol } = handle._httpMessage
19
+ info = `${method} ${protocol}://${host}${path}`
20
+ } else {
21
+ const { localAddress, localPort, remoteAddress, remotePort } = handle
22
+ info = `from ${localAddress}:${localPort} to ${remoteAddress}:${remotePort}`
23
+ }
24
+ output.debug('handle', 'TLS socket', info)
25
+ handle.destroy()
26
+ }
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,123 @@
1
+ 'use strict'
2
+
3
+ const log = (attributes) => {
4
+ console.log(JSON.stringify(attributes, (key, value) => {
5
+ if (key === 'headers' && typeof value === 'object') {
6
+ const literal = {}
7
+ value.forEach((headerValue, headerName) => {
8
+ literal[headerName] = headerValue
9
+ })
10
+ return literal
11
+ }
12
+ return value
13
+ }, 2))
14
+ }
15
+
16
+ require('./browser')({
17
+ metadata: {
18
+ name: 'happy-dom',
19
+ options: [
20
+ ['--debug [flag]', 'Enable more traces', false]
21
+ ],
22
+ capabilities: {
23
+ modules: ['happy-dom'],
24
+ scripts: true,
25
+ traces: ['multiplex']
26
+ }
27
+ },
28
+
29
+ async run ({
30
+ settings: { url, scripts, modules }
31
+ // options
32
+ }) {
33
+ const happyDom = require(modules['happy-dom'])
34
+ const { Browser } = happyDom
35
+ const browser = new Browser({
36
+ settings: {
37
+ fetch: {
38
+ interceptor: {
39
+ beforeAsyncRequest: async ({ request: { method, url, headers } }) => {
40
+ log({
41
+ timestamp: new Date().toISOString(),
42
+ channel: 'network',
43
+ type: 'request',
44
+ async: true,
45
+ request: { method, url, headers }
46
+ })
47
+ },
48
+ beforeSyncRequest: ({ request: { method, url, headers } }) => {
49
+ log({
50
+ timestamp: new Date().toISOString(),
51
+ channel: 'network',
52
+ type: 'request',
53
+ async: false,
54
+ request: { method, url, headers }
55
+ })
56
+ },
57
+ afterAsyncResponse: async ({ request: { method, url, headers }, response, window }) => {
58
+ const body = await response.text()
59
+ log({
60
+ timestamp: new Date().toISOString(),
61
+ channel: 'network',
62
+ type: 'response',
63
+ async: true,
64
+ request: { method, url, headers },
65
+ response: {
66
+ ...response,
67
+ body
68
+ }
69
+ })
70
+ return new window.Response(body, {
71
+ status: response.status,
72
+ statusText: response.statusText,
73
+ headers: response.headers
74
+ })
75
+ },
76
+ afterSyncResponse: ({ request: { method, url, headers }, response }) => {
77
+ log({
78
+ timestamp: new Date().toISOString(),
79
+ channel: 'network',
80
+ type: 'response',
81
+ async: false,
82
+ request: { method, url, headers },
83
+ response: {
84
+ ...response,
85
+ body: response.body.toString('utf8')
86
+ }
87
+ })
88
+ }
89
+ }
90
+ }
91
+ },
92
+ console: {
93
+ error: (...args) => log({
94
+ timestamp: new Date().toISOString(),
95
+ channel: 'console',
96
+ type: 'error',
97
+ message: args.join(' ')
98
+ }),
99
+ warn: (...args) => log({
100
+ timestamp: new Date().toISOString(),
101
+ channel: 'console',
102
+ type: 'warning',
103
+ message: args.join(' ')
104
+ }),
105
+ info: (...args) => log({
106
+ timestamp: new Date().toISOString(),
107
+ channel: 'console',
108
+ type: 'info',
109
+ message: args.join(' ')
110
+ }),
111
+ log: (...args) => log({
112
+ timestamp: new Date().toISOString(),
113
+ channel: 'console',
114
+ type: 'log',
115
+ message: args.join(' ')
116
+ })
117
+ }
118
+ })
119
+ const page = browser.newPage()
120
+ scripts.forEach(script => page.mainFrame.window.eval(script))
121
+ page.goto(url)
122
+ }
123
+ })
@@ -115,8 +115,8 @@ function fixCaseSensitiveSelectors ({ Document }) {
115
115
  const { querySelector, querySelectorAll } = Document.prototype
116
116
  Object.assign(Document.prototype, {
117
117
  querySelector (selectors) {
118
- const result = querySelector.call(this, selectors) || { length: 0 }
119
- if (result.length === 0 && selectors.match(uppercaseTag)) {
118
+ const result = querySelector.call(this, selectors)
119
+ if (result === null && selectors.match(uppercaseTag)) {
120
120
  console.log(JSON.stringify({
121
121
  timestamp: new Date().toISOString(),
122
122
  channel: 'debug',
@@ -23,6 +23,12 @@ require('./browser')({
23
23
  const { JSDOM, VirtualConsole } = jsdom
24
24
 
25
25
  const virtualConsole = new VirtualConsole()
26
+ virtualConsole.on('jsdomError', (...args) => console.log(JSON.stringify({
27
+ timestamp: new Date().toISOString(),
28
+ channel: 'console',
29
+ type: 'jsdomError',
30
+ message: args.join(' ')
31
+ })))
26
32
  virtualConsole.on('error', (...args) => console.log(JSON.stringify({
27
33
  timestamp: new Date().toISOString(),
28
34
  channel: 'console',
@@ -2,7 +2,9 @@
2
2
  <head>
3
3
  <title>ui5-test-runner</title>
4
4
  <link rel="stylesheet" href="/_/report/styles.css">
5
+ <script>window.module = window.module || {}</script>
5
6
  <script src="/_/punyexpr.js"></script>
7
+ <script>window.punyexpr = module.exports.punyexpr</script>
6
8
  <script src="/_/punybind.js"></script>
7
9
  <script src="/_/report/common.js"></script>
8
10
  <script src="/_/report/main.js"></script>
package/src/job.js CHANGED
@@ -150,7 +150,7 @@ function getCommand (cwd) {
150
150
  .option('--start-timeout <timeout>', '[💻🔗] Maximum waiting time for the start command (based on when the first URL becomes available)', timeout, 5000)
151
151
 
152
152
  .option('--end <script>', '[💻🔗] End script (will receive path to `job.js`)', string)
153
- .option('--end-timeout <timeout>', '[💻🔗] Maximum waiting time for the end script', timeout, 5000)
153
+ .option('--end-timeout <timeout>', '[💻🔗] Maximum waiting time for the end script', timeout, 15000)
154
154
 
155
155
  // Specific to legacy (and might be used with url if pointing to local project)
156
156
  .option('--ui5 <url>', '[💻📡] UI5 url', url, 'https://ui5.sap.com')
package/src/npm.js CHANGED
@@ -73,11 +73,41 @@ async function findDependencyPath (job, name) {
73
73
  return [globalPath, justInstalled]
74
74
  }
75
75
 
76
+ const noop = () => {}
77
+
78
+ function getSafeJobAndOutput (nullableJob) {
79
+ if (nullableJob) {
80
+ return { job: nullableJob, output: getOutput(nullableJob) }
81
+ }
82
+ return {
83
+ job: {},
84
+ output: {
85
+ debug: noop,
86
+ resolvedPackage: noop,
87
+ packageNotLatest: noop
88
+ }
89
+ }
90
+ }
91
+
92
+ async function checkLatest (nullableJob, name, installedVersion) {
93
+ const { job, output } = getSafeJobAndOutput(nullableJob)
94
+ if (!job.offline) {
95
+ output.debug('npm', `fetching latest version of package ${name}`)
96
+ const latestVersion = await npm(job, 'view', name, 'version')
97
+ if (latestVersion !== installedVersion) {
98
+ output.packageNotLatest(name, latestVersion)
99
+ }
100
+ } else {
101
+ output.debug('npm', `offline=${job.offline}`)
102
+ }
103
+ }
104
+
76
105
  module.exports = {
77
106
  resolveDependencyPath,
107
+ checkLatest,
78
108
 
79
- async resolvePackage (job, name) {
80
- const output = getOutput(job)
109
+ async resolvePackage (nullableJob, name) {
110
+ const { job, output } = getSafeJobAndOutput(nullableJob)
81
111
  let modulePath
82
112
  let justInstalled = false
83
113
  output.debug('npm', `resolving dependency path of package ${name}...`)
@@ -94,14 +124,10 @@ module.exports = {
94
124
  const installedPackage = require(join(modulePath, 'package.json'))
95
125
  const { version: installedVersion } = installedPackage
96
126
  output.resolvedPackage(name, modulePath, installedVersion)
97
- if (!justInstalled && !job.offline) {
98
- output.debug('npm', `fetching latest version of package ${name}`)
99
- const latestVersion = await npm(job, 'view', name, 'version')
100
- if (latestVersion !== installedVersion) {
101
- output.packageNotLatest(name, latestVersion)
102
- }
127
+ if (!justInstalled) {
128
+ checkLatest(job, name, installedVersion)
103
129
  } else {
104
- output.debug('npm', `justInstalled=${justInstalled} offline=${job.offline}`)
130
+ output.debug('npm', `justInstalled=${justInstalled}`)
105
131
  }
106
132
  return modulePath
107
133
  }
package/src/output.js CHANGED
@@ -270,6 +270,7 @@ function build (job) {
270
270
  if (job.debugDevMode) {
271
271
  log(job, p80()`⚠️ Development mode ⚠️`)
272
272
  }
273
+ return { name, version }
273
274
  },
274
275
 
275
276
  serving: url => {
@@ -384,6 +385,10 @@ function build (job) {
384
385
  wrap(() => log(job, `⚠️ [PKGVRS] latest version of ${name} is ${latestVersion}`))()
385
386
  },
386
387
 
388
+ detectedLeakOfHandles () {
389
+ wrap(() => log(job, '⚠️ [HDLEAK] leaking Node.js handle(s) detected. This may cause issues with the shutdown'))()
390
+ },
391
+
387
392
  browserStart (url) {
388
393
  const text = p80()`${getElapsed(job)} >> ${pad.lt(url)} [${filename(url)}]`
389
394
  if (interactive) {
package/src/report.js CHANGED
@@ -53,9 +53,14 @@ module.exports = {
53
53
  job.end = new Date()
54
54
  job.failed = !!job.failed
55
55
  job.testPageHashes = job.testPageUrls.map(url => filename(url))
56
+ output.debug('report', 'saving job...')
56
57
  await save(job)
58
+ output.debug('report', 'job saved.')
59
+ output.debug('report', 'generating text report...')
57
60
  await generateTextReport(job)
61
+ output.debug('report', 'text report generated.')
58
62
  const promises = job.reportGenerator.map(generator => {
63
+ output.debug('report', 'launching', generator, '...')
59
64
  const { promise, resolve } = allocPromise()
60
65
  const childProcess = fork(
61
66
  generator,
@@ -70,13 +75,16 @@ module.exports = {
70
75
  )
71
76
  const buffers = output.monitor(childProcess, false)
72
77
  childProcess.on('close', exitCode => {
78
+ output.debug('report', generator, 'ended with exit code', exitCode)
73
79
  if (exitCode !== 0) {
74
80
  output.reportGeneratorFailed(generator, exitCode, buffers)
75
81
  }
76
82
  resolve()
83
+ output.debug('report', generator, 'resolved')
77
84
  })
78
85
  return promise
79
86
  })
87
+ output.debug('report', 'generators count:', promises.length)
80
88
  await Promise.all(promises)
81
89
  job.status = 'Reports generated'
82
90
  }