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 itFailsFirefox, 26 describeFailsFirefox, 27} from './mocha-utils'; // eslint-disable-line import/extensions 28 29describe('network', function () { 30 setupTestBrowserHooks(); 31 setupTestPageAndContextHooks(); 32 33 describe('Page.Events.Request', function () { 34 it('should fire for navigation requests', async () => { 35 const { page, server } = getTestState(); 36 37 const requests = []; 38 page.on( 39 'request', 40 (request) => !utils.isFavicon(request) && requests.push(request) 41 ); 42 await page.goto(server.EMPTY_PAGE); 43 expect(requests.length).toBe(1); 44 }); 45 it('should fire for iframes', async () => { 46 const { page, server } = getTestState(); 47 48 const requests = []; 49 page.on( 50 'request', 51 (request) => !utils.isFavicon(request) && requests.push(request) 52 ); 53 await page.goto(server.EMPTY_PAGE); 54 await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); 55 expect(requests.length).toBe(2); 56 }); 57 it('should fire for fetches', async () => { 58 const { page, server } = getTestState(); 59 60 const requests = []; 61 page.on( 62 'request', 63 (request) => !utils.isFavicon(request) && requests.push(request) 64 ); 65 await page.goto(server.EMPTY_PAGE); 66 await page.evaluate(() => fetch('/empty.html')); 67 expect(requests.length).toBe(2); 68 }); 69 }); 70 71 describe('Request.frame', function () { 72 it('should work for main frame navigation request', async () => { 73 const { page, server } = getTestState(); 74 75 const requests = []; 76 page.on( 77 'request', 78 (request) => !utils.isFavicon(request) && requests.push(request) 79 ); 80 await page.goto(server.EMPTY_PAGE); 81 expect(requests.length).toBe(1); 82 expect(requests[0].frame()).toBe(page.mainFrame()); 83 }); 84 it('should work for subframe navigation request', async () => { 85 const { page, server } = getTestState(); 86 87 await page.goto(server.EMPTY_PAGE); 88 const requests = []; 89 page.on( 90 'request', 91 (request) => !utils.isFavicon(request) && requests.push(request) 92 ); 93 await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); 94 expect(requests.length).toBe(1); 95 expect(requests[0].frame()).toBe(page.frames()[1]); 96 }); 97 it('should work for fetch requests', async () => { 98 const { page, server } = getTestState(); 99 100 await page.goto(server.EMPTY_PAGE); 101 let requests = []; 102 page.on( 103 'request', 104 (request) => !utils.isFavicon(request) && requests.push(request) 105 ); 106 await page.evaluate(() => fetch('/digits/1.png')); 107 requests = requests.filter( 108 (request) => !request.url().includes('favicon') 109 ); 110 expect(requests.length).toBe(1); 111 expect(requests[0].frame()).toBe(page.mainFrame()); 112 }); 113 }); 114 115 describe('Request.headers', function () { 116 it('should work', async () => { 117 const { page, server, isChrome } = getTestState(); 118 119 const response = await page.goto(server.EMPTY_PAGE); 120 if (isChrome) 121 expect(response.request().headers()['user-agent']).toContain('Chrome'); 122 else 123 expect(response.request().headers()['user-agent']).toContain('Firefox'); 124 }); 125 }); 126 127 describe('Response.headers', function () { 128 it('should work', async () => { 129 const { page, server } = getTestState(); 130 131 server.setRoute('/empty.html', (req, res) => { 132 res.setHeader('foo', 'bar'); 133 res.end(); 134 }); 135 const response = await page.goto(server.EMPTY_PAGE); 136 expect(response.headers()['foo']).toBe('bar'); 137 }); 138 }); 139 140 describe('Response.fromCache', function () { 141 it('should return |false| for non-cached content', async () => { 142 const { page, server } = getTestState(); 143 144 const response = await page.goto(server.EMPTY_PAGE); 145 expect(response.fromCache()).toBe(false); 146 }); 147 148 it('should work', async () => { 149 const { page, server } = getTestState(); 150 151 const responses = new Map(); 152 page.on( 153 'response', 154 (r) => 155 !utils.isFavicon(r.request()) && 156 responses.set(r.url().split('/').pop(), r) 157 ); 158 159 // Load and re-load to make sure it's cached. 160 await page.goto(server.PREFIX + '/cached/one-style.html'); 161 await page.reload(); 162 163 expect(responses.size).toBe(2); 164 expect(responses.get('one-style.css').status()).toBe(200); 165 expect(responses.get('one-style.css').fromCache()).toBe(true); 166 expect(responses.get('one-style.html').status()).toBe(304); 167 expect(responses.get('one-style.html').fromCache()).toBe(false); 168 }); 169 }); 170 171 describe('Response.fromServiceWorker', function () { 172 it('should return |false| for non-service-worker content', async () => { 173 const { page, server } = getTestState(); 174 175 const response = await page.goto(server.EMPTY_PAGE); 176 expect(response.fromServiceWorker()).toBe(false); 177 }); 178 179 it('Response.fromServiceWorker', async () => { 180 const { page, server } = getTestState(); 181 182 const responses = new Map(); 183 page.on('response', (r) => responses.set(r.url().split('/').pop(), r)); 184 185 // Load and re-load to make sure serviceworker is installed and running. 186 await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html', { 187 waitUntil: 'networkidle2', 188 }); 189 await page.evaluate(async () => await globalThis.activationPromise); 190 await page.reload(); 191 192 expect(responses.size).toBe(2); 193 expect(responses.get('sw.html').status()).toBe(200); 194 expect(responses.get('sw.html').fromServiceWorker()).toBe(true); 195 expect(responses.get('style.css').status()).toBe(200); 196 expect(responses.get('style.css').fromServiceWorker()).toBe(true); 197 }); 198 }); 199 200 describe('Request.postData', function () { 201 it('should work', async () => { 202 const { page, server } = getTestState(); 203 204 await page.goto(server.EMPTY_PAGE); 205 server.setRoute('/post', (req, res) => res.end()); 206 let request = null; 207 page.on('request', (r) => (request = r)); 208 await page.evaluate(() => 209 fetch('./post', { 210 method: 'POST', 211 body: JSON.stringify({ foo: 'bar' }), 212 }) 213 ); 214 expect(request).toBeTruthy(); 215 expect(request.postData()).toBe('{"foo":"bar"}'); 216 }); 217 it('should be |undefined| when there is no post data', async () => { 218 const { page, server } = getTestState(); 219 220 const response = await page.goto(server.EMPTY_PAGE); 221 expect(response.request().postData()).toBe(undefined); 222 }); 223 }); 224 225 describe('Response.text', function () { 226 it('should work', async () => { 227 const { page, server } = getTestState(); 228 229 const response = await page.goto(server.PREFIX + '/simple.json'); 230 const responseText = (await response.text()).trimEnd(); 231 expect(responseText).toBe('{"foo": "bar"}'); 232 }); 233 it('should return uncompressed text', async () => { 234 const { page, server } = getTestState(); 235 236 server.enableGzip('/simple.json'); 237 const response = await page.goto(server.PREFIX + '/simple.json'); 238 expect(response.headers()['content-encoding']).toBe('gzip'); 239 const responseText = (await response.text()).trimEnd(); 240 expect(responseText).toBe('{"foo": "bar"}'); 241 }); 242 it('should throw when requesting body of redirected response', async () => { 243 const { page, server } = getTestState(); 244 245 server.setRedirect('/foo.html', '/empty.html'); 246 const response = await page.goto(server.PREFIX + '/foo.html'); 247 const redirectChain = response.request().redirectChain(); 248 expect(redirectChain.length).toBe(1); 249 const redirected = redirectChain[0].response(); 250 expect(redirected.status()).toBe(302); 251 let error = null; 252 await redirected.text().catch((error_) => (error = error_)); 253 expect(error.message).toContain( 254 'Response body is unavailable for redirect responses' 255 ); 256 }); 257 it('should wait until response completes', async () => { 258 const { page, server } = getTestState(); 259 260 await page.goto(server.EMPTY_PAGE); 261 // Setup server to trap request. 262 let serverResponse = null; 263 server.setRoute('/get', (req, res) => { 264 serverResponse = res; 265 // In Firefox, |fetch| will be hanging until it receives |Content-Type| header 266 // from server. 267 res.setHeader('Content-Type', 'text/plain; charset=utf-8'); 268 res.write('hello '); 269 }); 270 // Setup page to trap response. 271 let requestFinished = false; 272 page.on( 273 'requestfinished', 274 (r) => (requestFinished = requestFinished || r.url().includes('/get')) 275 ); 276 // send request and wait for server response 277 const [pageResponse] = await Promise.all([ 278 page.waitForResponse((r) => !utils.isFavicon(r.request())), 279 page.evaluate(() => fetch('./get', { method: 'GET' })), 280 server.waitForRequest('/get'), 281 ]); 282 283 expect(serverResponse).toBeTruthy(); 284 expect(pageResponse).toBeTruthy(); 285 expect(pageResponse.status()).toBe(200); 286 expect(requestFinished).toBe(false); 287 288 const responseText = pageResponse.text(); 289 // Write part of the response and wait for it to be flushed. 290 await new Promise((x) => serverResponse.write('wor', x)); 291 // Finish response. 292 await new Promise((x) => serverResponse.end('ld!', x)); 293 expect(await responseText).toBe('hello world!'); 294 }); 295 }); 296 297 describe('Response.json', function () { 298 it('should work', async () => { 299 const { page, server } = getTestState(); 300 301 const response = await page.goto(server.PREFIX + '/simple.json'); 302 expect(await response.json()).toEqual({ foo: 'bar' }); 303 }); 304 }); 305 306 describe('Response.buffer', function () { 307 it('should work', async () => { 308 const { page, server } = getTestState(); 309 310 const response = await page.goto(server.PREFIX + '/pptr.png'); 311 const imageBuffer = fs.readFileSync( 312 path.join(__dirname, 'assets', 'pptr.png') 313 ); 314 const responseBuffer = await response.buffer(); 315 expect(responseBuffer.equals(imageBuffer)).toBe(true); 316 }); 317 it('should work with compression', async () => { 318 const { page, server } = getTestState(); 319 320 server.enableGzip('/pptr.png'); 321 const response = await page.goto(server.PREFIX + '/pptr.png'); 322 const imageBuffer = fs.readFileSync( 323 path.join(__dirname, 'assets', 'pptr.png') 324 ); 325 const responseBuffer = await response.buffer(); 326 expect(responseBuffer.equals(imageBuffer)).toBe(true); 327 }); 328 }); 329 330 describe('Response.statusText', function () { 331 it('should work', async () => { 332 const { page, server } = getTestState(); 333 334 server.setRoute('/cool', (req, res) => { 335 res.writeHead(200, 'cool!'); 336 res.end(); 337 }); 338 const response = await page.goto(server.PREFIX + '/cool'); 339 expect(response.statusText()).toBe('cool!'); 340 }); 341 }); 342 343 describe('Network Events', function () { 344 it('Page.Events.Request', async () => { 345 const { page, server } = getTestState(); 346 347 const requests = []; 348 page.on('request', (request) => requests.push(request)); 349 await page.goto(server.EMPTY_PAGE); 350 expect(requests.length).toBe(1); 351 expect(requests[0].url()).toBe(server.EMPTY_PAGE); 352 expect(requests[0].resourceType()).toBe('document'); 353 expect(requests[0].method()).toBe('GET'); 354 expect(requests[0].response()).toBeTruthy(); 355 expect(requests[0].frame() === page.mainFrame()).toBe(true); 356 expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE); 357 }); 358 it('Page.Events.RequestServedFromCache', async () => { 359 const { page, server } = getTestState(); 360 361 const cached = []; 362 page.on('requestservedfromcache', (r) => 363 cached.push(r.url().split('/').pop()) 364 ); 365 366 await page.goto(server.PREFIX + '/cached/one-style.html'); 367 expect(cached).toEqual([]); 368 369 await page.reload(); 370 expect(cached).toEqual(['one-style.css']); 371 }); 372 it('Page.Events.Response', async () => { 373 const { page, server } = getTestState(); 374 375 const responses = []; 376 page.on('response', (response) => responses.push(response)); 377 await page.goto(server.EMPTY_PAGE); 378 expect(responses.length).toBe(1); 379 expect(responses[0].url()).toBe(server.EMPTY_PAGE); 380 expect(responses[0].status()).toBe(200); 381 expect(responses[0].ok()).toBe(true); 382 expect(responses[0].request()).toBeTruthy(); 383 const remoteAddress = responses[0].remoteAddress(); 384 // Either IPv6 or IPv4, depending on environment. 385 expect( 386 remoteAddress.ip.includes('::1') || remoteAddress.ip === '127.0.0.1' 387 ).toBe(true); 388 expect(remoteAddress.port).toBe(server.PORT); 389 }); 390 391 it('Page.Events.RequestFailed', async () => { 392 const { page, server, isChrome } = getTestState(); 393 394 await page.setRequestInterception(true); 395 page.on('request', (request) => { 396 if (request.url().endsWith('css')) request.abort(); 397 else request.continue(); 398 }); 399 const failedRequests = []; 400 page.on('requestfailed', (request) => failedRequests.push(request)); 401 await page.goto(server.PREFIX + '/one-style.html'); 402 expect(failedRequests.length).toBe(1); 403 expect(failedRequests[0].url()).toContain('one-style.css'); 404 expect(failedRequests[0].response()).toBe(null); 405 expect(failedRequests[0].resourceType()).toBe('stylesheet'); 406 if (isChrome) 407 expect(failedRequests[0].failure().errorText).toBe('net::ERR_FAILED'); 408 else 409 expect(failedRequests[0].failure().errorText).toBe('NS_ERROR_FAILURE'); 410 expect(failedRequests[0].frame()).toBeTruthy(); 411 }); 412 it('Page.Events.RequestFinished', async () => { 413 const { page, server } = getTestState(); 414 415 const requests = []; 416 page.on('requestfinished', (request) => requests.push(request)); 417 await page.goto(server.EMPTY_PAGE); 418 expect(requests.length).toBe(1); 419 expect(requests[0].url()).toBe(server.EMPTY_PAGE); 420 expect(requests[0].response()).toBeTruthy(); 421 expect(requests[0].frame() === page.mainFrame()).toBe(true); 422 expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE); 423 }); 424 it('should fire events in proper order', async () => { 425 const { page, server } = getTestState(); 426 427 const events = []; 428 page.on('request', () => events.push('request')); 429 page.on('response', () => events.push('response')); 430 page.on('requestfinished', () => events.push('requestfinished')); 431 await page.goto(server.EMPTY_PAGE); 432 expect(events).toEqual(['request', 'response', 'requestfinished']); 433 }); 434 it('should support redirects', async () => { 435 const { page, server } = getTestState(); 436 437 const events = []; 438 page.on('request', (request) => 439 events.push(`${request.method()} ${request.url()}`) 440 ); 441 page.on('response', (response) => 442 events.push(`${response.status()} ${response.url()}`) 443 ); 444 page.on('requestfinished', (request) => 445 events.push(`DONE ${request.url()}`) 446 ); 447 page.on('requestfailed', (request) => 448 events.push(`FAIL ${request.url()}`) 449 ); 450 server.setRedirect('/foo.html', '/empty.html'); 451 const FOO_URL = server.PREFIX + '/foo.html'; 452 const response = await page.goto(FOO_URL); 453 expect(events).toEqual([ 454 `GET ${FOO_URL}`, 455 `302 ${FOO_URL}`, 456 `DONE ${FOO_URL}`, 457 `GET ${server.EMPTY_PAGE}`, 458 `200 ${server.EMPTY_PAGE}`, 459 `DONE ${server.EMPTY_PAGE}`, 460 ]); 461 462 // Check redirect chain 463 const redirectChain = response.request().redirectChain(); 464 expect(redirectChain.length).toBe(1); 465 expect(redirectChain[0].url()).toContain('/foo.html'); 466 expect(redirectChain[0].response().remoteAddress().port).toBe( 467 server.PORT 468 ); 469 }); 470 }); 471 472 describe('Request.isNavigationRequest', () => { 473 it('should work', async () => { 474 const { page, server } = getTestState(); 475 476 const requests = new Map(); 477 page.on('request', (request) => 478 requests.set(request.url().split('/').pop(), request) 479 ); 480 server.setRedirect('/rrredirect', '/frames/one-frame.html'); 481 await page.goto(server.PREFIX + '/rrredirect'); 482 expect(requests.get('rrredirect').isNavigationRequest()).toBe(true); 483 expect(requests.get('one-frame.html').isNavigationRequest()).toBe(true); 484 expect(requests.get('frame.html').isNavigationRequest()).toBe(true); 485 expect(requests.get('script.js').isNavigationRequest()).toBe(false); 486 expect(requests.get('style.css').isNavigationRequest()).toBe(false); 487 }); 488 it('should work with request interception', async () => { 489 const { page, server } = getTestState(); 490 491 const requests = new Map(); 492 page.on('request', (request) => { 493 requests.set(request.url().split('/').pop(), request); 494 request.continue(); 495 }); 496 await page.setRequestInterception(true); 497 server.setRedirect('/rrredirect', '/frames/one-frame.html'); 498 await page.goto(server.PREFIX + '/rrredirect'); 499 expect(requests.get('rrredirect').isNavigationRequest()).toBe(true); 500 expect(requests.get('one-frame.html').isNavigationRequest()).toBe(true); 501 expect(requests.get('frame.html').isNavigationRequest()).toBe(true); 502 expect(requests.get('script.js').isNavigationRequest()).toBe(false); 503 expect(requests.get('style.css').isNavigationRequest()).toBe(false); 504 }); 505 it('should work when navigating to image', async () => { 506 const { page, server } = getTestState(); 507 508 const requests = []; 509 page.on('request', (request) => requests.push(request)); 510 await page.goto(server.PREFIX + '/pptr.png'); 511 expect(requests[0].isNavigationRequest()).toBe(true); 512 }); 513 }); 514 515 describe('Page.setExtraHTTPHeaders', function () { 516 it('should work', async () => { 517 const { page, server } = getTestState(); 518 519 await page.setExtraHTTPHeaders({ 520 foo: 'bar', 521 }); 522 const [request] = await Promise.all([ 523 server.waitForRequest('/empty.html'), 524 page.goto(server.EMPTY_PAGE), 525 ]); 526 expect(request.headers['foo']).toBe('bar'); 527 }); 528 it('should throw for non-string header values', async () => { 529 const { page } = getTestState(); 530 531 let error = null; 532 try { 533 // @ts-expect-error purposeful bad input 534 await page.setExtraHTTPHeaders({ foo: 1 }); 535 } catch (error_) { 536 error = error_; 537 } 538 expect(error.message).toBe( 539 'Expected value of header "foo" to be String, but "number" is found.' 540 ); 541 }); 542 }); 543 544 describe('Page.authenticate', function () { 545 it('should work', async () => { 546 const { page, server } = getTestState(); 547 548 server.setAuth('/empty.html', 'user', 'pass'); 549 let response = await page.goto(server.EMPTY_PAGE); 550 expect(response.status()).toBe(401); 551 await page.authenticate({ 552 username: 'user', 553 password: 'pass', 554 }); 555 response = await page.reload(); 556 expect(response.status()).toBe(200); 557 }); 558 it('should fail if wrong credentials', async () => { 559 const { page, server } = getTestState(); 560 561 // Use unique user/password since Chrome caches credentials per origin. 562 server.setAuth('/empty.html', 'user2', 'pass2'); 563 await page.authenticate({ 564 username: 'foo', 565 password: 'bar', 566 }); 567 const response = await page.goto(server.EMPTY_PAGE); 568 expect(response.status()).toBe(401); 569 }); 570 it('should allow disable authentication', async () => { 571 const { page, server } = getTestState(); 572 573 // Use unique user/password since Chrome caches credentials per origin. 574 server.setAuth('/empty.html', 'user3', 'pass3'); 575 await page.authenticate({ 576 username: 'user3', 577 password: 'pass3', 578 }); 579 let response = await page.goto(server.EMPTY_PAGE); 580 expect(response.status()).toBe(200); 581 await page.authenticate(null); 582 // Navigate to a different origin to bust Chrome's credential caching. 583 response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); 584 expect(response.status()).toBe(401); 585 }); 586 it('should not disable caching', async () => { 587 const { page, server } = getTestState(); 588 589 // Use unique user/password since Chrome caches credentials per origin. 590 server.setAuth('/cached/one-style.css', 'user4', 'pass4'); 591 server.setAuth('/cached/one-style.html', 'user4', 'pass4'); 592 await page.authenticate({ 593 username: 'user4', 594 password: 'pass4', 595 }); 596 597 const responses = new Map(); 598 page.on('response', (r) => responses.set(r.url().split('/').pop(), r)); 599 600 // Load and re-load to make sure it's cached. 601 await page.goto(server.PREFIX + '/cached/one-style.html'); 602 await page.reload(); 603 604 expect(responses.get('one-style.css').status()).toBe(200); 605 expect(responses.get('one-style.css').fromCache()).toBe(true); 606 expect(responses.get('one-style.html').status()).toBe(304); 607 expect(responses.get('one-style.html').fromCache()).toBe(false); 608 }); 609 }); 610}); 611