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 path from 'path'; 18import utils from './utils.js'; 19const { waitEvent } = utils; 20import expect from 'expect'; 21import sinon from 'sinon'; 22import { 23 getTestState, 24 setupTestBrowserHooks, 25 setupTestPageAndContextHooks, 26 itFailsFirefox, 27 describeFailsFirefox, 28} from './mocha-utils'; // eslint-disable-line import/extensions 29import { Page, Metrics } from '../lib/cjs/puppeteer/common/Page.js'; 30import { JSHandle } from '../lib/cjs/puppeteer/common/JSHandle.js'; 31 32describe('Page', function () { 33 setupTestBrowserHooks(); 34 setupTestPageAndContextHooks(); 35 describe('Page.close', function () { 36 it('should reject all promises when page is closed', async () => { 37 const { context } = getTestState(); 38 39 const newPage = await context.newPage(); 40 let error = null; 41 await Promise.all([ 42 newPage 43 .evaluate(() => new Promise(() => {})) 44 .catch((error_) => (error = error_)), 45 newPage.close(), 46 ]); 47 expect(error.message).toContain('Protocol error'); 48 }); 49 it('should not be visible in browser.pages', async () => { 50 const { browser } = getTestState(); 51 52 const newPage = await browser.newPage(); 53 expect(await browser.pages()).toContain(newPage); 54 await newPage.close(); 55 expect(await browser.pages()).not.toContain(newPage); 56 }); 57 it('should run beforeunload if asked for', async () => { 58 const { context, server, isChrome } = getTestState(); 59 60 const newPage = await context.newPage(); 61 await newPage.goto(server.PREFIX + '/beforeunload.html'); 62 // We have to interact with a page so that 'beforeunload' handlers 63 // fire. 64 await newPage.click('body'); 65 const pageClosingPromise = newPage.close({ runBeforeUnload: true }); 66 const dialog = await waitEvent(newPage, 'dialog'); 67 expect(dialog.type()).toBe('beforeunload'); 68 expect(dialog.defaultValue()).toBe(''); 69 if (isChrome) expect(dialog.message()).toBe(''); 70 else expect(dialog.message()).toBeTruthy(); 71 await dialog.accept(); 72 await pageClosingPromise; 73 }); 74 it('should *not* run beforeunload by default', async () => { 75 const { context, server } = getTestState(); 76 77 const newPage = await context.newPage(); 78 await newPage.goto(server.PREFIX + '/beforeunload.html'); 79 // We have to interact with a page so that 'beforeunload' handlers 80 // fire. 81 await newPage.click('body'); 82 await newPage.close(); 83 }); 84 it('should set the page close state', async () => { 85 const { context } = getTestState(); 86 87 const newPage = await context.newPage(); 88 expect(newPage.isClosed()).toBe(false); 89 await newPage.close(); 90 expect(newPage.isClosed()).toBe(true); 91 }); 92 it('should terminate network waiters', async () => { 93 const { context, server } = getTestState(); 94 95 const newPage = await context.newPage(); 96 const results = await Promise.all([ 97 newPage.waitForRequest(server.EMPTY_PAGE).catch((error) => error), 98 newPage.waitForResponse(server.EMPTY_PAGE).catch((error) => error), 99 newPage.close(), 100 ]); 101 for (let i = 0; i < 2; i++) { 102 const message = results[i].message; 103 expect(message).toContain('Target closed'); 104 expect(message).not.toContain('Timeout'); 105 } 106 }); 107 }); 108 109 describe('Page.Events.Load', function () { 110 it('should fire when expected', async () => { 111 const { page } = getTestState(); 112 113 await Promise.all([ 114 page.goto('about:blank'), 115 utils.waitEvent(page, 'load'), 116 ]); 117 }); 118 }); 119 120 // This test fails on Firefox on CI consistently but cannot be replicated 121 // locally. Skipping for now to unblock the Mitt release and given FF support 122 // isn't fully done yet but raising an issue to ask the FF folks to have a 123 // look at this. 124 describe('removing and adding event handlers', () => { 125 it('should correctly fire event handlers as they are added and then removed', async () => { 126 const { page, server } = getTestState(); 127 128 const handler = sinon.spy(); 129 page.on('response', handler); 130 await page.goto(server.EMPTY_PAGE); 131 expect(handler.callCount).toBe(1); 132 page.off('response', handler); 133 await page.goto(server.EMPTY_PAGE); 134 // Still one because we removed the handler. 135 expect(handler.callCount).toBe(1); 136 page.on('response', handler); 137 await page.goto(server.EMPTY_PAGE); 138 // Two now because we added the handler back. 139 expect(handler.callCount).toBe(2); 140 }); 141 }); 142 143 describe('Page.Events.error', function () { 144 it('should throw when page crashes', async () => { 145 const { page } = getTestState(); 146 147 let error = null; 148 page.on('error', (err) => (error = err)); 149 page.goto('chrome://crash').catch(() => {}); 150 await waitEvent(page, 'error'); 151 expect(error.message).toBe('Page crashed!'); 152 }); 153 }); 154 155 describe('Page.Events.Popup', function () { 156 it('should work', async () => { 157 const { page } = getTestState(); 158 159 const [popup] = await Promise.all([ 160 new Promise<Page>((x) => page.once('popup', x)), 161 page.evaluate(() => window.open('about:blank')), 162 ]); 163 expect(await page.evaluate(() => !!window.opener)).toBe(false); 164 expect(await popup.evaluate(() => !!window.opener)).toBe(true); 165 }); 166 it('should work with noopener', async () => { 167 const { page } = getTestState(); 168 169 const [popup] = await Promise.all([ 170 new Promise<Page>((x) => page.once('popup', x)), 171 page.evaluate(() => window.open('about:blank', null, 'noopener')), 172 ]); 173 expect(await page.evaluate(() => !!window.opener)).toBe(false); 174 expect(await popup.evaluate(() => !!window.opener)).toBe(false); 175 }); 176 it('should work with clicking target=_blank and without rel=opener', async () => { 177 const { page, server } = getTestState(); 178 179 await page.goto(server.EMPTY_PAGE); 180 await page.setContent('<a target=_blank href="/one-style.html">yo</a>'); 181 const [popup] = await Promise.all([ 182 new Promise<Page>((x) => page.once('popup', x)), 183 page.click('a'), 184 ]); 185 expect(await page.evaluate(() => !!window.opener)).toBe(false); 186 expect(await popup.evaluate(() => !!window.opener)).toBe(false); 187 }); 188 it('should work with clicking target=_blank and with rel=opener', async () => { 189 const { page, server } = getTestState(); 190 191 await page.goto(server.EMPTY_PAGE); 192 await page.setContent( 193 '<a target=_blank rel=opener href="/one-style.html">yo</a>' 194 ); 195 const [popup] = await Promise.all([ 196 new Promise<Page>((x) => page.once('popup', x)), 197 page.click('a'), 198 ]); 199 expect(await page.evaluate(() => !!window.opener)).toBe(false); 200 expect(await popup.evaluate(() => !!window.opener)).toBe(true); 201 }); 202 it('should work with fake-clicking target=_blank and rel=noopener', async () => { 203 const { page, server } = getTestState(); 204 205 await page.goto(server.EMPTY_PAGE); 206 await page.setContent( 207 '<a target=_blank rel=noopener href="/one-style.html">yo</a>' 208 ); 209 const [popup] = await Promise.all([ 210 new Promise<Page>((x) => page.once('popup', x)), 211 page.$eval('a', (a: HTMLAnchorElement) => a.click()), 212 ]); 213 expect(await page.evaluate(() => !!window.opener)).toBe(false); 214 expect(await popup.evaluate(() => !!window.opener)).toBe(false); 215 }); 216 it('should work with clicking target=_blank and rel=noopener', async () => { 217 const { page, server } = getTestState(); 218 219 await page.goto(server.EMPTY_PAGE); 220 await page.setContent( 221 '<a target=_blank rel=noopener href="/one-style.html">yo</a>' 222 ); 223 const [popup] = await Promise.all([ 224 new Promise<Page>((x) => page.once('popup', x)), 225 page.click('a'), 226 ]); 227 expect(await page.evaluate(() => !!window.opener)).toBe(false); 228 expect(await popup.evaluate(() => !!window.opener)).toBe(false); 229 }); 230 }); 231 232 describe('BrowserContext.overridePermissions', function () { 233 function getPermission(page, name) { 234 return page.evaluate( 235 (name) => 236 navigator.permissions.query({ name }).then((result) => result.state), 237 name 238 ); 239 } 240 241 it('should be prompt by default', async () => { 242 const { page, server } = getTestState(); 243 244 await page.goto(server.EMPTY_PAGE); 245 expect(await getPermission(page, 'geolocation')).toBe('prompt'); 246 }); 247 it('should deny permission when not listed', async () => { 248 const { page, server, context } = getTestState(); 249 250 await page.goto(server.EMPTY_PAGE); 251 await context.overridePermissions(server.EMPTY_PAGE, []); 252 expect(await getPermission(page, 'geolocation')).toBe('denied'); 253 }); 254 it('should fail when bad permission is given', async () => { 255 const { page, server, context } = getTestState(); 256 257 await page.goto(server.EMPTY_PAGE); 258 let error = null; 259 await context 260 // @ts-expect-error purposeful bad input for test 261 .overridePermissions(server.EMPTY_PAGE, ['foo']) 262 .catch((error_) => (error = error_)); 263 expect(error.message).toBe('Unknown permission: foo'); 264 }); 265 it('should grant permission when listed', async () => { 266 const { page, server, context } = getTestState(); 267 268 await page.goto(server.EMPTY_PAGE); 269 await context.overridePermissions(server.EMPTY_PAGE, ['geolocation']); 270 expect(await getPermission(page, 'geolocation')).toBe('granted'); 271 }); 272 it('should reset permissions', async () => { 273 const { page, server, context } = getTestState(); 274 275 await page.goto(server.EMPTY_PAGE); 276 await context.overridePermissions(server.EMPTY_PAGE, ['geolocation']); 277 expect(await getPermission(page, 'geolocation')).toBe('granted'); 278 await context.clearPermissionOverrides(); 279 expect(await getPermission(page, 'geolocation')).toBe('prompt'); 280 }); 281 it('should trigger permission onchange', async () => { 282 const { page, server, context } = getTestState(); 283 284 await page.goto(server.EMPTY_PAGE); 285 await page.evaluate(() => { 286 globalThis.events = []; 287 return navigator.permissions 288 .query({ name: 'geolocation' }) 289 .then(function (result) { 290 globalThis.events.push(result.state); 291 result.onchange = function () { 292 globalThis.events.push(result.state); 293 }; 294 }); 295 }); 296 expect(await page.evaluate(() => globalThis.events)).toEqual(['prompt']); 297 await context.overridePermissions(server.EMPTY_PAGE, []); 298 expect(await page.evaluate(() => globalThis.events)).toEqual([ 299 'prompt', 300 'denied', 301 ]); 302 await context.overridePermissions(server.EMPTY_PAGE, ['geolocation']); 303 expect(await page.evaluate(() => globalThis.events)).toEqual([ 304 'prompt', 305 'denied', 306 'granted', 307 ]); 308 await context.clearPermissionOverrides(); 309 expect(await page.evaluate(() => globalThis.events)).toEqual([ 310 'prompt', 311 'denied', 312 'granted', 313 'prompt', 314 ]); 315 }); 316 it( 317 'should isolate permissions between browser contexts', 318 async () => { 319 const { page, server, context, browser } = getTestState(); 320 321 await page.goto(server.EMPTY_PAGE); 322 const otherContext = await browser.createIncognitoBrowserContext(); 323 const otherPage = await otherContext.newPage(); 324 await otherPage.goto(server.EMPTY_PAGE); 325 expect(await getPermission(page, 'geolocation')).toBe('prompt'); 326 expect(await getPermission(otherPage, 'geolocation')).toBe('prompt'); 327 328 await context.overridePermissions(server.EMPTY_PAGE, []); 329 await otherContext.overridePermissions(server.EMPTY_PAGE, [ 330 'geolocation', 331 ]); 332 expect(await getPermission(page, 'geolocation')).toBe('denied'); 333 expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); 334 335 await context.clearPermissionOverrides(); 336 expect(await getPermission(page, 'geolocation')).toBe('prompt'); 337 expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); 338 339 await otherContext.close(); 340 } 341 ); 342 }); 343 344 describe('Page.setGeolocation', function () { 345 it('should work', async () => { 346 const { page, server, context } = getTestState(); 347 348 await context.overridePermissions(server.PREFIX, ['geolocation']); 349 await page.goto(server.EMPTY_PAGE); 350 await page.setGeolocation({ longitude: 10, latitude: 10 }); 351 const geolocation = await page.evaluate( 352 () => 353 new Promise((resolve) => 354 navigator.geolocation.getCurrentPosition((position) => { 355 resolve({ 356 latitude: position.coords.latitude, 357 longitude: position.coords.longitude, 358 }); 359 }) 360 ) 361 ); 362 expect(geolocation).toEqual({ 363 latitude: 10, 364 longitude: 10, 365 }); 366 }); 367 it('should throw when invalid longitude', async () => { 368 const { page } = getTestState(); 369 370 let error = null; 371 try { 372 await page.setGeolocation({ longitude: 200, latitude: 10 }); 373 } catch (error_) { 374 error = error_; 375 } 376 expect(error.message).toContain('Invalid longitude "200"'); 377 }); 378 }); 379 380 describe('Page.setOfflineMode', function () { 381 it('should work', async () => { 382 const { page, server } = getTestState(); 383 384 await page.setOfflineMode(true); 385 let error = null; 386 await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_)); 387 expect(error).toBeTruthy(); 388 await page.setOfflineMode(false); 389 const response = await page.reload(); 390 expect(response.status()).toBe(200); 391 }); 392 it('should emulate navigator.onLine', async () => { 393 const { page } = getTestState(); 394 395 expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); 396 await page.setOfflineMode(true); 397 expect(await page.evaluate(() => window.navigator.onLine)).toBe(false); 398 await page.setOfflineMode(false); 399 expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); 400 }); 401 }); 402 403 describe('ExecutionContext.queryObjects', function () { 404 it('should work', async () => { 405 const { page } = getTestState(); 406 407 // Instantiate an object 408 await page.evaluate(() => (globalThis.set = new Set(['hello', 'world']))); 409 const prototypeHandle = await page.evaluateHandle(() => Set.prototype); 410 const objectsHandle = await page.queryObjects(prototypeHandle); 411 const count = await page.evaluate( 412 (objects: JSHandle[]) => objects.length, 413 objectsHandle 414 ); 415 expect(count).toBe(1); 416 const values = await page.evaluate( 417 (objects) => Array.from(objects[0].values()), 418 objectsHandle 419 ); 420 expect(values).toEqual(['hello', 'world']); 421 }); 422 it('should work for non-blank page', async () => { 423 const { page, server } = getTestState(); 424 425 // Instantiate an object 426 await page.goto(server.EMPTY_PAGE); 427 await page.evaluate(() => (globalThis.set = new Set(['hello', 'world']))); 428 const prototypeHandle = await page.evaluateHandle(() => Set.prototype); 429 const objectsHandle = await page.queryObjects(prototypeHandle); 430 const count = await page.evaluate( 431 (objects: JSHandle[]) => objects.length, 432 objectsHandle 433 ); 434 expect(count).toBe(1); 435 }); 436 it('should fail for disposed handles', async () => { 437 const { page } = getTestState(); 438 439 const prototypeHandle = await page.evaluateHandle( 440 () => HTMLBodyElement.prototype 441 ); 442 await prototypeHandle.dispose(); 443 let error = null; 444 await page 445 .queryObjects(prototypeHandle) 446 .catch((error_) => (error = error_)); 447 expect(error.message).toBe('Prototype JSHandle is disposed!'); 448 }); 449 it('should fail primitive values as prototypes', async () => { 450 const { page } = getTestState(); 451 452 const prototypeHandle = await page.evaluateHandle(() => 42); 453 let error = null; 454 await page 455 .queryObjects(prototypeHandle) 456 .catch((error_) => (error = error_)); 457 expect(error.message).toBe( 458 'Prototype JSHandle must not be referencing primitive value' 459 ); 460 }); 461 }); 462 463 describe('Page.Events.Console', function () { 464 it('should work', async () => { 465 const { page } = getTestState(); 466 467 let message = null; 468 page.once('console', (m) => (message = m)); 469 await Promise.all([ 470 page.evaluate(() => console.log('hello', 5, { foo: 'bar' })), 471 waitEvent(page, 'console'), 472 ]); 473 expect(message.text()).toEqual('hello 5 JSHandle@object'); 474 expect(message.type()).toEqual('log'); 475 expect(message.args()).toHaveLength(3); 476 expect(message.location()).toEqual({ 477 url: expect.any(String), 478 lineNumber: expect.any(Number), 479 columnNumber: expect.any(Number), 480 }); 481 482 expect(await message.args()[0].jsonValue()).toEqual('hello'); 483 expect(await message.args()[1].jsonValue()).toEqual(5); 484 expect(await message.args()[2].jsonValue()).toEqual({ foo: 'bar' }); 485 }); 486 it('should work for different console API calls', async () => { 487 const { page } = getTestState(); 488 489 const messages = []; 490 page.on('console', (msg) => messages.push(msg)); 491 // All console events will be reported before `page.evaluate` is finished. 492 await page.evaluate(() => { 493 // A pair of time/timeEnd generates only one Console API call. 494 console.time('calling console.time'); 495 console.timeEnd('calling console.time'); 496 console.trace('calling console.trace'); 497 console.dir('calling console.dir'); 498 console.warn('calling console.warn'); 499 console.error('calling console.error'); 500 console.log(Promise.resolve('should not wait until resolved!')); 501 }); 502 expect(messages.map((msg) => msg.type())).toEqual([ 503 'timeEnd', 504 'trace', 505 'dir', 506 'warning', 507 'error', 508 'log', 509 ]); 510 expect(messages[0].text()).toContain('calling console.time'); 511 expect(messages.slice(1).map((msg) => msg.text())).toEqual([ 512 'calling console.trace', 513 'calling console.dir', 514 'calling console.warn', 515 'calling console.error', 516 'JSHandle@promise', 517 ]); 518 }); 519 it('should not fail for window object', async () => { 520 const { page } = getTestState(); 521 522 let message = null; 523 page.once('console', (msg) => (message = msg)); 524 await Promise.all([ 525 page.evaluate(() => console.error(window)), 526 waitEvent(page, 'console'), 527 ]); 528 expect(message.text()).toBe('JSHandle@object'); 529 }); 530 it('should trigger correct Log', async () => { 531 const { page, server, isChrome } = getTestState(); 532 533 await page.goto('about:blank'); 534 const [message] = await Promise.all([ 535 waitEvent(page, 'console'), 536 page.evaluate( 537 async (url: string) => fetch(url).catch(() => {}), 538 server.EMPTY_PAGE 539 ), 540 ]); 541 expect(message.text()).toContain('Access-Control-Allow-Origin'); 542 if (isChrome) expect(message.type()).toEqual('error'); 543 else expect(message.type()).toEqual('warn'); 544 }); 545 it('should have location when fetch fails', async () => { 546 const { page, server } = getTestState(); 547 548 // The point of this test is to make sure that we report console messages from 549 // Log domain: https://vanilla.aslushnikov.com/?Log.entryAdded 550 await page.goto(server.EMPTY_PAGE); 551 const [message] = await Promise.all([ 552 waitEvent(page, 'console'), 553 page.setContent(`<script>fetch('http://wat');</script>`), 554 ]); 555 expect(message.text()).toContain(`ERR_NAME_NOT_RESOLVED`); 556 expect(message.type()).toEqual('error'); 557 expect(message.location()).toEqual({ 558 url: 'http://wat/', 559 lineNumber: undefined, 560 }); 561 }); 562 it('should have location and stack trace for console API calls', async () => { 563 const { page, server, isChrome } = getTestState(); 564 565 await page.goto(server.EMPTY_PAGE); 566 const [message] = await Promise.all([ 567 waitEvent(page, 'console'), 568 page.goto(server.PREFIX + '/consolelog.html'), 569 ]); 570 expect(message.text()).toBe('yellow'); 571 expect(message.type()).toBe('log'); 572 expect(message.location()).toEqual({ 573 url: server.PREFIX + '/consolelog.html', 574 lineNumber: 8, 575 columnNumber: isChrome ? 16 : 8, // console.|log vs |console.log 576 }); 577 expect(message.stackTrace()).toEqual([ 578 { 579 url: server.PREFIX + '/consolelog.html', 580 lineNumber: 8, 581 columnNumber: isChrome ? 16 : 8, // console.|log vs |console.log 582 }, 583 { 584 url: server.PREFIX + '/consolelog.html', 585 lineNumber: 11, 586 columnNumber: 8, 587 }, 588 { 589 url: server.PREFIX + '/consolelog.html', 590 lineNumber: 13, 591 columnNumber: 6, 592 }, 593 ]); 594 }); 595 // @see https://github.com/puppeteer/puppeteer/issues/3865 596 it('should not throw when there are console messages in detached iframes', async () => { 597 const { page, server } = getTestState(); 598 599 await page.goto(server.EMPTY_PAGE); 600 await page.evaluate(async () => { 601 // 1. Create a popup that Puppeteer is not connected to. 602 const win = window.open( 603 window.location.href, 604 'Title', 605 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=200,top=0,left=0' 606 ); 607 await new Promise((x) => (win.onload = x)); 608 // 2. In this popup, create an iframe that console.logs a message. 609 win.document.body.innerHTML = `<iframe src='/consolelog.html'></iframe>`; 610 const frame = win.document.querySelector('iframe'); 611 await new Promise((x) => (frame.onload = x)); 612 // 3. After that, remove the iframe. 613 frame.remove(); 614 }); 615 const popupTarget = page 616 .browserContext() 617 .targets() 618 .find((target) => target !== page.target()); 619 // 4. Connect to the popup and make sure it doesn't throw. 620 await popupTarget.page(); 621 }); 622 }); 623 624 describe('Page.Events.DOMContentLoaded', function () { 625 it('should fire when expected', async () => { 626 const { page } = getTestState(); 627 628 page.goto('about:blank'); 629 await waitEvent(page, 'domcontentloaded'); 630 }); 631 }); 632 633 describe('Page.metrics', function () { 634 it('should get metrics from a page', async () => { 635 const { page } = getTestState(); 636 637 await page.goto('about:blank'); 638 const metrics = await page.metrics(); 639 checkMetrics(metrics); 640 }); 641 it('metrics event fired on console.timeStamp', async () => { 642 const { page } = getTestState(); 643 644 const metricsPromise = new Promise<{ metrics: Metrics; title: string }>( 645 (fulfill) => page.once('metrics', fulfill) 646 ); 647 await page.evaluate(() => console.timeStamp('test42')); 648 const metrics = await metricsPromise; 649 expect(metrics.title).toBe('test42'); 650 checkMetrics(metrics.metrics); 651 }); 652 function checkMetrics(metrics) { 653 const metricsToCheck = new Set([ 654 'Timestamp', 655 'Documents', 656 'Frames', 657 'JSEventListeners', 658 'Nodes', 659 'LayoutCount', 660 'RecalcStyleCount', 661 'LayoutDuration', 662 'RecalcStyleDuration', 663 'ScriptDuration', 664 'TaskDuration', 665 'JSHeapUsedSize', 666 'JSHeapTotalSize', 667 ]); 668 for (const name in metrics) { 669 expect(metricsToCheck.has(name)).toBeTruthy(); 670 expect(metrics[name]).toBeGreaterThanOrEqual(0); 671 metricsToCheck.delete(name); 672 } 673 expect(metricsToCheck.size).toBe(0); 674 } 675 }); 676 677 describe('Page.waitForRequest', function () { 678 it('should work', async () => { 679 const { page, server } = getTestState(); 680 681 await page.goto(server.EMPTY_PAGE); 682 const [request] = await Promise.all([ 683 page.waitForRequest(server.PREFIX + '/digits/2.png'), 684 page.evaluate(() => { 685 fetch('/digits/1.png'); 686 fetch('/digits/2.png'); 687 fetch('/digits/3.png'); 688 }), 689 ]); 690 expect(request.url()).toBe(server.PREFIX + '/digits/2.png'); 691 }); 692 it('should work with predicate', async () => { 693 const { page, server } = getTestState(); 694 695 await page.goto(server.EMPTY_PAGE); 696 const [request] = await Promise.all([ 697 page.waitForRequest( 698 (request) => request.url() === server.PREFIX + '/digits/2.png' 699 ), 700 page.evaluate(() => { 701 fetch('/digits/1.png'); 702 fetch('/digits/2.png'); 703 fetch('/digits/3.png'); 704 }), 705 ]); 706 expect(request.url()).toBe(server.PREFIX + '/digits/2.png'); 707 }); 708 it('should respect timeout', async () => { 709 const { page, puppeteer } = getTestState(); 710 711 let error = null; 712 await page 713 .waitForRequest(() => false, { timeout: 1 }) 714 .catch((error_) => (error = error_)); 715 expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); 716 }); 717 it('should respect default timeout', async () => { 718 const { page, puppeteer } = getTestState(); 719 720 let error = null; 721 page.setDefaultTimeout(1); 722 await page 723 .waitForRequest(() => false) 724 .catch((error_) => (error = error_)); 725 expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); 726 }); 727 it('should work with async predicate', async () => { 728 const { page, server } = getTestState(); 729 await page.goto(server.EMPTY_PAGE); 730 const [response] = await Promise.all([ 731 page.waitForResponse(async (response) => { 732 return response.url() === server.PREFIX + '/digits/2.png'; 733 }), 734 page.evaluate(() => { 735 fetch('/digits/1.png'); 736 fetch('/digits/2.png'); 737 fetch('/digits/3.png'); 738 }), 739 ]); 740 expect(response.url()).toBe(server.PREFIX + '/digits/2.png'); 741 }); 742 it('should work with no timeout', async () => { 743 const { page, server } = getTestState(); 744 745 await page.goto(server.EMPTY_PAGE); 746 const [request] = await Promise.all([ 747 page.waitForRequest(server.PREFIX + '/digits/2.png', { timeout: 0 }), 748 page.evaluate(() => 749 setTimeout(() => { 750 fetch('/digits/1.png'); 751 fetch('/digits/2.png'); 752 fetch('/digits/3.png'); 753 }, 50) 754 ), 755 ]); 756 expect(request.url()).toBe(server.PREFIX + '/digits/2.png'); 757 }); 758 }); 759 760 describe('Page.waitForResponse', function () { 761 it('should work', async () => { 762 const { page, server } = getTestState(); 763 764 await page.goto(server.EMPTY_PAGE); 765 const [response] = await Promise.all([ 766 page.waitForResponse(server.PREFIX + '/digits/2.png'), 767 page.evaluate(() => { 768 fetch('/digits/1.png'); 769 fetch('/digits/2.png'); 770 fetch('/digits/3.png'); 771 }), 772 ]); 773 expect(response.url()).toBe(server.PREFIX + '/digits/2.png'); 774 }); 775 it('should respect timeout', async () => { 776 const { page, puppeteer } = getTestState(); 777 778 let error = null; 779 await page 780 .waitForResponse(() => false, { timeout: 1 }) 781 .catch((error_) => (error = error_)); 782 expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); 783 }); 784 it('should respect default timeout', async () => { 785 const { page, puppeteer } = getTestState(); 786 787 let error = null; 788 page.setDefaultTimeout(1); 789 await page 790 .waitForResponse(() => false) 791 .catch((error_) => (error = error_)); 792 expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); 793 }); 794 it('should work with predicate', async () => { 795 const { page, server } = getTestState(); 796 797 await page.goto(server.EMPTY_PAGE); 798 const [response] = await Promise.all([ 799 page.waitForResponse( 800 (response) => response.url() === server.PREFIX + '/digits/2.png' 801 ), 802 page.evaluate(() => { 803 fetch('/digits/1.png'); 804 fetch('/digits/2.png'); 805 fetch('/digits/3.png'); 806 }), 807 ]); 808 expect(response.url()).toBe(server.PREFIX + '/digits/2.png'); 809 }); 810 it('should work with no timeout', async () => { 811 const { page, server } = getTestState(); 812 813 await page.goto(server.EMPTY_PAGE); 814 const [response] = await Promise.all([ 815 page.waitForResponse(server.PREFIX + '/digits/2.png', { timeout: 0 }), 816 page.evaluate(() => 817 setTimeout(() => { 818 fetch('/digits/1.png'); 819 fetch('/digits/2.png'); 820 fetch('/digits/3.png'); 821 }, 50) 822 ), 823 ]); 824 expect(response.url()).toBe(server.PREFIX + '/digits/2.png'); 825 }); 826 }); 827 828 describe('Page.exposeFunction', function () { 829 it('should work', async () => { 830 const { page } = getTestState(); 831 832 await page.exposeFunction('compute', function (a, b) { 833 return a * b; 834 }); 835 const result = await page.evaluate(async function () { 836 return await globalThis.compute(9, 4); 837 }); 838 expect(result).toBe(36); 839 }); 840 it('should throw exception in page context', async () => { 841 const { page } = getTestState(); 842 843 await page.exposeFunction('woof', function () { 844 throw new Error('WOOF WOOF'); 845 }); 846 const { message, stack } = await page.evaluate(async () => { 847 try { 848 await globalThis.woof(); 849 } catch (error) { 850 return { message: error.message, stack: error.stack }; 851 } 852 }); 853 expect(message).toBe('WOOF WOOF'); 854 expect(stack).toContain(__filename); 855 }); 856 it('should support throwing "null"', async () => { 857 const { page } = getTestState(); 858 859 await page.exposeFunction('woof', function () { 860 throw null; 861 }); 862 const thrown = await page.evaluate(async () => { 863 try { 864 await globalThis.woof(); 865 } catch (error) { 866 return error; 867 } 868 }); 869 expect(thrown).toBe(null); 870 }); 871 it('should be callable from-inside evaluateOnNewDocument', async () => { 872 const { page } = getTestState(); 873 874 let called = false; 875 await page.exposeFunction('woof', function () { 876 called = true; 877 }); 878 await page.evaluateOnNewDocument(() => globalThis.woof()); 879 await page.reload(); 880 expect(called).toBe(true); 881 }); 882 it('should survive navigation', async () => { 883 const { page, server } = getTestState(); 884 885 await page.exposeFunction('compute', function (a, b) { 886 return a * b; 887 }); 888 889 await page.goto(server.EMPTY_PAGE); 890 const result = await page.evaluate(async function () { 891 return await globalThis.compute(9, 4); 892 }); 893 expect(result).toBe(36); 894 }); 895 it('should await returned promise', async () => { 896 const { page } = getTestState(); 897 898 await page.exposeFunction('compute', function (a, b) { 899 return Promise.resolve(a * b); 900 }); 901 902 const result = await page.evaluate(async function () { 903 return await globalThis.compute(3, 5); 904 }); 905 expect(result).toBe(15); 906 }); 907 it('should work on frames', async () => { 908 const { page, server } = getTestState(); 909 910 await page.exposeFunction('compute', function (a, b) { 911 return Promise.resolve(a * b); 912 }); 913 914 await page.goto(server.PREFIX + '/frames/nested-frames.html'); 915 const frame = page.frames()[1]; 916 const result = await frame.evaluate(async function () { 917 return await globalThis.compute(3, 5); 918 }); 919 expect(result).toBe(15); 920 }); 921 it('should work on frames before navigation', async () => { 922 const { page, server } = getTestState(); 923 924 await page.goto(server.PREFIX + '/frames/nested-frames.html'); 925 await page.exposeFunction('compute', function (a, b) { 926 return Promise.resolve(a * b); 927 }); 928 929 const frame = page.frames()[1]; 930 const result = await frame.evaluate(async function () { 931 return await globalThis.compute(3, 5); 932 }); 933 expect(result).toBe(15); 934 }); 935 it('should work with complex objects', async () => { 936 const { page } = getTestState(); 937 938 await page.exposeFunction('complexObject', function (a, b) { 939 return { x: a.x + b.x }; 940 }); 941 const result = await page.evaluate<() => Promise<{ x: number }>>( 942 async () => globalThis.complexObject({ x: 5 }, { x: 2 }) 943 ); 944 expect(result.x).toBe(7); 945 }); 946 }); 947 948 describe('Page.Events.PageError', function () { 949 it('should fire', async () => { 950 const { page, server } = getTestState(); 951 952 let error = null; 953 page.once('pageerror', (e) => (error = e)); 954 await Promise.all([ 955 page.goto(server.PREFIX + '/error.html'), 956 waitEvent(page, 'pageerror'), 957 ]); 958 expect(error.message).toContain('Fancy'); 959 }); 960 }); 961 962 describe('Page.setUserAgent', function () { 963 it('should work', async () => { 964 const { page, server } = getTestState(); 965 966 expect(await page.evaluate(() => navigator.userAgent)).toContain( 967 'Mozilla' 968 ); 969 await page.setUserAgent('foobar'); 970 const [request] = await Promise.all([ 971 server.waitForRequest('/empty.html'), 972 page.goto(server.EMPTY_PAGE), 973 ]); 974 expect(request.headers['user-agent']).toBe('foobar'); 975 }); 976 it('should work for subframes', async () => { 977 const { page, server } = getTestState(); 978 979 expect(await page.evaluate(() => navigator.userAgent)).toContain( 980 'Mozilla' 981 ); 982 await page.setUserAgent('foobar'); 983 const [request] = await Promise.all([ 984 server.waitForRequest('/empty.html'), 985 utils.attachFrame(page, 'frame1', server.EMPTY_PAGE), 986 ]); 987 expect(request.headers['user-agent']).toBe('foobar'); 988 }); 989 it('should emulate device user-agent', async () => { 990 const { page, server, puppeteer } = getTestState(); 991 992 await page.goto(server.PREFIX + '/mobile.html'); 993 expect(await page.evaluate(() => navigator.userAgent)).not.toContain( 994 'iPhone' 995 ); 996 await page.setUserAgent(puppeteer.devices['iPhone 6'].userAgent); 997 expect(await page.evaluate(() => navigator.userAgent)).toContain( 998 'iPhone' 999 ); 1000 }); 1001 }); 1002 1003 describe('Page.setContent', function () { 1004 const expectedOutput = 1005 '<html><head></head><body><div>hello</div></body></html>'; 1006 it('should work', async () => { 1007 const { page } = getTestState(); 1008 1009 await page.setContent('<div>hello</div>'); 1010 const result = await page.content(); 1011 expect(result).toBe(expectedOutput); 1012 }); 1013 it('should work with doctype', async () => { 1014 const { page } = getTestState(); 1015 1016 const doctype = '<!DOCTYPE html>'; 1017 await page.setContent(`${doctype}<div>hello</div>`); 1018 const result = await page.content(); 1019 expect(result).toBe(`${doctype}${expectedOutput}`); 1020 }); 1021 it('should work with HTML 4 doctype', async () => { 1022 const { page } = getTestState(); 1023 1024 const doctype = 1025 '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" ' + 1026 '"http://www.w3.org/TR/html4/strict.dtd">'; 1027 await page.setContent(`${doctype}<div>hello</div>`); 1028 const result = await page.content(); 1029 expect(result).toBe(`${doctype}${expectedOutput}`); 1030 }); 1031 it('should respect timeout', async () => { 1032 const { page, server, puppeteer } = getTestState(); 1033 1034 const imgPath = '/img.png'; 1035 // stall for image 1036 server.setRoute(imgPath, () => {}); 1037 let error = null; 1038 await page 1039 .setContent(`<img src="${server.PREFIX + imgPath}"></img>`, { 1040 timeout: 1, 1041 }) 1042 .catch((error_) => (error = error_)); 1043 expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); 1044 }); 1045 it('should respect default navigation timeout', async () => { 1046 const { page, server, puppeteer } = getTestState(); 1047 1048 page.setDefaultNavigationTimeout(1); 1049 const imgPath = '/img.png'; 1050 // stall for image 1051 server.setRoute(imgPath, () => {}); 1052 let error = null; 1053 await page 1054 .setContent(`<img src="${server.PREFIX + imgPath}"></img>`) 1055 .catch((error_) => (error = error_)); 1056 expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError); 1057 }); 1058 it('should await resources to load', async () => { 1059 const { page, server } = getTestState(); 1060 1061 const imgPath = '/img.png'; 1062 let imgResponse = null; 1063 server.setRoute(imgPath, (req, res) => (imgResponse = res)); 1064 let loaded = false; 1065 const contentPromise = page 1066 .setContent(`<img src="${server.PREFIX + imgPath}"></img>`) 1067 .then(() => (loaded = true)); 1068 await server.waitForRequest(imgPath); 1069 expect(loaded).toBe(false); 1070 imgResponse.end(); 1071 await contentPromise; 1072 }); 1073 it('should work fast enough', async () => { 1074 const { page } = getTestState(); 1075 1076 for (let i = 0; i < 20; ++i) await page.setContent('<div>yo</div>'); 1077 }); 1078 it('should work with tricky content', async () => { 1079 const { page } = getTestState(); 1080 1081 await page.setContent('<div>hello world</div>' + '\x7F'); 1082 expect(await page.$eval('div', (div) => div.textContent)).toBe( 1083 'hello world' 1084 ); 1085 }); 1086 it('should work with accents', async () => { 1087 const { page } = getTestState(); 1088 1089 await page.setContent('<div>aberración</div>'); 1090 expect(await page.$eval('div', (div) => div.textContent)).toBe( 1091 'aberración' 1092 ); 1093 }); 1094 it('should work with emojis', async () => { 1095 const { page } = getTestState(); 1096 1097 await page.setContent('<div></div>'); 1098 expect(await page.$eval('div', (div) => div.textContent)).toBe(''); 1099 }); 1100 it('should work with newline', async () => { 1101 const { page } = getTestState(); 1102 1103 await page.setContent('<div>\n</div>'); 1104 expect(await page.$eval('div', (div) => div.textContent)).toBe('\n'); 1105 }); 1106 }); 1107 1108 describe('Page.setBypassCSP', function () { 1109 it('should bypass CSP meta tag', async () => { 1110 const { page, server } = getTestState(); 1111 1112 // Make sure CSP prohibits addScriptTag. 1113 await page.goto(server.PREFIX + '/csp.html'); 1114 await page 1115 .addScriptTag({ content: 'window.__injected = 42;' }) 1116 .catch((error) => void error); 1117 expect(await page.evaluate(() => globalThis.__injected)).toBe(undefined); 1118 1119 // By-pass CSP and try one more time. 1120 await page.setBypassCSP(true); 1121 await page.reload(); 1122 await page.addScriptTag({ content: 'window.__injected = 42;' }); 1123 expect(await page.evaluate(() => globalThis.__injected)).toBe(42); 1124 }); 1125 1126 it('should bypass CSP header', async () => { 1127 const { page, server } = getTestState(); 1128 1129 // Make sure CSP prohibits addScriptTag. 1130 server.setCSP('/empty.html', 'default-src "self"'); 1131 await page.goto(server.EMPTY_PAGE); 1132 await page 1133 .addScriptTag({ content: 'window.__injected = 42;' }) 1134 .catch((error) => void error); 1135 expect(await page.evaluate(() => globalThis.__injected)).toBe(undefined); 1136 1137 // By-pass CSP and try one more time. 1138 await page.setBypassCSP(true); 1139 await page.reload(); 1140 await page.addScriptTag({ content: 'window.__injected = 42;' }); 1141 expect(await page.evaluate(() => globalThis.__injected)).toBe(42); 1142 }); 1143 1144 it('should bypass after cross-process navigation', async () => { 1145 const { page, server } = getTestState(); 1146 1147 await page.setBypassCSP(true); 1148 await page.goto(server.PREFIX + '/csp.html'); 1149 await page.addScriptTag({ content: 'window.__injected = 42;' }); 1150 expect(await page.evaluate(() => globalThis.__injected)).toBe(42); 1151 1152 await page.goto(server.CROSS_PROCESS_PREFIX + '/csp.html'); 1153 await page.addScriptTag({ content: 'window.__injected = 42;' }); 1154 expect(await page.evaluate(() => globalThis.__injected)).toBe(42); 1155 }); 1156 it('should bypass CSP in iframes as well', async () => { 1157 const { page, server } = getTestState(); 1158 1159 await page.goto(server.EMPTY_PAGE); 1160 { 1161 // Make sure CSP prohibits addScriptTag in an iframe. 1162 const frame = await utils.attachFrame( 1163 page, 1164 'frame1', 1165 server.PREFIX + '/csp.html' 1166 ); 1167 await frame 1168 .addScriptTag({ content: 'window.__injected = 42;' }) 1169 .catch((error) => void error); 1170 expect(await frame.evaluate(() => globalThis.__injected)).toBe( 1171 undefined 1172 ); 1173 } 1174 1175 // By-pass CSP and try one more time. 1176 await page.setBypassCSP(true); 1177 await page.reload(); 1178 1179 { 1180 const frame = await utils.attachFrame( 1181 page, 1182 'frame1', 1183 server.PREFIX + '/csp.html' 1184 ); 1185 await frame 1186 .addScriptTag({ content: 'window.__injected = 42;' }) 1187 .catch((error) => void error); 1188 expect(await frame.evaluate(() => globalThis.__injected)).toBe(42); 1189 } 1190 }); 1191 }); 1192 1193 describe('Page.addScriptTag', function () { 1194 it('should throw an error if no options are provided', async () => { 1195 const { page } = getTestState(); 1196 1197 let error = null; 1198 try { 1199 // @ts-expect-error purposefully passing bad options 1200 await page.addScriptTag('/injectedfile.js'); 1201 } catch (error_) { 1202 error = error_; 1203 } 1204 expect(error.message).toBe( 1205 'Provide an object with a `url`, `path` or `content` property' 1206 ); 1207 }); 1208 1209 it('should work with a url', async () => { 1210 const { page, server } = getTestState(); 1211 1212 await page.goto(server.EMPTY_PAGE); 1213 const scriptHandle = await page.addScriptTag({ url: '/injectedfile.js' }); 1214 expect(scriptHandle.asElement()).not.toBeNull(); 1215 expect(await page.evaluate(() => globalThis.__injected)).toBe(42); 1216 }); 1217 1218 it('should work with a url and type=module', async () => { 1219 const { page, server } = getTestState(); 1220 1221 await page.goto(server.EMPTY_PAGE); 1222 await page.addScriptTag({ url: '/es6/es6import.js', type: 'module' }); 1223 expect(await page.evaluate(() => globalThis.__es6injected)).toBe(42); 1224 }); 1225 1226 it('should work with a path and type=module', async () => { 1227 const { page, server } = getTestState(); 1228 1229 await page.goto(server.EMPTY_PAGE); 1230 await page.addScriptTag({ 1231 path: path.join(__dirname, 'assets/es6/es6pathimport.js'), 1232 type: 'module', 1233 }); 1234 await page.waitForFunction('window.__es6injected'); 1235 expect(await page.evaluate(() => globalThis.__es6injected)).toBe(42); 1236 }); 1237 1238 it('should work with a content and type=module', async () => { 1239 const { page, server } = getTestState(); 1240 1241 await page.goto(server.EMPTY_PAGE); 1242 await page.addScriptTag({ 1243 content: `import num from '/es6/es6module.js';window.__es6injected = num;`, 1244 type: 'module', 1245 }); 1246 await page.waitForFunction('window.__es6injected'); 1247 expect(await page.evaluate(() => globalThis.__es6injected)).toBe(42); 1248 }); 1249 1250 it('should throw an error if loading from url fail', async () => { 1251 const { page, server } = getTestState(); 1252 1253 await page.goto(server.EMPTY_PAGE); 1254 let error = null; 1255 try { 1256 await page.addScriptTag({ url: '/nonexistfile.js' }); 1257 } catch (error_) { 1258 error = error_; 1259 } 1260 expect(error.message).toBe('Loading script from /nonexistfile.js failed'); 1261 }); 1262 1263 it('should work with a path', async () => { 1264 const { page, server } = getTestState(); 1265 1266 await page.goto(server.EMPTY_PAGE); 1267 const scriptHandle = await page.addScriptTag({ 1268 path: path.join(__dirname, 'assets/injectedfile.js'), 1269 }); 1270 expect(scriptHandle.asElement()).not.toBeNull(); 1271 expect(await page.evaluate(() => globalThis.__injected)).toBe(42); 1272 }); 1273 1274 it('should include sourcemap when path is provided', async () => { 1275 const { page, server } = getTestState(); 1276 1277 await page.goto(server.EMPTY_PAGE); 1278 await page.addScriptTag({ 1279 path: path.join(__dirname, 'assets/injectedfile.js'), 1280 }); 1281 const result = await page.evaluate( 1282 () => globalThis.__injectedError.stack 1283 ); 1284 expect(result).toContain(path.join('assets', 'injectedfile.js')); 1285 }); 1286 1287 it('should work with content', async () => { 1288 const { page, server } = getTestState(); 1289 1290 await page.goto(server.EMPTY_PAGE); 1291 const scriptHandle = await page.addScriptTag({ 1292 content: 'window.__injected = 35;', 1293 }); 1294 expect(scriptHandle.asElement()).not.toBeNull(); 1295 expect(await page.evaluate(() => globalThis.__injected)).toBe(35); 1296 }); 1297 1298 // @see https://github.com/puppeteer/puppeteer/issues/4840 1299 xit('should throw when added with content to the CSP page', async () => { 1300 const { page, server } = getTestState(); 1301 1302 await page.goto(server.PREFIX + '/csp.html'); 1303 let error = null; 1304 await page 1305 .addScriptTag({ content: 'window.__injected = 35;' }) 1306 .catch((error_) => (error = error_)); 1307 expect(error).toBeTruthy(); 1308 }); 1309 1310 it('should throw when added with URL to the CSP page', async () => { 1311 const { page, server } = getTestState(); 1312 1313 await page.goto(server.PREFIX + '/csp.html'); 1314 let error = null; 1315 await page 1316 .addScriptTag({ url: server.CROSS_PROCESS_PREFIX + '/injectedfile.js' }) 1317 .catch((error_) => (error = error_)); 1318 expect(error).toBeTruthy(); 1319 }); 1320 }); 1321 1322 describe('Page.addStyleTag', function () { 1323 it('should throw an error if no options are provided', async () => { 1324 const { page } = getTestState(); 1325 1326 let error = null; 1327 try { 1328 // @ts-expect-error purposefully passing bad input 1329 await page.addStyleTag('/injectedstyle.css'); 1330 } catch (error_) { 1331 error = error_; 1332 } 1333 expect(error.message).toBe( 1334 'Provide an object with a `url`, `path` or `content` property' 1335 ); 1336 }); 1337 1338 it('should work with a url', async () => { 1339 const { page, server } = getTestState(); 1340 1341 await page.goto(server.EMPTY_PAGE); 1342 const styleHandle = await page.addStyleTag({ url: '/injectedstyle.css' }); 1343 expect(styleHandle.asElement()).not.toBeNull(); 1344 expect( 1345 await page.evaluate( 1346 `window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')` 1347 ) 1348 ).toBe('rgb(255, 0, 0)'); 1349 }); 1350 1351 it('should throw an error if loading from url fail', async () => { 1352 const { page, server } = getTestState(); 1353 1354 await page.goto(server.EMPTY_PAGE); 1355 let error = null; 1356 try { 1357 await page.addStyleTag({ url: '/nonexistfile.js' }); 1358 } catch (error_) { 1359 error = error_; 1360 } 1361 expect(error.message).toBe('Loading style from /nonexistfile.js failed'); 1362 }); 1363 1364 it('should work with a path', async () => { 1365 const { page, server } = getTestState(); 1366 1367 await page.goto(server.EMPTY_PAGE); 1368 const styleHandle = await page.addStyleTag({ 1369 path: path.join(__dirname, 'assets/injectedstyle.css'), 1370 }); 1371 expect(styleHandle.asElement()).not.toBeNull(); 1372 expect( 1373 await page.evaluate( 1374 `window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')` 1375 ) 1376 ).toBe('rgb(255, 0, 0)'); 1377 }); 1378 1379 it('should include sourcemap when path is provided', async () => { 1380 const { page, server } = getTestState(); 1381 1382 await page.goto(server.EMPTY_PAGE); 1383 await page.addStyleTag({ 1384 path: path.join(__dirname, 'assets/injectedstyle.css'), 1385 }); 1386 const styleHandle = await page.$('style'); 1387 const styleContent = await page.evaluate( 1388 (style: HTMLStyleElement) => style.innerHTML, 1389 styleHandle 1390 ); 1391 expect(styleContent).toContain(path.join('assets', 'injectedstyle.css')); 1392 }); 1393 1394 it('should work with content', async () => { 1395 const { page, server } = getTestState(); 1396 1397 await page.goto(server.EMPTY_PAGE); 1398 const styleHandle = await page.addStyleTag({ 1399 content: 'body { background-color: green; }', 1400 }); 1401 expect(styleHandle.asElement()).not.toBeNull(); 1402 expect( 1403 await page.evaluate( 1404 `window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color')` 1405 ) 1406 ).toBe('rgb(0, 128, 0)'); 1407 }); 1408 1409 it( 1410 'should throw when added with content to the CSP page', 1411 async () => { 1412 const { page, server } = getTestState(); 1413 1414 await page.goto(server.PREFIX + '/csp.html'); 1415 let error = null; 1416 await page 1417 .addStyleTag({ content: 'body { background-color: green; }' }) 1418 .catch((error_) => (error = error_)); 1419 expect(error).toBeTruthy(); 1420 } 1421 ); 1422 1423 it('should throw when added with URL to the CSP page', async () => { 1424 const { page, server } = getTestState(); 1425 1426 await page.goto(server.PREFIX + '/csp.html'); 1427 let error = null; 1428 await page 1429 .addStyleTag({ 1430 url: server.CROSS_PROCESS_PREFIX + '/injectedstyle.css', 1431 }) 1432 .catch((error_) => (error = error_)); 1433 expect(error).toBeTruthy(); 1434 }); 1435 }); 1436 1437 describe('Page.url', function () { 1438 it('should work', async () => { 1439 const { page, server } = getTestState(); 1440 1441 expect(page.url()).toBe('about:blank'); 1442 await page.goto(server.EMPTY_PAGE); 1443 expect(page.url()).toBe(server.EMPTY_PAGE); 1444 }); 1445 }); 1446 1447 describe('Page.setJavaScriptEnabled', function () { 1448 it('should work', async () => { 1449 const { page } = getTestState(); 1450 1451 await page.setJavaScriptEnabled(false); 1452 await page.goto( 1453 'data:text/html, <script>var something = "forbidden"</script>' 1454 ); 1455 let error = null; 1456 await page.evaluate('something').catch((error_) => (error = error_)); 1457 expect(error.message).toContain('something is not defined'); 1458 1459 await page.setJavaScriptEnabled(true); 1460 await page.goto( 1461 'data:text/html, <script>var something = "forbidden"</script>' 1462 ); 1463 expect(await page.evaluate('something')).toBe('forbidden'); 1464 }); 1465 }); 1466 1467 describe('Page.setCacheEnabled', function () { 1468 it('should enable or disable the cache based on the state passed', async () => { 1469 const { page, server } = getTestState(); 1470 1471 await page.goto(server.PREFIX + '/cached/one-style.html'); 1472 const [cachedRequest] = await Promise.all([ 1473 server.waitForRequest('/cached/one-style.html'), 1474 page.reload(), 1475 ]); 1476 // Rely on "if-modified-since" caching in our test server. 1477 expect(cachedRequest.headers['if-modified-since']).not.toBe(undefined); 1478 1479 await page.setCacheEnabled(false); 1480 const [nonCachedRequest] = await Promise.all([ 1481 server.waitForRequest('/cached/one-style.html'), 1482 page.reload(), 1483 ]); 1484 expect(nonCachedRequest.headers['if-modified-since']).toBe(undefined); 1485 }); 1486 it( 1487 'should stay disabled when toggling request interception on/off', 1488 async () => { 1489 const { page, server } = getTestState(); 1490 1491 await page.setCacheEnabled(false); 1492 await page.setRequestInterception(true); 1493 await page.setRequestInterception(false); 1494 1495 await page.goto(server.PREFIX + '/cached/one-style.html'); 1496 const [nonCachedRequest] = await Promise.all([ 1497 server.waitForRequest('/cached/one-style.html'), 1498 page.reload(), 1499 ]); 1500 expect(nonCachedRequest.headers['if-modified-since']).toBe(undefined); 1501 } 1502 ); 1503 }); 1504 1505 describe('printing to PDF', function () { 1506 it('can print to PDF and save to file', async () => { 1507 // Printing to pdf is currently only supported in headless 1508 const { isHeadless, page } = getTestState(); 1509 1510 if (!isHeadless) return; 1511 1512 const outputFile = __dirname + '/assets/output.pdf'; 1513 await page.pdf({ path: outputFile }); 1514 expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0); 1515 fs.unlinkSync(outputFile); 1516 }); 1517 }); 1518 1519 describe('Page.title', function () { 1520 it('should return the page title', async () => { 1521 const { page, server } = getTestState(); 1522 1523 await page.goto(server.PREFIX + '/title.html'); 1524 expect(await page.title()).toBe('Woof-Woof'); 1525 }); 1526 }); 1527 1528 describe('Page.select', function () { 1529 it('should select single option', async () => { 1530 const { page, server } = getTestState(); 1531 1532 await page.goto(server.PREFIX + '/input/select.html'); 1533 await page.select('select', 'blue'); 1534 expect(await page.evaluate(() => globalThis.result.onInput)).toEqual([ 1535 'blue', 1536 ]); 1537 expect(await page.evaluate(() => globalThis.result.onChange)).toEqual([ 1538 'blue', 1539 ]); 1540 }); 1541 it('should select only first option', async () => { 1542 const { page, server } = getTestState(); 1543 1544 await page.goto(server.PREFIX + '/input/select.html'); 1545 await page.select('select', 'blue', 'green', 'red'); 1546 expect(await page.evaluate(() => globalThis.result.onInput)).toEqual([ 1547 'blue', 1548 ]); 1549 expect(await page.evaluate(() => globalThis.result.onChange)).toEqual([ 1550 'blue', 1551 ]); 1552 }); 1553 it('should not throw when select causes navigation', async () => { 1554 const { page, server } = getTestState(); 1555 1556 await page.goto(server.PREFIX + '/input/select.html'); 1557 await page.$eval('select', (select) => 1558 select.addEventListener( 1559 'input', 1560 () => ((window as any).location = '/empty.html') 1561 ) 1562 ); 1563 await Promise.all([ 1564 page.select('select', 'blue'), 1565 page.waitForNavigation(), 1566 ]); 1567 expect(page.url()).toContain('empty.html'); 1568 }); 1569 it('should select multiple options', async () => { 1570 const { page, server } = getTestState(); 1571 1572 await page.goto(server.PREFIX + '/input/select.html'); 1573 await page.evaluate(() => globalThis.makeMultiple()); 1574 await page.select('select', 'blue', 'green', 'red'); 1575 expect(await page.evaluate(() => globalThis.result.onInput)).toEqual([ 1576 'blue', 1577 'green', 1578 'red', 1579 ]); 1580 expect(await page.evaluate(() => globalThis.result.onChange)).toEqual([ 1581 'blue', 1582 'green', 1583 'red', 1584 ]); 1585 }); 1586 it('should respect event bubbling', async () => { 1587 const { page, server } = getTestState(); 1588 1589 await page.goto(server.PREFIX + '/input/select.html'); 1590 await page.select('select', 'blue'); 1591 expect( 1592 await page.evaluate(() => globalThis.result.onBubblingInput) 1593 ).toEqual(['blue']); 1594 expect( 1595 await page.evaluate(() => globalThis.result.onBubblingChange) 1596 ).toEqual(['blue']); 1597 }); 1598 it('should throw when element is not a <select>', async () => { 1599 const { page, server } = getTestState(); 1600 1601 let error = null; 1602 await page.goto(server.PREFIX + '/input/select.html'); 1603 await page.select('body', '').catch((error_) => (error = error_)); 1604 expect(error.message).toContain('Element is not a <select> element.'); 1605 }); 1606 it('should return [] on no matched values', async () => { 1607 const { page, server } = getTestState(); 1608 1609 await page.goto(server.PREFIX + '/input/select.html'); 1610 const result = await page.select('select', '42', 'abc'); 1611 expect(result).toEqual([]); 1612 }); 1613 it('should return an array of matched values', async () => { 1614 const { page, server } = getTestState(); 1615 1616 await page.goto(server.PREFIX + '/input/select.html'); 1617 await page.evaluate(() => globalThis.makeMultiple()); 1618 const result = await page.select('select', 'blue', 'black', 'magenta'); 1619 expect( 1620 result.reduce( 1621 (accumulator, current) => 1622 ['blue', 'black', 'magenta'].includes(current) && accumulator, 1623 true 1624 ) 1625 ).toEqual(true); 1626 }); 1627 it('should return an array of one element when multiple is not set', async () => { 1628 const { page, server } = getTestState(); 1629 1630 await page.goto(server.PREFIX + '/input/select.html'); 1631 const result = await page.select( 1632 'select', 1633 '42', 1634 'blue', 1635 'black', 1636 'magenta' 1637 ); 1638 expect(result.length).toEqual(1); 1639 }); 1640 it('should return [] on no values', async () => { 1641 const { page, server } = getTestState(); 1642 1643 await page.goto(server.PREFIX + '/input/select.html'); 1644 const result = await page.select('select'); 1645 expect(result).toEqual([]); 1646 }); 1647 it('should deselect all options when passed no values for a multiple select', async () => { 1648 const { page, server } = getTestState(); 1649 1650 await page.goto(server.PREFIX + '/input/select.html'); 1651 await page.evaluate(() => globalThis.makeMultiple()); 1652 await page.select('select', 'blue', 'black', 'magenta'); 1653 await page.select('select'); 1654 expect( 1655 await page.$eval('select', (select: HTMLSelectElement) => 1656 Array.from(select.options).every( 1657 (option: HTMLOptionElement) => !option.selected 1658 ) 1659 ) 1660 ).toEqual(true); 1661 }); 1662 it('should deselect all options when passed no values for a select without multiple', async () => { 1663 const { page, server } = getTestState(); 1664 1665 await page.goto(server.PREFIX + '/input/select.html'); 1666 await page.select('select', 'blue', 'black', 'magenta'); 1667 await page.select('select'); 1668 expect( 1669 await page.$eval('select', (select: HTMLSelectElement) => 1670 Array.from(select.options).every( 1671 (option: HTMLOptionElement) => !option.selected 1672 ) 1673 ) 1674 ).toEqual(true); 1675 }); 1676 it('should throw if passed in non-strings', async () => { 1677 const { page } = getTestState(); 1678 1679 await page.setContent('<select><option value="12"/></select>'); 1680 let error = null; 1681 try { 1682 // @ts-expect-error purposefully passing bad input 1683 await page.select('select', 12); 1684 } catch (error_) { 1685 error = error_; 1686 } 1687 expect(error.message).toContain('Values must be strings'); 1688 }); 1689 // @see https://github.com/puppeteer/puppeteer/issues/3327 1690 it( 1691 'should work when re-defining top-level Event class', 1692 async () => { 1693 const { page, server } = getTestState(); 1694 1695 await page.goto(server.PREFIX + '/input/select.html'); 1696 await page.evaluate(() => (window.Event = null)); 1697 await page.select('select', 'blue'); 1698 expect(await page.evaluate(() => globalThis.result.onInput)).toEqual([ 1699 'blue', 1700 ]); 1701 expect(await page.evaluate(() => globalThis.result.onChange)).toEqual([ 1702 'blue', 1703 ]); 1704 } 1705 ); 1706 }); 1707 1708 describe('Page.Events.Close', function () { 1709 itFailsFirefox('should work with window.close', async () => { 1710 const { page, context } = getTestState(); 1711 1712 const newPagePromise = new Promise<Page>((fulfill) => 1713 context.once('targetcreated', (target) => fulfill(target.page())) 1714 ); 1715 await page.evaluate( 1716 () => (window['newPage'] = window.open('about:blank')) 1717 ); 1718 const newPage = await newPagePromise; 1719 const closedPromise = new Promise((x) => newPage.on('close', x)); 1720 await page.evaluate(() => window['newPage'].close()); 1721 await closedPromise; 1722 }); 1723 it('should work with page.close', async () => { 1724 const { context } = getTestState(); 1725 1726 const newPage = await context.newPage(); 1727 const closedPromise = new Promise((x) => newPage.on('close', x)); 1728 await newPage.close(); 1729 await closedPromise; 1730 }); 1731 }); 1732 1733 describe('Page.browser', function () { 1734 it('should return the correct browser instance', async () => { 1735 const { page, browser } = getTestState(); 1736 1737 expect(page.browser()).toBe(browser); 1738 }); 1739 }); 1740 1741 describe('Page.browserContext', function () { 1742 it('should return the correct browser context instance', async () => { 1743 const { page, context } = getTestState(); 1744 1745 expect(page.browserContext()).toBe(context); 1746 }); 1747 }); 1748}); 1749