1/** 2 * Copyright 2017 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 */ 16import fs from 'fs'; 17import os from 'os'; 18import path from 'path'; 19import sinon from 'sinon'; 20import { promisify } from 'util'; 21import Protocol from 'devtools-protocol'; 22import { 23 getTestState, 24 itChromeOnly, 25 itFailsFirefox, 26 itOnlyRegularInstall, 27} from './mocha-utils'; // eslint-disable-line import/extensions 28import utils from './utils.js'; 29import expect from 'expect'; 30import rimraf from 'rimraf'; 31import { Page } from '../lib/cjs/puppeteer/common/Page.js'; 32 33const rmAsync = promisify(rimraf); 34const mkdtempAsync = promisify(fs.mkdtemp); 35const readFileAsync = promisify(fs.readFile); 36const statAsync = promisify(fs.stat); 37const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); 38const FIREFOX_TIMEOUT = 30 * 1000; 39 40describe('Launcher specs', function () { 41 if (getTestState().isFirefox) this.timeout(FIREFOX_TIMEOUT); 42 43 describe('Puppeteer', function () { 44 describe('BrowserFetcher', function () { 45 it('should download and extract chrome linux binary', async () => { 46 const { server, puppeteer } = getTestState(); 47 48 const downloadsFolder = await mkdtempAsync(TMP_FOLDER); 49 const browserFetcher = puppeteer.createBrowserFetcher({ 50 platform: 'linux', 51 path: downloadsFolder, 52 host: server.PREFIX, 53 }); 54 const expectedRevision = '123456'; 55 let revisionInfo = browserFetcher.revisionInfo(expectedRevision); 56 server.setRoute( 57 revisionInfo.url.substring(server.PREFIX.length), 58 (req, res) => { 59 server.serveFile(req, res, '/chromium-linux.zip'); 60 } 61 ); 62 63 expect(revisionInfo.local).toBe(false); 64 expect(browserFetcher.platform()).toBe('linux'); 65 expect(browserFetcher.product()).toBe('chrome'); 66 expect(!!browserFetcher.host()).toBe(true); 67 expect(await browserFetcher.canDownload('100000')).toBe(false); 68 expect(await browserFetcher.canDownload(expectedRevision)).toBe(true); 69 70 revisionInfo = await browserFetcher.download(expectedRevision); 71 expect(revisionInfo.local).toBe(true); 72 expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe( 73 'LINUX BINARY\n' 74 ); 75 const expectedPermissions = os.platform() === 'win32' ? 0o666 : 0o755; 76 expect( 77 (await statAsync(revisionInfo.executablePath)).mode & 0o777 78 ).toBe(expectedPermissions); 79 expect(await browserFetcher.localRevisions()).toEqual([ 80 expectedRevision, 81 ]); 82 await browserFetcher.remove(expectedRevision); 83 expect(await browserFetcher.localRevisions()).toEqual([]); 84 await rmAsync(downloadsFolder); 85 }); 86 it('should download and extract firefox linux binary', async () => { 87 const { server, puppeteer } = getTestState(); 88 89 const downloadsFolder = await mkdtempAsync(TMP_FOLDER); 90 const browserFetcher = puppeteer.createBrowserFetcher({ 91 platform: 'linux', 92 path: downloadsFolder, 93 host: server.PREFIX, 94 product: 'firefox', 95 }); 96 const expectedVersion = '75.0a1'; 97 let revisionInfo = browserFetcher.revisionInfo(expectedVersion); 98 server.setRoute( 99 revisionInfo.url.substring(server.PREFIX.length), 100 (req, res) => { 101 server.serveFile( 102 req, 103 res, 104 `/firefox-${expectedVersion}.en-US.linux-x86_64.tar.bz2` 105 ); 106 } 107 ); 108 109 expect(revisionInfo.local).toBe(false); 110 expect(browserFetcher.platform()).toBe('linux'); 111 expect(browserFetcher.product()).toBe('firefox'); 112 expect(await browserFetcher.canDownload('100000')).toBe(false); 113 expect(await browserFetcher.canDownload(expectedVersion)).toBe(true); 114 115 revisionInfo = await browserFetcher.download(expectedVersion); 116 expect(revisionInfo.local).toBe(true); 117 expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe( 118 'FIREFOX LINUX BINARY\n' 119 ); 120 const expectedPermissions = os.platform() === 'win32' ? 0o666 : 0o755; 121 expect( 122 (await statAsync(revisionInfo.executablePath)).mode & 0o777 123 ).toBe(expectedPermissions); 124 expect(await browserFetcher.localRevisions()).toEqual([ 125 expectedVersion, 126 ]); 127 await browserFetcher.remove(expectedVersion); 128 expect(await browserFetcher.localRevisions()).toEqual([]); 129 await rmAsync(downloadsFolder); 130 }); 131 }); 132 133 describe('Browser.disconnect', function () { 134 it('should reject navigation when browser closes', async () => { 135 const { server, puppeteer, defaultBrowserOptions } = getTestState(); 136 server.setRoute('/one-style.css', () => {}); 137 const browser = await puppeteer.launch(defaultBrowserOptions); 138 const remote = await puppeteer.connect({ 139 browserWSEndpoint: browser.wsEndpoint(), 140 }); 141 const page = await remote.newPage(); 142 const navigationPromise = page 143 .goto(server.PREFIX + '/one-style.html', { timeout: 60000 }) 144 .catch((error_) => error_); 145 await server.waitForRequest('/one-style.css'); 146 remote.disconnect(); 147 const error = await navigationPromise; 148 expect(error.message).toBe( 149 'Navigation failed because browser has disconnected!' 150 ); 151 await browser.close(); 152 }); 153 it('should reject waitForSelector when browser closes', async () => { 154 const { server, puppeteer, defaultBrowserOptions } = getTestState(); 155 156 server.setRoute('/empty.html', () => {}); 157 const browser = await puppeteer.launch(defaultBrowserOptions); 158 const remote = await puppeteer.connect({ 159 browserWSEndpoint: browser.wsEndpoint(), 160 }); 161 const page = await remote.newPage(); 162 const watchdog = page 163 .waitForSelector('div', { timeout: 60000 }) 164 .catch((error_) => error_); 165 remote.disconnect(); 166 const error = await watchdog; 167 expect(error.message).toContain('Protocol error'); 168 await browser.close(); 169 }); 170 }); 171 describe('Browser.close', function () { 172 it('should terminate network waiters', async () => { 173 const { server, puppeteer, defaultBrowserOptions } = getTestState(); 174 175 const browser = await puppeteer.launch(defaultBrowserOptions); 176 const remote = await puppeteer.connect({ 177 browserWSEndpoint: browser.wsEndpoint(), 178 }); 179 const newPage = await remote.newPage(); 180 const results = await Promise.all([ 181 newPage.waitForRequest(server.EMPTY_PAGE).catch((error) => error), 182 newPage.waitForResponse(server.EMPTY_PAGE).catch((error) => error), 183 browser.close(), 184 ]); 185 for (let i = 0; i < 2; i++) { 186 const message = results[i].message; 187 expect(message).toContain('Target closed'); 188 expect(message).not.toContain('Timeout'); 189 } 190 await browser.close(); 191 }); 192 }); 193 describe('Puppeteer.launch', function () { 194 it('should reject all promises when browser is closed', async () => { 195 const { defaultBrowserOptions, puppeteer } = getTestState(); 196 const browser = await puppeteer.launch(defaultBrowserOptions); 197 const page = await browser.newPage(); 198 let error = null; 199 const neverResolves = page 200 .evaluate(() => new Promise(() => {})) 201 .catch((error_) => (error = error_)); 202 await browser.close(); 203 await neverResolves; 204 expect(error.message).toContain('Protocol error'); 205 }); 206 it('should reject if executable path is invalid', async () => { 207 const { defaultBrowserOptions, puppeteer } = getTestState(); 208 209 let waitError = null; 210 const options = Object.assign({}, defaultBrowserOptions, { 211 executablePath: 'random-invalid-path', 212 }); 213 await puppeteer.launch(options).catch((error) => (waitError = error)); 214 expect(waitError.message).toContain('Failed to launch'); 215 }); 216 it('userDataDir option', async () => { 217 const { defaultBrowserOptions, puppeteer } = getTestState(); 218 219 const userDataDir = await mkdtempAsync(TMP_FOLDER); 220 const options = Object.assign({ userDataDir }, defaultBrowserOptions); 221 const browser = await puppeteer.launch(options); 222 // Open a page to make sure its functional. 223 await browser.newPage(); 224 expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); 225 await browser.close(); 226 expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); 227 // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 228 await rmAsync(userDataDir).catch(() => {}); 229 }); 230 it('userDataDir argument', async () => { 231 const { isChrome, puppeteer, defaultBrowserOptions } = getTestState(); 232 233 const userDataDir = await mkdtempAsync(TMP_FOLDER); 234 const options = Object.assign({}, defaultBrowserOptions); 235 if (isChrome) { 236 options.args = [ 237 ...(defaultBrowserOptions.args || []), 238 `--user-data-dir=${userDataDir}`, 239 ]; 240 } else { 241 options.args = [ 242 ...(defaultBrowserOptions.args || []), 243 `-profile`, 244 userDataDir, 245 ]; 246 } 247 const browser = await puppeteer.launch(options); 248 expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); 249 await browser.close(); 250 expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); 251 // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 252 await rmAsync(userDataDir).catch(() => {}); 253 }); 254 it('userDataDir option should restore state', async () => { 255 const { server, puppeteer, defaultBrowserOptions } = getTestState(); 256 257 const userDataDir = await mkdtempAsync(TMP_FOLDER); 258 const options = Object.assign({ userDataDir }, defaultBrowserOptions); 259 const browser = await puppeteer.launch(options); 260 const page = await browser.newPage(); 261 await page.goto(server.EMPTY_PAGE); 262 await page.evaluate(() => (localStorage.hey = 'hello')); 263 await browser.close(); 264 265 const browser2 = await puppeteer.launch(options); 266 const page2 = await browser2.newPage(); 267 await page2.goto(server.EMPTY_PAGE); 268 expect(await page2.evaluate(() => localStorage.hey)).toBe('hello'); 269 await browser2.close(); 270 // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 271 await rmAsync(userDataDir).catch(() => {}); 272 }); 273 // This mysteriously fails on Windows on AppVeyor. See 274 // https://github.com/puppeteer/puppeteer/issues/4111 275 xit('userDataDir option should restore cookies', async () => { 276 const { server, puppeteer, defaultBrowserOptions } = getTestState(); 277 278 const userDataDir = await mkdtempAsync(TMP_FOLDER); 279 const options = Object.assign({ userDataDir }, defaultBrowserOptions); 280 const browser = await puppeteer.launch(options); 281 const page = await browser.newPage(); 282 await page.goto(server.EMPTY_PAGE); 283 await page.evaluate( 284 () => 285 (document.cookie = 286 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT') 287 ); 288 await browser.close(); 289 290 const browser2 = await puppeteer.launch(options); 291 const page2 = await browser2.newPage(); 292 await page2.goto(server.EMPTY_PAGE); 293 expect(await page2.evaluate(() => document.cookie)).toBe( 294 'doSomethingOnlyOnce=true' 295 ); 296 await browser2.close(); 297 // This might throw. See https://github.com/puppeteer/puppeteer/issues/2778 298 await rmAsync(userDataDir).catch(() => {}); 299 }); 300 it('should return the default arguments', async () => { 301 const { isChrome, isFirefox, puppeteer } = getTestState(); 302 303 if (isChrome) { 304 expect(puppeteer.defaultArgs()).toContain('--no-first-run'); 305 expect(puppeteer.defaultArgs()).toContain('--headless'); 306 expect(puppeteer.defaultArgs({ headless: false })).not.toContain( 307 '--headless' 308 ); 309 expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain( 310 `--user-data-dir=${path.resolve('foo')}` 311 ); 312 } else if (isFirefox) { 313 expect(puppeteer.defaultArgs()).toContain('--headless'); 314 expect(puppeteer.defaultArgs()).toContain('--no-remote'); 315 expect(puppeteer.defaultArgs()).toContain('--foreground'); 316 expect(puppeteer.defaultArgs({ headless: false })).not.toContain( 317 '--headless' 318 ); 319 expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain( 320 '--profile' 321 ); 322 expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain( 323 'foo' 324 ); 325 } else { 326 expect(puppeteer.defaultArgs()).toContain('-headless'); 327 expect(puppeteer.defaultArgs({ headless: false })).not.toContain( 328 '-headless' 329 ); 330 expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain( 331 '-profile' 332 ); 333 expect(puppeteer.defaultArgs({ userDataDir: 'foo' })).toContain( 334 path.resolve('foo') 335 ); 336 } 337 }); 338 it('should report the correct product', async () => { 339 const { isChrome, isFirefox, puppeteer } = getTestState(); 340 if (isChrome) expect(puppeteer.product).toBe('chrome'); 341 else if (isFirefox) expect(puppeteer.product).toBe('firefox'); 342 }); 343 it('should work with no default arguments', async () => { 344 const { defaultBrowserOptions, puppeteer } = getTestState(); 345 const options = Object.assign({}, defaultBrowserOptions); 346 options.ignoreDefaultArgs = true; 347 const browser = await puppeteer.launch(options); 348 const page = await browser.newPage(); 349 expect(await page.evaluate('11 * 11')).toBe(121); 350 await page.close(); 351 await browser.close(); 352 }); 353 it('should filter out ignored default arguments', async () => { 354 const { defaultBrowserOptions, puppeteer } = getTestState(); 355 // Make sure we launch with `--enable-automation` by default. 356 const defaultArgs = puppeteer.defaultArgs(); 357 const browser = await puppeteer.launch( 358 Object.assign({}, defaultBrowserOptions, { 359 // Ignore first and third default argument. 360 ignoreDefaultArgs: [defaultArgs[0], defaultArgs[2]], 361 }) 362 ); 363 const spawnargs = browser.process().spawnargs; 364 if (!spawnargs) { 365 throw new Error('spawnargs not present'); 366 } 367 expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1); 368 expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1); 369 expect(spawnargs.indexOf(defaultArgs[2])).toBe(-1); 370 await browser.close(); 371 }); 372 it('should have default URL when launching browser', async function () { 373 const { defaultBrowserOptions, puppeteer } = getTestState(); 374 const browser = await puppeteer.launch(defaultBrowserOptions); 375 const pages = (await browser.pages()).map((page) => page.url()); 376 expect(pages).toEqual(['about:blank']); 377 await browser.close(); 378 }); 379 it( 380 'should have custom URL when launching browser', 381 async () => { 382 const { server, puppeteer, defaultBrowserOptions } = getTestState(); 383 384 const options = Object.assign({}, defaultBrowserOptions); 385 options.args = [server.EMPTY_PAGE].concat(options.args || []); 386 const browser = await puppeteer.launch(options); 387 const pages = await browser.pages(); 388 expect(pages.length).toBe(1); 389 const page = pages[0]; 390 if (page.url() !== server.EMPTY_PAGE) await page.waitForNavigation(); 391 expect(page.url()).toBe(server.EMPTY_PAGE); 392 await browser.close(); 393 } 394 ); 395 it('should set the default viewport', async () => { 396 const { puppeteer, defaultBrowserOptions } = getTestState(); 397 const options = Object.assign({}, defaultBrowserOptions, { 398 defaultViewport: { 399 width: 456, 400 height: 789, 401 }, 402 }); 403 const browser = await puppeteer.launch(options); 404 const page = await browser.newPage(); 405 expect(await page.evaluate('window.innerWidth')).toBe(456); 406 expect(await page.evaluate('window.innerHeight')).toBe(789); 407 await browser.close(); 408 }); 409 it('should disable the default viewport', async () => { 410 const { puppeteer, defaultBrowserOptions } = getTestState(); 411 const options = Object.assign({}, defaultBrowserOptions, { 412 defaultViewport: null, 413 }); 414 const browser = await puppeteer.launch(options); 415 const page = await browser.newPage(); 416 expect(page.viewport()).toBe(null); 417 await browser.close(); 418 }); 419 it('should take fullPage screenshots when defaultViewport is null', async () => { 420 const { server, puppeteer, defaultBrowserOptions } = getTestState(); 421 422 const options = Object.assign({}, defaultBrowserOptions, { 423 defaultViewport: null, 424 }); 425 const browser = await puppeteer.launch(options); 426 const page = await browser.newPage(); 427 await page.goto(server.PREFIX + '/grid.html'); 428 const screenshot = await page.screenshot({ 429 fullPage: true, 430 }); 431 expect(screenshot).toBeInstanceOf(Buffer); 432 await browser.close(); 433 }); 434 itChromeOnly( 435 'should launch Chrome properly with --no-startup-window and waitForInitialPage=false', 436 async () => { 437 const { defaultBrowserOptions, puppeteer } = getTestState(); 438 const options = { 439 args: ['--no-startup-window'], 440 waitForInitialPage: false, 441 // This is needed to prevent Puppeteer from adding an initial blank page. 442 // See also https://github.com/puppeteer/puppeteer/blob/ad6b736039436fcc5c0a262e5b575aa041427be3/src/node/Launcher.ts#L200 443 ignoreDefaultArgs: true, 444 ...defaultBrowserOptions, 445 }; 446 const browser = await puppeteer.launch(options); 447 const pages = await browser.pages(); 448 expect(pages.length).toBe(0); 449 await browser.close(); 450 } 451 ); 452 }); 453 454 describe('Puppeteer.launch', function () { 455 let productName; 456 457 before(async () => { 458 const { puppeteer } = getTestState(); 459 productName = puppeteer._productName; 460 }); 461 462 after(async () => { 463 const { puppeteer } = getTestState(); 464 // @ts-expect-error launcher is a private property that users can't 465 // touch, but for testing purposes we need to reset it. 466 puppeteer._lazyLauncher = undefined; 467 puppeteer._productName = productName; 468 }); 469 470 itOnlyRegularInstall('should be able to launch Chrome', async () => { 471 const { puppeteer } = getTestState(); 472 const browser = await puppeteer.launch({ product: 'chrome' }); 473 const userAgent = await browser.userAgent(); 474 await browser.close(); 475 expect(userAgent).toContain('Chrome'); 476 }); 477 478 it('falls back to launching chrome if there is an unknown product but logs a warning', async () => { 479 const { puppeteer } = getTestState(); 480 const consoleStub = sinon.stub(console, 'warn'); 481 // @ts-expect-error purposeful bad input 482 const browser = await puppeteer.launch({ product: 'SO_NOT_A_PRODUCT' }); 483 const userAgent = await browser.userAgent(); 484 await browser.close(); 485 expect(userAgent).toContain('Chrome'); 486 expect(consoleStub.callCount).toEqual(1); 487 expect(consoleStub.firstCall.args).toEqual([ 488 'Warning: unknown product name SO_NOT_A_PRODUCT. Falling back to chrome.', 489 ]); 490 }); 491 492 itOnlyRegularInstall( 493 'should be able to launch Firefox', 494 async function () { 495 this.timeout(FIREFOX_TIMEOUT); 496 const { puppeteer } = getTestState(); 497 const browser = await puppeteer.launch({ product: 'firefox' }); 498 const userAgent = await browser.userAgent(); 499 await browser.close(); 500 expect(userAgent).toContain('Firefox'); 501 } 502 ); 503 }); 504 505 describe('Puppeteer.connect', function () { 506 it('should be able to connect multiple times to the same browser', async () => { 507 const { puppeteer, defaultBrowserOptions } = getTestState(); 508 509 const originalBrowser = await puppeteer.launch(defaultBrowserOptions); 510 const otherBrowser = await puppeteer.connect({ 511 browserWSEndpoint: originalBrowser.wsEndpoint(), 512 }); 513 const page = await otherBrowser.newPage(); 514 expect(await page.evaluate(() => 7 * 8)).toBe(56); 515 otherBrowser.disconnect(); 516 517 const secondPage = await originalBrowser.newPage(); 518 expect(await secondPage.evaluate(() => 7 * 6)).toBe(42); 519 await originalBrowser.close(); 520 }); 521 it('should be able to close remote browser', async () => { 522 const { defaultBrowserOptions, puppeteer } = getTestState(); 523 524 const originalBrowser = await puppeteer.launch(defaultBrowserOptions); 525 const remoteBrowser = await puppeteer.connect({ 526 browserWSEndpoint: originalBrowser.wsEndpoint(), 527 }); 528 await Promise.all([ 529 utils.waitEvent(originalBrowser, 'disconnected'), 530 remoteBrowser.close(), 531 ]); 532 }); 533 it('should support ignoreHTTPSErrors option', async () => { 534 const { httpsServer, puppeteer, defaultBrowserOptions } = 535 getTestState(); 536 537 const originalBrowser = await puppeteer.launch(defaultBrowserOptions); 538 const browserWSEndpoint = originalBrowser.wsEndpoint(); 539 540 const browser = await puppeteer.connect({ 541 browserWSEndpoint, 542 ignoreHTTPSErrors: true, 543 }); 544 const page = await browser.newPage(); 545 let error = null; 546 const [serverRequest, response] = await Promise.all([ 547 httpsServer.waitForRequest('/empty.html'), 548 page.goto(httpsServer.EMPTY_PAGE).catch((error_) => (error = error_)), 549 ]); 550 expect(error).toBe(null); 551 expect(response.ok()).toBe(true); 552 expect(response.securityDetails()).toBeTruthy(); 553 const protocol = serverRequest.socket.getProtocol().replace('v', ' '); 554 expect(response.securityDetails().protocol()).toBe(protocol); 555 await page.close(); 556 await browser.close(); 557 }); 558 it('should support targetFilter option', async () => { 559 const { server, puppeteer, defaultBrowserOptions } = getTestState(); 560 561 const originalBrowser = await puppeteer.launch(defaultBrowserOptions); 562 const browserWSEndpoint = originalBrowser.wsEndpoint(); 563 564 const page1 = await originalBrowser.newPage(); 565 await page1.goto(server.EMPTY_PAGE); 566 567 const page2 = await originalBrowser.newPage(); 568 await page2.goto(server.EMPTY_PAGE + '?should-be-ignored'); 569 570 const browser = await puppeteer.connect({ 571 browserWSEndpoint, 572 targetFilter: (targetInfo: Protocol.Target.TargetInfo) => 573 !targetInfo.url.includes('should-be-ignored'), 574 }); 575 576 const pages = await browser.pages(); 577 578 await page2.close(); 579 await page1.close(); 580 await browser.close(); 581 582 expect(pages.map((p: Page) => p.url()).sort()).toEqual([ 583 'about:blank', 584 server.EMPTY_PAGE, 585 ]); 586 }); 587 it( 588 'should be able to reconnect to a disconnected browser', 589 async () => { 590 const { server, puppeteer, defaultBrowserOptions } = getTestState(); 591 592 const originalBrowser = await puppeteer.launch(defaultBrowserOptions); 593 const browserWSEndpoint = originalBrowser.wsEndpoint(); 594 const page = await originalBrowser.newPage(); 595 await page.goto(server.PREFIX + '/frames/nested-frames.html'); 596 originalBrowser.disconnect(); 597 598 const browser = await puppeteer.connect({ browserWSEndpoint }); 599 const pages = await browser.pages(); 600 const restoredPage = pages.find( 601 (page) => 602 page.url() === server.PREFIX + '/frames/nested-frames.html' 603 ); 604 expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([ 605 'http://localhost:<PORT>/frames/nested-frames.html', 606 ' http://localhost:<PORT>/frames/two-frames.html (2frames)', 607 ' http://localhost:<PORT>/frames/frame.html (uno)', 608 ' http://localhost:<PORT>/frames/frame.html (dos)', 609 ' http://localhost:<PORT>/frames/frame.html (aframe)', 610 ]); 611 expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56); 612 await browser.close(); 613 } 614 ); 615 // @see https://github.com/puppeteer/puppeteer/issues/4197#issuecomment-481793410 616 it( 617 'should be able to connect to the same page simultaneously', 618 async () => { 619 const { puppeteer } = getTestState(); 620 621 const browserOne = await puppeteer.launch(); 622 const browserTwo = await puppeteer.connect({ 623 browserWSEndpoint: browserOne.wsEndpoint(), 624 }); 625 const [page1, page2] = await Promise.all([ 626 new Promise<Page>((x) => 627 browserOne.once('targetcreated', (target) => x(target.page())) 628 ), 629 browserTwo.newPage(), 630 ]); 631 expect(await page1.evaluate(() => 7 * 8)).toBe(56); 632 expect(await page2.evaluate(() => 7 * 6)).toBe(42); 633 await browserOne.close(); 634 } 635 ); 636 it('should be able to reconnect', async () => { 637 const { puppeteer, server } = getTestState(); 638 const browserOne = await puppeteer.launch(); 639 const browserWSEndpoint = browserOne.wsEndpoint(); 640 const pageOne = await browserOne.newPage(); 641 await pageOne.goto(server.EMPTY_PAGE); 642 browserOne.disconnect(); 643 644 const browserTwo = await puppeteer.connect({ browserWSEndpoint }); 645 const pages = await browserTwo.pages(); 646 const pageTwo = pages.find((page) => page.url() === server.EMPTY_PAGE); 647 await pageTwo.reload(); 648 const bodyHandle = await pageTwo.waitForSelector('body', { 649 timeout: 10000, 650 }); 651 await bodyHandle.dispose(); 652 await browserTwo.close(); 653 }); 654 }); 655 describe('Puppeteer.executablePath', function () { 656 itOnlyRegularInstall('should work', async () => { 657 const { puppeteer } = getTestState(); 658 659 const executablePath = puppeteer.executablePath(); 660 expect(fs.existsSync(executablePath)).toBe(true); 661 expect(fs.realpathSync(executablePath)).toBe(executablePath); 662 }); 663 }); 664 }); 665 666 describe('Browser target events', function () { 667 it('should work', async () => { 668 const { server, puppeteer, defaultBrowserOptions } = getTestState(); 669 670 const browser = await puppeteer.launch(defaultBrowserOptions); 671 const events = []; 672 browser.on('targetcreated', () => events.push('CREATED')); 673 browser.on('targetchanged', () => events.push('CHANGED')); 674 browser.on('targetdestroyed', () => events.push('DESTROYED')); 675 const page = await browser.newPage(); 676 await page.goto(server.EMPTY_PAGE); 677 await page.close(); 678 expect(events).toEqual(['CREATED', 'CHANGED', 'DESTROYED']); 679 await browser.close(); 680 }); 681 }); 682 683 describe('Browser.Events.disconnected', function () { 684 it('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async () => { 685 const { puppeteer, defaultBrowserOptions } = getTestState(); 686 const originalBrowser = await puppeteer.launch(defaultBrowserOptions); 687 const browserWSEndpoint = originalBrowser.wsEndpoint(); 688 const remoteBrowser1 = await puppeteer.connect({ browserWSEndpoint }); 689 const remoteBrowser2 = await puppeteer.connect({ browserWSEndpoint }); 690 691 let disconnectedOriginal = 0; 692 let disconnectedRemote1 = 0; 693 let disconnectedRemote2 = 0; 694 originalBrowser.on('disconnected', () => ++disconnectedOriginal); 695 remoteBrowser1.on('disconnected', () => ++disconnectedRemote1); 696 remoteBrowser2.on('disconnected', () => ++disconnectedRemote2); 697 698 await Promise.all([ 699 utils.waitEvent(remoteBrowser2, 'disconnected'), 700 remoteBrowser2.disconnect(), 701 ]); 702 703 expect(disconnectedOriginal).toBe(0); 704 expect(disconnectedRemote1).toBe(0); 705 expect(disconnectedRemote2).toBe(1); 706 707 await Promise.all([ 708 utils.waitEvent(remoteBrowser1, 'disconnected'), 709 utils.waitEvent(originalBrowser, 'disconnected'), 710 originalBrowser.close(), 711 ]); 712 713 expect(disconnectedOriginal).toBe(1); 714 expect(disconnectedRemote1).toBe(1); 715 expect(disconnectedRemote2).toBe(1); 716 }); 717 }); 718}); 719