diff --git a/src/server/page.ts b/src/server/page.ts index 3b2434d045a10..5296e35d62360 100644 --- a/src/server/page.ts +++ b/src/server/page.ts @@ -293,9 +293,13 @@ export class Page extends EventEmitter { async reload(controller: ProgressController, options: types.NavigateOptions): Promise { this.mainFrame().setupNavigationProgressController(controller); const response = await controller.run(async progress => { - const waitPromise = this.mainFrame()._waitForNavigation(progress, options); - await this._delegate.reload(); - return waitPromise; + // Note: waitForNavigation may fail before we get response to reload(), + // so we should await it immediately. + const [response] = await Promise.all([ + this.mainFrame()._waitForNavigation(progress, options), + this._delegate.reload(), + ]); + return response; }, this._timeoutSettings.navigationTimeout(options)); await this._doSlowMo(); return response; @@ -304,13 +308,20 @@ export class Page extends EventEmitter { async goBack(controller: ProgressController, options: types.NavigateOptions): Promise { this.mainFrame().setupNavigationProgressController(controller); const response = await controller.run(async progress => { - const waitPromise = this.mainFrame()._waitForNavigation(progress, options); + // Note: waitForNavigation may fail before we get response to goBack, + // so we should catch it immediately. + let error: Error | undefined; + const waitPromise = this.mainFrame()._waitForNavigation(progress, options).catch(e => { + error = e; + return null; + }); const result = await this._delegate.goBack(); - if (!result) { - waitPromise.catch(() => {}); + if (!result) return null; - } - return waitPromise; + const response = await waitPromise; + if (error) + throw error; + return response; }, this._timeoutSettings.navigationTimeout(options)); await this._doSlowMo(); return response; @@ -319,13 +330,20 @@ export class Page extends EventEmitter { async goForward(controller: ProgressController, options: types.NavigateOptions): Promise { this.mainFrame().setupNavigationProgressController(controller); const response = await controller.run(async progress => { - const waitPromise = this.mainFrame()._waitForNavigation(progress, options); + // Note: waitForNavigation may fail before we get response to goForward, + // so we should catch it immediately. + let error: Error | undefined; + const waitPromise = this.mainFrame()._waitForNavigation(progress, options).catch(e => { + error = e; + return null; + }); const result = await this._delegate.goForward(); - if (!result) { - waitPromise.catch(() => {}); + if (!result) return null; - } - return waitPromise; + const response = await waitPromise; + if (error) + throw error; + return response; }, this._timeoutSettings.navigationTimeout(options)); await this._doSlowMo(); return response; diff --git a/test/page-history.spec.ts b/test/page-history.spec.ts index 38058fad0a100..b10a9419e1b52 100644 --- a/test/page-history.spec.ts +++ b/test/page-history.spec.ts @@ -92,3 +92,67 @@ it('page.reload should work with data url', async ({page, server}) => { expect(await page.reload()).toBe(null); expect(await page.content()).toContain('hello'); }); + +it('page.reload during renderer-initiated navigation', async ({page, server}) => { + await page.goto(server.PREFIX + '/one-style.html'); + await page.setContent(`
Form is here
`); + server.setRoute('/post', (req, res) => {}); + + let callback; + const reloadFailedPromise = new Promise(f => callback = f); + page.once('request', async () => { + await page.reload().catch(e => {}); + callback(); + }); + const clickPromise = page.click('input[type=submit]').catch(e => {}); + await reloadFailedPromise; + await clickPromise; + + // Form submit should be canceled, and reload should eventually arrive + // to the original one-style.html. + await page.waitForSelector('text=hello'); +}); + +it('page.goBack during renderer-initiated navigation', async ({page, server}) => { + await page.goto(server.PREFIX + '/one-style.html'); + await page.goto(server.EMPTY_PAGE); + await page.setContent(`
Form is here
`); + server.setRoute('/post', (req, res) => {}); + + let callback; + const reloadFailedPromise = new Promise(f => callback = f); + page.once('request', async () => { + await page.goBack().catch(e => {}); + callback(); + }); + const clickPromise = page.click('input[type=submit]').catch(e => {}); + await reloadFailedPromise; + await clickPromise; + + // Form submit should be canceled, and goBack should eventually arrive + // to the original one-style.html. + await page.waitForSelector('text=hello'); +}); + +it('page.goForward during renderer-initiated navigation', async ({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.goto(server.PREFIX + '/one-style.html'); + await page.goBack(); + + await page.setContent(`
Form is here
`); + server.setRoute('/post', (req, res) => {}); + + let callback; + const reloadFailedPromise = new Promise(f => callback = f); + page.once('request', async () => { + await page.goForward().catch(e => {}); + callback(); + }); + const clickPromise = page.click('input[type=submit]').catch(e => {}); + await reloadFailedPromise; + await clickPromise; + + // Form submit should be canceled, and goForward should eventually arrive + // to the original one-style.html. + await page.waitForSelector('text=hello'); +});