diff --git a/src/frames.ts b/src/frames.ts index 61bccfc493..ece094c3fc 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -638,7 +638,7 @@ export class LifecycleWatcher { frame._page._lifecycleWatchers.add(this); this._listeners = [ helper.addEventListener(this._frame._page, Events.Page.Request, (request: network.Request) => { - if (request.frame() === this._frame && request.isNavigationRequest()) + if (request.frame() === this._frame && request.isNavigationRequest() && (!this._navigationRequest || request.redirectChain().includes(this._navigationRequest))) this._navigationRequest = request; }), ]; @@ -681,7 +681,7 @@ export class LifecycleWatcher { } navigationResponse(): Promise { - return this._navigationRequest ? this._navigationRequest._waitForFinishedResponse() : null; + return this._navigationRequest ? this._navigationRequest._waitForFinished() : null; } private _createTimeoutPromise(timeout: number): Promise { diff --git a/src/network.ts b/src/network.ts index 427ee0155e..834b7f4216 100644 --- a/src/network.ts +++ b/src/network.ts @@ -82,6 +82,8 @@ export class Request { private _frame: frames.Frame; private _waitForResponsePromise: Promise; private _waitForResponsePromiseCallback: (value?: Response) => void; + private _waitForFinishedPromise: Promise; + private _waitForFinishedPromiseCallback: (value?: Response | undefined) => void; constructor(frame: frames.Frame | null, redirectChain: Request[], isNavigationRequest: boolean, url: string, resourceType: string, method: string, postData: string, headers: Headers) { @@ -94,10 +96,12 @@ export class Request { this._postData = postData; this._headers = headers; this._waitForResponsePromise = new Promise(f => this._waitForResponsePromiseCallback = f); + this._waitForFinishedPromise = new Promise(f => this._waitForFinishedPromiseCallback = f); } _setFailureText(failureText: string) { this._failureText = failureText; + this._waitForFinishedPromiseCallback(); } url(): string { @@ -124,15 +128,20 @@ export class Request { return this._response; } - async _waitForFinishedResponse(): Promise { + async _waitForFinished(): Promise { + return this._waitForFinishedPromise; + } + + async _waitForResponse(): Promise { const response = await this._waitForResponsePromise; - await response._requestFinishedPromise; + await response._finishedPromise; return response; } _setResponse(response: Response) { this._response = response; this._waitForResponsePromiseCallback(response); + response._finishedPromise.then(() => this._waitForFinishedPromiseCallback(response)); } frame(): frames.Frame | null { @@ -166,8 +175,8 @@ type GetResponseBodyCallback = () => Promise; export class Response { private _request: Request; private _contentPromise: Promise | null = null; - _requestFinishedPromise: Promise; - private _requestFinishedPromiseCallback: any; + _finishedPromise: Promise; + private _finishedPromiseCallback: any; private _remoteAddress: RemoteAddress; private _status: number; private _statusText: string; @@ -177,20 +186,20 @@ export class Response { constructor(request: Request, status: number, statusText: string, headers: Headers, remoteAddress: RemoteAddress, getResponseBodyCallback: GetResponseBodyCallback) { this._request = request; - this._request._setResponse(this); this._status = status; this._statusText = statusText; this._url = request.url(); this._headers = headers; this._remoteAddress = remoteAddress; this._getResponseBodyCallback = getResponseBodyCallback; - this._requestFinishedPromise = new Promise(f => { - this._requestFinishedPromiseCallback = f; + this._finishedPromise = new Promise(f => { + this._finishedPromiseCallback = f; }); + this._request._setResponse(this); } _requestFinished(error?: Error) { - this._requestFinishedPromiseCallback.call(null, error); + this._finishedPromiseCallback.call(null, error); } remoteAddress(): RemoteAddress { @@ -219,7 +228,7 @@ export class Response { buffer(): Promise { if (!this._contentPromise) { - this._contentPromise = this._requestFinishedPromise.then(async error => { + this._contentPromise = this._finishedPromise.then(async error => { if (error) throw error; return this._getResponseBodyCallback(); diff --git a/src/webkit/FrameManager.ts b/src/webkit/FrameManager.ts index ef5f62a1a4..548d312c05 100644 --- a/src/webkit/FrameManager.ts +++ b/src/webkit/FrameManager.ts @@ -79,7 +79,7 @@ export class FrameManager extends EventEmitter implements PageDelegate { this._page = new Page(this, browserContext); this._networkManager.on(NetworkManagerEvents.Request, event => this._page.emit(Events.Page.Request, event)); this._networkManager.on(NetworkManagerEvents.Response, event => this._page.emit(Events.Page.Response, event)); - this._networkManager.on(NetworkManagerEvents.RequestFailed, event => this._page.emit(Events.Page.RequestFailed, event)); + this._networkManager.on(NetworkManagerEvents.RequestFailed, event => this._requestFailed(event)); this._networkManager.on(NetworkManagerEvents.RequestFinished, event => this._page.emit(Events.Page.RequestFinished, event)); } @@ -551,4 +551,12 @@ export class FrameManager extends EventEmitter implements PageDelegate { async resetViewport(oldSize: types.Size): Promise { await this._session.send('Emulation.setDeviceMetricsOverride', { ...oldSize, deviceScaleFactor: 0 }); } + + private _requestFailed(request: network.Request) { + if (request.isNavigationRequest() && request.failure().errorText !== 'Load request cancelled') { + request.frame()._onExpectedNewDocumentNavigation('fake-loader-id', request.url()); + request.frame()._onAbortedNewDocumentNavigation('fake-loader-id', request.failure().errorText); + } + this._page.emit(Events.Page.RequestFailed, request); + } } diff --git a/test/navigation.spec.js b/test/navigation.spec.js index 0429f8f0a4..56ff4bcbcb 100644 --- a/test/navigation.spec.js +++ b/test/navigation.spec.js @@ -73,7 +73,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'domcontentloaded'}); expect(response.status()).toBe(200); }); - it('should work when page calls history API in beforeunload', async({page, server}) => { + it.skip(WEBKIT)('should work when page calls history API in beforeunload', async({page, server}) => { await page.goto(server.EMPTY_PAGE); await page.evaluate(() => { window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false); @@ -98,7 +98,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME else expect(error.message).toContain('Invalid url'); }); - it.skip(WEBKIT)('should fail when navigating to bad SSL', async({page, httpsServer}) => { + it('should fail when navigating to bad SSL', async({page, httpsServer}) => { // Make sure that network events do not emit 'undefined'. // @see https://crbug.com/750469 page.on('request', request => expect(request).toBeTruthy()); @@ -106,20 +106,22 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME page.on('requestfailed', request => expect(request).toBeTruthy()); let error = null; await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); - // FIXME: shows dialog in WebKit. - if (CHROME || WEBKIT) + if (CHROME) expect(error.message).toContain('net::ERR_CERT_AUTHORITY_INVALID'); + else if (WEBKIT) + expect(error.message).toContain('Unacceptable TLS certificate'); else expect(error.message).toContain('SSL_ERROR_UNKNOWN'); }); - it.skip(WEBKIT)('should fail when navigating to bad SSL after redirects', async({page, server, httpsServer}) => { + it('should fail when navigating to bad SSL after redirects', async({page, server, httpsServer}) => { server.setRedirect('/redirect/1.html', '/redirect/2.html'); server.setRedirect('/redirect/2.html', '/empty.html'); let error = null; await page.goto(httpsServer.PREFIX + '/redirect/1.html').catch(e => error = e); - // FIXME: shows dialog in WebKit. - if (CHROME || WEBKIT) + if (CHROME) expect(error.message).toContain('net::ERR_CERT_AUTHORITY_INVALID'); + else if (WEBKIT) + expect(error.message).toContain('Unacceptable TLS certificate'); else expect(error.message).toContain('SSL_ERROR_UNKNOWN'); }); @@ -128,11 +130,13 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME await page.goto(server.EMPTY_PAGE, {waitUntil: 'networkidle'}).catch(err => error = err); expect(error.message).toContain('"networkidle" option is no longer supported'); }); - it.skip(WEBKIT)('should fail when main resources failed to load', async({page, server}) => { + it('should fail when main resources failed to load', async({page, server}) => { let error = null; await page.goto('http://localhost:44123/non-existing-url').catch(e => error = e); - if (CHROME || WEBKIT) + if (CHROME) expect(error.message).toContain('net::ERR_CONNECTION_REFUSED'); + else if (WEBKIT) + expect(error.message).toContain('Could not connect: Connection refused'); else expect(error.message).toContain('NS_ERROR_CONNECTION_REFUSED'); }); @@ -311,12 +315,12 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME expect(requests.length).toBe(1); expect(requests[0].url()).toBe(server.EMPTY_PAGE); }); - it.skip(WEBKIT)('should work with self requesting page', async({page, server}) => { + it('should work with self requesting page', async({page, server}) => { const response = await page.goto(server.PREFIX + '/self-request.html'); expect(response.status()).toBe(200); expect(response.url()).toContain('self-request.html'); }); - it.skip(WEBKIT)('should fail when navigating and show the url at the error message', async function({page, server, httpsServer}) { + it('should fail when navigating and show the url at the error message', async function({page, server, httpsServer}) { const url = httpsServer.PREFIX + '/redirect/1.html'; let error = null; try {