Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 1 | // Copyright 2020 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Jack Franklin | e245d1a | 2020-02-13 15:25:13 | [diff] [blame] | 5 | /* eslint-disable no-console */ |
| 6 | // no-console disabled here as this is a test runner and expects to output to the console |
| 7 | |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 8 | import * as Mocha from 'mocha'; |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 9 | import * as puppeteer from 'puppeteer'; |
Paul Lewis | bd88d7f | 2020-02-05 17:06:43 | [diff] [blame] | 10 | import {spawn} from 'child_process'; |
| 11 | import {join} from 'path'; |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 12 | import {store} from './helper.js'; |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 13 | |
Paul Lewis | 32473f0 | 2020-02-05 15:55:19 | [diff] [blame] | 14 | const testListPath = process.env['TEST_LIST']; |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 15 | const envChromeBinary = process.env['CHROME_BIN']; |
| 16 | const envDebug = !!process.env['DEBUG']; |
| 17 | const envPort = process.env['PORT'] || 9222; |
Paul Lewis | 5824802 | 2020-02-07 14:02:02 | [diff] [blame] | 18 | const envNoShuffle = !!process.env['NO_SHUFFLE']; |
Paul Lewis | c9f5e3a | 2020-02-17 15:47:49 | [diff] [blame] | 19 | const envInteractive = !!process.env['INTERACTIVE']; |
| 20 | const interactivePage = 'http://localhost:8090/test/screenshots/interactive/index.html'; |
Paul Lewis | d4802d6 | 2020-02-06 11:26:54 | [diff] [blame] | 21 | const blankPage = 'data:text/html,'; |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 22 | const headless = !envDebug; |
| 23 | const width = 1280; |
| 24 | const height = 720; |
| 25 | |
Paul Lewis | 96a0b3c | 2020-02-06 16:02:41 | [diff] [blame] | 26 | let mochaRun: Mocha.Runner; |
Paul Lewis | 285715e | 2020-02-05 15:23:48 | [diff] [blame] | 27 | let exitCode = 0; |
| 28 | |
Paul Lewis | 96a0b3c | 2020-02-06 16:02:41 | [diff] [blame] | 29 | function interruptionHandler() { |
| 30 | console.log('\n'); |
| 31 | if (mochaRun) { |
| 32 | console.log('Aborting tests'); |
| 33 | mochaRun.abort(); |
| 34 | } |
| 35 | exitCode = 1; |
| 36 | shutdown(); |
| 37 | } |
| 38 | |
| 39 | function shutdown() { |
| 40 | console.log('\n'); |
| 41 | console.log('Stopping hosted mode server'); |
| 42 | hostedModeServer.kill(); |
| 43 | |
| 44 | console.log(`Exiting with status code ${exitCode}`); |
| 45 | process.exit(exitCode); |
| 46 | } |
| 47 | |
| 48 | process.on('SIGINT', interruptionHandler); |
| 49 | process.on('SIGTERM', interruptionHandler); |
| 50 | process.on('uncaughtException', interruptionHandler); |
| 51 | process.stdin.resume(); |
| 52 | |
Tim van der Lippe | c14a134 | 2020-02-10 14:21:40 | [diff] [blame] | 53 | if (!testListPath) { |
Mathias Bynens | 23ee1aa | 2020-03-02 12:06:38 | [diff] [blame^] | 54 | throw new Error('Must specify a list of tests in the "TEST_LIST" environment variable.'); |
Tim van der Lippe | c14a134 | 2020-02-10 14:21:40 | [diff] [blame] | 55 | } |
| 56 | |
| 57 | const launchArgs = [`--remote-debugging-port=${envPort}`]; |
| 58 | |
Paul Lewis | 96a0b3c | 2020-02-06 16:02:41 | [diff] [blame] | 59 | // 1. Launch Chromium. |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 60 | const opts: puppeteer.LaunchOptions = { |
| 61 | headless, |
| 62 | executablePath: envChromeBinary, |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 63 | defaultViewport: null, |
| 64 | }; |
| 65 | |
| 66 | // Toggle either viewport or window size depending on headless vs not. |
| 67 | if (headless) { |
| 68 | opts.defaultViewport = {width, height}; |
| 69 | } |
| 70 | else { |
Tim van der Lippe | c14a134 | 2020-02-10 14:21:40 | [diff] [blame] | 71 | launchArgs.push(`--window-size=${width},${height}`); |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 72 | } |
| 73 | |
Tim van der Lippe | c14a134 | 2020-02-10 14:21:40 | [diff] [blame] | 74 | opts.args = launchArgs; |
| 75 | |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 76 | const launchedBrowser = puppeteer.launch(opts); |
| 77 | const pages: puppeteer.Page[] = []; |
| 78 | |
Paul Lewis | bd88d7f | 2020-02-05 17:06:43 | [diff] [blame] | 79 | // 2. Start DevTools hosted mode. |
Tim van der Lippe | c14a134 | 2020-02-10 14:21:40 | [diff] [blame] | 80 | function handleHostedModeError(data: Error) { |
Tim van der Lippe | 54c6faa | 2020-02-20 15:42:56 | [diff] [blame] | 81 | console.log(`Hosted mode server: ${data}`); |
Paul Lewis | 96a0b3c | 2020-02-06 16:02:41 | [diff] [blame] | 82 | interruptionHandler(); |
Paul Lewis | bd88d7f | 2020-02-05 17:06:43 | [diff] [blame] | 83 | } |
| 84 | |
| 85 | console.log('Spawning hosted mode server'); |
Paul Lewis | 0a228d4 | 2020-02-05 17:13:29 | [diff] [blame] | 86 | const serverScriptPath = join(__dirname, '..', '..', 'scripts', 'hosted_mode', 'server.js'); |
Paul Lewis | bd88d7f | 2020-02-05 17:06:43 | [diff] [blame] | 87 | const cwd = join(__dirname, '..', '..'); |
| 88 | const {execPath} = process; |
Paul Lewis | 3432ad6 | 2020-02-13 09:39:31 | [diff] [blame] | 89 | const hostedModeServer = spawn(execPath, [serverScriptPath], { cwd }); |
Paul Lewis | bd88d7f | 2020-02-05 17:06:43 | [diff] [blame] | 90 | hostedModeServer.on('error', handleHostedModeError); |
| 91 | hostedModeServer.stderr.on('data', handleHostedModeError); |
| 92 | |
Tim van der Lippe | c14a134 | 2020-02-10 14:21:40 | [diff] [blame] | 93 | interface DevToolsTarget { |
| 94 | url: string; |
| 95 | id: string; |
| 96 | } |
| 97 | |
Paul Lewis | bd88d7f | 2020-02-05 17:06:43 | [diff] [blame] | 98 | // 3. Spin up the test environment |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 99 | (async function() { |
| 100 | try { |
Paul Lewis | c9f5e3a | 2020-02-17 15:47:49 | [diff] [blame] | 101 | let screenshotPage: puppeteer.Page | undefined; |
| 102 | if (envInteractive) { |
| 103 | const screenshotBrowser = await puppeteer.launch({ |
| 104 | headless: false, |
| 105 | executablePath: envChromeBinary, |
| 106 | defaultViewport: null, |
| 107 | args: [`--window-size=${width},${height}`], |
| 108 | }); |
| 109 | screenshotPage = await screenshotBrowser.newPage(); |
| 110 | await screenshotPage.goto(interactivePage, {waitUntil: ['domcontentloaded']}); |
| 111 | } |
| 112 | |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 113 | const browser = await launchedBrowser; |
| 114 | |
| 115 | // Load the target page. |
| 116 | const srcPage = await browser.newPage(); |
Paul Lewis | d4802d6 | 2020-02-06 11:26:54 | [diff] [blame] | 117 | await srcPage.goto(blankPage); |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 118 | pages.push(srcPage); |
| 119 | |
| 120 | // Now get the DevTools listings. |
| 121 | const devtools = await browser.newPage(); |
| 122 | await devtools.goto(`http://localhost:${envPort}/json`); |
| 123 | |
| 124 | // Find the appropriate item to inspect the target page. |
| 125 | const listing = await devtools.$('pre'); |
| 126 | const json = await devtools.evaluate(listing => listing.textContent, listing); |
Tim van der Lippe | c14a134 | 2020-02-10 14:21:40 | [diff] [blame] | 127 | const targets: DevToolsTarget[] = JSON.parse(json); |
Jack Franklin | e245d1a | 2020-02-13 15:25:13 | [diff] [blame] | 128 | const target = targets.find(target => target.url === blankPage); |
Paul Lewis | 74cb8b7 | 2020-02-11 11:05:23 | [diff] [blame] | 129 | if (!target) { |
| 130 | throw new Error(`Unable to find target page: ${blankPage}`); |
| 131 | } |
| 132 | |
| 133 | const {id} = target; |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 134 | await devtools.close(); |
| 135 | |
| 136 | // Connect to the DevTools frontend. |
| 137 | const frontend = await browser.newPage(); |
Yang Guo | 49346f1 | 2020-02-06 09:52:02 | [diff] [blame] | 138 | const frontendUrl = `http://localhost:8090/front_end/devtools_app.html?ws=localhost:${envPort}/devtools/page/${id}`; |
Paul Lewis | 96a0b3c | 2020-02-06 16:02:41 | [diff] [blame] | 139 | await frontend.goto(frontendUrl, {waitUntil: ['networkidle2', 'domcontentloaded']}); |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 140 | |
Jack Franklin | e245d1a | 2020-02-13 15:25:13 | [diff] [blame] | 141 | frontend.on('error', err => { |
Paul Lewis | 97e4a75 | 2020-02-05 16:06:33 | [diff] [blame] | 142 | console.log('Error in Frontend'); |
| 143 | console.log(err); |
| 144 | }); |
| 145 | |
Jack Franklin | e245d1a | 2020-02-13 15:25:13 | [diff] [blame] | 146 | frontend.on('pageerror', err => { |
Paul Lewis | 97e4a75 | 2020-02-05 16:06:33 | [diff] [blame] | 147 | console.log('Page Error in Frontend'); |
| 148 | console.log(err); |
| 149 | }); |
| 150 | |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 151 | const resetPages = |
Philip Pfaffe | 664c560 | 2020-02-03 16:29:03 | [diff] [blame] | 152 | async (...enabledExperiments: string[]) => { |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 153 | // Reload the target page. |
Paul Lewis | d4802d6 | 2020-02-06 11:26:54 | [diff] [blame] | 154 | await srcPage.goto(blankPage, {waitUntil: ['domcontentloaded']}); |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 155 | |
| 156 | // Clear any local storage settings. |
| 157 | await frontend.evaluate(() => localStorage.clear()); |
| 158 | |
Jack Franklin | e245d1a | 2020-02-13 15:25:13 | [diff] [blame] | 159 | await frontend.evaluate(enabledExperiments => { |
Philip Pfaffe | 664c560 | 2020-02-03 16:29:03 | [diff] [blame] | 160 | for (const experiment of enabledExperiments) { |
Tim van der Lippe | c14a134 | 2020-02-10 14:21:40 | [diff] [blame] | 161 | // @ts-ignore |
Philip Pfaffe | 664c560 | 2020-02-03 16:29:03 | [diff] [blame] | 162 | globalThis.Root.Runtime.experiments.setEnabled(experiment, true); |
| 163 | } |
| 164 | }, enabledExperiments); |
| 165 | |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 166 | // Reload the DevTools frontend and await the elements panel. |
Paul Lewis | d4802d6 | 2020-02-06 11:26:54 | [diff] [blame] | 167 | await frontend.goto(blankPage, {waitUntil: ['domcontentloaded']}); |
| 168 | await frontend.goto(frontendUrl, {waitUntil: ['networkidle2', 'domcontentloaded']}); |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 169 | await frontend.waitForSelector('.elements'); |
Jack Franklin | e245d1a | 2020-02-13 15:25:13 | [diff] [blame] | 170 | }; |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 171 | |
Paul Lewis | c9f5e3a | 2020-02-17 15:47:49 | [diff] [blame] | 172 | store(browser, srcPage, frontend, screenshotPage, resetPages); |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 173 | |
| 174 | // 3. Run tests. |
| 175 | do { |
| 176 | if (envDebug) { |
| 177 | logHelp(); |
| 178 | } |
| 179 | |
| 180 | await waitForInput(); |
| 181 | await runTests(); |
| 182 | if (envDebug) { |
| 183 | await resetPages(); |
| 184 | } |
| 185 | } while (envDebug); |
| 186 | |
| 187 | } catch (err) { |
| 188 | console.warn(err); |
| 189 | } finally { |
Paul Lewis | bd88d7f | 2020-02-05 17:06:43 | [diff] [blame] | 190 | shutdown(); |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 191 | } |
| 192 | })(); |
| 193 | |
| 194 | async function waitForInput() { |
Jack Franklin | e245d1a | 2020-02-13 15:25:13 | [diff] [blame] | 195 | return new Promise(resolve => { |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 196 | if (!envDebug) { |
| 197 | resolve(); |
| 198 | return; |
| 199 | } |
| 200 | |
| 201 | process.stdin.setRawMode(true); |
| 202 | process.stdin.resume(); |
Jack Franklin | e245d1a | 2020-02-13 15:25:13 | [diff] [blame] | 203 | process.stdin.on('data', async str => { |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 204 | // Listen for ctrl+c to exit. |
| 205 | if (str.toString() === '\x03') { |
Paul Lewis | 96a0b3c | 2020-02-06 16:02:41 | [diff] [blame] | 206 | interruptionHandler(); |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 207 | } |
| 208 | resolve(); |
| 209 | }); |
| 210 | }); |
| 211 | } |
| 212 | |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 213 | async function runTests() { |
Tim van der Lippe | c14a134 | 2020-02-10 14:21:40 | [diff] [blame] | 214 | const {testList} = await import(testListPath!); |
Paul Lewis | 5824802 | 2020-02-07 14:02:02 | [diff] [blame] | 215 | const shuffledTests = shuffleTestFiles(testList); |
Paul Lewis | 32473f0 | 2020-02-05 15:55:19 | [diff] [blame] | 216 | |
Jack Franklin | e245d1a | 2020-02-13 15:25:13 | [diff] [blame] | 217 | return new Promise(resolve => { |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 218 | const mocha = new Mocha(); |
Paul Lewis | 5824802 | 2020-02-07 14:02:02 | [diff] [blame] | 219 | for (const test of shuffledTests) { |
Paul Lewis | 32473f0 | 2020-02-05 15:55:19 | [diff] [blame] | 220 | mocha.addFile(test); |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 221 | } |
| 222 | mocha.ui('bdd'); |
| 223 | mocha.reporter('list'); |
Paul Lewis | c9f5e3a | 2020-02-17 15:47:49 | [diff] [blame] | 224 | mocha.timeout((envDebug || envInteractive) ? 300000 : 4000); |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 225 | |
| 226 | mochaRun = mocha.run(); |
Paul Lewis | e27cd53 | 2020-02-03 09:45:36 | [diff] [blame] | 227 | mochaRun.on('end', () => { |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 228 | (mocha as any).unloadFiles(); |
| 229 | resolve(); |
| 230 | }); |
Paul Lewis | 285715e | 2020-02-05 15:23:48 | [diff] [blame] | 231 | |
| 232 | mochaRun.on('fail', () => { |
| 233 | exitCode = 1; |
| 234 | }); |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 235 | }); |
| 236 | } |
| 237 | |
| 238 | function logHelp() { |
| 239 | console.log('Running in debug mode.'); |
| 240 | console.log(' - Press any key to run the test suite.'); |
| 241 | console.log(' - Press ctrl + c to quit.'); |
Tim van der Lippe | 54c6faa | 2020-02-20 15:42:56 | [diff] [blame] | 242 | hostedModeServer.stdout.on('data', (message: any) => { |
| 243 | console.log(`Hosted mode server: ${message}`); |
| 244 | }); |
Paul Lewis | b8b3801 | 2020-01-22 17:18:47 | [diff] [blame] | 245 | } |
Paul Lewis | 5824802 | 2020-02-07 14:02:02 | [diff] [blame] | 246 | |
| 247 | function shuffleTestFiles(files: string[]) { |
| 248 | if (envNoShuffle) { |
| 249 | console.log('Running tests unshuffled'); |
| 250 | return files; |
| 251 | } |
| 252 | |
Tim van der Lippe | c14a134 | 2020-02-10 14:21:40 | [diff] [blame] | 253 | const swap = (arr: string[], a: number, b: number) => { |
Paul Lewis | 5824802 | 2020-02-07 14:02:02 | [diff] [blame] | 254 | const temp = arr[a]; |
| 255 | arr[a] = arr[b]; |
| 256 | arr[b] = temp; |
Tim van der Lippe | c14a134 | 2020-02-10 14:21:40 | [diff] [blame] | 257 | }; |
Paul Lewis | 5824802 | 2020-02-07 14:02:02 | [diff] [blame] | 258 | |
| 259 | for (let i = files.length; i >= 0; i--) { |
| 260 | const a = Math.floor(Math.random() * files.length); |
| 261 | const b = Math.floor(Math.random() * files.length); |
| 262 | |
| 263 | swap(files, a, b); |
| 264 | } |
| 265 | |
| 266 | console.log(`Running tests in the following order:\n${files.join('\n')}`); |
| 267 | return files; |
| 268 | } |