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
28import { HTTPResponse } from '../lib/cjs/puppeteer/api-docs-entry.js';
29
30describe('network', function () {
31  setupTestBrowserHooks();
32  setupTestPageAndContextHooks();
33
34  describe('Page.Events.Request', function () {
35    it('should fire for navigation requests', async () => {
36      const { page, server } = getTestState();
37
38      const requests = [];
39      page.on(
40        'request',
41        (request) => !utils.isFavicon(request) && requests.push(request)
42      );
43      await page.goto(server.EMPTY_PAGE);
44      expect(requests.length).toBe(1);
45    });
46    it('should fire for iframes', async () => {
47      const { page, server } = getTestState();
48
49      const requests = [];
50      page.on(
51        'request',
52        (request) => !utils.isFavicon(request) && requests.push(request)
53      );
54      await page.goto(server.EMPTY_PAGE);
55      await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
56      expect(requests.length).toBe(2);
57    });
58    it('should fire for fetches', async () => {
59      const { page, server } = getTestState();
60
61      const requests = [];
62      page.on(
63        'request',
64        (request) => !utils.isFavicon(request) && requests.push(request)
65      );
66      await page.goto(server.EMPTY_PAGE);
67      await page.evaluate(() => fetch('/empty.html'));
68      expect(requests.length).toBe(2);
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('Request.initiator', () => {
141    it('shoud return the initiator', async () => {
142      const { page, server } = getTestState();
143
144      const initiators = new Map();
145      page.on('request', (request) =>
146        initiators.set(request.url().split('/').pop(), request.initiator())
147      );
148      await page.goto(server.PREFIX + '/initiator.html');
149
150      expect(initiators.get('initiator.html').type).toBe('other');
151      expect(initiators.get('initiator.js').type).toBe('parser');
152      expect(initiators.get('initiator.js').url).toBe(
153        server.PREFIX + '/initiator.html'
154      );
155      expect(initiators.get('frame.html').type).toBe('parser');
156      expect(initiators.get('frame.html').url).toBe(
157        server.PREFIX + '/initiator.html'
158      );
159      expect(initiators.get('script.js').type).toBe('parser');
160      expect(initiators.get('script.js').url).toBe(
161        server.PREFIX + '/frames/frame.html'
162      );
163      expect(initiators.get('style.css').type).toBe('parser');
164      expect(initiators.get('style.css').url).toBe(
165        server.PREFIX + '/frames/frame.html'
166      );
167      expect(initiators.get('initiator.js').type).toBe('parser');
168      expect(initiators.get('injectedfile.js').type).toBe('script');
169      expect(initiators.get('injectedfile.js').stack.callFrames[0].url).toBe(
170        server.PREFIX + '/initiator.js'
171      );
172      expect(initiators.get('injectedstyle.css').type).toBe('script');
173      expect(initiators.get('injectedstyle.css').stack.callFrames[0].url).toBe(
174        server.PREFIX + '/initiator.js'
175      );
176      expect(initiators.get('initiator.js').url).toBe(
177        server.PREFIX + '/initiator.html'
178      );
179    });
180  });
181
182  describe('Response.fromCache', function () {
183    it('should return |false| for non-cached content', async () => {
184      const { page, server } = getTestState();
185
186      const response = await page.goto(server.EMPTY_PAGE);
187      expect(response.fromCache()).toBe(false);
188    });
189
190    it('should work', async () => {
191      const { page, server } = getTestState();
192
193      const responses = new Map();
194      page.on(
195        'response',
196        (r) =>
197          !utils.isFavicon(r.request()) &&
198          responses.set(r.url().split('/').pop(), r)
199      );
200
201      // Load and re-load to make sure it's cached.
202      await page.goto(server.PREFIX + '/cached/one-style.html');
203      await page.reload();
204
205      expect(responses.size).toBe(2);
206      expect(responses.get('one-style.css').status()).toBe(200);
207      expect(responses.get('one-style.css').fromCache()).toBe(true);
208      expect(responses.get('one-style.html').status()).toBe(304);
209      expect(responses.get('one-style.html').fromCache()).toBe(false);
210    });
211  });
212
213  describe('Response.fromServiceWorker', function () {
214    it('should return |false| for non-service-worker content', async () => {
215      const { page, server } = getTestState();
216
217      const response = await page.goto(server.EMPTY_PAGE);
218      expect(response.fromServiceWorker()).toBe(false);
219    });
220
221    it('Response.fromServiceWorker', async () => {
222      const { page, server } = getTestState();
223
224      const responses = new Map();
225      page.on('response', (r) => responses.set(r.url().split('/').pop(), r));
226
227      // Load and re-load to make sure serviceworker is installed and running.
228      await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html', {
229        waitUntil: 'networkidle2',
230      });
231      await page.evaluate(async () => await globalThis.activationPromise);
232      await page.reload();
233
234      expect(responses.size).toBe(2);
235      expect(responses.get('sw.html').status()).toBe(200);
236      expect(responses.get('sw.html').fromServiceWorker()).toBe(true);
237      expect(responses.get('style.css').status()).toBe(200);
238      expect(responses.get('style.css').fromServiceWorker()).toBe(true);
239    });
240  });
241
242  describe('Request.postData', function () {
243    it('should work', async () => {
244      const { page, server } = getTestState();
245
246      await page.goto(server.EMPTY_PAGE);
247      server.setRoute('/post', (req, res) => res.end());
248      let request = null;
249      page.on('request', (r) => (request = r));
250      await page.evaluate(() =>
251        fetch('./post', {
252          method: 'POST',
253          body: JSON.stringify({ foo: 'bar' }),
254        })
255      );
256      expect(request).toBeTruthy();
257      expect(request.postData()).toBe('{"foo":"bar"}');
258    });
259    it('should be |undefined| when there is no post data', async () => {
260      const { page, server } = getTestState();
261
262      const response = await page.goto(server.EMPTY_PAGE);
263      expect(response.request().postData()).toBe(undefined);
264    });
265  });
266
267  describe('Response.text', function () {
268    it('should work', async () => {
269      const { page, server } = getTestState();
270
271      const response = await page.goto(server.PREFIX + '/simple.json');
272      const responseText = (await response.text()).trimEnd();
273      expect(responseText).toBe('{"foo": "bar"}');
274    });
275    it('should return uncompressed text', async () => {
276      const { page, server } = getTestState();
277
278      server.enableGzip('/simple.json');
279      const response = await page.goto(server.PREFIX + '/simple.json');
280      expect(response.headers()['content-encoding']).toBe('gzip');
281      const responseText = (await response.text()).trimEnd();
282      expect(responseText).toBe('{"foo": "bar"}');
283    });
284    it('should throw when requesting body of redirected response', async () => {
285      const { page, server } = getTestState();
286
287      server.setRedirect('/foo.html', '/empty.html');
288      const response = await page.goto(server.PREFIX + '/foo.html');
289      const redirectChain = response.request().redirectChain();
290      expect(redirectChain.length).toBe(1);
291      const redirected = redirectChain[0].response();
292      expect(redirected.status()).toBe(302);
293      let error = null;
294      await redirected.text().catch((error_) => (error = error_));
295      expect(error.message).toContain(
296        'Response body is unavailable for redirect responses'
297      );
298    });
299    it('should wait until response completes', async () => {
300      const { page, server } = getTestState();
301
302      await page.goto(server.EMPTY_PAGE);
303      // Setup server to trap request.
304      let serverResponse = null;
305      server.setRoute('/get', (req, res) => {
306        serverResponse = res;
307        // In Firefox, |fetch| will be hanging until it receives |Content-Type| header
308        // from server.
309        res.setHeader('Content-Type', 'text/plain; charset=utf-8');
310        res.write('hello ');
311      });
312      // Setup page to trap response.
313      let requestFinished = false;
314      page.on(
315        'requestfinished',
316        (r) => (requestFinished = requestFinished || r.url().includes('/get'))
317      );
318      // send request and wait for server response
319      const [pageResponse] = await Promise.all([
320        page.waitForResponse((r) => !utils.isFavicon(r.request())),
321        page.evaluate(() => fetch('./get', { method: 'GET' })),
322        server.waitForRequest('/get'),
323      ]);
324
325      expect(serverResponse).toBeTruthy();
326      expect(pageResponse).toBeTruthy();
327      expect(pageResponse.status()).toBe(200);
328      expect(requestFinished).toBe(false);
329
330      const responseText = pageResponse.text();
331      // Write part of the response and wait for it to be flushed.
332      await new Promise((x) => serverResponse.write('wor', x));
333      // Finish response.
334      await new Promise((x) => serverResponse.end('ld!', x));
335      expect(await responseText).toBe('hello world!');
336    });
337  });
338
339  describe('Response.json', function () {
340    it('should work', async () => {
341      const { page, server } = getTestState();
342
343      const response = await page.goto(server.PREFIX + '/simple.json');
344      expect(await response.json()).toEqual({ foo: 'bar' });
345    });
346  });
347
348  describe('Response.buffer', function () {
349    it('should work', async () => {
350      const { page, server } = getTestState();
351
352      const response = await page.goto(server.PREFIX + '/pptr.png');
353      const imageBuffer = fs.readFileSync(
354        path.join(__dirname, 'assets', 'pptr.png')
355      );
356      const responseBuffer = await response.buffer();
357      expect(responseBuffer.equals(imageBuffer)).toBe(true);
358    });
359    it('should work with compression', async () => {
360      const { page, server } = getTestState();
361
362      server.enableGzip('/pptr.png');
363      const response = await page.goto(server.PREFIX + '/pptr.png');
364      const imageBuffer = fs.readFileSync(
365        path.join(__dirname, 'assets', 'pptr.png')
366      );
367      const responseBuffer = await response.buffer();
368      expect(responseBuffer.equals(imageBuffer)).toBe(true);
369    });
370    it('should throw if the response does not have a body', async () => {
371      const { page, server } = getTestState();
372
373      await page.goto(server.PREFIX + '/empty.html');
374
375      server.setRoute('/test.html', (req, res) => {
376        res.setHeader('Access-Control-Allow-Origin', '*');
377        res.setHeader('Access-Control-Allow-Headers', 'x-ping');
378        res.end('Hello World');
379      });
380      const url = server.CROSS_PROCESS_PREFIX + '/test.html';
381      const responsePromise = new Promise<HTTPResponse>((resolve) => {
382        page.on('response', (response) => {
383          // Get the preflight response.
384          if (
385            response.request().method() === 'OPTIONS' &&
386            response.url() === url
387          ) {
388            resolve(response);
389          }
390        });
391      });
392
393      // Trigger a request with a preflight.
394      await page.evaluate<(src: string) => void>(async (src) => {
395        const response = await fetch(src, {
396          method: 'POST',
397          headers: { 'x-ping': 'pong' },
398        });
399        return response;
400      }, url);
401
402      const response = await responsePromise;
403      await expect(response.buffer()).rejects.toThrowError(
404        'Could not load body for this request. This might happen if the request is a preflight request.'
405      );
406    });
407  });
408
409  describe('Response.statusText', function () {
410    it('should work', async () => {
411      const { page, server } = getTestState();
412
413      server.setRoute('/cool', (req, res) => {
414        res.writeHead(200, 'cool!');
415        res.end();
416      });
417      const response = await page.goto(server.PREFIX + '/cool');
418      expect(response.statusText()).toBe('cool!');
419    });
420
421    it('handles missing status text', async () => {
422      const { page, server } = getTestState();
423
424      server.setRoute('/nostatus', (req, res) => {
425        res.writeHead(200, '');
426        res.end();
427      });
428      const response = await page.goto(server.PREFIX + '/nostatus');
429      expect(response.statusText()).toBe('');
430    });
431  });
432
433  describe('Network Events', function () {
434    it('Page.Events.Request', async () => {
435      const { page, server } = getTestState();
436
437      const requests = [];
438      page.on('request', (request) => requests.push(request));
439      await page.goto(server.EMPTY_PAGE);
440      expect(requests.length).toBe(1);
441      expect(requests[0].url()).toBe(server.EMPTY_PAGE);
442      expect(requests[0].resourceType()).toBe('document');
443      expect(requests[0].method()).toBe('GET');
444      expect(requests[0].response()).toBeTruthy();
445      expect(requests[0].frame() === page.mainFrame()).toBe(true);
446      expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE);
447    });
448    it('Page.Events.RequestServedFromCache', async () => {
449      const { page, server } = getTestState();
450
451      const cached = [];
452      page.on('requestservedfromcache', (r) =>
453        cached.push(r.url().split('/').pop())
454      );
455
456      await page.goto(server.PREFIX + '/cached/one-style.html');
457      expect(cached).toEqual([]);
458
459      await page.reload();
460      expect(cached).toEqual(['one-style.css']);
461    });
462    it('Page.Events.Response', async () => {
463      const { page, server } = getTestState();
464
465      const responses = [];
466      page.on('response', (response) => responses.push(response));
467      await page.goto(server.EMPTY_PAGE);
468      expect(responses.length).toBe(1);
469      expect(responses[0].url()).toBe(server.EMPTY_PAGE);
470      expect(responses[0].status()).toBe(200);
471      expect(responses[0].ok()).toBe(true);
472      expect(responses[0].request()).toBeTruthy();
473      const remoteAddress = responses[0].remoteAddress();
474      // Either IPv6 or IPv4, depending on environment.
475      expect(
476        remoteAddress.ip.includes('::1') || remoteAddress.ip === '127.0.0.1'
477      ).toBe(true);
478      expect(remoteAddress.port).toBe(server.PORT);
479    });
480
481    it('Page.Events.RequestFailed', async () => {
482      const { page, server, isChrome } = getTestState();
483
484      await page.setRequestInterception(true);
485      page.on('request', (request) => {
486        if (request.url().endsWith('css')) request.abort();
487        else request.continue();
488      });
489      const failedRequests = [];
490      page.on('requestfailed', (request) => failedRequests.push(request));
491      await page.goto(server.PREFIX + '/one-style.html');
492      expect(failedRequests.length).toBe(1);
493      expect(failedRequests[0].url()).toContain('one-style.css');
494      expect(failedRequests[0].response()).toBe(null);
495      expect(failedRequests[0].resourceType()).toBe('stylesheet');
496      if (isChrome)
497        expect(failedRequests[0].failure().errorText).toBe('net::ERR_FAILED');
498      else
499        expect(failedRequests[0].failure().errorText).toBe('NS_ERROR_FAILURE');
500      expect(failedRequests[0].frame()).toBeTruthy();
501    });
502    it('Page.Events.RequestFinished', async () => {
503      const { page, server } = getTestState();
504
505      const requests = [];
506      page.on('requestfinished', (request) => requests.push(request));
507      await page.goto(server.EMPTY_PAGE);
508      expect(requests.length).toBe(1);
509      expect(requests[0].url()).toBe(server.EMPTY_PAGE);
510      expect(requests[0].response()).toBeTruthy();
511      expect(requests[0].frame() === page.mainFrame()).toBe(true);
512      expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE);
513    });
514    it('should fire events in proper order', async () => {
515      const { page, server } = getTestState();
516
517      const events = [];
518      page.on('request', () => events.push('request'));
519      page.on('response', () => events.push('response'));
520      page.on('requestfinished', () => events.push('requestfinished'));
521      await page.goto(server.EMPTY_PAGE);
522      expect(events).toEqual(['request', 'response', 'requestfinished']);
523    });
524    it('should support redirects', async () => {
525      const { page, server } = getTestState();
526
527      const events = [];
528      page.on('request', (request) =>
529        events.push(`${request.method()} ${request.url()}`)
530      );
531      page.on('response', (response) =>
532        events.push(`${response.status()} ${response.url()}`)
533      );
534      page.on('requestfinished', (request) =>
535        events.push(`DONE ${request.url()}`)
536      );
537      page.on('requestfailed', (request) =>
538        events.push(`FAIL ${request.url()}`)
539      );
540      server.setRedirect('/foo.html', '/empty.html');
541      const FOO_URL = server.PREFIX + '/foo.html';
542      const response = await page.goto(FOO_URL);
543      expect(events).toEqual([
544        `GET ${FOO_URL}`,
545        `302 ${FOO_URL}`,
546        `DONE ${FOO_URL}`,
547        `GET ${server.EMPTY_PAGE}`,
548        `200 ${server.EMPTY_PAGE}`,
549        `DONE ${server.EMPTY_PAGE}`,
550      ]);
551
552      // Check redirect chain
553      const redirectChain = response.request().redirectChain();
554      expect(redirectChain.length).toBe(1);
555      expect(redirectChain[0].url()).toContain('/foo.html');
556      expect(redirectChain[0].response().remoteAddress().port).toBe(
557        server.PORT
558      );
559    });
560  });
561
562  describe('Request.isNavigationRequest', () => {
563    it('should work', async () => {
564      const { page, server } = getTestState();
565
566      const requests = new Map();
567      page.on('request', (request) =>
568        requests.set(request.url().split('/').pop(), request)
569      );
570      server.setRedirect('/rrredirect', '/frames/one-frame.html');
571      await page.goto(server.PREFIX + '/rrredirect');
572      expect(requests.get('rrredirect').isNavigationRequest()).toBe(true);
573      expect(requests.get('one-frame.html').isNavigationRequest()).toBe(true);
574      expect(requests.get('frame.html').isNavigationRequest()).toBe(true);
575      expect(requests.get('script.js').isNavigationRequest()).toBe(false);
576      expect(requests.get('style.css').isNavigationRequest()).toBe(false);
577    });
578    it('should work with request interception', async () => {
579      const { page, server } = getTestState();
580
581      const requests = new Map();
582      page.on('request', (request) => {
583        requests.set(request.url().split('/').pop(), request);
584        request.continue();
585      });
586      await page.setRequestInterception(true);
587      server.setRedirect('/rrredirect', '/frames/one-frame.html');
588      await page.goto(server.PREFIX + '/rrredirect');
589      expect(requests.get('rrredirect').isNavigationRequest()).toBe(true);
590      expect(requests.get('one-frame.html').isNavigationRequest()).toBe(true);
591      expect(requests.get('frame.html').isNavigationRequest()).toBe(true);
592      expect(requests.get('script.js').isNavigationRequest()).toBe(false);
593      expect(requests.get('style.css').isNavigationRequest()).toBe(false);
594    });
595    // This `itFailsFirefox` should be preserved in mozilla-central (Firefox).
596    // See https://bugzilla.mozilla.org/show_bug.cgi?id=1748254
597    // or https://github.com/puppeteer/puppeteer/pull/7846
598    itFailsFirefox('should work when navigating to image', async () => {
599      const { page, server } = getTestState();
600
601      const requests = [];
602      page.on('request', (request) => requests.push(request));
603      await page.goto(server.PREFIX + '/pptr.png');
604      expect(requests[0].isNavigationRequest()).toBe(true);
605    });
606  });
607
608  describe('Page.setExtraHTTPHeaders', function () {
609    it('should work', async () => {
610      const { page, server } = getTestState();
611
612      await page.setExtraHTTPHeaders({
613        foo: 'bar',
614      });
615      const [request] = await Promise.all([
616        server.waitForRequest('/empty.html'),
617        page.goto(server.EMPTY_PAGE),
618      ]);
619      expect(request.headers['foo']).toBe('bar');
620    });
621    it('should throw for non-string header values', async () => {
622      const { page } = getTestState();
623
624      let error = null;
625      try {
626        // @ts-expect-error purposeful bad input
627        await page.setExtraHTTPHeaders({ foo: 1 });
628      } catch (error_) {
629        error = error_;
630      }
631      expect(error.message).toBe(
632        'Expected value of header "foo" to be String, but "number" is found.'
633      );
634    });
635  });
636
637  describe('Page.authenticate', function () {
638    it('should work', async () => {
639      const { page, server } = getTestState();
640
641      server.setAuth('/empty.html', 'user', 'pass');
642      let response = await page.goto(server.EMPTY_PAGE);
643      expect(response.status()).toBe(401);
644      await page.authenticate({
645        username: 'user',
646        password: 'pass',
647      });
648      response = await page.reload();
649      expect(response.status()).toBe(200);
650    });
651    it('should fail if wrong credentials', async () => {
652      const { page, server } = getTestState();
653
654      // Use unique user/password since Chrome caches credentials per origin.
655      server.setAuth('/empty.html', 'user2', 'pass2');
656      await page.authenticate({
657        username: 'foo',
658        password: 'bar',
659      });
660      const response = await page.goto(server.EMPTY_PAGE);
661      expect(response.status()).toBe(401);
662    });
663    it('should allow disable authentication', async () => {
664      const { page, server } = getTestState();
665
666      // Use unique user/password since Chrome caches credentials per origin.
667      server.setAuth('/empty.html', 'user3', 'pass3');
668      await page.authenticate({
669        username: 'user3',
670        password: 'pass3',
671      });
672      let response = await page.goto(server.EMPTY_PAGE);
673      expect(response.status()).toBe(200);
674      await page.authenticate(null);
675      // Navigate to a different origin to bust Chrome's credential caching.
676      response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
677      expect(response.status()).toBe(401);
678    });
679    it('should not disable caching', async () => {
680      const { page, server } = getTestState();
681
682      // Use unique user/password since Chrome caches credentials per origin.
683      server.setAuth('/cached/one-style.css', 'user4', 'pass4');
684      server.setAuth('/cached/one-style.html', 'user4', 'pass4');
685      await page.authenticate({
686        username: 'user4',
687        password: 'pass4',
688      });
689
690      const responses = new Map();
691      page.on('response', (r) => responses.set(r.url().split('/').pop(), r));
692
693      // Load and re-load to make sure it's cached.
694      await page.goto(server.PREFIX + '/cached/one-style.html');
695      await page.reload();
696
697      expect(responses.get('one-style.css').status()).toBe(200);
698      expect(responses.get('one-style.css').fromCache()).toBe(true);
699      expect(responses.get('one-style.html').status()).toBe(304);
700      expect(responses.get('one-style.html').fromCache()).toBe(false);
701    });
702  });
703
704  describe('raw network headers', async () => {
705    it('Same-origin set-cookie navigation', async () => {
706      const { page, server } = getTestState();
707
708      const setCookieString = 'foo=bar';
709      server.setRoute('/empty.html', (req, res) => {
710        res.setHeader('set-cookie', setCookieString);
711        res.end('hello world');
712      });
713      const response = await page.goto(server.EMPTY_PAGE);
714      expect(response.headers()['set-cookie']).toBe(setCookieString);
715    });
716
717    it('Same-origin set-cookie subresource', async () => {
718      const { page, server } = getTestState();
719      await page.goto(server.EMPTY_PAGE);
720
721      const setCookieString = 'foo=bar';
722      server.setRoute('/foo', (req, res) => {
723        res.setHeader('set-cookie', setCookieString);
724        res.end('hello world');
725      });
726
727      const responsePromise = new Promise<HTTPResponse>((resolve) =>
728        page.on('response', (response) => resolve(response))
729      );
730      page.evaluate(() => {
731        const xhr = new XMLHttpRequest();
732        xhr.open('GET', '/foo');
733        xhr.send();
734      });
735      const subresourceResponse = await responsePromise;
736      expect(subresourceResponse.headers()['set-cookie']).toBe(setCookieString);
737    });
738
739    it('Cross-origin set-cookie', async () => {
740      const { httpsServer, puppeteer, defaultBrowserOptions } = getTestState();
741
742      const browser = await puppeteer.launch({
743        ...defaultBrowserOptions,
744        ignoreHTTPSErrors: true,
745      });
746
747      const page = await browser.newPage();
748
749      try {
750        await page.goto(httpsServer.PREFIX + '/empty.html');
751
752        const setCookieString = 'hello=world';
753        httpsServer.setRoute('/setcookie.html', (req, res) => {
754          res.setHeader('Access-Control-Allow-Origin', '*');
755          res.setHeader('set-cookie', setCookieString);
756          res.end();
757        });
758        await page.goto(httpsServer.PREFIX + '/setcookie.html');
759
760        const response = await new Promise<HTTPResponse>((resolve) => {
761          page.on('response', resolve);
762          const url = httpsServer.CROSS_PROCESS_PREFIX + '/setcookie.html';
763          page.evaluate<(src: string) => void>((src) => {
764            const xhr = new XMLHttpRequest();
765            xhr.open('GET', src);
766            xhr.send();
767          }, url);
768        });
769        expect(response.headers()['set-cookie']).toBe(setCookieString);
770      } finally {
771        await page.close();
772        await browser.close();
773      }
774    });
775  });
776});
777