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 fs from 'fs'; 18import path from 'path'; 19import utils from './utils.js'; 20import expect from 'expect'; 21import { 22 getTestState, 23 setupTestBrowserHooks, 24 setupTestPageAndContextHooks, 25 describeFailsFirefox, 26} from './mocha-utils'; // eslint-disable-line import/extensions 27 28describe('request interception', function () { 29 setupTestBrowserHooks(); 30 setupTestPageAndContextHooks(); 31 describe('Page.setRequestInterception', function () { 32 it('should intercept', async () => { 33 const { page, server } = getTestState(); 34 35 await page.setRequestInterception(true); 36 page.on('request', (request) => { 37 if (utils.isFavicon(request)) { 38 request.continue(); 39 return; 40 } 41 expect(request.url()).toContain('empty.html'); 42 expect(request.headers()['user-agent']).toBeTruthy(); 43 expect(request.method()).toBe('GET'); 44 expect(request.postData()).toBe(undefined); 45 expect(request.isNavigationRequest()).toBe(true); 46 expect(request.resourceType()).toBe('document'); 47 expect(request.frame() === page.mainFrame()).toBe(true); 48 expect(request.frame().url()).toBe('about:blank'); 49 request.continue(); 50 }); 51 const response = await page.goto(server.EMPTY_PAGE); 52 expect(response.ok()).toBe(true); 53 expect(response.remoteAddress().port).toBe(server.PORT); 54 }); 55 // @see https://github.com/puppeteer/puppeteer/pull/3105 56 it('should work when POST is redirected with 302', async () => { 57 const { page, server } = getTestState(); 58 59 server.setRedirect('/rredirect', '/empty.html'); 60 await page.goto(server.EMPTY_PAGE); 61 await page.setRequestInterception(true); 62 page.on('request', (request) => request.continue()); 63 await page.setContent(` 64 <form action='/rredirect' method='post'> 65 <input type="hidden" id="foo" name="foo" value="FOOBAR"> 66 </form> 67 `); 68 await Promise.all([ 69 page.$eval('form', (form: HTMLFormElement) => form.submit()), 70 page.waitForNavigation(), 71 ]); 72 }); 73 // @see https://github.com/puppeteer/puppeteer/issues/3973 74 it('should work when header manipulation headers with redirect', async () => { 75 const { page, server } = getTestState(); 76 77 server.setRedirect('/rrredirect', '/empty.html'); 78 await page.setRequestInterception(true); 79 page.on('request', (request) => { 80 const headers = Object.assign({}, request.headers(), { 81 foo: 'bar', 82 }); 83 request.continue({ headers }); 84 }); 85 await page.goto(server.PREFIX + '/rrredirect'); 86 }); 87 // @see https://github.com/puppeteer/puppeteer/issues/4743 88 it('should be able to remove headers', async () => { 89 const { page, server } = getTestState(); 90 91 await page.setRequestInterception(true); 92 page.on('request', (request) => { 93 const headers = Object.assign({}, request.headers(), { 94 foo: 'bar', 95 origin: undefined, // remove "origin" header 96 }); 97 request.continue({ headers }); 98 }); 99 100 const [serverRequest] = await Promise.all([ 101 server.waitForRequest('/empty.html'), 102 page.goto(server.PREFIX + '/empty.html'), 103 ]); 104 105 expect(serverRequest.headers.origin).toBe(undefined); 106 }); 107 it('should contain referer header', async () => { 108 const { page, server } = getTestState(); 109 110 await page.setRequestInterception(true); 111 const requests = []; 112 page.on('request', (request) => { 113 if (!utils.isFavicon(request)) requests.push(request); 114 request.continue(); 115 }); 116 await page.goto(server.PREFIX + '/one-style.html'); 117 expect(requests[1].url()).toContain('/one-style.css'); 118 expect(requests[1].headers().referer).toContain('/one-style.html'); 119 }); 120 it('should properly return navigation response when URL has cookies', async () => { 121 const { page, server } = getTestState(); 122 123 // Setup cookie. 124 await page.goto(server.EMPTY_PAGE); 125 await page.setCookie({ name: 'foo', value: 'bar' }); 126 127 // Setup request interception. 128 await page.setRequestInterception(true); 129 page.on('request', (request) => request.continue()); 130 const response = await page.reload(); 131 expect(response.status()).toBe(200); 132 }); 133 it('should stop intercepting', async () => { 134 const { page, server } = getTestState(); 135 136 await page.setRequestInterception(true); 137 page.once('request', (request) => request.continue()); 138 await page.goto(server.EMPTY_PAGE); 139 await page.setRequestInterception(false); 140 await page.goto(server.EMPTY_PAGE); 141 }); 142 it('should show custom HTTP headers', async () => { 143 const { page, server } = getTestState(); 144 145 await page.setExtraHTTPHeaders({ 146 foo: 'bar', 147 }); 148 await page.setRequestInterception(true); 149 page.on('request', (request) => { 150 expect(request.headers()['foo']).toBe('bar'); 151 request.continue(); 152 }); 153 const response = await page.goto(server.EMPTY_PAGE); 154 expect(response.ok()).toBe(true); 155 }); 156 // @see https://github.com/puppeteer/puppeteer/issues/4337 157 it('should work with redirect inside sync XHR', async () => { 158 const { page, server } = getTestState(); 159 160 await page.goto(server.EMPTY_PAGE); 161 server.setRedirect('/logo.png', '/pptr.png'); 162 await page.setRequestInterception(true); 163 page.on('request', (request) => request.continue()); 164 const status = await page.evaluate(async () => { 165 const request = new XMLHttpRequest(); 166 request.open('GET', '/logo.png', false); // `false` makes the request synchronous 167 request.send(null); 168 return request.status; 169 }); 170 expect(status).toBe(200); 171 }); 172 it('should work with custom referer headers', async () => { 173 const { page, server } = getTestState(); 174 175 await page.setExtraHTTPHeaders({ referer: server.EMPTY_PAGE }); 176 await page.setRequestInterception(true); 177 page.on('request', (request) => { 178 expect(request.headers()['referer']).toBe(server.EMPTY_PAGE); 179 request.continue(); 180 }); 181 const response = await page.goto(server.EMPTY_PAGE); 182 expect(response.ok()).toBe(true); 183 }); 184 it('should be abortable', async () => { 185 const { page, server } = getTestState(); 186 187 await page.setRequestInterception(true); 188 page.on('request', (request) => { 189 if (request.url().endsWith('.css')) request.abort(); 190 else request.continue(); 191 }); 192 let failedRequests = 0; 193 page.on('requestfailed', () => ++failedRequests); 194 const response = await page.goto(server.PREFIX + '/one-style.html'); 195 expect(response.ok()).toBe(true); 196 expect(response.request().failure()).toBe(null); 197 expect(failedRequests).toBe(1); 198 }); 199 it('should be abortable with custom error codes', async () => { 200 const { page, server } = getTestState(); 201 202 await page.setRequestInterception(true); 203 page.on('request', (request) => { 204 request.abort('internetdisconnected'); 205 }); 206 let failedRequest = null; 207 page.on('requestfailed', (request) => (failedRequest = request)); 208 await page.goto(server.EMPTY_PAGE).catch(() => {}); 209 expect(failedRequest).toBeTruthy(); 210 expect(failedRequest.failure().errorText).toBe( 211 'net::ERR_INTERNET_DISCONNECTED' 212 ); 213 }); 214 it('should send referer', async () => { 215 const { page, server } = getTestState(); 216 217 await page.setExtraHTTPHeaders({ 218 referer: 'http://google.com/', 219 }); 220 await page.setRequestInterception(true); 221 page.on('request', (request) => request.continue()); 222 const [request] = await Promise.all([ 223 server.waitForRequest('/grid.html'), 224 page.goto(server.PREFIX + '/grid.html'), 225 ]); 226 expect(request.headers['referer']).toBe('http://google.com/'); 227 }); 228 it('should fail navigation when aborting main resource', async () => { 229 const { page, server, isChrome } = getTestState(); 230 231 await page.setRequestInterception(true); 232 page.on('request', (request) => request.abort()); 233 let error = null; 234 await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_)); 235 expect(error).toBeTruthy(); 236 if (isChrome) expect(error.message).toContain('net::ERR_FAILED'); 237 else expect(error.message).toContain('NS_ERROR_FAILURE'); 238 }); 239 it('should work with redirects', async () => { 240 const { page, server } = getTestState(); 241 242 await page.setRequestInterception(true); 243 const requests = []; 244 page.on('request', (request) => { 245 request.continue(); 246 requests.push(request); 247 }); 248 server.setRedirect( 249 '/non-existing-page.html', 250 '/non-existing-page-2.html' 251 ); 252 server.setRedirect( 253 '/non-existing-page-2.html', 254 '/non-existing-page-3.html' 255 ); 256 server.setRedirect( 257 '/non-existing-page-3.html', 258 '/non-existing-page-4.html' 259 ); 260 server.setRedirect('/non-existing-page-4.html', '/empty.html'); 261 const response = await page.goto( 262 server.PREFIX + '/non-existing-page.html' 263 ); 264 expect(response.status()).toBe(200); 265 expect(response.url()).toContain('empty.html'); 266 expect(requests.length).toBe(5); 267 expect(requests[2].resourceType()).toBe('document'); 268 // Check redirect chain 269 const redirectChain = response.request().redirectChain(); 270 expect(redirectChain.length).toBe(4); 271 expect(redirectChain[0].url()).toContain('/non-existing-page.html'); 272 expect(redirectChain[2].url()).toContain('/non-existing-page-3.html'); 273 for (let i = 0; i < redirectChain.length; ++i) { 274 const request = redirectChain[i]; 275 expect(request.isNavigationRequest()).toBe(true); 276 expect(request.redirectChain().indexOf(request)).toBe(i); 277 } 278 }); 279 it('should work with redirects for subresources', async () => { 280 const { page, server } = getTestState(); 281 282 await page.setRequestInterception(true); 283 const requests = []; 284 page.on('request', (request) => { 285 request.continue(); 286 if (!utils.isFavicon(request)) requests.push(request); 287 }); 288 server.setRedirect('/one-style.css', '/two-style.css'); 289 server.setRedirect('/two-style.css', '/three-style.css'); 290 server.setRedirect('/three-style.css', '/four-style.css'); 291 server.setRoute('/four-style.css', (req, res) => 292 res.end('body {box-sizing: border-box; }') 293 ); 294 295 const response = await page.goto(server.PREFIX + '/one-style.html'); 296 expect(response.status()).toBe(200); 297 expect(response.url()).toContain('one-style.html'); 298 expect(requests.length).toBe(5); 299 expect(requests[0].resourceType()).toBe('document'); 300 expect(requests[1].resourceType()).toBe('stylesheet'); 301 // Check redirect chain 302 const redirectChain = requests[1].redirectChain(); 303 expect(redirectChain.length).toBe(3); 304 expect(redirectChain[0].url()).toContain('/one-style.css'); 305 expect(redirectChain[2].url()).toContain('/three-style.css'); 306 }); 307 it('should be able to abort redirects', async () => { 308 const { page, server, isChrome } = getTestState(); 309 310 await page.setRequestInterception(true); 311 server.setRedirect('/non-existing.json', '/non-existing-2.json'); 312 server.setRedirect('/non-existing-2.json', '/simple.html'); 313 page.on('request', (request) => { 314 if (request.url().includes('non-existing-2')) request.abort(); 315 else request.continue(); 316 }); 317 await page.goto(server.EMPTY_PAGE); 318 const result = await page.evaluate(async () => { 319 try { 320 await fetch('/non-existing.json'); 321 } catch (error) { 322 return error.message; 323 } 324 }); 325 if (isChrome) expect(result).toContain('Failed to fetch'); 326 else expect(result).toContain('NetworkError'); 327 }); 328 it('should work with equal requests', async () => { 329 const { page, server } = getTestState(); 330 331 await page.goto(server.EMPTY_PAGE); 332 let responseCount = 1; 333 server.setRoute('/zzz', (req, res) => res.end(responseCount++ * 11 + '')); 334 await page.setRequestInterception(true); 335 336 let spinner = false; 337 // Cancel 2nd request. 338 page.on('request', (request) => { 339 if (utils.isFavicon(request)) { 340 request.continue(); 341 return; 342 } 343 spinner ? request.abort() : request.continue(); 344 spinner = !spinner; 345 }); 346 const results = await page.evaluate(() => 347 Promise.all([ 348 fetch('/zzz') 349 .then((response) => response.text()) 350 .catch(() => 'FAILED'), 351 fetch('/zzz') 352 .then((response) => response.text()) 353 .catch(() => 'FAILED'), 354 fetch('/zzz') 355 .then((response) => response.text()) 356 .catch(() => 'FAILED'), 357 ]) 358 ); 359 expect(results).toEqual(['11', 'FAILED', '22']); 360 }); 361 it('should navigate to dataURL and fire dataURL requests', async () => { 362 const { page } = getTestState(); 363 364 await page.setRequestInterception(true); 365 const requests = []; 366 page.on('request', (request) => { 367 requests.push(request); 368 request.continue(); 369 }); 370 const dataURL = 'data:text/html,<div>yo</div>'; 371 const response = await page.goto(dataURL); 372 expect(response.status()).toBe(200); 373 expect(requests.length).toBe(1); 374 expect(requests[0].url()).toBe(dataURL); 375 }); 376 it('should be able to fetch dataURL and fire dataURL requests', async () => { 377 const { page, server } = getTestState(); 378 379 await page.goto(server.EMPTY_PAGE); 380 await page.setRequestInterception(true); 381 const requests = []; 382 page.on('request', (request) => { 383 requests.push(request); 384 request.continue(); 385 }); 386 const dataURL = 'data:text/html,<div>yo</div>'; 387 const text = await page.evaluate( 388 (url: string) => fetch(url).then((r) => r.text()), 389 dataURL 390 ); 391 expect(text).toBe('<div>yo</div>'); 392 expect(requests.length).toBe(1); 393 expect(requests[0].url()).toBe(dataURL); 394 }); 395 it('should navigate to URL with hash and fire requests without hash', async () => { 396 const { page, server } = getTestState(); 397 398 await page.setRequestInterception(true); 399 const requests = []; 400 page.on('request', (request) => { 401 requests.push(request); 402 request.continue(); 403 }); 404 const response = await page.goto(server.EMPTY_PAGE + '#hash'); 405 expect(response.status()).toBe(200); 406 expect(response.url()).toBe(server.EMPTY_PAGE); 407 expect(requests.length).toBe(1); 408 expect(requests[0].url()).toBe(server.EMPTY_PAGE); 409 }); 410 it('should work with encoded server', async () => { 411 const { page, server } = getTestState(); 412 413 // The requestWillBeSent will report encoded URL, whereas interception will 414 // report URL as-is. @see crbug.com/759388 415 await page.setRequestInterception(true); 416 page.on('request', (request) => request.continue()); 417 const response = await page.goto( 418 server.PREFIX + '/some nonexisting page' 419 ); 420 expect(response.status()).toBe(404); 421 }); 422 it('should work with badly encoded server', async () => { 423 const { page, server } = getTestState(); 424 425 await page.setRequestInterception(true); 426 server.setRoute('/malformed?rnd=%911', (req, res) => res.end()); 427 page.on('request', (request) => request.continue()); 428 const response = await page.goto(server.PREFIX + '/malformed?rnd=%911'); 429 expect(response.status()).toBe(200); 430 }); 431 it('should work with encoded server - 2', async () => { 432 const { page, server } = getTestState(); 433 434 // The requestWillBeSent will report URL as-is, whereas interception will 435 // report encoded URL for stylesheet. @see crbug.com/759388 436 await page.setRequestInterception(true); 437 const requests = []; 438 page.on('request', (request) => { 439 request.continue(); 440 requests.push(request); 441 }); 442 const response = await page.goto( 443 `data:text/html,<link rel="stylesheet" href="${server.PREFIX}/fonts?helvetica|arial"/>` 444 ); 445 expect(response.status()).toBe(200); 446 expect(requests.length).toBe(2); 447 expect(requests[1].response().status()).toBe(404); 448 }); 449 it('should not throw "Invalid Interception Id" if the request was cancelled', async () => { 450 const { page, server } = getTestState(); 451 452 await page.setContent('<iframe></iframe>'); 453 await page.setRequestInterception(true); 454 let request = null; 455 page.on('request', async (r) => (request = r)); 456 page.$eval( 457 'iframe', 458 (frame: HTMLIFrameElement, url: string) => (frame.src = url), 459 server.EMPTY_PAGE 460 ), 461 // Wait for request interception. 462 await utils.waitEvent(page, 'request'); 463 // Delete frame to cause request to be canceled. 464 await page.$eval('iframe', (frame) => frame.remove()); 465 let error = null; 466 await request.continue().catch((error_) => (error = error_)); 467 expect(error).toBe(null); 468 }); 469 it('should throw if interception is not enabled', async () => { 470 const { page, server } = getTestState(); 471 472 let error = null; 473 page.on('request', async (request) => { 474 try { 475 await request.continue(); 476 } catch (error_) { 477 error = error_; 478 } 479 }); 480 await page.goto(server.EMPTY_PAGE); 481 expect(error.message).toContain('Request Interception is not enabled'); 482 }); 483 it('should work with file URLs', async () => { 484 const { page } = getTestState(); 485 486 await page.setRequestInterception(true); 487 const urls = new Set(); 488 page.on('request', (request) => { 489 urls.add(request.url().split('/').pop()); 490 request.continue(); 491 }); 492 await page.goto( 493 pathToFileURL(path.join(__dirname, 'assets', 'one-style.html')) 494 ); 495 expect(urls.size).toBe(2); 496 expect(urls.has('one-style.html')).toBe(true); 497 expect(urls.has('one-style.css')).toBe(true); 498 }); 499 it('should not cache if cache disabled', async () => { 500 const { page, server } = getTestState(); 501 502 // Load and re-load to make sure it's cached. 503 await page.goto(server.PREFIX + '/cached/one-style.html'); 504 505 await page.setRequestInterception(true); 506 await page.setCacheEnabled(false); 507 page.on('request', (request) => request.continue()); 508 509 const cached = []; 510 page.on('requestservedfromcache', (r) => cached.push(r)); 511 512 await page.reload(); 513 expect(cached.length).toBe(0); 514 }); 515 it('should cache if cache enabled', async () => { 516 const { page, server } = getTestState(); 517 518 // Load and re-load to make sure it's cached. 519 await page.goto(server.PREFIX + '/cached/one-style.html'); 520 521 await page.setRequestInterception(true); 522 await page.setCacheEnabled(true); 523 page.on('request', (request) => request.continue()); 524 525 const cached = []; 526 page.on('requestservedfromcache', (r) => cached.push(r)); 527 528 await page.reload(); 529 expect(cached.length).toBe(1); 530 }); 531 it('should load fonts if cache enabled', async () => { 532 const { page, server } = getTestState(); 533 534 await page.setRequestInterception(true); 535 await page.setCacheEnabled(true); 536 page.on('request', (request) => request.continue()); 537 538 await page.goto(server.PREFIX + '/cached/one-style-font.html'); 539 await page.waitForResponse((r) => r.url().endsWith('/one-style.woff')); 540 }); 541 }); 542 543 describe('Request.continue', function () { 544 it('should work', async () => { 545 const { page, server } = getTestState(); 546 547 await page.setRequestInterception(true); 548 page.on('request', (request) => request.continue()); 549 await page.goto(server.EMPTY_PAGE); 550 }); 551 it('should amend HTTP headers', async () => { 552 const { page, server } = getTestState(); 553 554 await page.setRequestInterception(true); 555 page.on('request', (request) => { 556 const headers = Object.assign({}, request.headers()); 557 headers['FOO'] = 'bar'; 558 request.continue({ headers }); 559 }); 560 await page.goto(server.EMPTY_PAGE); 561 const [request] = await Promise.all([ 562 server.waitForRequest('/sleep.zzz'), 563 page.evaluate(() => fetch('/sleep.zzz')), 564 ]); 565 expect(request.headers['foo']).toBe('bar'); 566 }); 567 it('should redirect in a way non-observable to page', async () => { 568 const { page, server } = getTestState(); 569 570 await page.setRequestInterception(true); 571 page.on('request', (request) => { 572 const redirectURL = request.url().includes('/empty.html') 573 ? server.PREFIX + '/consolelog.html' 574 : undefined; 575 request.continue({ url: redirectURL }); 576 }); 577 let consoleMessage = null; 578 page.on('console', (msg) => (consoleMessage = msg)); 579 await page.goto(server.EMPTY_PAGE); 580 expect(page.url()).toBe(server.EMPTY_PAGE); 581 expect(consoleMessage.text()).toBe('yellow'); 582 }); 583 it('should amend method', async () => { 584 const { page, server } = getTestState(); 585 586 await page.goto(server.EMPTY_PAGE); 587 588 await page.setRequestInterception(true); 589 page.on('request', (request) => { 590 request.continue({ method: 'POST' }); 591 }); 592 const [request] = await Promise.all([ 593 server.waitForRequest('/sleep.zzz'), 594 page.evaluate(() => fetch('/sleep.zzz')), 595 ]); 596 expect(request.method).toBe('POST'); 597 }); 598 it('should amend post data', async () => { 599 const { page, server } = getTestState(); 600 601 await page.goto(server.EMPTY_PAGE); 602 603 await page.setRequestInterception(true); 604 page.on('request', (request) => { 605 request.continue({ postData: 'doggo' }); 606 }); 607 const [serverRequest] = await Promise.all([ 608 server.waitForRequest('/sleep.zzz'), 609 page.evaluate(() => 610 fetch('/sleep.zzz', { method: 'POST', body: 'birdy' }) 611 ), 612 ]); 613 expect(await serverRequest.postBody).toBe('doggo'); 614 }); 615 it('should amend both post data and method on navigation', async () => { 616 const { page, server } = getTestState(); 617 618 await page.setRequestInterception(true); 619 page.on('request', (request) => { 620 request.continue({ method: 'POST', postData: 'doggo' }); 621 }); 622 const [serverRequest] = await Promise.all([ 623 server.waitForRequest('/empty.html'), 624 page.goto(server.EMPTY_PAGE), 625 ]); 626 expect(serverRequest.method).toBe('POST'); 627 expect(await serverRequest.postBody).toBe('doggo'); 628 }); 629 it('should fail if the header value is invalid', async () => { 630 const { page, server } = getTestState(); 631 632 let error; 633 await page.setRequestInterception(true); 634 page.on('request', async (request) => { 635 await request 636 .continue({ 637 headers: { 638 'X-Invalid-Header': 'a\nb', 639 }, 640 }) 641 .catch((error_) => { 642 error = error_; 643 }); 644 await request.continue(); 645 }); 646 await page.goto(server.PREFIX + '/empty.html'); 647 expect(error.message).toMatch(/Invalid header/); 648 }); 649 }); 650 651 describe('Request.respond', function () { 652 it('should work', async () => { 653 const { page, server } = getTestState(); 654 655 await page.setRequestInterception(true); 656 page.on('request', (request) => { 657 request.respond({ 658 status: 201, 659 headers: { 660 foo: 'bar', 661 }, 662 body: 'Yo, page!', 663 }); 664 }); 665 const response = await page.goto(server.EMPTY_PAGE); 666 expect(response.status()).toBe(201); 667 expect(response.headers().foo).toBe('bar'); 668 expect(await page.evaluate(() => document.body.textContent)).toBe( 669 'Yo, page!' 670 ); 671 }); 672 it('should work with status code 422', async () => { 673 const { page, server } = getTestState(); 674 675 await page.setRequestInterception(true); 676 page.on('request', (request) => { 677 request.respond({ 678 status: 422, 679 body: 'Yo, page!', 680 }); 681 }); 682 const response = await page.goto(server.EMPTY_PAGE); 683 expect(response.status()).toBe(422); 684 expect(response.statusText()).toBe('Unprocessable Entity'); 685 expect(await page.evaluate(() => document.body.textContent)).toBe( 686 'Yo, page!' 687 ); 688 }); 689 it('should redirect', async () => { 690 const { page, server } = getTestState(); 691 692 await page.setRequestInterception(true); 693 page.on('request', (request) => { 694 if (!request.url().includes('rrredirect')) { 695 request.continue(); 696 return; 697 } 698 request.respond({ 699 status: 302, 700 headers: { 701 location: server.EMPTY_PAGE, 702 }, 703 }); 704 }); 705 const response = await page.goto(server.PREFIX + '/rrredirect'); 706 expect(response.request().redirectChain().length).toBe(1); 707 expect(response.request().redirectChain()[0].url()).toBe( 708 server.PREFIX + '/rrredirect' 709 ); 710 expect(response.url()).toBe(server.EMPTY_PAGE); 711 }); 712 it('should allow mocking binary responses', async () => { 713 const { page, server } = getTestState(); 714 715 await page.setRequestInterception(true); 716 page.on('request', (request) => { 717 const imageBuffer = fs.readFileSync( 718 path.join(__dirname, 'assets', 'pptr.png') 719 ); 720 request.respond({ 721 contentType: 'image/png', 722 body: imageBuffer, 723 }); 724 }); 725 await page.evaluate((PREFIX) => { 726 const img = document.createElement('img'); 727 img.src = PREFIX + '/does-not-exist.png'; 728 document.body.appendChild(img); 729 return new Promise((fulfill) => (img.onload = fulfill)); 730 }, server.PREFIX); 731 const img = await page.$('img'); 732 expect(await img.screenshot()).toBeGolden('mock-binary-response.png'); 733 }); 734 it('should stringify intercepted request response headers', async () => { 735 const { page, server } = getTestState(); 736 737 await page.setRequestInterception(true); 738 page.on('request', (request) => { 739 request.respond({ 740 status: 200, 741 headers: { 742 foo: true, 743 }, 744 body: 'Yo, page!', 745 }); 746 }); 747 const response = await page.goto(server.EMPTY_PAGE); 748 expect(response.status()).toBe(200); 749 const headers = response.headers(); 750 expect(headers.foo).toBe('true'); 751 expect(await page.evaluate(() => document.body.textContent)).toBe( 752 'Yo, page!' 753 ); 754 }); 755 it('should fail if the header value is invalid', async () => { 756 const { page, server } = getTestState(); 757 758 let error; 759 await page.setRequestInterception(true); 760 page.on('request', async (request) => { 761 await request 762 .respond({ 763 headers: { 764 'X-Invalid-Header': 'a\nb', 765 }, 766 }) 767 .catch((error_) => { 768 error = error_; 769 }); 770 await request.respond({ 771 status: 200, 772 body: 'Hello World', 773 }); 774 }); 775 await page.goto(server.PREFIX + '/empty.html'); 776 expect(error.message).toMatch(/Invalid header/); 777 }); 778 }); 779}); 780 781/** 782 * @param {string} path 783 * @returns {string} 784 */ 785function pathToFileURL(path) { 786 let pathName = path.replace(/\\/g, '/'); 787 // Windows drive letter must be prefixed with a slash. 788 if (!pathName.startsWith('/')) pathName = '/' + pathName; 789 return 'file://' + pathName; 790} 791