fix(har): restart redirected navigation (#14939)
This commit is contained in:
parent
030e7d211c
commit
ed6b14f0f4
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import type { HAREntry, HARFile, HARResponse } from '../../types/types';
|
import type { HAREntry, HARFile } from '../../types/types';
|
||||||
import { debugLogger } from '../common/debugLogger';
|
import { debugLogger } from '../common/debugLogger';
|
||||||
import { rewriteErrorMessage } from '../utils/stackTrace';
|
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||||
import { ZipFile } from '../utils/zipFile';
|
import { ZipFile } from '../utils/zipFile';
|
||||||
|
|
@ -51,9 +51,9 @@ export class HarRouter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handle(route: Route) {
|
private async _handle(route: Route) {
|
||||||
let response;
|
let entry;
|
||||||
try {
|
try {
|
||||||
response = harFindResponse(this._harFile, {
|
entry = harFindResponse(this._harFile, {
|
||||||
url: route.request().url(),
|
url: route.request().url(),
|
||||||
method: route.request().method()
|
method: route.request().method()
|
||||||
});
|
});
|
||||||
|
|
@ -62,8 +62,15 @@ export class HarRouter {
|
||||||
debugLogger.log('api', e);
|
debugLogger.log('api', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response) {
|
if (entry) {
|
||||||
|
// If navigation is being redirected, restart it with the final url to ensure the document's url changes.
|
||||||
|
if (entry.request.url !== route.request().url() && route.request().isNavigationRequest()) {
|
||||||
|
debugLogger.log('api', `redirecting HAR navigation: ${route.request().url()} => ${entry.request.url}`);
|
||||||
|
await route._abort(undefined, entry.request.url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
debugLogger.log('api', `serving from HAR: ${route.request().method()} ${route.request().url()}`);
|
debugLogger.log('api', `serving from HAR: ${route.request().method()} ${route.request().url()}`);
|
||||||
|
const response = entry.response;
|
||||||
const sha1 = (response.content as any)._sha1;
|
const sha1 = (response.content as any)._sha1;
|
||||||
|
|
||||||
if (this._zipFile && sha1) {
|
if (this._zipFile && sha1) {
|
||||||
|
|
@ -106,7 +113,7 @@ export class HarRouter {
|
||||||
|
|
||||||
const redirectStatus = [301, 302, 303, 307, 308];
|
const redirectStatus = [301, 302, 303, 307, 308];
|
||||||
|
|
||||||
function harFindResponse(har: HARFile, params: { url: string, method: string }): HARResponse | undefined {
|
function harFindResponse(har: HARFile, params: { url: string, method: string }): HAREntry | undefined {
|
||||||
const harLog = har.log;
|
const harLog = har.log;
|
||||||
const visited = new Set<HAREntry>();
|
const visited = new Set<HAREntry>();
|
||||||
let url = params.url;
|
let url = params.url;
|
||||||
|
|
@ -131,6 +138,6 @@ function harFindResponse(har: HARFile, params: { url: string, method: string }):
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry.response;
|
return entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -283,8 +283,12 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
|
||||||
}
|
}
|
||||||
|
|
||||||
async abort(errorCode?: string) {
|
async abort(errorCode?: string) {
|
||||||
|
await this._abort(errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _abort(errorCode?: string, redirectAbortedNavigationToUrl?: string) {
|
||||||
this._checkNotHandled();
|
this._checkNotHandled();
|
||||||
await this._raceWithPageClose(this._channel.abort({ errorCode }));
|
await this._raceWithPageClose(this._channel.abort({ errorCode, redirectAbortedNavigationToUrl }));
|
||||||
this._reportHandled(true);
|
this._reportHandled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3125,9 +3125,11 @@ export interface RouteChannel extends RouteEventTarget, Channel {
|
||||||
}
|
}
|
||||||
export type RouteAbortParams = {
|
export type RouteAbortParams = {
|
||||||
errorCode?: string,
|
errorCode?: string,
|
||||||
|
redirectAbortedNavigationToUrl?: string,
|
||||||
};
|
};
|
||||||
export type RouteAbortOptions = {
|
export type RouteAbortOptions = {
|
||||||
errorCode?: string,
|
errorCode?: string,
|
||||||
|
redirectAbortedNavigationToUrl?: string,
|
||||||
};
|
};
|
||||||
export type RouteAbortResult = void;
|
export type RouteAbortResult = void;
|
||||||
export type RouteContinueParams = {
|
export type RouteContinueParams = {
|
||||||
|
|
|
||||||
|
|
@ -2457,6 +2457,7 @@ Route:
|
||||||
abort:
|
abort:
|
||||||
parameters:
|
parameters:
|
||||||
errorCode: string?
|
errorCode: string?
|
||||||
|
redirectAbortedNavigationToUrl: string?
|
||||||
|
|
||||||
continue:
|
continue:
|
||||||
parameters:
|
parameters:
|
||||||
|
|
|
||||||
|
|
@ -1169,6 +1169,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
scheme.RequestRawRequestHeadersParams = tOptional(tObject({}));
|
scheme.RequestRawRequestHeadersParams = tOptional(tObject({}));
|
||||||
scheme.RouteAbortParams = tObject({
|
scheme.RouteAbortParams = tObject({
|
||||||
errorCode: tOptional(tString),
|
errorCode: tOptional(tString),
|
||||||
|
redirectAbortedNavigationToUrl: tOptional(tString),
|
||||||
});
|
});
|
||||||
scheme.RouteContinueParams = tObject({
|
scheme.RouteContinueParams = tObject({
|
||||||
url: tOptional(tString),
|
url: tOptional(tString),
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import { rewriteErrorMessage } from '../../utils/stackTrace';
|
||||||
import { assert, createGuid, headersArrayToObject } from '../../utils';
|
import { assert, createGuid, headersArrayToObject } from '../../utils';
|
||||||
import * as dialog from '../dialog';
|
import * as dialog from '../dialog';
|
||||||
import * as dom from '../dom';
|
import * as dom from '../dom';
|
||||||
import type * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import { helper } from '../helper';
|
import { helper } from '../helper';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import type { PageBinding, PageDelegate } from '../page';
|
import type { PageBinding, PageDelegate } from '../page';
|
||||||
|
|
@ -588,7 +588,7 @@ class FrameSession {
|
||||||
async _navigate(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult> {
|
async _navigate(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult> {
|
||||||
const response = await this._client.send('Page.navigate', { url, referrer, frameId: frame._id });
|
const response = await this._client.send('Page.navigate', { url, referrer, frameId: frame._id });
|
||||||
if (response.errorText)
|
if (response.errorText)
|
||||||
throw new Error(`${response.errorText} at ${url}`);
|
throw new frames.NavigationAbortedError(response.loaderId, `${response.errorText} at ${url}`);
|
||||||
return { newDocumentId: response.loaderId };
|
return { newDocumentId: response.loaderId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,9 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel> im
|
||||||
frame.on(Frame.Events.RemoveLifecycle, lifecycleEvent => {
|
frame.on(Frame.Events.RemoveLifecycle, lifecycleEvent => {
|
||||||
this._dispatchEvent('loadstate', { remove: lifecycleEvent });
|
this._dispatchEvent('loadstate', { remove: lifecycleEvent });
|
||||||
});
|
});
|
||||||
frame.on(Frame.Events.Navigation, (event: NavigationEvent) => {
|
frame.on(Frame.Events.InternalNavigation, (event: NavigationEvent) => {
|
||||||
|
if (!event.isPublic)
|
||||||
|
return;
|
||||||
const params = { url: event.url, name: event.name, error: event.error ? event.error.message : undefined };
|
const params = { url: event.url, name: event.name, error: event.error ? event.error.message : undefined };
|
||||||
if (event.newDocument)
|
if (event.newDocument)
|
||||||
(params as any).newDocument = { request: RequestDispatcher.fromNullable(this._scope, event.newDocument.request || null) };
|
(params as any).newDocument = { request: RequestDispatcher.fromNullable(this._scope, event.newDocument.request || null) };
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ export class RouteDispatcher extends Dispatcher<Route, channels.RouteChannel> im
|
||||||
}
|
}
|
||||||
|
|
||||||
async abort(params: channels.RouteAbortParams): Promise<void> {
|
async abort(params: channels.RouteAbortParams): Promise<void> {
|
||||||
await this._object.abort(params.errorCode || 'failed');
|
await this._object.abort(params.errorCode || 'failed', params.redirectAbortedNavigationToUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,11 +75,21 @@ export type NavigationEvent = {
|
||||||
// Error for cross-document navigations if any. When error is present,
|
// Error for cross-document navigations if any. When error is present,
|
||||||
// the navigation did not commit.
|
// the navigation did not commit.
|
||||||
error?: Error,
|
error?: Error,
|
||||||
|
// Wether this event should be visible to the clients via the public APIs.
|
||||||
|
isPublic?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchedulableTask<T> = (injectedScript: js.JSHandle<InjectedScript>) => Promise<js.JSHandle<InjectedScriptPoll<T>>>;
|
export type SchedulableTask<T> = (injectedScript: js.JSHandle<InjectedScript>) => Promise<js.JSHandle<InjectedScriptPoll<T>>>;
|
||||||
export type DomTaskBody<T, R, E> = (progress: InjectedScriptProgress, element: E, data: T, elements: Element[]) => R | symbol;
|
export type DomTaskBody<T, R, E> = (progress: InjectedScriptProgress, element: E, data: T, elements: Element[]) => R | symbol;
|
||||||
|
|
||||||
|
export class NavigationAbortedError extends Error {
|
||||||
|
readonly documentId?: string;
|
||||||
|
constructor(documentId: string | undefined, message: string) {
|
||||||
|
super(message);
|
||||||
|
this.documentId = documentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type SelectorInFrame = {
|
type SelectorInFrame = {
|
||||||
frame: Frame;
|
frame: Frame;
|
||||||
info: SelectorInfo;
|
info: SelectorInfo;
|
||||||
|
|
@ -228,8 +238,8 @@ export class FrameManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
frame._onClearLifecycle();
|
frame._onClearLifecycle();
|
||||||
const navigationEvent: NavigationEvent = { url, name, newDocument: frame._currentDocument };
|
const navigationEvent: NavigationEvent = { url, name, newDocument: frame._currentDocument, isPublic: true };
|
||||||
frame.emit(Frame.Events.Navigation, navigationEvent);
|
frame.emit(Frame.Events.InternalNavigation, navigationEvent);
|
||||||
if (!initial) {
|
if (!initial) {
|
||||||
debugLogger.log('api', ` navigated to "${url}"`);
|
debugLogger.log('api', ` navigated to "${url}"`);
|
||||||
this._page.frameNavigatedToNewDocument(frame);
|
this._page.frameNavigatedToNewDocument(frame);
|
||||||
|
|
@ -243,8 +253,8 @@ export class FrameManager {
|
||||||
if (!frame)
|
if (!frame)
|
||||||
return;
|
return;
|
||||||
frame._url = url;
|
frame._url = url;
|
||||||
const navigationEvent: NavigationEvent = { url, name: frame._name };
|
const navigationEvent: NavigationEvent = { url, name: frame._name, isPublic: true };
|
||||||
frame.emit(Frame.Events.Navigation, navigationEvent);
|
frame.emit(Frame.Events.InternalNavigation, navigationEvent);
|
||||||
debugLogger.log('api', ` navigated to "${url}"`);
|
debugLogger.log('api', ` navigated to "${url}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -258,10 +268,11 @@ export class FrameManager {
|
||||||
url: frame._url,
|
url: frame._url,
|
||||||
name: frame._name,
|
name: frame._name,
|
||||||
newDocument: frame.pendingDocument(),
|
newDocument: frame.pendingDocument(),
|
||||||
error: new Error(errorText),
|
error: new NavigationAbortedError(documentId, errorText),
|
||||||
|
isPublic: !frame._pendingNavigationRedirectAfterAbort
|
||||||
};
|
};
|
||||||
frame.setPendingDocument(undefined);
|
frame.setPendingDocument(undefined);
|
||||||
frame.emit(Frame.Events.Navigation, navigationEvent);
|
frame.emit(Frame.Events.InternalNavigation, navigationEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
frameDetached(frameId: string) {
|
frameDetached(frameId: string) {
|
||||||
|
|
@ -433,7 +444,7 @@ export class FrameManager {
|
||||||
|
|
||||||
export class Frame extends SdkObject {
|
export class Frame extends SdkObject {
|
||||||
static Events = {
|
static Events = {
|
||||||
Navigation: 'navigation',
|
InternalNavigation: 'internalnavigation',
|
||||||
AddLifecycle: 'addlifecycle',
|
AddLifecycle: 'addlifecycle',
|
||||||
RemoveLifecycle: 'removelifecycle',
|
RemoveLifecycle: 'removelifecycle',
|
||||||
};
|
};
|
||||||
|
|
@ -456,6 +467,7 @@ export class Frame extends SdkObject {
|
||||||
readonly _detachedPromise: Promise<void>;
|
readonly _detachedPromise: Promise<void>;
|
||||||
private _detachedCallback = () => {};
|
private _detachedCallback = () => {};
|
||||||
private _raceAgainstEvaluationStallingEventsPromises = new Set<ManualPromise<any>>();
|
private _raceAgainstEvaluationStallingEventsPromises = new Set<ManualPromise<any>>();
|
||||||
|
_pendingNavigationRedirectAfterAbort: { url: string, documentId: string } | undefined;
|
||||||
|
|
||||||
constructor(page: Page, id: string, parentFrame: Frame | null) {
|
constructor(page: Page, id: string, parentFrame: Frame | null) {
|
||||||
super(page, 'frame');
|
super(page, 'frame');
|
||||||
|
|
@ -586,15 +598,29 @@ export class Frame extends SdkObject {
|
||||||
this._subtreeLifecycleEvents = events;
|
this._subtreeLifecycleEvents = events;
|
||||||
}
|
}
|
||||||
|
|
||||||
async raceNavigationAction<T>(action: () => Promise<T>): Promise<T> {
|
async raceNavigationAction(progress: Progress, options: types.GotoOptions, action: () => Promise<network.Response | null>): Promise<network.Response | null> {
|
||||||
return Promise.race([
|
return Promise.race([
|
||||||
this._page._disconnectedPromise.then(() => { throw new Error('Navigation failed because page was closed!'); }),
|
this._page._disconnectedPromise.then(() => { throw new Error('Navigation failed because page was closed!'); }),
|
||||||
this._page._crashedPromise.then(() => { throw new Error('Navigation failed because page crashed!'); }),
|
this._page._crashedPromise.then(() => { throw new Error('Navigation failed because page crashed!'); }),
|
||||||
this._detachedPromise.then(() => { throw new Error('Navigating frame was detached!'); }),
|
this._detachedPromise.then(() => { throw new Error('Navigating frame was detached!'); }),
|
||||||
action(),
|
action().catch(e => {
|
||||||
|
if (this._pendingNavigationRedirectAfterAbort && e instanceof NavigationAbortedError) {
|
||||||
|
const { url, documentId } = this._pendingNavigationRedirectAfterAbort;
|
||||||
|
this._pendingNavigationRedirectAfterAbort = undefined;
|
||||||
|
if (e.documentId === documentId) {
|
||||||
|
progress.log(`redirecting navigation to "${url}"`);
|
||||||
|
return this._gotoAction(progress, url, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
redirectNavigationAfterAbort(url: string, documentId: string) {
|
||||||
|
this._pendingNavigationRedirectAfterAbort = { url, documentId };
|
||||||
|
}
|
||||||
|
|
||||||
async goto(metadata: CallMetadata, url: string, options: types.GotoOptions = {}): Promise<network.Response | null> {
|
async goto(metadata: CallMetadata, url: string, options: types.GotoOptions = {}): Promise<network.Response | null> {
|
||||||
const constructedNavigationURL = constructURLBasedOnBaseURL(this._page._browserContext._options.baseURL, url);
|
const constructedNavigationURL = constructURLBasedOnBaseURL(this._page._browserContext._options.baseURL, url);
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
|
|
@ -602,57 +628,59 @@ export class Frame extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _goto(progress: Progress, url: string, options: types.GotoOptions): Promise<network.Response | null> {
|
private async _goto(progress: Progress, url: string, options: types.GotoOptions): Promise<network.Response | null> {
|
||||||
return this.raceNavigationAction(async () => {
|
return this.raceNavigationAction(progress, options, async () => this._gotoAction(progress, url, options));
|
||||||
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
}
|
||||||
progress.log(`navigating to "${url}", waiting until "${waitUntil}"`);
|
|
||||||
const headers = this._page._state.extraHTTPHeaders || [];
|
private async _gotoAction(progress: Progress, url: string, options: types.GotoOptions): Promise<network.Response | null> {
|
||||||
const refererHeader = headers.find(h => h.name.toLowerCase() === 'referer');
|
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||||
let referer = refererHeader ? refererHeader.value : undefined;
|
progress.log(`navigating to "${url}", waiting until "${waitUntil}"`);
|
||||||
if (options.referer !== undefined) {
|
const headers = this._page._state.extraHTTPHeaders || [];
|
||||||
if (referer !== undefined && referer !== options.referer)
|
const refererHeader = headers.find(h => h.name.toLowerCase() === 'referer');
|
||||||
throw new Error('"referer" is already specified as extra HTTP header');
|
let referer = refererHeader ? refererHeader.value : undefined;
|
||||||
referer = options.referer;
|
if (options.referer !== undefined) {
|
||||||
|
if (referer !== undefined && referer !== options.referer)
|
||||||
|
throw new Error('"referer" is already specified as extra HTTP header');
|
||||||
|
referer = options.referer;
|
||||||
|
}
|
||||||
|
url = helper.completeUserURL(url);
|
||||||
|
|
||||||
|
const sameDocument = helper.waitForEvent(progress, this, Frame.Events.InternalNavigation, (e: NavigationEvent) => !e.newDocument);
|
||||||
|
const navigateResult = await this._page._delegate.navigateFrame(this, url, referer);
|
||||||
|
|
||||||
|
let event: NavigationEvent;
|
||||||
|
if (navigateResult.newDocumentId) {
|
||||||
|
sameDocument.dispose();
|
||||||
|
event = await helper.waitForEvent(progress, this, Frame.Events.InternalNavigation, (event: NavigationEvent) => {
|
||||||
|
// We are interested either in this specific document, or any other document that
|
||||||
|
// did commit and replaced the expected document.
|
||||||
|
return event.newDocument && (event.newDocument.documentId === navigateResult.newDocumentId || !event.error);
|
||||||
|
}).promise;
|
||||||
|
|
||||||
|
if (event.newDocument!.documentId !== navigateResult.newDocumentId) {
|
||||||
|
// This is just a sanity check. In practice, new navigation should
|
||||||
|
// cancel the previous one and report "request cancelled"-like error.
|
||||||
|
throw new Error('Navigation interrupted by another one');
|
||||||
}
|
}
|
||||||
url = helper.completeUserURL(url);
|
if (event.error)
|
||||||
|
throw event.error;
|
||||||
|
} else {
|
||||||
|
event = await sameDocument.promise;
|
||||||
|
}
|
||||||
|
|
||||||
const sameDocument = helper.waitForEvent(progress, this, Frame.Events.Navigation, (e: NavigationEvent) => !e.newDocument);
|
if (!this._subtreeLifecycleEvents.has(waitUntil))
|
||||||
const navigateResult = await this._page._delegate.navigateFrame(this, url, referer);
|
await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise;
|
||||||
|
|
||||||
let event: NavigationEvent;
|
const request = event.newDocument ? event.newDocument.request : undefined;
|
||||||
if (navigateResult.newDocumentId) {
|
const response = request ? request._finalRequest().response() : null;
|
||||||
sameDocument.dispose();
|
await this._page._doSlowMo();
|
||||||
event = await helper.waitForEvent(progress, this, Frame.Events.Navigation, (event: NavigationEvent) => {
|
return response;
|
||||||
// We are interested either in this specific document, or any other document that
|
|
||||||
// did commit and replaced the expected document.
|
|
||||||
return event.newDocument && (event.newDocument.documentId === navigateResult.newDocumentId || !event.error);
|
|
||||||
}).promise;
|
|
||||||
|
|
||||||
if (event.newDocument!.documentId !== navigateResult.newDocumentId) {
|
|
||||||
// This is just a sanity check. In practice, new navigation should
|
|
||||||
// cancel the previous one and report "request cancelled"-like error.
|
|
||||||
throw new Error('Navigation interrupted by another one');
|
|
||||||
}
|
|
||||||
if (event.error)
|
|
||||||
throw event.error;
|
|
||||||
} else {
|
|
||||||
event = await sameDocument.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._subtreeLifecycleEvents.has(waitUntil))
|
|
||||||
await helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e: types.LifecycleEvent) => e === waitUntil).promise;
|
|
||||||
|
|
||||||
const request = event.newDocument ? event.newDocument.request : undefined;
|
|
||||||
const response = request ? request._finalRequest().response() : null;
|
|
||||||
await this._page._doSlowMo();
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _waitForNavigation(progress: Progress, options: types.NavigateOptions): Promise<network.Response | null> {
|
async _waitForNavigation(progress: Progress, options: types.NavigateOptions): Promise<network.Response | null> {
|
||||||
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||||
progress.log(`waiting for navigation until "${waitUntil}"`);
|
progress.log(`waiting for navigation until "${waitUntil}"`);
|
||||||
|
|
||||||
const navigationEvent: NavigationEvent = await helper.waitForEvent(progress, this, Frame.Events.Navigation, (event: NavigationEvent) => {
|
const navigationEvent: NavigationEvent = await helper.waitForEvent(progress, this, Frame.Events.InternalNavigation, (event: NavigationEvent) => {
|
||||||
// Any failed navigation results in a rejection.
|
// Any failed navigation results in a rejection.
|
||||||
if (event.error)
|
if (event.error)
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -835,28 +863,31 @@ export class Frame extends SdkObject {
|
||||||
|
|
||||||
async setContent(metadata: CallMetadata, html: string, options: types.NavigateOptions = {}): Promise<void> {
|
async setContent(metadata: CallMetadata, html: string, options: types.NavigateOptions = {}): Promise<void> {
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
return controller.run(progress => this.raceNavigationAction(async () => {
|
return controller.run(async progress => {
|
||||||
const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil;
|
await this.raceNavigationAction(progress, options, async () => {
|
||||||
progress.log(`setting frame content, waiting until "${waitUntil}"`);
|
const waitUntil = options.waitUntil === undefined ? 'load' : options.waitUntil;
|
||||||
const tag = `--playwright--set--content--${this._id}--${++this._setContentCounter}--`;
|
progress.log(`setting frame content, waiting until "${waitUntil}"`);
|
||||||
const context = await this._utilityContext();
|
const tag = `--playwright--set--content--${this._id}--${++this._setContentCounter}--`;
|
||||||
const lifecyclePromise = new Promise((resolve, reject) => {
|
const context = await this._utilityContext();
|
||||||
this._page._frameManager._consoleMessageTags.set(tag, () => {
|
const lifecyclePromise = new Promise((resolve, reject) => {
|
||||||
// Clear lifecycle right after document.open() - see 'tag' below.
|
this._page._frameManager._consoleMessageTags.set(tag, () => {
|
||||||
this._onClearLifecycle();
|
// Clear lifecycle right after document.open() - see 'tag' below.
|
||||||
this._waitForLoadState(progress, waitUntil).then(resolve).catch(reject);
|
this._onClearLifecycle();
|
||||||
|
this._waitForLoadState(progress, waitUntil).then(resolve).catch(reject);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
const contentPromise = context.evaluate(({ html, tag }) => {
|
||||||
|
window.stop();
|
||||||
|
document.open();
|
||||||
|
console.debug(tag); // eslint-disable-line no-console
|
||||||
|
document.write(html);
|
||||||
|
document.close();
|
||||||
|
}, { html, tag });
|
||||||
|
await Promise.all([contentPromise, lifecyclePromise]);
|
||||||
|
await this._page._doSlowMo();
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
const contentPromise = context.evaluate(({ html, tag }) => {
|
}, this._page._timeoutSettings.navigationTimeout(options));
|
||||||
window.stop();
|
|
||||||
document.open();
|
|
||||||
console.debug(tag); // eslint-disable-line no-console
|
|
||||||
document.write(html);
|
|
||||||
document.close();
|
|
||||||
}, { html, tag });
|
|
||||||
await Promise.all([contentPromise, lifecyclePromise]);
|
|
||||||
await this._page._doSlowMo();
|
|
||||||
}), this._page._timeoutSettings.navigationTimeout(options));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
name(): string {
|
name(): string {
|
||||||
|
|
@ -1712,7 +1743,9 @@ class SignalBarrier {
|
||||||
if (frame.parentFrame())
|
if (frame.parentFrame())
|
||||||
return;
|
return;
|
||||||
this.retain();
|
this.retain();
|
||||||
const waiter = helper.waitForEvent(null, frame, Frame.Events.Navigation, (e: NavigationEvent) => {
|
const waiter = helper.waitForEvent(null, frame, Frame.Events.InternalNavigation, (e: NavigationEvent) => {
|
||||||
|
if (!e.isPublic)
|
||||||
|
return false;
|
||||||
if (!e.error && this._progress)
|
if (!e.error && this._progress)
|
||||||
this._progress.log(` navigated to "${frame._url}"`);
|
this._progress.log(` navigated to "${frame._url}"`);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -244,8 +244,10 @@ export class Route extends SdkObject {
|
||||||
return this._request;
|
return this._request;
|
||||||
}
|
}
|
||||||
|
|
||||||
async abort(errorCode: string = 'failed') {
|
async abort(errorCode: string = 'failed', redirectAbortedNavigationToUrl?: string) {
|
||||||
this._startHandling();
|
this._startHandling();
|
||||||
|
if (redirectAbortedNavigationToUrl)
|
||||||
|
this._request.frame().redirectNavigationAfterAbort(redirectAbortedNavigationToUrl, this._request._documentId!);
|
||||||
await this._delegate.abort(errorCode);
|
await this._delegate.abort(errorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -348,7 +348,7 @@ export class Page extends SdkObject {
|
||||||
|
|
||||||
async reload(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> {
|
async reload(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> {
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
return controller.run(progress => this.mainFrame().raceNavigationAction(async () => {
|
return controller.run(progress => this.mainFrame().raceNavigationAction(progress, options, async () => {
|
||||||
// Note: waitForNavigation may fail before we get response to reload(),
|
// Note: waitForNavigation may fail before we get response to reload(),
|
||||||
// so we should await it immediately.
|
// so we should await it immediately.
|
||||||
const [response] = await Promise.all([
|
const [response] = await Promise.all([
|
||||||
|
|
@ -362,7 +362,7 @@ export class Page extends SdkObject {
|
||||||
|
|
||||||
async goBack(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> {
|
async goBack(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> {
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
return controller.run(progress => this.mainFrame().raceNavigationAction(async () => {
|
return controller.run(progress => this.mainFrame().raceNavigationAction(progress, options, async () => {
|
||||||
// Note: waitForNavigation may fail before we get response to goBack,
|
// Note: waitForNavigation may fail before we get response to goBack,
|
||||||
// so we should catch it immediately.
|
// so we should catch it immediately.
|
||||||
let error: Error | undefined;
|
let error: Error | undefined;
|
||||||
|
|
@ -383,7 +383,7 @@ export class Page extends SdkObject {
|
||||||
|
|
||||||
async goForward(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> {
|
async goForward(metadata: CallMetadata, options: types.NavigateOptions): Promise<network.Response | null> {
|
||||||
const controller = new ProgressController(metadata, this);
|
const controller = new ProgressController(metadata, this);
|
||||||
return controller.run(progress => this.mainFrame().raceNavigationAction(async () => {
|
return controller.run(progress => this.mainFrame().raceNavigationAction(progress, options, async () => {
|
||||||
// Note: waitForNavigation may fail before we get response to goForward,
|
// Note: waitForNavigation may fail before we get response to goForward,
|
||||||
// so we should catch it immediately.
|
// so we should catch it immediately.
|
||||||
let error: Error | undefined;
|
let error: Error | undefined;
|
||||||
|
|
|
||||||
|
|
@ -394,7 +394,10 @@ class ContextRecorder extends EventEmitter {
|
||||||
});
|
});
|
||||||
this._pageAliases.delete(page);
|
this._pageAliases.delete(page);
|
||||||
});
|
});
|
||||||
frame.on(Frame.Events.Navigation, () => this._onFrameNavigated(frame, page));
|
frame.on(Frame.Events.InternalNavigation, event => {
|
||||||
|
if (event.isPublic)
|
||||||
|
this._onFrameNavigated(frame, page);
|
||||||
|
});
|
||||||
page.on(Page.Events.Download, () => this._onDownload(page));
|
page.on(Page.Events.Download, () => this._onDownload(page));
|
||||||
page.on(Page.Events.Dialog, () => this._onDialog(page));
|
page.on(Page.Events.Dialog, () => this._onDialog(page));
|
||||||
const suffix = this._pageAliases.size ? String(++this._lastPopupOrdinal) : '';
|
const suffix = this._pageAliases.size ? String(++this._lastPopupOrdinal) : '';
|
||||||
|
|
|
||||||
623
tests/assets/har-redirect.har
Normal file
623
tests/assets/har-redirect.har
Normal file
|
|
@ -0,0 +1,623 @@
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"version": "1.2",
|
||||||
|
"creator": {
|
||||||
|
"name": "Playwright",
|
||||||
|
"version": "1.23.0-next"
|
||||||
|
},
|
||||||
|
"browser": {
|
||||||
|
"name": "chromium",
|
||||||
|
"version": "103.0.5060.42"
|
||||||
|
},
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"startedDateTime": "2022-06-16T21:41:23.901Z",
|
||||||
|
"id": "page@8f314969edc000996eb5c2ab22f0e6b3",
|
||||||
|
"title": "Microsoft",
|
||||||
|
"pageTimings": {
|
||||||
|
"onContentLoad": 8363,
|
||||||
|
"onLoad": 8896
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"_requestref": "request@7d6e0ddb1e1e25f6e5c4a7c943c0bae1",
|
||||||
|
"_frameref": "frame@3767e074ecde4cb8372abba2f6f9bb4f",
|
||||||
|
"_monotonicTime": 110928357.437,
|
||||||
|
"startedDateTime": "2022-06-16T21:41:23.951Z",
|
||||||
|
"time": 93.99,
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "https://theverge.com/",
|
||||||
|
"httpVersion": "HTTP/2.0",
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": ":authority",
|
||||||
|
"value": "theverge.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ":method",
|
||||||
|
"value": "GET"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ":path",
|
||||||
|
"value": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ":scheme",
|
||||||
|
"value": "https"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "accept",
|
||||||
|
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "accept-encoding",
|
||||||
|
"value": "gzip, deflate, br"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "accept-language",
|
||||||
|
"value": "en-US,en;q=0.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-ch-ua",
|
||||||
|
"value": "\"Chromium\";v=\"103\", \".Not/A)Brand\";v=\"99\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-ch-ua-mobile",
|
||||||
|
"value": "?0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-ch-ua-platform",
|
||||||
|
"value": "\"Linux\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-fetch-dest",
|
||||||
|
"value": "document"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-fetch-mode",
|
||||||
|
"value": "navigate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-fetch-site",
|
||||||
|
"value": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-fetch-user",
|
||||||
|
"value": "?1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "upgrade-insecure-requests",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user-agent",
|
||||||
|
"value": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.42 Safari/537.36"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryString": [],
|
||||||
|
"headersSize": 644,
|
||||||
|
"bodySize": 0
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 301,
|
||||||
|
"statusText": "",
|
||||||
|
"httpVersion": "HTTP/2.0",
|
||||||
|
"cookies": [
|
||||||
|
{
|
||||||
|
"name": "vmidv1",
|
||||||
|
"value": "9faf31ab-1415-4b90-b367-24b670205f41",
|
||||||
|
"expires": "2027-06-15T21:41:24.000Z",
|
||||||
|
"domain": "theverge.com",
|
||||||
|
"path": "/",
|
||||||
|
"sameSite": "Lax",
|
||||||
|
"secure": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "accept-ranges",
|
||||||
|
"value": "bytes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "date",
|
||||||
|
"value": "Thu, 16 Jun 2022 21:41:24 GMT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "location",
|
||||||
|
"value": "http://www.theverge.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "retry-after",
|
||||||
|
"value": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"value": "Varnish"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "set-cookie",
|
||||||
|
"value": "vmidv1=9faf31ab-1415-4b90-b367-24b670205f41;Expires=Tue, 15 Jun 2027 21:41:24 GMT;Domain=theverge.com;Path=/;SameSite=Lax;Secure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "via",
|
||||||
|
"value": "1.1 varnish"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cache",
|
||||||
|
"value": "HIT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cache-hits",
|
||||||
|
"value": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-served-by",
|
||||||
|
"value": "cache-pao17442-PAO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-timer",
|
||||||
|
"value": "S1655415684.005867,VS0,VE0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"content": {
|
||||||
|
"size": -1,
|
||||||
|
"mimeType": "x-unknown",
|
||||||
|
"compression": 0
|
||||||
|
},
|
||||||
|
"headersSize": 425,
|
||||||
|
"bodySize": 0,
|
||||||
|
"redirectURL": "http://www.theverge.com/",
|
||||||
|
"_transferSize": 425
|
||||||
|
},
|
||||||
|
"cache": {
|
||||||
|
"beforeRequest": null,
|
||||||
|
"afterRequest": null
|
||||||
|
},
|
||||||
|
"timings": {
|
||||||
|
"dns": 0,
|
||||||
|
"connect": 34.151,
|
||||||
|
"ssl": 28.074,
|
||||||
|
"send": 0,
|
||||||
|
"wait": 27.549,
|
||||||
|
"receive": 4.216
|
||||||
|
},
|
||||||
|
"pageref": "page@8f314969edc000996eb5c2ab22f0e6b3",
|
||||||
|
"serverIPAddress": "151.101.65.52",
|
||||||
|
"_serverPort": 443,
|
||||||
|
"_securityDetails": {
|
||||||
|
"protocol": "TLS 1.2",
|
||||||
|
"subjectName": "*.americanninjawarriornation.com",
|
||||||
|
"issuer": "GlobalSign Atlas R3 DV TLS CA 2022 Q1",
|
||||||
|
"validFrom": 1644853133,
|
||||||
|
"validTo": 1679153932
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_requestref": "request@5c7a316ee46a095bda80c23ddc8c740d",
|
||||||
|
"_frameref": "frame@3767e074ecde4cb8372abba2f6f9bb4f",
|
||||||
|
"_monotonicTime": 110928427.603,
|
||||||
|
"startedDateTime": "2022-06-16T21:41:24.022Z",
|
||||||
|
"time": 44.39499999999999,
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "http://www.theverge.com/",
|
||||||
|
"httpVersion": "HTTP/1.1",
|
||||||
|
"cookies": [],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "Accept",
|
||||||
|
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Accept-Encoding",
|
||||||
|
"value": "gzip, deflate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Accept-Language",
|
||||||
|
"value": "en-US,en;q=0.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Connection",
|
||||||
|
"value": "keep-alive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Host",
|
||||||
|
"value": "www.theverge.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Upgrade-Insecure-Requests",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "User-Agent",
|
||||||
|
"value": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.42 Safari/537.36"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryString": [],
|
||||||
|
"headersSize": 423,
|
||||||
|
"bodySize": 0
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 301,
|
||||||
|
"statusText": "Moved Permanently",
|
||||||
|
"httpVersion": "HTTP/1.1",
|
||||||
|
"cookies": [
|
||||||
|
{
|
||||||
|
"name": "_chorus_geoip_continent",
|
||||||
|
"value": "NA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vmidv1",
|
||||||
|
"value": "4e0c1265-10f8-4cb1-a5de-1c3cf70b531c",
|
||||||
|
"expires": "2027-06-15T21:41:24.000Z",
|
||||||
|
"domain": "www.theverge.com",
|
||||||
|
"path": "/",
|
||||||
|
"sameSite": "Lax",
|
||||||
|
"secure": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "Accept-Ranges",
|
||||||
|
"value": "bytes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Age",
|
||||||
|
"value": "2615"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Connection",
|
||||||
|
"value": "keep-alive"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Content-Length",
|
||||||
|
"value": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Content-Type",
|
||||||
|
"value": "text/html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Date",
|
||||||
|
"value": "Thu, 16 Jun 2022 21:41:24 GMT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Location",
|
||||||
|
"value": "https://www.theverge.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Server",
|
||||||
|
"value": "nginx"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Set-Cookie",
|
||||||
|
"value": "_chorus_geoip_continent=NA; expires=Fri, 17 Jun 2022 21:41:24 GMT; path=/;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Set-Cookie",
|
||||||
|
"value": "vmidv1=4e0c1265-10f8-4cb1-a5de-1c3cf70b531c;Expires=Tue, 15 Jun 2027 21:41:24 GMT;Domain=www.theverge.com;Path=/;SameSite=Lax;Secure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vary",
|
||||||
|
"value": "X-Forwarded-Proto, Cookie, X-Chorus-Unison-Testing, X-Chorus-Require-Privacy-Consent, X-Chorus-Restrict-In-Privacy-Consent-Region, Accept-Encoding"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Via",
|
||||||
|
"value": "1.1 varnish"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "X-Cache",
|
||||||
|
"value": "HIT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "X-Cache-Hits",
|
||||||
|
"value": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "X-Served-By",
|
||||||
|
"value": "cache-pao17450-PAO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "X-Timer",
|
||||||
|
"value": "S1655415684.035748,VS0,VE0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"content": {
|
||||||
|
"size": -1,
|
||||||
|
"mimeType": "text/html",
|
||||||
|
"compression": 0
|
||||||
|
},
|
||||||
|
"headersSize": 731,
|
||||||
|
"bodySize": 0,
|
||||||
|
"redirectURL": "https://www.theverge.com/",
|
||||||
|
"_transferSize": 731
|
||||||
|
},
|
||||||
|
"cache": {
|
||||||
|
"beforeRequest": null,
|
||||||
|
"afterRequest": null
|
||||||
|
},
|
||||||
|
"timings": {
|
||||||
|
"dns": 2.742,
|
||||||
|
"connect": 10.03,
|
||||||
|
"ssl": 14.123,
|
||||||
|
"send": 0,
|
||||||
|
"wait": 15.023,
|
||||||
|
"receive": 2.477
|
||||||
|
},
|
||||||
|
"pageref": "page@8f314969edc000996eb5c2ab22f0e6b3",
|
||||||
|
"serverIPAddress": "151.101.189.52",
|
||||||
|
"_serverPort": 80,
|
||||||
|
"_securityDetails": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_requestref": "request@17664a6093c12c97d41efbff3a502adb",
|
||||||
|
"_frameref": "frame@3767e074ecde4cb8372abba2f6f9bb4f",
|
||||||
|
"_monotonicTime": 110928455.901,
|
||||||
|
"startedDateTime": "2022-06-16T21:41:24.050Z",
|
||||||
|
"time": 50.29199999999999,
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "https://www.theverge.com/",
|
||||||
|
"httpVersion": "HTTP/2.0",
|
||||||
|
"cookies": [
|
||||||
|
{
|
||||||
|
"name": "vmidv1",
|
||||||
|
"value": "9faf31ab-1415-4b90-b367-24b670205f41"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "_chorus_geoip_continent",
|
||||||
|
"value": "NA"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": ":authority",
|
||||||
|
"value": "www.theverge.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ":method",
|
||||||
|
"value": "GET"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ":path",
|
||||||
|
"value": "/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ":scheme",
|
||||||
|
"value": "https"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "accept",
|
||||||
|
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "accept-encoding",
|
||||||
|
"value": "gzip, deflate, br"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "accept-language",
|
||||||
|
"value": "en-US,en;q=0.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cookie",
|
||||||
|
"value": "vmidv1=9faf31ab-1415-4b90-b367-24b670205f41; _chorus_geoip_continent=NA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-ch-ua",
|
||||||
|
"value": "\"Chromium\";v=\"103\", \".Not/A)Brand\";v=\"99\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-ch-ua-mobile",
|
||||||
|
"value": "?0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-ch-ua-platform",
|
||||||
|
"value": "\"Linux\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-fetch-dest",
|
||||||
|
"value": "document"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-fetch-mode",
|
||||||
|
"value": "navigate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-fetch-site",
|
||||||
|
"value": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sec-fetch-user",
|
||||||
|
"value": "?1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "upgrade-insecure-requests",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user-agent",
|
||||||
|
"value": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.42 Safari/537.36"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"queryString": [],
|
||||||
|
"headersSize": 729,
|
||||||
|
"bodySize": 0
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"status": 200,
|
||||||
|
"statusText": "",
|
||||||
|
"httpVersion": "HTTP/2.0",
|
||||||
|
"cookies": [
|
||||||
|
{
|
||||||
|
"name": "_chorus_geoip_continent",
|
||||||
|
"value": "NA"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vmidv1",
|
||||||
|
"value": "40d8fd14-5ac3-4757-9e9c-efb106e82d3a",
|
||||||
|
"expires": "2027-06-15T21:41:24.000Z",
|
||||||
|
"domain": "www.theverge.com",
|
||||||
|
"path": "/",
|
||||||
|
"sameSite": "Lax",
|
||||||
|
"secure": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "accept-ranges",
|
||||||
|
"value": "bytes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "age",
|
||||||
|
"value": "263"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cache-control",
|
||||||
|
"value": "max-age=0, public, must-revalidate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-encoding",
|
||||||
|
"value": "br"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-length",
|
||||||
|
"value": "14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-security-policy",
|
||||||
|
"value": "default-src https: data: 'unsafe-inline' 'unsafe-eval'; child-src https: data: blob:; connect-src https: data: blob: ; font-src https: data:; img-src https: data: blob:; media-src https: data: blob:; object-src https:; script-src https: data: blob: 'unsafe-inline' 'unsafe-eval'; style-src https: 'unsafe-inline'; block-all-mixed-content; upgrade-insecure-requests"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "text/html; charset=utf-8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "date",
|
||||||
|
"value": "Thu, 16 Jun 2022 21:41:24 GMT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "etag",
|
||||||
|
"value": "W/\"d498ef668223d015000070a66a181e85\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "link",
|
||||||
|
"value": "<https://concertads-configs.vox-cdn.com/sbn/verge/config.json>; rel=preload; as=fetch; crossorigin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "referrer-policy",
|
||||||
|
"value": "strict-origin-when-cross-origin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"value": "nginx"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "set-cookie",
|
||||||
|
"value": "_chorus_geoip_continent=NA; expires=Fri, 17 Jun 2022 21:41:24 GMT; path=/;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "set-cookie",
|
||||||
|
"value": "vmidv1=40d8fd14-5ac3-4757-9e9c-efb106e82d3a;Expires=Tue, 15 Jun 2027 21:41:24 GMT;Domain=www.theverge.com;Path=/;SameSite=Lax;Secure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "strict-transport-security",
|
||||||
|
"value": "max-age=31556952; preload"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vary",
|
||||||
|
"value": "Accept-Encoding, X-Chorus-Unison-Testing, X-Chorus-Require-Privacy-Consent, X-Chorus-Restrict-In-Privacy-Consent-Region, Origin, X-Forwarded-Proto, Cookie, X-Chorus-Unison-Testing, X-Chorus-Require-Privacy-Consent, X-Chorus-Restrict-In-Privacy-Consent-Region"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "via",
|
||||||
|
"value": "1.1 varnish"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cache",
|
||||||
|
"value": "HIT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-cache-hits",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-content-type-options",
|
||||||
|
"value": "nosniff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-download-options",
|
||||||
|
"value": "noopen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-frame-options",
|
||||||
|
"value": "SAMEORIGIN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-permitted-cross-domain-policies",
|
||||||
|
"value": "none"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-request-id",
|
||||||
|
"value": "97363ad70e272e63641c0bb784fa06a01b848dfd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-runtime",
|
||||||
|
"value": "0.257911"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-served-by",
|
||||||
|
"value": "cache-pao17436-PAO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-timer",
|
||||||
|
"value": "S1655415684.075077,VS0,VE1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-xss-protection",
|
||||||
|
"value": "1; mode=block"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"content": {
|
||||||
|
"size": 14,
|
||||||
|
"mimeType": "text/html",
|
||||||
|
"compression": 0,
|
||||||
|
"text": "<h1>hello</h1>"
|
||||||
|
},
|
||||||
|
"headersSize": 1742,
|
||||||
|
"bodySize": 48716,
|
||||||
|
"redirectURL": "",
|
||||||
|
"_transferSize": 48716
|
||||||
|
},
|
||||||
|
"cache": {
|
||||||
|
"beforeRequest": null,
|
||||||
|
"afterRequest": null
|
||||||
|
},
|
||||||
|
"timings": {
|
||||||
|
"dns": 0.016,
|
||||||
|
"connect": 24.487,
|
||||||
|
"ssl": 17.406,
|
||||||
|
"send": 0,
|
||||||
|
"wait": 8.383,
|
||||||
|
"receive": -1
|
||||||
|
},
|
||||||
|
"pageref": "page@8f314969edc000996eb5c2ab22f0e6b3",
|
||||||
|
"serverIPAddress": "151.101.189.52",
|
||||||
|
"_serverPort": 443,
|
||||||
|
"_securityDetails": {
|
||||||
|
"protocol": "TLS 1.2",
|
||||||
|
"subjectName": "*.americanninjawarriornation.com",
|
||||||
|
"issuer": "GlobalSign Atlas R3 DV TLS CA 2022 Q1",
|
||||||
|
"validFrom": 1644853133,
|
||||||
|
"validTo": 1679153932
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -104,3 +104,65 @@ it('newPage should fulfill from har, matching the method and following redirects
|
||||||
await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 0, 0)');
|
await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 0, 0)');
|
||||||
await page.close();
|
await page.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should change document URL after redirected navigation', async ({ contextFactory, isAndroid, asset }) => {
|
||||||
|
it.fixme(isAndroid);
|
||||||
|
|
||||||
|
const path = asset('har-redirect.har');
|
||||||
|
const context = await contextFactory({ har: { path } });
|
||||||
|
const page = await context.newPage();
|
||||||
|
const [response] = await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.goto('https://theverge.com/')
|
||||||
|
]);
|
||||||
|
await expect(page).toHaveURL('https://www.theverge.com/');
|
||||||
|
await expect(response.request().url()).toBe('https://www.theverge.com/');
|
||||||
|
expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should goBack to redirected navigation', async ({ contextFactory, isAndroid, asset, server }) => {
|
||||||
|
it.fixme(isAndroid);
|
||||||
|
|
||||||
|
const path = asset('har-redirect.har');
|
||||||
|
const context = await contextFactory({ har: { path, urlFilter: /.*theverge.*/ } });
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto('https://theverge.com/');
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
await expect(page).toHaveURL(server.EMPTY_PAGE);
|
||||||
|
const response = await page.goBack();
|
||||||
|
await expect(page).toHaveURL('https://www.theverge.com/');
|
||||||
|
await expect(response.request().url()).toBe('https://www.theverge.com/');
|
||||||
|
expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should goForward to redirected navigation', async ({ contextFactory, isAndroid, asset, server }) => {
|
||||||
|
it.fixme(isAndroid);
|
||||||
|
|
||||||
|
const path = asset('har-redirect.har');
|
||||||
|
const context = await contextFactory({ har: { path, urlFilter: /.*theverge.*/ } });
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
await expect(page).toHaveURL(server.EMPTY_PAGE);
|
||||||
|
await page.goto('https://theverge.com/');
|
||||||
|
await expect(page).toHaveURL('https://www.theverge.com/');
|
||||||
|
await page.goBack();
|
||||||
|
await expect(page).toHaveURL(server.EMPTY_PAGE);
|
||||||
|
const response = await page.goForward();
|
||||||
|
await expect(page).toHaveURL('https://www.theverge.com/');
|
||||||
|
await expect(response.request().url()).toBe('https://www.theverge.com/');
|
||||||
|
expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reload redirected navigation', async ({ contextFactory, isAndroid, asset, server }) => {
|
||||||
|
it.fixme(isAndroid);
|
||||||
|
|
||||||
|
const path = asset('har-redirect.har');
|
||||||
|
const context = await contextFactory({ har: { path, urlFilter: /.*theverge.*/ } });
|
||||||
|
const page = await context.newPage();
|
||||||
|
await page.goto('https://theverge.com/');
|
||||||
|
await expect(page).toHaveURL('https://www.theverge.com/');
|
||||||
|
const response = await page.reload();
|
||||||
|
await expect(page).toHaveURL('https://www.theverge.com/');
|
||||||
|
await expect(response.request().url()).toBe('https://www.theverge.com/');
|
||||||
|
expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/');
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue