1/** 2 * Copyright 2018 Google Inc. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import path from 'path'; 18import os from 'os'; 19import fs from 'fs'; 20import { promisify } from 'util'; 21import expect from 'expect'; 22import { 23 getTestState, 24 describeChromeOnly, 25 itFailsWindows, 26} from './mocha-utils'; // eslint-disable-line import/extensions 27import rimraf from 'rimraf'; 28 29const rmAsync = promisify(rimraf); 30const mkdtempAsync = promisify(fs.mkdtemp); 31 32const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); 33 34const extensionPath = path.join(__dirname, 'assets', 'simple-extension'); 35 36describeChromeOnly('headful tests', function () { 37 /* These tests fire up an actual browser so let's 38 * allow a higher timeout 39 */ 40 this.timeout(20 * 1000); 41 42 let headfulOptions; 43 let headlessOptions; 44 let extensionOptions; 45 let forcedOopifOptions; 46 const browsers = []; 47 48 beforeEach(() => { 49 const { server, defaultBrowserOptions } = getTestState(); 50 headfulOptions = Object.assign({}, defaultBrowserOptions, { 51 headless: false, 52 }); 53 headlessOptions = Object.assign({}, defaultBrowserOptions, { 54 headless: true, 55 }); 56 57 extensionOptions = Object.assign({}, defaultBrowserOptions, { 58 headless: false, 59 args: [ 60 `--disable-extensions-except=${extensionPath}`, 61 `--load-extension=${extensionPath}`, 62 ], 63 }); 64 65 forcedOopifOptions = Object.assign({}, defaultBrowserOptions, { 66 headless: false, 67 devtools: true, 68 args: [ 69 `--host-rules=MAP oopifdomain 127.0.0.1`, 70 `--isolate-origins=${server.PREFIX.replace( 71 'localhost', 72 'oopifdomain' 73 )}`, 74 ], 75 }); 76 }); 77 78 async function launchBrowser(puppeteer, options) { 79 const browser = await puppeteer.launch(options); 80 browsers.push(browser); 81 return browser; 82 } 83 84 afterEach(() => { 85 for (const i in browsers) { 86 const browser = browsers[i]; 87 if (browser.isConnected()) { 88 browser.close(); 89 } 90 delete browsers[i]; 91 } 92 }); 93 94 describe('HEADFUL', function () { 95 it('background_page target type should be available', async () => { 96 const { puppeteer } = getTestState(); 97 const browserWithExtension = await launchBrowser( 98 puppeteer, 99 extensionOptions 100 ); 101 const page = await browserWithExtension.newPage(); 102 const backgroundPageTarget = await browserWithExtension.waitForTarget( 103 (target) => target.type() === 'background_page' 104 ); 105 await page.close(); 106 await browserWithExtension.close(); 107 expect(backgroundPageTarget).toBeTruthy(); 108 }); 109 it('target.page() should return a background_page', async function () { 110 const { puppeteer } = getTestState(); 111 const browserWithExtension = await launchBrowser( 112 puppeteer, 113 extensionOptions 114 ); 115 const backgroundPageTarget = await browserWithExtension.waitForTarget( 116 (target) => target.type() === 'background_page' 117 ); 118 const page = await backgroundPageTarget.page(); 119 expect(await page.evaluate(() => 2 * 3)).toBe(6); 120 expect(await page.evaluate(() => globalThis.MAGIC)).toBe(42); 121 await browserWithExtension.close(); 122 }); 123 it('should have default url when launching browser', async function () { 124 const { puppeteer } = getTestState(); 125 const browser = await launchBrowser(puppeteer, extensionOptions); 126 const pages = (await browser.pages()).map((page) => page.url()); 127 expect(pages).toEqual(['about:blank']); 128 await browser.close(); 129 }); 130 itFailsWindows( 131 'headless should be able to read cookies written by headful', 132 async () => { 133 /* Needs investigation into why but this fails consistently on Windows CI. */ 134 const { server, puppeteer } = getTestState(); 135 136 const userDataDir = await mkdtempAsync(TMP_FOLDER); 137 // Write a cookie in headful chrome 138 const headfulBrowser = await launchBrowser( 139 puppeteer, 140 Object.assign({ userDataDir }, headfulOptions) 141 ); 142 const headfulPage = await headfulBrowser.newPage(); 143 await headfulPage.goto(server.EMPTY_PAGE); 144 await headfulPage.evaluate( 145 () => 146 (document.cookie = 147 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT') 148 ); 149 await headfulBrowser.close(); 150 // Read the cookie from headless chrome 151 const headlessBrowser = await launchBrowser( 152 puppeteer, 153 Object.assign({ userDataDir }, headlessOptions) 154 ); 155 const headlessPage = await headlessBrowser.newPage(); 156 await headlessPage.goto(server.EMPTY_PAGE); 157 const cookie = await headlessPage.evaluate(() => document.cookie); 158 await headlessBrowser.close(); 159 // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 160 await rmAsync(userDataDir).catch(() => {}); 161 expect(cookie).toBe('foo=true'); 162 } 163 ); 164 // TODO: Support OOOPIF. @see https://github.com/puppeteer/puppeteer/issues/2548 165 xit('OOPIF: should report google.com frame', async () => { 166 const { server, puppeteer } = getTestState(); 167 168 // https://google.com is isolated by default in Chromium embedder. 169 const browser = await launchBrowser(puppeteer, headfulOptions); 170 const page = await browser.newPage(); 171 await page.goto(server.EMPTY_PAGE); 172 await page.setRequestInterception(true); 173 page.on('request', (r) => r.respond({ body: 'YO, GOOGLE.COM' })); 174 await page.evaluate(() => { 175 const frame = document.createElement('iframe'); 176 frame.setAttribute('src', 'https://google.com/'); 177 document.body.appendChild(frame); 178 return new Promise((x) => (frame.onload = x)); 179 }); 180 await page.waitForSelector('iframe[src="https://google.com/"]'); 181 const urls = page 182 .frames() 183 .map((frame) => frame.url()) 184 .sort(); 185 expect(urls).toEqual([server.EMPTY_PAGE, 'https://google.com/']); 186 await browser.close(); 187 }); 188 it('OOPIF: should expose events within OOPIFs', async () => { 189 const { server, puppeteer } = getTestState(); 190 191 const browser = await launchBrowser(puppeteer, forcedOopifOptions); 192 const page = await browser.newPage(); 193 194 // Setup our session listeners to observe OOPIF activity. 195 const session = await page.target().createCDPSession(); 196 const networkEvents = []; 197 const otherSessions = []; 198 await session.send('Target.setAutoAttach', { 199 autoAttach: true, 200 flatten: true, 201 waitForDebuggerOnStart: true, 202 }); 203 session.on('sessionattached', async (session) => { 204 otherSessions.push(session); 205 206 session.on('Network.requestWillBeSent', (params) => 207 networkEvents.push(params) 208 ); 209 await session.send('Network.enable'); 210 await session.send('Runtime.runIfWaitingForDebugger'); 211 }); 212 213 // Navigate to the empty page and add an OOPIF iframe with at least one request. 214 await page.goto(server.EMPTY_PAGE); 215 await page.evaluate((frameUrl) => { 216 const frame = document.createElement('iframe'); 217 frame.setAttribute('src', frameUrl); 218 document.body.appendChild(frame); 219 return new Promise((x, y) => { 220 frame.onload = x; 221 frame.onerror = y; 222 }); 223 }, server.PREFIX.replace('localhost', 'oopifdomain') + '/one-style.html'); 224 await page.waitForSelector('iframe'); 225 226 // Ensure we found the iframe session. 227 expect(otherSessions).toHaveLength(1); 228 229 // Resume the iframe and trigger another request. 230 const iframeSession = otherSessions[0]; 231 await iframeSession.send('Runtime.evaluate', { 232 expression: `fetch('/fetch')`, 233 awaitPromise: true, 234 }); 235 await browser.close(); 236 237 const requests = networkEvents.map((event) => event.request.url); 238 expect(requests).toContain(`http://oopifdomain:${server.PORT}/fetch`); 239 }); 240 it('should close browser with beforeunload page', async () => { 241 const { server, puppeteer } = getTestState(); 242 243 const browser = await launchBrowser(puppeteer, headfulOptions); 244 const page = await browser.newPage(); 245 await page.goto(server.PREFIX + '/beforeunload.html'); 246 // We have to interact with a page so that 'beforeunload' handlers 247 // fire. 248 await page.click('body'); 249 await browser.close(); 250 }); 251 it('should open devtools when "devtools: true" option is given', async () => { 252 const { puppeteer } = getTestState(); 253 254 const browser = await launchBrowser( 255 puppeteer, 256 Object.assign({ devtools: true }, headfulOptions) 257 ); 258 const context = await browser.createIncognitoBrowserContext(); 259 await Promise.all([ 260 context.newPage(), 261 browser.waitForTarget((target) => target.url().includes('devtools://')), 262 ]); 263 await browser.close(); 264 }); 265 }); 266 267 describe('Page.bringToFront', function () { 268 it('should work', async () => { 269 const { puppeteer } = getTestState(); 270 const browser = await launchBrowser(puppeteer, headfulOptions); 271 const page1 = await browser.newPage(); 272 const page2 = await browser.newPage(); 273 274 await page1.bringToFront(); 275 expect(await page1.evaluate(() => document.visibilityState)).toBe( 276 'visible' 277 ); 278 expect(await page2.evaluate(() => document.visibilityState)).toBe( 279 'hidden' 280 ); 281 282 await page2.bringToFront(); 283 expect(await page1.evaluate(() => document.visibilityState)).toBe( 284 'hidden' 285 ); 286 expect(await page2.evaluate(() => document.visibilityState)).toBe( 287 'visible' 288 ); 289 290 await page1.close(); 291 await page2.close(); 292 await browser.close(); 293 }); 294 }); 295 296 describe('Page.screenshot', function () { 297 it('should run in parallel in multiple pages', async () => { 298 const { server, puppeteer } = getTestState(); 299 const browser = await puppeteer.launch(headfulOptions); 300 const context = await browser.createIncognitoBrowserContext(); 301 302 const N = 2; 303 const pages = await Promise.all( 304 Array(N) 305 .fill(0) 306 .map(async () => { 307 const page = await context.newPage(); 308 await page.goto(server.PREFIX + '/grid.html'); 309 return page; 310 }) 311 ); 312 const promises = []; 313 for (let i = 0; i < N; ++i) 314 promises.push( 315 pages[i].screenshot({ 316 clip: { x: 50 * i, y: 0, width: 50, height: 50 }, 317 }) 318 ); 319 const screenshots = await Promise.all(promises); 320 for (let i = 0; i < N; ++i) 321 expect(screenshots[i]).toBeGolden(`grid-cell-${i}.png`); 322 await Promise.all(pages.map((page) => page.close())); 323 324 await browser.close(); 325 }); 326 }); 327}); 328