UNPKG

8.59 kBJavaScriptView Raw
1'use strict'
2
3const { fork } = require('child_process')
4const { join } = require('path')
5const { writeFile, readFile, open, stat, unlink } = require('fs/promises')
6const { recreateDir, filename, allocPromise } = require('./tools')
7const { getPageTimeout, pageTimedOut } = require('./timeout')
8const { getOutput } = require('./output')
9const { resolvePackage } = require('./npm')
10const { UTRError } = require('./error')
11const { $browsers } = require('./symbols')
12
13let lastScreenshotId = 0
14const screenshots = {}
15
16async function instantiate (job, config) {
17 const { dir, url } = config
18 await recreateDir(dir)
19 const browserConfig = {
20 capabilities: job.browserCapabilities,
21 modules: job.browserModules,
22 ...config,
23 args: job.browserArgs
24 }
25 const browserConfigPath = join(dir, 'browser.json')
26 await writeFile(browserConfigPath, JSON.stringify(browserConfig, undefined, 2))
27 const stdoutFilename = join(dir, 'stdout.txt')
28 const stderrFilename = join(dir, 'stderr.txt')
29 const stdout = await open(stdoutFilename, 'w')
30 const stderr = await open(stderrFilename, 'w')
31 const childProcess = fork(job.browser, [browserConfigPath], {
32 stdio: [0, stdout, stderr, 'ipc']
33 })
34 const { promise, resolve } = allocPromise()
35 childProcess.on('close', async code => {
36 await stdout.close()
37 await stderr.close()
38 if (code !== 0) {
39 getOutput(job).browserFailed(url, code, dir)
40 }
41 resolve(code)
42 })
43 childProcess.closed = promise
44 childProcess.stdoutFilename = stdoutFilename
45 childProcess.stderrFilename = stderrFilename
46 return childProcess
47}
48
49async function probe (job) {
50 if (job.browserCapabilities) {
51 return
52 }
53 const output = getOutput(job)
54 job.status = 'Probing browser instantiation command'
55
56 async function execute (folder) {
57 const dir = join(job.reportDir, folder)
58 const capabilities = join(dir, 'capabilities.json')
59 const childProcess = await instantiate(job, {
60 url: 'about:blank',
61 capabilities,
62 dir
63 })
64 const code = await childProcess.closed
65 if (code !== 0) {
66 throw UTRError.BROWSER_PROBE_FAILED(code.toString())
67 }
68 let browserCapabilities
69 try {
70 browserCapabilities = Object.assign({
71 modules: [],
72 screenshot: null,
73 scripts: false,
74 parallel: true,
75 traces: []
76 }, JSON.parse((await readFile(capabilities)).toString()))
77 } catch (e) {
78 throw UTRError.MISSING_OR_INVALID_BROWSER_CAPABILITIES(e.message)
79 }
80 return browserCapabilities
81 }
82
83 const browserCapabilities = await execute('probe')
84 job.browserCapabilities = browserCapabilities
85
86 const { modules } = browserCapabilities
87 const resolvedModules = {}
88 if (modules.length) {
89 for await (const name of browserCapabilities.modules) {
90 resolvedModules[name] = await resolvePackage(job, name)
91 }
92 }
93 job.browserModules = resolvedModules
94 if (browserCapabilities['probe-with-modules']) {
95 job.browserCapabilities = await execute('probe/with-modules')
96 }
97
98 output.browserCapabilities(job.browserCapabilities)
99}
100
101async function start (job, url, scripts = []) {
102 const output = getOutput(job)
103 if (!job[$browsers]) {
104 job[$browsers] = {}
105 }
106 output.browserStart(url)
107 const reportDir = join(job.reportDir, filename(url))
108 const resolvedScripts = []
109 for await (const script of scripts) {
110 if (script.endsWith('.js')) {
111 const scriptFilename = join(__dirname, 'inject', script)
112 const scriptContent = (await readFile(scriptFilename)).toString()
113 resolvedScripts.push(scriptContent)
114 } else {
115 resolvedScripts.push(script)
116 }
117 }
118 if (resolvedScripts.length) {
119 resolvedScripts.unshift(`window['ui5-test-runner/base-host'] = 'http://localhost:${job.port}'
120`)
121 }
122 const pageBrowser = {
123 url,
124 reportDir,
125 scripts: resolvedScripts,
126 retry: 0
127 }
128 const { promise, resolve, reject } = allocPromise()
129 pageBrowser.done = value => {
130 delete job[$browsers][url]
131 resolve(value)
132 }
133 pageBrowser.failed = reason => {
134 delete job[$browsers][url]
135 reject(reason)
136 }
137 job[$browsers][url] = pageBrowser
138 await run(job, pageBrowser)
139 await promise
140 output.browserStopped(url)
141}
142
143async function run (job, pageBrowser) {
144 const output = getOutput(job)
145 const { url, retry, reportDir, scripts } = pageBrowser
146 let dir = reportDir
147 if (retry) {
148 output.browserRetry(url, retry)
149 dir = join(dir, retry.toString())
150 if (pageBrowser.console.count) {
151 try {
152 await pageBrowser.console.flush
153 .then(() => unlink(join(reportDir, 'console.jsonl')))
154 } catch (e) {
155 // ignore
156 }
157 }
158 }
159 pageBrowser.console = {
160 count: 0,
161 byApi: {},
162 flush: Promise.resolve()
163 }
164 await recreateDir(dir)
165 delete pageBrowser.stopped
166 const childProcess = await instantiate(job, {
167 url,
168 retry,
169 scripts,
170 dir
171 })
172 pageBrowser.childProcess = childProcess
173 const timeout = getPageTimeout(job)
174 if (timeout) {
175 pageBrowser.timeoutId = setTimeout(() => {
176 output.browserTimeout(url, dir)
177 pageTimedOut(job, url)
178 stop(job, url)
179 }, timeout)
180 }
181 childProcess.on('message', message => {
182 if (message.command === 'screenshot') {
183 const { id } = message
184 screenshots[id]()
185 delete screenshots[id]
186 } else if (message.command === 'console') {
187 ++pageBrowser.console.count
188 if (!pageBrowser.console.byApi[message.api]) {
189 pageBrowser.console.byApi[message.api] = 1
190 } else {
191 ++pageBrowser.console.byApi[message.api]
192 }
193 pageBrowser.console.flush = pageBrowser.console.flush
194 .then(() => writeFile(join(reportDir, 'console.jsonl'), JSON.stringify({
195 t: message.t,
196 api: message.api,
197 args: message.args
198 }) + '\n', {
199 flag: 'a+'
200 }))
201 }
202 })
203 childProcess.on('close', async code => {
204 if (!pageBrowser.stopped) {
205 if (code === 0) {
206 output.browserClosed(url, code, dir)
207 }
208 childProcess.closed.then(() => stop(job, url, true))
209 }
210 })
211}
212
213async function screenshot (job, url, filename) {
214 if (!job.browserCapabilities.screenshot) {
215 throw UTRError.BROWSER_SCREENSHOT_NOT_SUPPORTED()
216 }
217 const output = getOutput(job)
218 const id = ++lastScreenshotId
219 try {
220 const { childProcess, reportDir } = job[$browsers][url]
221 const absoluteFilename = join(reportDir, filename + job.browserCapabilities.screenshot)
222 if (childProcess.connected) {
223 output.debug('screenshot', id, url, absoluteFilename)
224 const { promise, resolve, reject } = allocPromise()
225 screenshots[id] = resolve
226 output.debug('screenshot', id, 'sending command')
227 childProcess.send({
228 id,
229 command: 'screenshot',
230 filename: absoluteFilename
231 })
232 const timeoutId = setTimeout(() => {
233 reject(UTRError.BROWSER_SCREENSHOT_TIMEOUT())
234 }, job.screenshotTimeout)
235 output.debug('screenshot', id, 'command sent, waiting for answer')
236 await promise
237 output.debug('screenshot', id, 'answer received')
238 clearTimeout(timeoutId)
239 const result = await stat(absoluteFilename)
240 output.debug('screenshot', id, 'file size :', result.size)
241 if (!result.isFile() || result.size === 0) {
242 throw new Error('File expected')
243 }
244 output.debug('screenshot', id, 'done')
245 return absoluteFilename
246 }
247 } catch (e) {
248 output.debug('screenshot', id, e.message)
249 if (e.code === UTRError.BROWSER_SCREENSHOT_TIMEOUT_CODE) {
250 throw e
251 }
252 throw UTRError.BROWSER_SCREENSHOT_FAILED(e.toString())
253 }
254}
255
256async function stop (job, url, retry = false) {
257 const pageBrowser = job[$browsers][url]
258 if (pageBrowser) {
259 pageBrowser.stopped = true
260 const { childProcess, done, failed, timeoutId } = pageBrowser
261 if (timeoutId) {
262 clearTimeout(timeoutId)
263 }
264 if (childProcess.connected) {
265 /* istanbul ignore else */
266 if (!job.debugKeepBrowserOpen) {
267 childProcess.send({ command: 'stop' })
268 }
269 const { promise: closeTimeout, resolve } = allocPromise()
270 const timeoutId = setTimeout(resolve, job.browserCloseTimeout)
271 await Promise.race([
272 childProcess.closed,
273 closeTimeout
274 ])
275 clearTimeout(timeoutId)
276 }
277 await pageBrowser.console.flush
278 if (retry) {
279 if (++pageBrowser.retry <= job.browserRetry) {
280 run(job, pageBrowser)
281 } else {
282 failed(UTRError.BROWSER_FAILED())
283 }
284 } else {
285 done()
286 }
287 }
288}
289
290module.exports = { probe, start, screenshot, stop }