some more

This commit is contained in:
Simon Knott 2025-01-23 15:15:49 +01:00
parent 10646b1c25
commit 74678855cf
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
4 changed files with 51 additions and 59 deletions

View file

@ -45,6 +45,7 @@ import { findValidator, ValidationError, type ValidatorContext } from '../protoc
import { createInstrumentation } from './clientInstrumentation';
import type { ClientInstrumentation } from './clientInstrumentation';
import { formatCallLog, rewriteErrorMessage, zones } from '../utils';
import { MockingProxy } from './mockingProxy';
class Root extends ChannelOwner<channels.RootChannel> {
constructor(connection: Connection) {
@ -279,6 +280,9 @@ export class Connection extends EventEmitter {
if (!this._localUtils)
this._localUtils = result as LocalUtils;
break;
case 'MockingProxy':
result = new MockingProxy(parent, type, guid, initializer);
break;
case 'Page':
result = new Page(parent, type, guid, initializer);
break;

View file

@ -17,7 +17,6 @@
import type * as channels from '@protocol/channels';
import { ChannelOwner } from './channelOwner';
import type { Size } from './types';
import { APIRequestContext } from './fetch';
type DeviceDescriptor = {
userAgent: string,
@ -31,7 +30,6 @@ type Devices = { [name: string]: DeviceDescriptor };
export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
readonly devices: Devices;
readonly requestContext: APIRequestContext;
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.LocalUtilsInitializer) {
super(parent, type, guid, initializer);
@ -39,6 +37,5 @@ export class LocalUtils extends ChannelOwner<channels.LocalUtilsChannel> {
this.devices = {};
for (const { name, descriptor } of initializer.deviceDescriptors)
this.devices[name] = descriptor;
this.requestContext = APIRequestContext.from(initializer.requestContext);
}
}

View file

@ -18,7 +18,6 @@ import * as network from './network';
import { urlMatches, urlMatchesEqual, type URLMatch } from '../utils/isomorphic/urlMatch';
import type { LocalUtils } from './localUtils';
import type * as channels from '@protocol/channels';
import { EventEmitter } from './eventEmitter';
import type { WaitForEventOptions } from './types';
import { Waiter } from './waiter';
import { Events } from './events';
@ -26,31 +25,34 @@ import { isString } from '../utils/isomorphic/stringUtils';
import { isRegExp } from '../utils';
import { trimUrl } from './page';
import { TimeoutSettings } from '../common/timeoutSettings';
import { ChannelOwner } from './channelOwner';
import { APIRequestContext } from './fetch';
export class MockingProxyFactory implements api.MockingProxyFactory {
constructor(private _localUtils: LocalUtils) {}
async newProxy(port: number): Promise<api.MockingProxy> {
return await MockingProxy.create(this._localUtils, port);
async newProxy(port?: number): Promise<api.MockingProxy> {
const { mockingProxy } = await this._localUtils._channel.newMockingProxy({ port });
return (mockingProxy as any)._object;
}
}
export class MockingProxy extends EventEmitter implements api.MockingProxy {
_routes: network.RouteHandler[] = [];
private _localUtils: LocalUtils;
export class MockingProxy extends ChannelOwner<channels.MockingProxyChannel> implements api.MockingProxy {
private _routes: network.RouteHandler[] = [];
private _port: number;
private _timeoutSettings = new TimeoutSettings();
private _requestContext: APIRequestContext;
private routeListener = ({ route }: channels.LocalUtilsRouteEvent) => {
private routeListener = ({ route }: channels.MockingProxyRouteEvent) => {
this._onRoute(network.Route.from(route));
};
private failedListener = (params: channels.LocalUtilsRequestFailedEvent) => {
private failedListener = (params: channels.MockingProxyRequestFailedEvent) => {
const request = network.Request.from(params.request);
if (params.failureText)
request._failureText = params.failureText;
request._setResponseEndTiming(params.responseEndTiming);
this.emit('requestfailed', request);
};
private finishedListener = (params: channels.LocalUtilsRequestFinishedEvent) => {
private finishedListener = (params: channels.MockingProxyRequestFinishedEvent) => {
const { responseEndTiming } = params;
const request = network.Request.from(params.request);
const response = network.Response.fromNullable(params.response);
@ -58,42 +60,32 @@ export class MockingProxy extends EventEmitter implements api.MockingProxy {
this.emit('requestfinished', request);
response?._finishedPromise.resolve(null);
};
private responseListener = ({ response }: channels.LocalUtilsResponseEvent) => {
private responseListener = ({ response }: channels.MockingProxyResponseEvent) => {
this.emit('response', network.Response.from(response));
};
private requestListener = ({ request }: channels.LocalUtilsRequestEvent) => {
private requestListener = ({ request }: channels.MockingProxyRequestEvent) => {
this.emit('request', network.Request.from(request));
};
private constructor(localUtils: LocalUtils, port: number) {
super();
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.MockingProxyInitializer) {
super(parent, type, guid, initializer);
this._localUtils = localUtils;
this._port = port;
this._port = initializer.port;
this._requestContext = APIRequestContext.from(initializer.requestContext);
this._localUtils._channel.on('route', this.routeListener);
this._localUtils._channel.on('request', this.requestListener);
this._localUtils._channel.on('requestFailed', this.failedListener);
this._localUtils._channel.on('requestFinished', this.finishedListener);
this._localUtils._channel.on('response', this.responseListener);
}
private async _start() {
await this._localUtils._channel.setServerNetworkInterceptionPatterns({ patterns: [], port: this._port });
}
static async create(localUtils: LocalUtils, port: number) {
const instance = new MockingProxy(localUtils, port);
await instance._start();
return instance;
this._channel.on('route', this.routeListener);
this._channel.on('request', this.requestListener);
this._channel.on('requestFailed', this.failedListener);
this._channel.on('requestFinished', this.finishedListener);
this._channel.on('response', this.responseListener);
}
dispose() {
this._localUtils._channel.off('route', this.routeListener);
this._localUtils._channel.off('request', this.requestListener);
this._localUtils._channel.off('requestFailed', this.failedListener);
this._localUtils._channel.off('requestFinished', this.finishedListener);
this._localUtils._channel.off('response', this.responseListener);
this._channel.off('route', this.routeListener);
this._channel.off('request', this.requestListener);
this._channel.off('requestFailed', this.failedListener);
this._channel.off('requestFinished', this.finishedListener);
this._channel.off('response', this.responseListener);
}
async route(url: URLMatch, handler: network.RouteHandlerCallback, options: { times?: number } = {}): Promise<void> {
@ -127,7 +119,7 @@ export class MockingProxy extends EventEmitter implements api.MockingProxy {
}
async _onRoute(route: network.Route) {
route._request = this._localUtils.requestContext;
route._request = this._requestContext;
const routeHandlers = this._routes.slice();
for (const routeHandler of routeHandlers) {
if (!routeHandler.matches(route.request().url()))
@ -149,8 +141,7 @@ export class MockingProxy extends EventEmitter implements api.MockingProxy {
}
private async _updateInterceptionPatterns() {
const patterns = network.RouteHandler.prepareInterceptionPatterns(this._routes);
await this._localUtils._channel.setServerNetworkInterceptionPatterns({ patterns, port: this._port });
await this._channel.setInterceptionPatterns({ patterns: network.RouteHandler.prepareInterceptionPatterns(this._routes) });
}
async waitForRequest(urlOrPredicate: string | RegExp | ((r: network.Request) => boolean | Promise<boolean>), options: { timeout?: number } = {}): Promise<network.Request> {
@ -182,10 +173,10 @@ export class MockingProxy extends EventEmitter implements api.MockingProxy {
}
private async _waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions, logLine?: string): Promise<any> {
return await this._localUtils._wrapApiCall(async () => {
return await this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this._localUtils, event);
const waiter = Waiter.createForEvent(this, event);
if (logLine)
waiter.log(logLine);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);

View file

@ -18,13 +18,13 @@ import { playwrightTest as baseTest, expect } from '../config/browserTest';
import { pipeline } from 'stream/promises';
import { suppressCertificateWarning } from 'tests/config/utils';
const test = baseTest.extend<{ proxiedRequest: APIRequestContext }, { mockingProxy: MockingProxy }>({
mockingProxy: [async ({ playwright }, use, testInfo) => {
const test = baseTest.extend<{ proxiedRequest: APIRequestContext }, { mockproxy: MockingProxy }>({
mockproxy: [async ({ playwright }, use, testInfo) => {
const port = 32181 + testInfo.parallelIndex;
const proxy = await playwright.mockingProxy.newProxy(port);
await use(proxy);
}, { scope: 'worker' }],
proxiedRequest: async ({ request, mockingProxy: mockproxy }, use) => {
proxiedRequest: async ({ request, mockproxy }, use) => {
const originalFetch = request.fetch;
request.fetch = function(urlOrRequest, options) {
if (typeof urlOrRequest !== 'string')
@ -36,12 +36,12 @@ const test = baseTest.extend<{ proxiedRequest: APIRequestContext }, { mockingPro
},
});
test.beforeEach(async ({ mockingProxy: mockproxy }) => {
test.beforeEach(async ({ mockproxy }) => {
await mockproxy.unrouteAll();
});
test.describe('transparent', () => {
test('generates events', async ({ server, proxiedRequest, mockingProxy: mockproxy }) => {
test('generates events', async ({ server, proxiedRequest, mockproxy }) => {
const events: string[] = [];
mockproxy.on('request', () => {
events.push('request');
@ -58,7 +58,7 @@ test.describe('transparent', () => {
expect(events).toEqual(['request', 'response', 'requestfinished']);
});
test('event properties', async ({ server, proxiedRequest, mockingProxy: mockproxy }) => {
test('event properties', async ({ server, proxiedRequest, mockproxy }) => {
const [
requestFinished,
request,
@ -122,7 +122,7 @@ test.describe('transparent', () => {
});
});
test('securityDetails', async ({ httpsServer, proxiedRequest, mockingProxy: mockproxy }) => {
test('securityDetails', async ({ httpsServer, proxiedRequest, mockproxy }) => {
const oldValue = process.env['NODE_TLS_REJECT_UNAUTHORIZED'];
// https://stackoverflow.com/a/21961005/552185
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
@ -149,7 +149,7 @@ test.describe('transparent', () => {
}
});
test('request with body', async ({ server, proxiedRequest, mockingProxy: mockproxy }) => {
test('request with body', async ({ server, proxiedRequest, mockproxy }) => {
server.setRoute('/echo', (req, res) => pipeline(req, res));
const [
requestEvent,
@ -173,7 +173,7 @@ test.describe('transparent', () => {
});
});
test('request failed', async ({ server, proxiedRequest, mockingProxy: mockproxy }) => {
test('request failed', async ({ server, proxiedRequest, mockproxy }) => {
server.setRoute('/failure', (req, res) => {
res.socket.destroy();
});
@ -196,14 +196,14 @@ test.describe('transparent', () => {
});
});
test('stalling', async ({ server, proxiedRequest, mockingProxy: mockproxy }) => {
test('stalling', async ({ server, proxiedRequest, mockproxy }) => {
const routes: Route[] = [];
await mockproxy.route('**/abort', route => routes.push(route));
await expect(() => proxiedRequest.get(server.PREFIX + '/abort', { timeout: 100 })).rejects.toThrowError('Request timed out after 100ms');
expect(routes.length).toBe(1);
});
test('route properties', async ({ server, proxiedRequest, mockingProxy: mockproxy }) => {
test('route properties', async ({ server, proxiedRequest, mockproxy }) => {
const routes: Route[] = [];
await mockproxy.route('**/*', (route, request) => {
expect(route.request()).toBe(request);
@ -214,12 +214,12 @@ test('route properties', async ({ server, proxiedRequest, mockingProxy: mockprox
expect(routes.length).toBe(1);
});
test('aborting', async ({ server, proxiedRequest, mockingProxy: mockproxy }) => {
test('aborting', async ({ server, proxiedRequest, mockproxy }) => {
await mockproxy.route('**/abort', route => route.abort());
await expect(() => proxiedRequest.get(server.PREFIX + '/abort', { timeout: 100 })).rejects.toThrowError('Request timed out after 100ms');
});
test('fulfill', async ({ server, proxiedRequest, mockingProxy: mockproxy }) => {
test('fulfill', async ({ server, proxiedRequest, mockproxy }) => {
let apiCalls = 0;
server.setRoute('/endpoint', (req, res) => {
apiCalls++;
@ -233,7 +233,7 @@ test('fulfill', async ({ server, proxiedRequest, mockingProxy: mockproxy }) => {
expect(apiCalls).toBe(0);
});
test('continue', async ({ server, proxiedRequest, mockingProxy: mockproxy }) => {
test('continue', async ({ server, proxiedRequest, mockproxy }) => {
server.setRoute('/echo', (req, res) => {
res.setHeader('request-method', req.method);
res.writeHead(200, req.headers);
@ -255,7 +255,7 @@ test('continue', async ({ server, proxiedRequest, mockingProxy: mockproxy }) =>
expect(response.headers()['x-add']).toBe('baz');
});
test('fallback', async ({ server, proxiedRequest, mockingProxy: mockproxy }) => {
test('fallback', async ({ server, proxiedRequest, mockproxy }) => {
server.setRoute('/foo', (req, res) => {
res.end('ok');
});
@ -266,7 +266,7 @@ test('fallback', async ({ server, proxiedRequest, mockingProxy: mockproxy }) =>
expect(await response.text()).toBe('ok');
});
test('fetch', async ({ server, proxiedRequest, mockingProxy: mockproxy }) => {
test('fetch', async ({ server, proxiedRequest, mockproxy }) => {
server.setRoute('/foo', (req, res) => {
res.end('ok');
});