-
Notifications
You must be signed in to change notification settings - Fork 6k
feat(routeFromHar): add interceptAPIRequests option #41294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
9586308
e0fc0be
c2281e0
ab2893c
9fab9ca
f5c0b22
7a74db2
aed8a6d
08a1035
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,11 +39,13 @@ import type { Browser, BrowserOptions } from './browser'; | |
| import type { ConsoleMessage } from './console'; | ||
| import type { Download } from './download'; | ||
| import type * as frames from './frames'; | ||
| import type { HarBackend } from './harBackend'; | ||
| import type { PageError } from './page'; | ||
| import type { Progress } from './progress'; | ||
| import type { ClientCertificatesProxy } from './socksClientCertificatesInterceptor'; | ||
| import type { SerializedStorage } from '@injected/storageScript'; | ||
| import type * as types from './types'; | ||
| import type { URLMatch } from '@isomorphic/urlMatch'; | ||
| import type * as channels from './channels'; | ||
|
|
||
| const BrowserContextEvent = { | ||
|
|
@@ -120,6 +122,7 @@ export abstract class BrowserContext<EM extends EventMap = EventMap> extends Sdk | |
| private _playwrightBindingExposed?: Promise<void>; | ||
| readonly dialogManager: DialogManager; | ||
| private _consoleApiExposed = false; | ||
| private _harForAPIRequests: HarForAPIRequestsRegistration[] = []; | ||
|
|
||
| constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) { | ||
| super(browser, 'browser-context'); | ||
|
|
@@ -738,8 +741,36 @@ export abstract class BrowserContext<EM extends EventMap = EventMap> extends Sdk | |
| async notifyRoutesInFlightAboutRemovedHandler(handler: network.RouteHandler): Promise<void> { | ||
| await Promise.all([...this._routesInFlight].map(route => route.removeHandler(handler))); | ||
| } | ||
|
|
||
| addHarForAPIRequests(options: { harBackend: HarBackend, urlMatch: URLMatch | undefined, notFound: 'abort' | 'fallback', baseURL: string | undefined }): { dispose: () => void } { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT: i think this needs a more clear name like
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed to |
||
| const registration: HarForAPIRequestsRegistration = { | ||
| harBackend: options.harBackend, | ||
| urlMatch: options.urlMatch, | ||
| notFound: options.notFound, | ||
| baseURL: options.baseURL, | ||
| }; | ||
| this._harForAPIRequests.push(registration); | ||
|
stkevintan marked this conversation as resolved.
Outdated
|
||
| return { | ||
| dispose: () => { | ||
| const index = this._harForAPIRequests.indexOf(registration); | ||
| if (index !== -1) | ||
| this._harForAPIRequests.splice(index, 1); | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| harForAPIRequests(): readonly HarForAPIRequestsRegistration[] { | ||
| return this._harForAPIRequests; | ||
| } | ||
| } | ||
|
|
||
| export type HarForAPIRequestsRegistration = { | ||
| harBackend: HarBackend; | ||
| urlMatch: URLMatch | undefined; | ||
| notFound: 'abort' | 'fallback'; | ||
| baseURL: string | undefined; | ||
| }; | ||
|
|
||
| export function validateBrowserContextOptions(options: types.BrowserContextOptions, browserOptions: BrowserOptions) { | ||
| if (options.noDefaultViewport && options.deviceScaleFactor !== undefined) | ||
| throw new Error(`"deviceScaleFactor" option is not supported with null "viewport"`); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ import * as zlib from 'zlib'; | |
| import { createGuid } from '@utils/crypto'; | ||
| import { httpHappyEyeballsAgent, httpsHappyEyeballsAgent, timingForSocket } from '@utils/happyEyeballs'; | ||
| import { assert } from '@isomorphic/assert'; | ||
| import { constructURLBasedOnBaseURL } from '@isomorphic/urlMatch'; | ||
| import { constructURLBasedOnBaseURL, urlMatches } from '@isomorphic/urlMatch'; | ||
| import { eventsHelper } from '@utils/eventsHelper'; | ||
| import { monotonicTime } from '@isomorphic/time'; | ||
| import { createProxyAgent } from '@utils/network'; | ||
|
|
@@ -150,6 +150,10 @@ export abstract class APIRequestContext extends SdkObject { | |
| abstract addCookies(cookies: channels.NetworkCookie[]): Promise<void>; | ||
| abstract cookies(progress: Progress, url: URL): Promise<channels.NetworkCookie[]>; | ||
|
|
||
| protected async _lookupInHar(progress: Progress, url: URL, method: string, headers: HeadersObject, postData: Buffer | undefined): Promise<SendRequestResult | undefined> { | ||
| return undefined; | ||
| } | ||
|
|
||
| protected _disposeImpl() { | ||
| this._disposed = true; | ||
| APIRequestContext.allInstances.delete(this); | ||
|
|
@@ -225,7 +229,14 @@ export abstract class APIRequestContext extends SdkObject { | |
| const postData = serializePostData(params, headers); | ||
| if (postData) | ||
| setHeader(headers, 'content-length', String(postData.byteLength)); | ||
| const { body, log, response } = await this._sendRequestWithRetries(progress, requestUrl, options, postData, params.maxRetries); | ||
| const harResponse = await this._lookupInHar(progress, requestUrl, method, headers, postData); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think this will prevent the dispatch of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. The short-circuit bypassed |
||
| let body: Buffer; | ||
| let log: string[]; | ||
| let response: Omit<channels.APIResponse, 'fetchUid'>; | ||
| if (harResponse) | ||
| ({ body, log, response } = harResponse); | ||
| else | ||
| ({ body, log, response } = await this._sendRequestWithRetries(progress, requestUrl, options, postData, params.maxRetries)); | ||
|
stkevintan marked this conversation as resolved.
Outdated
|
||
| const failOnStatusCode = params.failOnStatusCode !== undefined ? params.failOnStatusCode : !!defaults.failOnStatusCode; | ||
| if (failOnStatusCode && (response.status < 200 || response.status >= 400)) { | ||
| let responseText = ''; | ||
|
|
@@ -682,6 +693,47 @@ export class BrowserContextAPIRequestContext extends APIRequestContext { | |
| override async storageState(progress: Progress, indexedDB?: boolean): Promise<channels.APIRequestContextStorageStateResult> { | ||
| return this._context.storageState(progress, indexedDB); | ||
| } | ||
|
|
||
| protected override async _lookupInHar(progress: Progress, url: URL, method: string, headers: HeadersObject, postData: Buffer | undefined): Promise<SendRequestResult | undefined> { | ||
| const registrations = this._context.harForAPIRequests(); | ||
| if (!registrations.length) | ||
| return undefined; | ||
| const urlString = url.toString(); | ||
| const log: string[] = []; | ||
| log.push(`→ ${method} ${urlString}`); | ||
|
stkevintan marked this conversation as resolved.
Outdated
|
||
| const headersArray: HeadersArray = Object.entries(headers).map(([name, value]) => ({ name, value })); | ||
| for (const registration of registrations) { | ||
|
stkevintan marked this conversation as resolved.
Outdated
|
||
| if (!urlMatches(registration.baseURL, urlString, registration.urlMatch)) | ||
| continue; | ||
| const lookupResult = await progress.race(registration.harBackend.lookup(urlString, method, headersArray, postData, false, { apiRequestOnly: true })); | ||
|
stkevintan marked this conversation as resolved.
Outdated
|
||
| if (lookupResult.action === 'error') { | ||
| log.push(`HAR: ${lookupResult.message ?? 'lookup failed'}`); | ||
| continue; | ||
| } | ||
| if (lookupResult.action === 'noentry') { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kept |
||
| if (registration.notFound === 'abort') | ||
| throw new Error(`Request "${method} ${urlString}" was not found in the HAR file`); | ||
|
stkevintan marked this conversation as resolved.
|
||
| continue; | ||
| } | ||
| if (lookupResult.action === 'redirect') { | ||
| // Not expected for non-navigation API requests, but treat as fulfill miss. | ||
| log.push(`HAR: ignoring redirect entry for ${urlString}`); | ||
| continue; | ||
| } | ||
| log.push(`← ${lookupResult.status ?? 0} (from HAR)`); | ||
| return { | ||
| body: lookupResult.body ?? Buffer.from(''), | ||
| log, | ||
| response: { | ||
|
stkevintan marked this conversation as resolved.
|
||
| url: urlString, | ||
|
stkevintan marked this conversation as resolved.
Outdated
|
||
| status: lookupResult.status ?? 0, | ||
| statusText: '', | ||
|
stkevintan marked this conversation as resolved.
Outdated
|
||
| headers: lookupResult.headers ?? [], | ||
| }, | ||
| }; | ||
|
stkevintan marked this conversation as resolved.
|
||
| } | ||
| return undefined; | ||
| } | ||
| } | ||
|
|
||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.