implement first pass

This commit is contained in:
Simon Knott 2025-02-04 11:27:16 +01:00
parent 8712024f3a
commit aa2aef146d
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
7 changed files with 32 additions and 17 deletions

View file

@ -70,7 +70,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
_closeWasCalled = false; _closeWasCalled = false;
private _closeReason: string | undefined; private _closeReason: string | undefined;
private _harRouters: HarRouter[] = []; private _harRouters: HarRouter[] = [];
private _registeredListeners: RegisteredListener[] = [];
_mockingProxy?: MockingProxy; _mockingProxy?: MockingProxy;
static from(context: channels.BrowserContextChannel): BrowserContext { static from(context: channels.BrowserContextChannel): BrowserContext {
@ -96,7 +95,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
this._channel.on('page', ({ page }) => this._onPage(Page.from(page))); this._channel.on('page', ({ page }) => this._onPage(Page.from(page)));
this._channel.on('route', params => { this._channel.on('route', params => {
const route = network.Route.from(params.route); const route = network.Route.from(params.route);
route._context = this.request; route._apiRequestContext = this.request;
this._onRoute(route); this._onRoute(route);
}); });
this._channel.on('webSocketRoute', ({ webSocketRoute }) => this._onWebSocketRoute(network.WebSocketRoute.from(webSocketRoute))); this._channel.on('webSocketRoute', ({ webSocketRoute }) => this._onWebSocketRoute(network.WebSocketRoute.from(webSocketRoute)));
@ -467,7 +466,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
this._disposeHarRouters(); this._disposeHarRouters();
this.tracing._resetStackCounter(); this.tracing._resetStackCounter();
this.emit(Events.BrowserContext.Close, this); this.emit(Events.BrowserContext.Close, this);
eventsHelper.removeEventListeners(this._registeredListeners);
} }
async [Symbol.asyncDispose]() { async [Symbol.asyncDispose]() {

View file

@ -19,6 +19,7 @@ import { ChannelOwner } from './channelOwner';
import { APIRequestContext } from './fetch'; import { APIRequestContext } from './fetch';
import { assert } from '../utils'; import { assert } from '../utils';
import type { Page } from './page'; import type { Page } from './page';
import { Events } from './events';
export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> { export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> {
private _pages = new Map<string, Page>(); private _pages = new Map<string, Page>();
@ -29,8 +30,8 @@ export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> {
const requestContext = APIRequestContext.from(initializer.requestContext); const requestContext = APIRequestContext.from(initializer.requestContext);
this._channel.on('route', async (params: channels.MockingProxyRouteEvent) => { this._channel.on('route', async (params: channels.MockingProxyRouteEvent) => {
const route = network.Route.from(params.route); const route = network.Route.from(params.route);
route._context = requestContext; route._apiRequestContext = requestContext;
const page = route.request()._safePage()!; const page = route.request()._pageForMockingProxy!;
await page._onRoute(route); await page._onRoute(route);
}); });
@ -44,7 +45,7 @@ export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> {
this._channel.on('requestFailed', async (params: channels.MockingProxyRequestFailedEvent) => { this._channel.on('requestFailed', async (params: channels.MockingProxyRequestFailedEvent) => {
const request = network.Request.from(params.request); const request = network.Request.from(params.request);
const page = request._safePage()!; const page = request._pageForMockingProxy!;
page.context()._onRequestFailed(request, params.responseEndTiming, params.failureText, page); page.context()._onRequestFailed(request, params.responseEndTiming, params.failureText, page);
}); });
@ -52,20 +53,27 @@ export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> {
const { responseEndTiming } = params; const { responseEndTiming } = params;
const request = network.Request.from(params.request); const request = network.Request.from(params.request);
const response = network.Response.fromNullable(params.response); const response = network.Response.fromNullable(params.response);
const page = request._safePage()!; const page = request._pageForMockingProxy!;
page.context()._onRequestFinished(request, response, page, responseEndTiming); page.context()._onRequestFinished(request, response, page, responseEndTiming);
}); });
this._channel.on('response', async (params: channels.MockingProxyResponseEvent) => { this._channel.on('response', async (params: channels.MockingProxyResponseEvent) => {
const response = network.Response.from(params.response); const response = network.Response.from(params.response);
const page = response.request()._safePage()!; const page = response.request()._pageForMockingProxy!;
page.context()._onResponse(response, page); page.context()._onResponse(response, page);
}); });
} }
static from(channel: channels.MockingProxyChannel): MockingProxy {
return (channel as any)._object;
}
async instrumentPage(page: Page) { async instrumentPage(page: Page) {
const correlation = page._guid.split('@')[1]; const correlation = page._guid.split('@')[1];
this._pages.set(correlation, page); this._pages.set(correlation, page);
page.on(Events.Page.Close, () => {
this._pages.delete(correlation);
});
const proxyUrl = `http://localhost:${this._initializer.port}/pw_meta:${correlation}/`; const proxyUrl = `http://localhost:${this._initializer.port}/pw_meta:${correlation}/`;
await page.setExtraHTTPHeaders({ await page.setExtraHTTPHeaders({
'x-playwright-proxy': encodeURIComponent(proxyUrl) 'x-playwright-proxy': encodeURIComponent(proxyUrl)

View file

@ -219,7 +219,7 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap
} }
_safePage(): Page | null { _safePage(): Page | null {
return this._pageForMockingProxy ?? Frame.fromNullable(this._initializer.frame)?._page ?? null; return Frame.fromNullable(this._initializer.frame)?._page ?? null;
} }
serviceWorker(): Worker | null { serviceWorker(): Worker | null {
@ -294,7 +294,7 @@ export class Request extends ChannelOwner<channels.RequestChannel> implements ap
export class Route extends ChannelOwner<channels.RouteChannel> implements api.Route { export class Route extends ChannelOwner<channels.RouteChannel> implements api.Route {
private _handlingPromise: ManualPromise<boolean> | null = null; private _handlingPromise: ManualPromise<boolean> | null = null;
_context!: APIRequestContext; _apiRequestContext!: APIRequestContext;
_didThrow: boolean = false; _didThrow: boolean = false;
static from(route: channels.RouteChannel): Route { static from(route: channels.RouteChannel): Route {
@ -342,7 +342,7 @@ export class Route extends ChannelOwner<channels.RouteChannel> implements api.Ro
async fetch(options: FallbackOverrides & { maxRedirects?: number, maxRetries?: number, timeout?: number } = {}): Promise<APIResponse> { async fetch(options: FallbackOverrides & { maxRedirects?: number, maxRetries?: number, timeout?: number } = {}): Promise<APIResponse> {
return await this._wrapApiCall(async () => { return await this._wrapApiCall(async () => {
return await this._context._innerFetch({ request: this.request(), data: options.postData, ...options }); return await this._apiRequestContext._innerFetch({ request: this.request(), data: options.postData, ...options });
}); });
} }

View file

@ -140,7 +140,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
this._channel.on('locatorHandlerTriggered', ({ uid }) => this._onLocatorHandlerTriggered(uid)); this._channel.on('locatorHandlerTriggered', ({ uid }) => this._onLocatorHandlerTriggered(uid));
this._channel.on('route', params => { this._channel.on('route', params => {
const route = Route.from(params.route); const route = Route.from(params.route);
route._context = this.context().request; route._apiRequestContext = this.context().request;
this._onRoute(route); this._onRoute(route);
}); });
this._channel.on('webSocketRoute', ({ webSocketRoute }) => this._onWebSocketRoute(WebSocketRoute.from(webSocketRoute))); this._channel.on('webSocketRoute', ({ webSocketRoute }) => this._onWebSocketRoute(WebSocketRoute.from(webSocketRoute)));

View file

@ -20,8 +20,9 @@ import { Android } from './android';
import { BrowserType } from './browserType'; import { BrowserType } from './browserType';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
import { Electron } from './electron'; import { Electron } from './electron';
import { APIRequest, type NewContextOptions } from './fetch'; import { APIRequest } from './fetch';
import { Selectors, SelectorsOwner } from './selectors'; import { Selectors, SelectorsOwner } from './selectors';
import { MockingProxy } from './mockingProxy';
export class Playwright extends ChannelOwner<channels.PlaywrightChannel> { export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
readonly _android: Android; readonly _android: Android;
@ -77,6 +78,6 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
async _startMockingProxy() { async _startMockingProxy() {
const requestContext = await this.request._newContext(undefined, this._connection.localUtils()._channel); const requestContext = await this.request._newContext(undefined, this._connection.localUtils()._channel);
const { mockingProxy } = await this._connection.localUtils()._channel.newMockingProxy({ requestContext: requestContext._channel }); const { mockingProxy } = await this._connection.localUtils()._channel.newMockingProxy({ requestContext: requestContext._channel });
return (mockingProxy as any)._object; return MockingProxy.from(mockingProxy);
} }
} }

View file

@ -30,7 +30,7 @@ export class MockingProxyDispatcher extends Dispatcher<MockingProxy, channels.Mo
private constructor(scope: RootDispatcher, mockingProxy: MockingProxy) { private constructor(scope: RootDispatcher, mockingProxy: MockingProxy) {
super(scope, mockingProxy, 'MockingProxy', { super(scope, mockingProxy, 'MockingProxy', {
port: mockingProxy.port, port: mockingProxy.port(),
requestContext: APIRequestContextDispatcher.from(scope, mockingProxy.fetchRequest), requestContext: APIRequestContextDispatcher.from(scope, mockingProxy.fetchRequest),
}); });
@ -56,4 +56,8 @@ export class MockingProxyDispatcher extends Dispatcher<MockingProxy, channels.Mo
}); });
}); });
} }
override _onDispose(): void {
this._object.stop();
}
} }

View file

@ -58,7 +58,11 @@ export class MockingProxy extends SdkObject implements RequestContext {
await this._httpServer.start(); await this._httpServer.start();
} }
get port() { async stop() {
await this._httpServer.stop();
}
port() {
return this._httpServer.port(); return this._httpServer.port();
} }
@ -245,7 +249,7 @@ async function collectBody(req: http.IncomingMessage) {
}); });
} }
export class WorkerHttpServer extends HttpServer { class WorkerHttpServer extends HttpServer {
override handleCORS(request: http.IncomingMessage, response: http.ServerResponse): boolean { override handleCORS(request: http.IncomingMessage, response: http.ServerResponse): boolean {
return false; return false;
} }