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 utils from './utils.js';
18import expect from 'expect';
19import {
20  getTestState,
21  setupTestBrowserHooks,
22  setupTestPageAndContextHooks,
23  itFailsFirefox,
24  describeFailsFirefox,
25} from './mocha-utils'; // eslint-disable-line import/extensions
26import os from 'os';
27
28describe('navigation', function () {
29  setupTestBrowserHooks();
30  setupTestPageAndContextHooks();
31  describe('Page.goto', function () {
32    it('should work', async () => {
33      const { page, server } = getTestState();
34
35      await page.goto(server.EMPTY_PAGE);
36      expect(page.url()).toBe(server.EMPTY_PAGE);
37    });
38    it('should work with anchor navigation', async () => {
39      const { page, server } = getTestState();
40
41      await page.goto(server.EMPTY_PAGE);
42      expect(page.url()).toBe(server.EMPTY_PAGE);
43      await page.goto(server.EMPTY_PAGE + '#foo');
44      expect(page.url()).toBe(server.EMPTY_PAGE + '#foo');
45      await page.goto(server.EMPTY_PAGE + '#bar');
46      expect(page.url()).toBe(server.EMPTY_PAGE + '#bar');
47    });
48    it('should work with redirects', async () => {
49      const { page, server } = getTestState();
50
51      server.setRedirect('/redirect/1.html', '/redirect/2.html');
52      server.setRedirect('/redirect/2.html', '/empty.html');
53      await page.goto(server.PREFIX + '/redirect/1.html');
54      expect(page.url()).toBe(server.EMPTY_PAGE);
55    });
56    it('should navigate to about:blank', async () => {
57      const { page } = getTestState();
58
59      const response = await page.goto('about:blank');
60      expect(response).toBe(null);
61    });
62    it('should return response when page changes its URL after load', async () => {
63      const { page, server } = getTestState();
64
65      const response = await page.goto(server.PREFIX + '/historyapi.html');
66      expect(response.status()).toBe(200);
67    });
68    it('should work with subframes return 204', async () => {
69      const { page, server } = getTestState();
70
71      server.setRoute('/frames/frame.html', (req, res) => {
72        res.statusCode = 204;
73        res.end();
74      });
75      let error = null;
76      await page
77        .goto(server.PREFIX + '/frames/one-frame.html')
78        .catch((error_) => (error = error_));
79      expect(error).toBe(null);
80    });
81    it('should fail when server returns 204', async () => {
82      const { page, server, isChrome } = getTestState();
83
84      server.setRoute('/empty.html', (req, res) => {
85        res.statusCode = 204;
86        res.end();
87      });
88      let error = null;
89      await page.goto(server.EMPTY_PAGE).catch((error_) => (error = error_));
90      expect(error).not.toBe(null);
91      if (isChrome) expect(error.message).toContain('net::ERR_ABORTED');
92      else expect(error.message).toContain('NS_BINDING_ABORTED');
93    });
94    it('should navigate to empty page with domcontentloaded', async () => {
95      const { page, server } = getTestState();
96
97      const response = await page.goto(server.EMPTY_PAGE, {
98        waitUntil: 'domcontentloaded',
99      });
100      expect(response.status()).toBe(200);
101    });
102    it('should work when page calls history API in beforeunload', async () => {
103      const { page, server } = getTestState();
104
105      await page.goto(server.EMPTY_PAGE);
106      await page.evaluate(() => {
107        window.addEventListener(
108          'beforeunload',
109          () => history.replaceState(null, 'initial', window.location.href),
110          false
111        );
112      });
113      const response = await page.goto(server.PREFIX + '/grid.html');
114      expect(response.status()).toBe(200);
115    });
116    it(
117      'should navigate to empty page with networkidle0',
118      async () => {
119        const { page, server } = getTestState();
120
121        const response = await page.goto(server.EMPTY_PAGE, {
122          waitUntil: 'networkidle0',
123        });
124        expect(response.status()).toBe(200);
125      }
126    );
127    it(
128      'should navigate to empty page with networkidle2',
129      async () => {
130        const { page, server } = getTestState();
131
132        const response = await page.goto(server.EMPTY_PAGE, {
133          waitUntil: 'networkidle2',
134        });
135        expect(response.status()).toBe(200);
136      }
137    );
138    it('should fail when navigating to bad url', async () => {
139      const { page, isChrome } = getTestState();
140
141      let error = null;
142      await page.goto('asdfasdf').catch((error_) => (error = error_));
143      if (isChrome)
144        expect(error.message).toContain('Cannot navigate to invalid URL');
145      else expect(error.message).toContain('Invalid url');
146    });
147
148    /* If you are running this on pre-Catalina versions of macOS this will fail locally.
149    /* Mac OSX Catalina outputs a different message than other platforms.
150     * See https://support.google.com/chrome/thread/18125056?hl=en for details.
151     * If you're running pre-Catalina Mac OSX this test will fail locally.
152     */
153    const EXPECTED_SSL_CERT_MESSAGE =
154      os.platform() === 'darwin'
155        ? 'net::ERR_CERT_INVALID'
156        : 'net::ERR_CERT_AUTHORITY_INVALID';
157
158    it('should fail when navigating to bad SSL', async () => {
159      const { page, httpsServer, isChrome } = getTestState();
160
161      // Make sure that network events do not emit 'undefined'.
162      // @see https://crbug.com/750469
163      const requests = [];
164      page.on('request', () => requests.push('request'));
165      page.on('requestfinished', () => requests.push('requestfinished'));
166      page.on('requestfailed', () => requests.push('requestfailed'));
167
168      let error = null;
169      await page
170        .goto(httpsServer.EMPTY_PAGE)
171        .catch((error_) => (error = error_));
172      if (isChrome) expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE);
173      else expect(error.message).toContain('SSL_ERROR_UNKNOWN');
174
175      expect(requests.length).toBe(2);
176      expect(requests[0]).toBe('request');
177      expect(requests[1]).toBe('requestfailed');
178    });
179    it('should fail when navigating to bad SSL after redirects', async () => {
180      const { page, server, httpsServer, isChrome } = getTestState();
181
182      server.setRedirect('/redirect/1.html', '/redirect/2.html');
183      server.setRedirect('/redirect/2.html', '/empty.html');
184      let error = null;
185      await page
186        .goto(httpsServer.PREFIX + '/redirect/1.html')
187        .catch((error_) => (error = error_));
188      if (isChrome) expect(error.message).toContain(EXPECTED_SSL_CERT_MESSAGE);
189      else expect(error.message).toContain('SSL_ERROR_UNKNOWN');
190    });
191    it('should throw if networkidle is passed as an option', async () => {
192      const { page, server } = getTestState();
193
194      let error = null;
195      await page
196        // @ts-expect-error purposefully passing an old option
197        .goto(server.EMPTY_PAGE, { waitUntil: 'networkidle' })
198        .catch((error_) => (error = error_));
199      expect(error.message).toContain(
200        '"networkidle" option is no longer supported'
201      );
202    });
203    it('should fail when main resources failed to load', async () => {
204      const { page, isChrome } = getTestState();
205
206      let error = null;
207      await page
208        .goto('http://localhost:44123/non-existing-url')
209        .catch((error_) => (error = error_));
210      if (isChrome)
211        expect(error.message).toContain('net::ERR_CONNECTION_REFUSED');
212      else expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED');
213    });
214    it('should fail when exceeding maximum navigation timeout', async () => {
215      const { page, server, puppeteer } = getTestState();
216
217      // Hang for request to the empty.html
218      server.setRoute('/empty.html', () => {});
219      let error = null;
220      await page
221        .goto(server.PREFIX + '/empty.html', { timeout: 1 })
222        .catch((error_) => (error = error_));
223      expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
224      expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
225    });
226    it('should fail when exceeding default maximum navigation timeout', async () => {
227      const { page, server, puppeteer } = getTestState();
228
229      // Hang for request to the empty.html
230      server.setRoute('/empty.html', () => {});
231      let error = null;
232      page.setDefaultNavigationTimeout(1);
233      await page
234        .goto(server.PREFIX + '/empty.html')
235        .catch((error_) => (error = error_));
236      expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
237      expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
238    });
239    it('should fail when exceeding default maximum timeout', async () => {
240      const { page, server, puppeteer } = getTestState();
241
242      // Hang for request to the empty.html
243      server.setRoute('/empty.html', () => {});
244      let error = null;
245      page.setDefaultTimeout(1);
246      await page
247        .goto(server.PREFIX + '/empty.html')
248        .catch((error_) => (error = error_));
249      expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
250      expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
251    });
252    it('should prioritize default navigation timeout over default timeout', async () => {
253      const { page, server, puppeteer } = getTestState();
254
255      // Hang for request to the empty.html
256      server.setRoute('/empty.html', () => {});
257      let error = null;
258      page.setDefaultTimeout(0);
259      page.setDefaultNavigationTimeout(1);
260      await page
261        .goto(server.PREFIX + '/empty.html')
262        .catch((error_) => (error = error_));
263      expect(error.message).toContain('Navigation timeout of 1 ms exceeded');
264      expect(error).toBeInstanceOf(puppeteer.errors.TimeoutError);
265    });
266    it('should disable timeout when its set to 0', async () => {
267      const { page, server } = getTestState();
268
269      let error = null;
270      let loaded = false;
271      page.once('load', () => (loaded = true));
272      await page
273        .goto(server.PREFIX + '/grid.html', { timeout: 0, waitUntil: ['load'] })
274        .catch((error_) => (error = error_));
275      expect(error).toBe(null);
276      expect(loaded).toBe(true);
277    });
278    it('should work when navigating to valid url', async () => {
279      const { page, server } = getTestState();
280
281      const response = await page.goto(server.EMPTY_PAGE);
282      expect(response.ok()).toBe(true);
283    });
284    it('should work when navigating to data url', async () => {
285      const { page } = getTestState();
286
287      const response = await page.goto('data:text/html,hello');
288      expect(response.ok()).toBe(true);
289    });
290    it('should work when navigating to 404', async () => {
291      const { page, server } = getTestState();
292
293      const response = await page.goto(server.PREFIX + '/not-found');
294      expect(response.ok()).toBe(false);
295      expect(response.status()).toBe(404);
296    });
297    it('should return last response in redirect chain', async () => {
298      const { page, server } = getTestState();
299
300      server.setRedirect('/redirect/1.html', '/redirect/2.html');
301      server.setRedirect('/redirect/2.html', '/redirect/3.html');
302      server.setRedirect('/redirect/3.html', server.EMPTY_PAGE);
303      const response = await page.goto(server.PREFIX + '/redirect/1.html');
304      expect(response.ok()).toBe(true);
305      expect(response.url()).toBe(server.EMPTY_PAGE);
306    });
307    it(
308      'should wait for network idle to succeed navigation',
309      async () => {
310        const { page, server } = getTestState();
311
312        let responses = [];
313        // Hold on to a bunch of requests without answering.
314        server.setRoute('/fetch-request-a.js', (req, res) =>
315          responses.push(res)
316        );
317        server.setRoute('/fetch-request-b.js', (req, res) =>
318          responses.push(res)
319        );
320        server.setRoute('/fetch-request-c.js', (req, res) =>
321          responses.push(res)
322        );
323        server.setRoute('/fetch-request-d.js', (req, res) =>
324          responses.push(res)
325        );
326        const initialFetchResourcesRequested = Promise.all([
327          server.waitForRequest('/fetch-request-a.js'),
328          server.waitForRequest('/fetch-request-b.js'),
329          server.waitForRequest('/fetch-request-c.js'),
330        ]);
331        const secondFetchResourceRequested = server.waitForRequest(
332          '/fetch-request-d.js'
333        );
334
335        // Navigate to a page which loads immediately and then does a bunch of
336        // requests via javascript's fetch method.
337        const navigationPromise = page.goto(
338          server.PREFIX + '/networkidle.html',
339          {
340            waitUntil: 'networkidle0',
341          }
342        );
343        // Track when the navigation gets completed.
344        let navigationFinished = false;
345        navigationPromise.then(() => (navigationFinished = true));
346
347        // Wait for the page's 'load' event.
348        await new Promise((fulfill) => page.once('load', fulfill));
349        expect(navigationFinished).toBe(false);
350
351        // Wait for the initial three resources to be requested.
352        await initialFetchResourcesRequested;
353
354        // Expect navigation still to be not finished.
355        expect(navigationFinished).toBe(false);
356
357        // Respond to initial requests.
358        for (const response of responses) {
359          response.statusCode = 404;
360          response.end(`File not found`);
361        }
362
363        // Reset responses array
364        responses = [];
365
366        // Wait for the second round to be requested.
367        await secondFetchResourceRequested;
368        // Expect navigation still to be not finished.
369        expect(navigationFinished).toBe(false);
370
371        // Respond to requests.
372        for (const response of responses) {
373          response.statusCode = 404;
374          response.end(`File not found`);
375        }
376
377        const response = await navigationPromise;
378        // Expect navigation to succeed.
379        expect(response.ok()).toBe(true);
380      }
381    );
382    it('should not leak listeners during navigation', async () => {
383      const { page, server } = getTestState();
384
385      let warning = null;
386      const warningHandler = (w) => (warning = w);
387      process.on('warning', warningHandler);
388      for (let i = 0; i < 20; ++i) await page.goto(server.EMPTY_PAGE);
389      process.removeListener('warning', warningHandler);
390      expect(warning).toBe(null);
391    });
392    it('should not leak listeners during bad navigation', async () => {
393      const { page } = getTestState();
394
395      let warning = null;
396      const warningHandler = (w) => (warning = w);
397      process.on('warning', warningHandler);
398      for (let i = 0; i < 20; ++i)
399        await page.goto('asdf').catch(() => {
400          /* swallow navigation error */
401        });
402      process.removeListener('warning', warningHandler);
403      expect(warning).toBe(null);
404    });
405    it('should not leak listeners during navigation of 11 pages', async () => {
406      const { context, server } = getTestState();
407
408      let warning = null;
409      const warningHandler = (w) => (warning = w);
410      process.on('warning', warningHandler);
411      await Promise.all(
412        [...Array(20)].map(async () => {
413          const page = await context.newPage();
414          await page.goto(server.EMPTY_PAGE);
415          await page.close();
416        })
417      );
418      process.removeListener('warning', warningHandler);
419      expect(warning).toBe(null);
420    });
421    it(
422      'should navigate to dataURL and fire dataURL requests',
423      async () => {
424        const { page } = getTestState();
425
426        const requests = [];
427        page.on(
428          'request',
429          (request) => !utils.isFavicon(request) && requests.push(request)
430        );
431        const dataURL = 'data:text/html,<div>yo</div>';
432        const response = await page.goto(dataURL);
433        expect(response.status()).toBe(200);
434        expect(requests.length).toBe(1);
435        expect(requests[0].url()).toBe(dataURL);
436      }
437    );
438    it(
439      'should navigate to URL with hash and fire requests without hash',
440      async () => {
441        const { page, server } = getTestState();
442
443        const requests = [];
444        page.on(
445          'request',
446          (request) => !utils.isFavicon(request) && requests.push(request)
447        );
448        const response = await page.goto(server.EMPTY_PAGE + '#hash');
449        expect(response.status()).toBe(200);
450        expect(response.url()).toBe(server.EMPTY_PAGE);
451        expect(requests.length).toBe(1);
452        expect(requests[0].url()).toBe(server.EMPTY_PAGE);
453      }
454    );
455    it('should work with self requesting page', async () => {
456      const { page, server } = getTestState();
457
458      const response = await page.goto(server.PREFIX + '/self-request.html');
459      expect(response.status()).toBe(200);
460      expect(response.url()).toContain('self-request.html');
461    });
462    it('should fail when navigating and show the url at the error message', async () => {
463      const { page, httpsServer } = getTestState();
464
465      const url = httpsServer.PREFIX + '/redirect/1.html';
466      let error = null;
467      try {
468        await page.goto(url);
469      } catch (error_) {
470        error = error_;
471      }
472      expect(error.message).toContain(url);
473    });
474    it('should send referer', async () => {
475      const { page, server } = getTestState();
476
477      const [request1, request2] = await Promise.all([
478        server.waitForRequest('/grid.html'),
479        server.waitForRequest('/digits/1.png'),
480        page.goto(server.PREFIX + '/grid.html', {
481          referer: 'http://google.com/',
482        }),
483      ]);
484      expect(request1.headers['referer']).toBe('http://google.com/');
485      // Make sure subresources do not inherit referer.
486      expect(request2.headers['referer']).toBe(server.PREFIX + '/grid.html');
487    });
488  });
489
490  describe('Page.waitForNavigation', function () {
491    it('should work', async () => {
492      const { page, server } = getTestState();
493
494      await page.goto(server.EMPTY_PAGE);
495      const [response] = await Promise.all([
496        page.waitForNavigation(),
497        page.evaluate(
498          (url: string) => (window.location.href = url),
499          server.PREFIX + '/grid.html'
500        ),
501      ]);
502      expect(response.ok()).toBe(true);
503      expect(response.url()).toContain('grid.html');
504    });
505    it('should work with both domcontentloaded and load', async () => {
506      const { page, server } = getTestState();
507
508      let response = null;
509      server.setRoute('/one-style.css', (req, res) => (response = res));
510      const navigationPromise = page.goto(server.PREFIX + '/one-style.html');
511      const domContentLoadedPromise = page.waitForNavigation({
512        waitUntil: 'domcontentloaded',
513      });
514
515      let bothFired = false;
516      const bothFiredPromise = page
517        .waitForNavigation({
518          waitUntil: ['load', 'domcontentloaded'],
519        })
520        .then(() => (bothFired = true));
521
522      await server.waitForRequest('/one-style.css');
523      await domContentLoadedPromise;
524      expect(bothFired).toBe(false);
525      response.end();
526      await bothFiredPromise;
527      await navigationPromise;
528    });
529    it('should work with clicking on anchor links', async () => {
530      const { page, server } = getTestState();
531
532      await page.goto(server.EMPTY_PAGE);
533      await page.setContent(`<a href='#foobar'>foobar</a>`);
534      const [response] = await Promise.all([
535        page.waitForNavigation(),
536        page.click('a'),
537      ]);
538      expect(response).toBe(null);
539      expect(page.url()).toBe(server.EMPTY_PAGE + '#foobar');
540    });
541    it('should work with history.pushState()', async () => {
542      const { page, server } = getTestState();
543
544      await page.goto(server.EMPTY_PAGE);
545      await page.setContent(`
546        <a onclick='javascript:pushState()'>SPA</a>
547        <script>
548          function pushState() { history.pushState({}, '', 'wow.html') }
549        </script>
550      `);
551      const [response] = await Promise.all([
552        page.waitForNavigation(),
553        page.click('a'),
554      ]);
555      expect(response).toBe(null);
556      expect(page.url()).toBe(server.PREFIX + '/wow.html');
557    });
558    it('should work with history.replaceState()', async () => {
559      const { page, server } = getTestState();
560
561      await page.goto(server.EMPTY_PAGE);
562      await page.setContent(`
563        <a onclick='javascript:replaceState()'>SPA</a>
564        <script>
565          function replaceState() { history.replaceState({}, '', '/replaced.html') }
566        </script>
567      `);
568      const [response] = await Promise.all([
569        page.waitForNavigation(),
570        page.click('a'),
571      ]);
572      expect(response).toBe(null);
573      expect(page.url()).toBe(server.PREFIX + '/replaced.html');
574    });
575    it(
576      'should work with DOM history.back()/history.forward()',
577      async () => {
578        const { page, server } = getTestState();
579
580        await page.goto(server.EMPTY_PAGE);
581        await page.setContent(`
582        <a id=back onclick='javascript:goBack()'>back</a>
583        <a id=forward onclick='javascript:goForward()'>forward</a>
584        <script>
585          function goBack() { history.back(); }
586          function goForward() { history.forward(); }
587          history.pushState({}, '', '/first.html');
588          history.pushState({}, '', '/second.html');
589        </script>
590      `);
591        expect(page.url()).toBe(server.PREFIX + '/second.html');
592        const [backResponse] = await Promise.all([
593          page.waitForNavigation(),
594          page.click('a#back'),
595        ]);
596        expect(backResponse).toBe(null);
597        expect(page.url()).toBe(server.PREFIX + '/first.html');
598        const [forwardResponse] = await Promise.all([
599          page.waitForNavigation(),
600          page.click('a#forward'),
601        ]);
602        expect(forwardResponse).toBe(null);
603        expect(page.url()).toBe(server.PREFIX + '/second.html');
604      }
605    );
606    it(
607      'should work when subframe issues window.stop()',
608      async () => {
609        const { page, server } = getTestState();
610
611        server.setRoute('/frames/style.css', () => {});
612        const navigationPromise = page.goto(
613          server.PREFIX + '/frames/one-frame.html'
614        );
615        const frame = await utils.waitEvent(page, 'frameattached');
616        await new Promise<void>((fulfill) => {
617          page.on('framenavigated', (f) => {
618            if (f === frame) fulfill();
619          });
620        });
621        await Promise.all([
622          frame.evaluate(() => window.stop()),
623          navigationPromise,
624        ]);
625      }
626    );
627  });
628
629  describe('Page.goBack', function () {
630    it('should work', async () => {
631      const { page, server } = getTestState();
632
633      await page.goto(server.EMPTY_PAGE);
634      await page.goto(server.PREFIX + '/grid.html');
635
636      let response = await page.goBack();
637      expect(response.ok()).toBe(true);
638      expect(response.url()).toContain(server.EMPTY_PAGE);
639
640      response = await page.goForward();
641      expect(response.ok()).toBe(true);
642      expect(response.url()).toContain('/grid.html');
643
644      response = await page.goForward();
645      expect(response).toBe(null);
646    });
647    it('should work with HistoryAPI', async () => {
648      const { page, server } = getTestState();
649
650      await page.goto(server.EMPTY_PAGE);
651      await page.evaluate(() => {
652        history.pushState({}, '', '/first.html');
653        history.pushState({}, '', '/second.html');
654      });
655      expect(page.url()).toBe(server.PREFIX + '/second.html');
656
657      await page.goBack();
658      expect(page.url()).toBe(server.PREFIX + '/first.html');
659      await page.goBack();
660      expect(page.url()).toBe(server.EMPTY_PAGE);
661      await page.goForward();
662      expect(page.url()).toBe(server.PREFIX + '/first.html');
663    });
664  });
665
666  describe('Frame.goto', function () {
667    it('should navigate subframes', async () => {
668      const { page, server } = getTestState();
669
670      await page.goto(server.PREFIX + '/frames/one-frame.html');
671      expect(page.frames()[0].url()).toContain('/frames/one-frame.html');
672      expect(page.frames()[1].url()).toContain('/frames/frame.html');
673
674      const response = await page.frames()[1].goto(server.EMPTY_PAGE);
675      expect(response.ok()).toBe(true);
676      expect(response.frame()).toBe(page.frames()[1]);
677    });
678    it('should reject when frame detaches', async () => {
679      const { page, server } = getTestState();
680
681      await page.goto(server.PREFIX + '/frames/one-frame.html');
682
683      server.setRoute('/empty.html', () => {});
684      const navigationPromise = page
685        .frames()[1]
686        .goto(server.EMPTY_PAGE)
687        .catch((error_) => error_);
688      await server.waitForRequest('/empty.html');
689
690      await page.$eval('iframe', (frame) => frame.remove());
691      const error = await navigationPromise;
692      expect(error.message).toBe('Navigating frame was detached');
693    });
694    it('should return matching responses', async () => {
695      const { page, server } = getTestState();
696
697      // Disable cache: otherwise, chromium will cache similar requests.
698      await page.setCacheEnabled(false);
699      await page.goto(server.EMPTY_PAGE);
700      // Attach three frames.
701      const frames = await Promise.all([
702        utils.attachFrame(page, 'frame1', server.EMPTY_PAGE),
703        utils.attachFrame(page, 'frame2', server.EMPTY_PAGE),
704        utils.attachFrame(page, 'frame3', server.EMPTY_PAGE),
705      ]);
706      // Navigate all frames to the same URL.
707      const serverResponses = [];
708      server.setRoute('/one-style.html', (req, res) =>
709        serverResponses.push(res)
710      );
711      const navigations = [];
712      for (let i = 0; i < 3; ++i) {
713        navigations.push(frames[i].goto(server.PREFIX + '/one-style.html'));
714        await server.waitForRequest('/one-style.html');
715      }
716      // Respond from server out-of-order.
717      const serverResponseTexts = ['AAA', 'BBB', 'CCC'];
718      for (const i of [1, 2, 0]) {
719        serverResponses[i].end(serverResponseTexts[i]);
720        const response = await navigations[i];
721        expect(response.frame()).toBe(frames[i]);
722        expect(await response.text()).toBe(serverResponseTexts[i]);
723      }
724    });
725  });
726
727  describe('Frame.waitForNavigation', function () {
728    it('should work', async () => {
729      const { page, server } = getTestState();
730
731      await page.goto(server.PREFIX + '/frames/one-frame.html');
732      const frame = page.frames()[1];
733      const [response] = await Promise.all([
734        frame.waitForNavigation(),
735        frame.evaluate(
736          (url: string) => (window.location.href = url),
737          server.PREFIX + '/grid.html'
738        ),
739      ]);
740      expect(response.ok()).toBe(true);
741      expect(response.url()).toContain('grid.html');
742      expect(response.frame()).toBe(frame);
743      expect(page.url()).toContain('/frames/one-frame.html');
744    });
745    it('should fail when frame detaches', async () => {
746      const { page, server } = getTestState();
747
748      await page.goto(server.PREFIX + '/frames/one-frame.html');
749      const frame = page.frames()[1];
750
751      server.setRoute('/empty.html', () => {});
752      let error = null;
753      const navigationPromise = frame
754        .waitForNavigation()
755        .catch((error_) => (error = error_));
756      await Promise.all([
757        server.waitForRequest('/empty.html'),
758        frame.evaluate(() => ((window as any).location = '/empty.html')),
759      ]);
760      await page.$eval('iframe', (frame) => frame.remove());
761      await navigationPromise;
762      expect(error.message).toBe('Navigating frame was detached');
763    });
764  });
765
766  describe('Page.reload', function () {
767    it('should work', async () => {
768      const { page, server } = getTestState();
769
770      await page.goto(server.EMPTY_PAGE);
771      await page.evaluate(() => (globalThis._foo = 10));
772      await page.reload();
773      expect(await page.evaluate(() => globalThis._foo)).toBe(undefined);
774    });
775  });
776});
777