mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 13:45:36 +03:00
fix(navigation): expect navigation on navigation request (#248)
This commit is contained in:
parent
f539afa2a7
commit
09bf6b1906
@ -81,7 +81,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||
this._client.on('Page.fileChooserOpened', event => this._onFileChooserOpened(event));
|
||||
this._client.on('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId));
|
||||
this._client.on('Page.frameDetached', event => this._onFrameDetached(event.frameId));
|
||||
this._client.on('Page.frameNavigated', event => this._onFrameNavigated(event.frame));
|
||||
this._client.on('Page.frameNavigated', event => this._onFrameNavigated(event.frame, false));
|
||||
this._client.on('Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId));
|
||||
this._client.on('Page.javascriptDialogOpening', event => this._onDialog(event));
|
||||
this._client.on('Page.lifecycleEvent', event => this._onLifecycleEvent(event));
|
||||
@ -199,14 +199,12 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||
const frame = this._frames.get(event.frameId);
|
||||
if (!frame)
|
||||
return;
|
||||
if (event.name === 'init') {
|
||||
if (event.name === 'init')
|
||||
frame._firedLifecycleEvents.clear();
|
||||
frame._expectNewDocumentNavigation(event.loaderId);
|
||||
} else if (event.name === 'load') {
|
||||
else if (event.name === 'load')
|
||||
frame._lifecycleEvent('load');
|
||||
} else if (event.name === 'DOMContentLoaded') {
|
||||
else if (event.name === 'DOMContentLoaded')
|
||||
frame._lifecycleEvent('domcontentloaded');
|
||||
}
|
||||
}
|
||||
|
||||
_onFrameStoppedLoading(frameId: string) {
|
||||
@ -220,7 +218,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||
_handleFrameTree(frameTree: Protocol.Page.FrameTree) {
|
||||
if (frameTree.frame.parentId)
|
||||
this._onFrameAttached(frameTree.frame.id, frameTree.frame.parentId);
|
||||
this._onFrameNavigated(frameTree.frame);
|
||||
this._onFrameNavigated(frameTree.frame, true);
|
||||
if (!frameTree.childFrames)
|
||||
return;
|
||||
|
||||
@ -254,7 +252,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||
this._page.emit(Events.Page.FrameAttached, frame);
|
||||
}
|
||||
|
||||
_onFrameNavigated(framePayload: Protocol.Page.Frame) {
|
||||
_onFrameNavigated(framePayload: Protocol.Page.Frame, initial: boolean) {
|
||||
const isMainFrame = !framePayload.parentId;
|
||||
let frame = isMainFrame ? this._mainFrame : this._frames.get(framePayload.id);
|
||||
assert(isMainFrame || frame, 'We either navigate top level or have old version of the navigated frame');
|
||||
@ -279,7 +277,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||
this._mainFrame = frame;
|
||||
}
|
||||
|
||||
frame._onCommittedNewDocumentNavigation(framePayload.url, framePayload.name, framePayload.loaderId);
|
||||
frame._onCommittedNewDocumentNavigation(framePayload.url, framePayload.name, framePayload.loaderId, initial);
|
||||
|
||||
this._page.emit(Events.Page.FrameNavigated, frame);
|
||||
}
|
||||
|
@ -201,8 +201,6 @@ export class NetworkManager extends EventEmitter {
|
||||
const frame = event.frameId ? this._frameManager.frame(event.frameId) : null;
|
||||
const isNavigationRequest = event.requestId === event.loaderId && event.type === 'Document';
|
||||
const documentId = isNavigationRequest ? event.loaderId : undefined;
|
||||
if (documentId && frame)
|
||||
frame._expectNewDocumentNavigation(documentId, event.request.url);
|
||||
const request = new InterceptableRequest(this._client, frame, interceptionId, documentId, this._userRequestInterceptionEnabled, event, redirectChain);
|
||||
this._requestIdToRequest.set(event.requestId, request);
|
||||
this.emit(NetworkManagerEvents.Request, request.request);
|
||||
@ -258,20 +256,12 @@ export class NetworkManager extends EventEmitter {
|
||||
// @see https://crbug.com/750469
|
||||
if (!request)
|
||||
return;
|
||||
request.request._setFailureText(event.errorText);
|
||||
const response = request.request.response();
|
||||
if (response)
|
||||
response._requestFinished();
|
||||
this._requestIdToRequest.delete(request._requestId);
|
||||
this._attemptedAuthentications.delete(request._interceptionId);
|
||||
if (request._documentId && request.request.frame()) {
|
||||
const isCurrentDocument = request.request.frame()._lastDocumentId === request._documentId;
|
||||
let errorText = event.errorText;
|
||||
if (event.canceled)
|
||||
errorText += '; maybe frame was detached?';
|
||||
if (!isCurrentDocument)
|
||||
request.request.frame()._onAbortedNewDocumentNavigation(request._documentId, errorText);
|
||||
}
|
||||
request.request._setFailureText(event.errorText, event.canceled);
|
||||
this.emit(NetworkManagerEvents.RequestFailed, request.request);
|
||||
}
|
||||
}
|
||||
@ -298,7 +288,7 @@ class InterceptableRequest {
|
||||
this._documentId = documentId;
|
||||
this._allowInterception = allowInterception;
|
||||
|
||||
this.request = new network.Request(frame, redirectChain, !!documentId,
|
||||
this.request = new network.Request(frame, redirectChain, documentId,
|
||||
event.request.url, event.type.toLowerCase(), event.request.method, event.request.postData, headersObject(event.request.headers));
|
||||
(this.request as any)[interceptableRequestSymbol] = this;
|
||||
}
|
||||
|
@ -137,8 +137,6 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||
}
|
||||
|
||||
_onNavigationStarted(params) {
|
||||
const frame = this._frames.get(params.frameId);
|
||||
frame._expectNewDocumentNavigation(params.navigationId, params.url);
|
||||
}
|
||||
|
||||
_onNavigationAborted(params) {
|
||||
@ -148,7 +146,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||
|
||||
_onNavigationCommitted(params) {
|
||||
const frame = this._frames.get(params.frameId);
|
||||
frame._onCommittedNewDocumentNavigation(params.url, params.name, params.navigationId);
|
||||
frame._onCommittedNewDocumentNavigation(params.url, params.name, params.navigationId, false);
|
||||
this._page.emit(Events.Page.FrameNavigated, frame);
|
||||
}
|
||||
|
||||
|
@ -126,7 +126,7 @@ export class NetworkManager extends EventEmitter {
|
||||
this._requests.delete(request._id);
|
||||
if (request.request.response())
|
||||
request.request.response()._requestFinished();
|
||||
request.request._setFailureText(event.errorCode);
|
||||
request.request._setFailureText(event.errorCode, event.errorCode === 'NS_BINDING_ABORTED');
|
||||
this.emit(NetworkManagerEvents.RequestFailed, request.request);
|
||||
}
|
||||
}
|
||||
@ -180,7 +180,7 @@ class InterceptableRequest {
|
||||
for (const {name, value} of payload.headers)
|
||||
headers[name.toLowerCase()] = value;
|
||||
|
||||
this.request = new network.Request(frame, redirectChain, payload.isNavigationRequest,
|
||||
this.request = new network.Request(frame, redirectChain, payload.navigationId,
|
||||
payload.url, causeToResourceType[payload.cause] || 'other', payload.method, payload.postData, headers);
|
||||
(this.request as any)[interceptableRequestSymbol] = this;
|
||||
}
|
||||
|
@ -422,9 +422,9 @@ export class Frame {
|
||||
return context.evaluate(() => document.title);
|
||||
}
|
||||
|
||||
_expectNewDocumentNavigation(documentId: string, url?: string) {
|
||||
_onNavigationRequest(request: network.Request) {
|
||||
for (const watcher of this._page._lifecycleWatchers)
|
||||
watcher._onExpectedNewDocumentNavigation(this, documentId, url);
|
||||
watcher._onNavigationRequest(this, request);
|
||||
}
|
||||
|
||||
_onAbortedNewDocumentNavigation(documentId: string, errorText: string) {
|
||||
@ -432,11 +432,15 @@ export class Frame {
|
||||
watcher._onAbortedNewDocumentNavigation(this, documentId, errorText);
|
||||
}
|
||||
|
||||
_onCommittedNewDocumentNavigation(url: string, name: string, documentId: string) {
|
||||
_onCommittedNewDocumentNavigation(url: string, name: string, documentId: string, initial: boolean) {
|
||||
this._url = url;
|
||||
this._name = name;
|
||||
this._lastDocumentId = documentId;
|
||||
this._firedLifecycleEvents.clear();
|
||||
if (!initial) {
|
||||
for (const watcher of this._page._lifecycleWatchers)
|
||||
watcher._onCommittedNewDocumentNavigation(this);
|
||||
}
|
||||
}
|
||||
|
||||
_onCommittedSameDocumentNavigation(url: string) {
|
||||
@ -600,7 +604,6 @@ export class LifecycleWatcher {
|
||||
private _navigationAbortedCallback: (err: Error) => void;
|
||||
private _maximumTimer: NodeJS.Timer;
|
||||
private _hasSameDocumentNavigation: boolean;
|
||||
private _listeners: RegisteredListener[];
|
||||
private _targetUrl?: string;
|
||||
private _expectedDocumentId?: string;
|
||||
|
||||
@ -623,12 +626,6 @@ export class LifecycleWatcher {
|
||||
this._frame._page._disconnectedPromise.then(() => new Error('Navigation failed because browser has disconnected!')),
|
||||
]);
|
||||
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() && (!this._navigationRequest || request.redirectChain().includes(this._navigationRequest)))
|
||||
this._navigationRequest = request;
|
||||
}),
|
||||
];
|
||||
this._checkLifecycleComplete();
|
||||
}
|
||||
|
||||
@ -647,10 +644,19 @@ export class LifecycleWatcher {
|
||||
this._checkLifecycleComplete();
|
||||
}
|
||||
|
||||
_onExpectedNewDocumentNavigation(frame: Frame, documentId: string, url?: string) {
|
||||
_onNavigationRequest(frame: Frame, request: network.Request) {
|
||||
assert(request._documentId);
|
||||
if (frame === this._frame && this._expectedDocumentId === undefined) {
|
||||
this._expectedDocumentId = documentId;
|
||||
this._targetUrl = url;
|
||||
this._navigationRequest = request;
|
||||
this._expectedDocumentId = request._documentId;
|
||||
this._targetUrl = request.url();
|
||||
}
|
||||
}
|
||||
|
||||
_onCommittedNewDocumentNavigation(frame: Frame) {
|
||||
if (frame === this._frame && this._expectedDocumentId === undefined) {
|
||||
this._expectedDocumentId = frame._lastDocumentId;
|
||||
this._targetUrl = frame.url();
|
||||
}
|
||||
}
|
||||
|
||||
@ -668,7 +674,7 @@ export class LifecycleWatcher {
|
||||
}
|
||||
|
||||
navigationResponse(): Promise<network.Response | null> {
|
||||
return this._navigationRequest ? this._navigationRequest._waitForFinished() : null;
|
||||
return this._navigationRequest ? this._navigationRequest._finalRequest._waitForFinished() : null;
|
||||
}
|
||||
|
||||
private _createTimeoutPromise(timeout: number): Promise<Error | null> {
|
||||
@ -704,7 +710,6 @@ export class LifecycleWatcher {
|
||||
|
||||
dispose() {
|
||||
this._frame._page._lifecycleWatchers.delete(this);
|
||||
helper.removeEventListeners(this._listeners);
|
||||
clearTimeout(this._maximumTimer);
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,8 @@ export type Headers = { [key: string]: string };
|
||||
export class Request {
|
||||
private _response: Response | null = null;
|
||||
_redirectChain: Request[];
|
||||
private _isNavigationRequest: boolean;
|
||||
_finalRequest: Request;
|
||||
readonly _documentId?: string;
|
||||
private _failureText: string | null = null;
|
||||
private _url: string;
|
||||
private _resourceType: string;
|
||||
@ -85,11 +86,14 @@ export class Request {
|
||||
private _waitForFinishedPromise: Promise<Response | undefined>;
|
||||
private _waitForFinishedPromiseCallback: (value?: Response | undefined) => void;
|
||||
|
||||
constructor(frame: frames.Frame | null, redirectChain: Request[], isNavigationRequest: boolean,
|
||||
constructor(frame: frames.Frame | null, redirectChain: Request[], documentId: string,
|
||||
url: string, resourceType: string, method: string, postData: string, headers: Headers) {
|
||||
this._frame = frame;
|
||||
this._redirectChain = redirectChain;
|
||||
this._isNavigationRequest = isNavigationRequest;
|
||||
this._finalRequest = this;
|
||||
for (const request of redirectChain)
|
||||
request._finalRequest = this;
|
||||
this._documentId = documentId;
|
||||
this._url = url;
|
||||
this._resourceType = resourceType;
|
||||
this._method = method;
|
||||
@ -97,10 +101,20 @@ export class Request {
|
||||
this._headers = headers;
|
||||
this._waitForResponsePromise = new Promise(f => this._waitForResponsePromiseCallback = f);
|
||||
this._waitForFinishedPromise = new Promise(f => this._waitForFinishedPromiseCallback = f);
|
||||
if (documentId && frame)
|
||||
frame._onNavigationRequest(this);
|
||||
}
|
||||
|
||||
_setFailureText(failureText: string) {
|
||||
_setFailureText(failureText: string, canceled: boolean) {
|
||||
this._failureText = failureText;
|
||||
if (this._documentId && this._frame) {
|
||||
const isCurrentDocument = this._frame._lastDocumentId === this._documentId;
|
||||
let errorText = failureText;
|
||||
if (canceled)
|
||||
errorText += '; maybe frame was detached?';
|
||||
if (!isCurrentDocument)
|
||||
this._frame._onAbortedNewDocumentNavigation(this._documentId, errorText);
|
||||
}
|
||||
this._waitForFinishedPromiseCallback();
|
||||
}
|
||||
|
||||
@ -149,7 +163,7 @@ export class Request {
|
||||
}
|
||||
|
||||
isNavigationRequest(): boolean {
|
||||
return this._isNavigationRequest;
|
||||
return !!this._documentId;
|
||||
}
|
||||
|
||||
redirectChain(): Request[] {
|
||||
|
@ -230,9 +230,7 @@ export class FrameManager extends EventEmitter implements PageDelegate {
|
||||
|
||||
// Append session id to avoid cross-process loaderId clash.
|
||||
const documentId = this._session._sessionId + '::' + framePayload.loaderId;
|
||||
if (!initial)
|
||||
frame._expectNewDocumentNavigation(documentId);
|
||||
frame._onCommittedNewDocumentNavigation(framePayload.url, framePayload.name, documentId);
|
||||
frame._onCommittedNewDocumentNavigation(framePayload.url, framePayload.name, documentId, initial);
|
||||
|
||||
this._page.emit(Events.Page.FrameNavigated, frame);
|
||||
}
|
||||
|
@ -101,8 +101,6 @@ export class NetworkManager extends EventEmitter {
|
||||
// TODO(einbinder) this will fail if we are an XHR document request
|
||||
const isNavigationRequest = event.type === 'Document';
|
||||
const documentId = isNavigationRequest ? this._session._sessionId + '::' + event.loaderId : undefined;
|
||||
if (documentId)
|
||||
frame._expectNewDocumentNavigation(documentId, event.request.url);
|
||||
const request = new InterceptableRequest(frame, undefined, event, redirectChain, documentId);
|
||||
this._requestIdToRequest.set(event.requestId, request);
|
||||
this.emit(NetworkManagerEvents.Request, request.request);
|
||||
@ -158,20 +156,12 @@ export class NetworkManager extends EventEmitter {
|
||||
// @see https://crbug.com/750469
|
||||
if (!request)
|
||||
return;
|
||||
request.request._setFailureText(event.errorText);
|
||||
const response = request.request.response();
|
||||
if (response)
|
||||
response._requestFinished();
|
||||
this._requestIdToRequest.delete(request._requestId);
|
||||
this._attemptedAuthentications.delete(request._interceptionId);
|
||||
if (request._documentId) {
|
||||
const isCurrentDocument = request.request.frame()._lastDocumentId === request._documentId;
|
||||
let errorText = event.errorText;
|
||||
if (errorText.includes('cancelled'))
|
||||
errorText += '; maybe frame was detached?';
|
||||
if (!isCurrentDocument)
|
||||
request.request.frame()._onAbortedNewDocumentNavigation(request._documentId, errorText);
|
||||
}
|
||||
request.request._setFailureText(event.errorText, event.errorText.includes('cancelled'));
|
||||
this.emit(NetworkManagerEvents.RequestFailed, request.request);
|
||||
}
|
||||
}
|
||||
@ -192,7 +182,7 @@ class InterceptableRequest {
|
||||
this._requestId = event.requestId;
|
||||
this._interceptionId = interceptionId;
|
||||
this._documentId = documentId;
|
||||
this.request = new network.Request(frame, redirectChain, !!documentId, event.request.url,
|
||||
this.request = new network.Request(frame, redirectChain, documentId, event.request.url,
|
||||
event.type ? event.type.toLowerCase() : 'Unknown', event.request.method, event.request.postData, headersObject(event.request.headers));
|
||||
(this.request as any)[interceptableRequestSymbol] = this;
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
|
||||
describe('Network Events', function() {
|
||||
it('Page.Events.Request', async({page, server}) => {
|
||||
const requests = [];
|
||||
page.on('request', request => requests.push(request));
|
||||
page.on('request', request => !utils.isFavicon(request) && requests.push(request));
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
expect(requests.length).toBe(1);
|
||||
expect(requests[0].url()).toBe(server.EMPTY_PAGE);
|
||||
@ -278,10 +278,10 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
|
||||
});
|
||||
it('should support redirects', async({page, server}) => {
|
||||
const events = [];
|
||||
page.on('request', request => events.push(`${request.method()} ${request.url()}`));
|
||||
page.on('response', response => events.push(`${response.status()} ${response.url()}`));
|
||||
page.on('requestfinished', request => events.push(`DONE ${request.url()}`));
|
||||
page.on('requestfailed', request => events.push(`FAIL ${request.url()}`));
|
||||
page.on('request', request => !utils.isFavicon(request) && events.push(`${request.method()} ${request.url()}`));
|
||||
page.on('response', response => !utils.isFavicon(response.request()) && events.push(`${response.status()} ${response.url()}`));
|
||||
page.on('requestfinished', request => !utils.isFavicon(request) && events.push(`DONE ${request.url()}`));
|
||||
page.on('requestfailed', request => !utils.isFavicon(request) && events.push(`FAIL ${request.url()}`));
|
||||
server.setRedirect('/foo.html', '/empty.html');
|
||||
const FOO_URL = server.PREFIX + '/foo.html';
|
||||
const response = await page.goto(FOO_URL);
|
||||
|
Loading…
Reference in New Issue
Block a user