chore: move recorder to server side (#5128)

This commit is contained in:
Pavel Feldman 2021-01-24 08:44:11 -08:00 коммит произвёл GitHub
Родитель 3e4e511d84
Коммит be9bef513e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
22 изменённых файлов: 68 добавлений и 84 удалений

Просмотреть файл

@ -22,7 +22,6 @@ import * as path from 'path';
import * as program from 'commander'; import * as program from 'commander';
import * as os from 'os'; import * as os from 'os';
import * as fs from 'fs'; import * as fs from 'fs';
import * as consoleApiSource from '../generated/consoleApiSource';
import { OutputMultiplexer, TerminalOutput, FileOutput } from './codegen/outputs'; import { OutputMultiplexer, TerminalOutput, FileOutput } from './codegen/outputs';
import { CodeGenerator, CodeGeneratorOutput } from './codegen/codeGenerator'; import { CodeGenerator, CodeGeneratorOutput } from './codegen/codeGenerator';
import { JavaScriptLanguageGenerator, LanguageGenerator } from './codegen/languages'; import { JavaScriptLanguageGenerator, LanguageGenerator } from './codegen/languages';
@ -318,7 +317,7 @@ async function openPage(context: BrowserContext, url: string | undefined): Promi
async function open(options: Options, url: string | undefined) { async function open(options: Options, url: string | undefined) {
const { context } = await launchContext(options, false); const { context } = await launchContext(options, false);
(context as any)._extendInjectedScript(consoleApiSource.source); (context as any)._exposeConsoleApi();
await openPage(context, url); await openPage(context, url);
if (process.env.PWCLI_EXIT_FOR_TEST) if (process.env.PWCLI_EXIT_FOR_TEST)
await Promise.all(context.pages().map(p => p.close())); await Promise.all(context.pages().map(p => p.close()));
@ -347,7 +346,7 @@ async function codegen(options: Options, url: string | undefined, target: string
const generator = new CodeGenerator(browserName, launchOptions, contextOptions, output, languageGenerator, options.device, options.saveStorage); const generator = new CodeGenerator(browserName, launchOptions, contextOptions, output, languageGenerator, options.device, options.saveStorage);
new RecorderController(context, generator); new RecorderController(context, generator);
(context as any)._extendInjectedScript(consoleApiSource.source); (context as any)._exposeConsoleApi();
await openPage(context, url); await openPage(context, url);
if (process.env.PWCLI_EXIT_FOR_TEST) if (process.env.PWCLI_EXIT_FOR_TEST)
await Promise.all(context.pages().map(p => p.close())); await Promise.all(context.pages().map(p => p.close()));

Просмотреть файл

@ -18,7 +18,6 @@ import type { Page, BrowserContext, Frame, Download, Dialog } from '../../..';
import * as actions from './recorderActions'; import * as actions from './recorderActions';
import { CodeGenerator, ActionInContext } from './codeGenerator'; import { CodeGenerator, ActionInContext } from './codeGenerator';
import { toClickOptions, toModifiers } from './utils'; import { toClickOptions, toModifiers } from './utils';
import * as recorderSource from '../../generated/recorderSource';
type BindingSource = { frame: Frame, page: Page }; type BindingSource = { frame: Frame, page: Page };
@ -30,7 +29,7 @@ export class RecorderController {
private _timers = new Set<NodeJS.Timeout>(); private _timers = new Set<NodeJS.Timeout>();
constructor(context: BrowserContext, generator: CodeGenerator) { constructor(context: BrowserContext, generator: CodeGenerator) {
(context as any)._extendInjectedScript(recorderSource.source); (context as any)._enableRecorder();
this._generator = generator; this._generator = generator;

Просмотреть файл

@ -18,7 +18,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { installDebugController } from '../debug/debugController'; import { installInspectorController } from '../server/inspector/inspectorController';
import { DispatcherConnection } from '../dispatchers/dispatcher'; import { DispatcherConnection } from '../dispatchers/dispatcher';
import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher'; import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher';
import { installBrowsersWithProgressBar } from '../install/installer'; import { installBrowsersWithProgressBar } from '../install/installer';
@ -38,7 +38,7 @@ export function printProtocol() {
} }
export function runServer() { export function runServer() {
installDebugController(); installInspectorController();
installTracer(); installTracer();
installHarTracer(); installHarTracer();

Просмотреть файл

@ -29,7 +29,6 @@ import { Waiter } from './waiter';
import { URLMatch, Headers, WaitForEventOptions, BrowserContextOptions, StorageState } from './types'; import { URLMatch, Headers, WaitForEventOptions, BrowserContextOptions, StorageState } from './types';
import { isUnderTest, headersObjectToArray, mkdirIfNeeded } from '../utils/utils'; import { isUnderTest, headersObjectToArray, mkdirIfNeeded } from '../utils/utils';
import { isSafeCloseError } from '../utils/errors'; import { isSafeCloseError } from '../utils/errors';
import { serializeArgument } from './jsHandle';
import * as api from '../../types/types'; import * as api from '../../types/types';
import * as structs from '../../types/structs'; import * as structs from '../../types/structs';
@ -255,8 +254,12 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
} }
} }
async _extendInjectedScript<Arg>(source: string, arg?: Arg) { async _exposeConsoleApi() {
await this._channel.extendInjectedScript({ source, arg: serializeArgument(arg) }); await this._channel.exposeConsoleApi();
}
async _enableRecorder<Arg>() {
await this._channel.enableRecorder();
} }
} }

Просмотреть файл

@ -21,7 +21,6 @@ import * as channels from '../protocol/channels';
import { RouteDispatcher, RequestDispatcher } from './networkDispatchers'; import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
import { CRBrowserContext } from '../server/chromium/crBrowser'; import { CRBrowserContext } from '../server/chromium/crBrowser';
import { CDPSessionDispatcher } from './cdpSessionDispatcher'; import { CDPSessionDispatcher } from './cdpSessionDispatcher';
import { parseArgument } from './jsHandleDispatcher';
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer> implements channels.BrowserContextChannel { export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer> implements channels.BrowserContextChannel {
private _context: BrowserContext; private _context: BrowserContext;
@ -126,8 +125,12 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
await this._context.close(); await this._context.close();
} }
async extendInjectedScript(params: channels.BrowserContextExtendInjectedScriptParams): Promise<void> { async exposeConsoleApi(): Promise<void> {
await this._context.extendInjectedScript(params.source, parseArgument(params.arg)); await this._context.exposeConsoleApi();
}
async enableRecorder(): Promise<void> {
await this._context.enableRecorder();
} }
async crNewCDPSession(params: channels.BrowserContextCrNewCDPSessionParams): Promise<channels.BrowserContextCrNewCDPSessionResult> { async crNewCDPSession(params: channels.BrowserContextCrNewCDPSessionParams): Promise<channels.BrowserContextCrNewCDPSessionResult> {

Просмотреть файл

@ -20,7 +20,7 @@ import type { Playwright as PlaywrightAPI } from './client/playwright';
import { PlaywrightDispatcher } from './dispatchers/playwrightDispatcher'; import { PlaywrightDispatcher } from './dispatchers/playwrightDispatcher';
import { Connection } from './client/connection'; import { Connection } from './client/connection';
import { BrowserServerLauncherImpl } from './browserServerImpl'; import { BrowserServerLauncherImpl } from './browserServerImpl';
import { installDebugController } from './debug/debugController'; import { installInspectorController } from './server/inspector/inspectorController';
import { installTracer } from './trace/tracer'; import { installTracer } from './trace/tracer';
import { installHarTracer } from './trace/harTracer'; import { installHarTracer } from './trace/harTracer';
import * as path from 'path'; import * as path from 'path';
@ -28,7 +28,7 @@ import * as path from 'path';
function setupInProcess(): PlaywrightAPI { function setupInProcess(): PlaywrightAPI {
const playwright = new PlaywrightImpl(path.join(__dirname, '..'), require('../browsers.json')['browsers']); const playwright = new PlaywrightImpl(path.join(__dirname, '..'), require('../browsers.json')['browsers']);
installDebugController(); installInspectorController();
installTracer(); installTracer();
installHarTracer(); installHarTracer();

Просмотреть файл

@ -552,7 +552,8 @@ export interface BrowserContextChannel extends Channel {
setNetworkInterceptionEnabled(params: BrowserContextSetNetworkInterceptionEnabledParams, metadata?: Metadata): Promise<BrowserContextSetNetworkInterceptionEnabledResult>; setNetworkInterceptionEnabled(params: BrowserContextSetNetworkInterceptionEnabledParams, metadata?: Metadata): Promise<BrowserContextSetNetworkInterceptionEnabledResult>;
setOffline(params: BrowserContextSetOfflineParams, metadata?: Metadata): Promise<BrowserContextSetOfflineResult>; setOffline(params: BrowserContextSetOfflineParams, metadata?: Metadata): Promise<BrowserContextSetOfflineResult>;
storageState(params?: BrowserContextStorageStateParams, metadata?: Metadata): Promise<BrowserContextStorageStateResult>; storageState(params?: BrowserContextStorageStateParams, metadata?: Metadata): Promise<BrowserContextStorageStateResult>;
extendInjectedScript(params: BrowserContextExtendInjectedScriptParams, metadata?: Metadata): Promise<BrowserContextExtendInjectedScriptResult>; exposeConsoleApi(params?: BrowserContextExposeConsoleApiParams, metadata?: Metadata): Promise<BrowserContextExposeConsoleApiResult>;
enableRecorder(params?: BrowserContextEnableRecorderParams, metadata?: Metadata): Promise<BrowserContextEnableRecorderResult>;
crNewCDPSession(params: BrowserContextCrNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextCrNewCDPSessionResult>; crNewCDPSession(params: BrowserContextCrNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextCrNewCDPSessionResult>;
} }
export type BrowserContextBindingCallEvent = { export type BrowserContextBindingCallEvent = {
@ -694,14 +695,12 @@ export type BrowserContextStorageStateResult = {
cookies: NetworkCookie[], cookies: NetworkCookie[],
origins: OriginStorage[], origins: OriginStorage[],
}; };
export type BrowserContextExtendInjectedScriptParams = { export type BrowserContextExposeConsoleApiParams = {};
source: string, export type BrowserContextExposeConsoleApiOptions = {};
arg: SerializedArgument, export type BrowserContextExposeConsoleApiResult = void;
}; export type BrowserContextEnableRecorderParams = {};
export type BrowserContextExtendInjectedScriptOptions = { export type BrowserContextEnableRecorderOptions = {};
export type BrowserContextEnableRecorderResult = void;
};
export type BrowserContextExtendInjectedScriptResult = void;
export type BrowserContextCrNewCDPSessionParams = { export type BrowserContextCrNewCDPSessionParams = {
page: PageChannel, page: PageChannel,
}; };

Просмотреть файл

@ -599,11 +599,11 @@ BrowserContext:
type: array type: array
items: OriginStorage items: OriginStorage
extendInjectedScript: exposeConsoleApi:
experimental: True
enableRecorder:
experimental: True experimental: True
parameters:
source: string
arg: SerializedArgument
crNewCDPSession: crNewCDPSession:
parameters: parameters:

Просмотреть файл

@ -335,10 +335,8 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
offline: tBoolean, offline: tBoolean,
}); });
scheme.BrowserContextStorageStateParams = tOptional(tObject({})); scheme.BrowserContextStorageStateParams = tOptional(tObject({}));
scheme.BrowserContextExtendInjectedScriptParams = tObject({ scheme.BrowserContextExposeConsoleApiParams = tOptional(tObject({}));
source: tString, scheme.BrowserContextEnableRecorderParams = tOptional(tObject({}));
arg: tType('SerializedArgument'),
});
scheme.BrowserContextCrNewCDPSessionParams = tObject({ scheme.BrowserContextCrNewCDPSessionParams = tObject({
page: tChannel('Page'), page: tChannel('Page'),
}); });

Просмотреть файл

@ -17,7 +17,7 @@
import * as debug from 'debug'; import * as debug from 'debug';
import * as http from 'http'; import * as http from 'http';
import * as WebSocket from 'ws'; import * as WebSocket from 'ws';
import { installDebugController } from '../debug/debugController'; import { installInspectorController } from '../server/inspector/inspectorController';
import { DispatcherConnection } from '../dispatchers/dispatcher'; import { DispatcherConnection } from '../dispatchers/dispatcher';
import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher'; import { PlaywrightDispatcher } from '../dispatchers/playwrightDispatcher';
import { Playwright } from '../server/playwright'; import { Playwright } from '../server/playwright';
@ -27,7 +27,7 @@ import { installHarTracer } from '../trace/harTracer';
const debugLog = debug('pw:server'); const debugLog = debug('pw:server');
installDebugController(); installInspectorController();
installTracer(); installTracer();
installHarTracer(); installHarTracer();

Просмотреть файл

@ -19,6 +19,8 @@ import { EventEmitter } from 'events';
import { TimeoutSettings } from '../utils/timeoutSettings'; import { TimeoutSettings } from '../utils/timeoutSettings';
import { mkdirIfNeeded } from '../utils/utils'; import { mkdirIfNeeded } from '../utils/utils';
import { Browser, BrowserOptions } from './browser'; import { Browser, BrowserOptions } from './browser';
import * as consoleApiSource from '../generated/consoleApiSource';
import * as recorderSource from '../generated/recorderSource';
import * as dom from './dom'; import * as dom from './dom';
import { Download } from './download'; import { Download } from './download';
import * as frames from './frames'; import * as frames from './frames';
@ -379,8 +381,16 @@ export abstract class BrowserContext extends EventEmitter {
} }
} }
async extendInjectedScript(source: string, arg?: any) { async exposeConsoleApi() {
const installInFrame = (frame: frames.Frame) => frame.extendInjectedScript(source, arg).catch(e => {}); await this._extendInjectedScript(consoleApiSource.source);
}
async enableRecorder() {
await this._extendInjectedScript(recorderSource.source);
}
private async _extendInjectedScript(source: string) {
const installInFrame = (frame: frames.Frame) => frame.extendInjectedScript(source).catch(e => {});
const installInPage = (page: Page) => { const installInPage = (page: Page) => {
page.on(Page.Events.InternalFrameNavigatedToNewDocument, installInFrame); page.on(Page.Events.InternalFrameNavigatedToNewDocument, installInFrame);
return Promise.all(page.frames().map(installInFrame)); return Promise.all(page.frames().map(installInFrame));

Просмотреть файл

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import type InjectedScript from '../../server/injected/injectedScript'; import type InjectedScript from '../../injected/injectedScript';
import { generateSelector } from './selectorGenerator'; import { generateSelector } from './selectorGenerator';
export class ConsoleAPI { export class ConsoleAPI {

Просмотреть файл

@ -15,7 +15,7 @@
*/ */
const path = require('path'); const path = require('path');
const InlineSource = require('../../server/injected/webpack-inline-source-plugin'); const InlineSource = require('../../injected/webpack-inline-source-plugin');
/** @type {import('webpack').Configuration} */ /** @type {import('webpack').Configuration} */
module.exports = { module.exports = {
@ -45,6 +45,6 @@ module.exports = {
path: path.resolve(__dirname, '../../../lib/server/injected/packed') path: path.resolve(__dirname, '../../../lib/server/injected/packed')
}, },
plugins: [ plugins: [
new InlineSource(path.join(__dirname, '..', '..', 'generated', 'consoleApiSource.ts')), new InlineSource(path.join(__dirname, '..', '..', '..', 'generated', 'consoleApiSource.ts')),
] ]
}; };

Просмотреть файл

Просмотреть файл

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
import type * as actions from '../codegen/recorderActions'; import type * as actions from '../../../cli/codegen/recorderActions';
import type InjectedScript from '../../server/injected/injectedScript'; import type InjectedScript from '../../injected/injectedScript';
import { generateSelector } from '../../debug/injected/selectorGenerator'; import { generateSelector } from './selectorGenerator';
import { html } from './html'; import { html } from './html';
declare global { declare global {

Просмотреть файл

@ -15,7 +15,7 @@
*/ */
const path = require('path'); const path = require('path');
const InlineSource = require('../../server/injected/webpack-inline-source-plugin'); const InlineSource = require('../../injected/webpack-inline-source-plugin');
/** @type {import('webpack').Configuration} */ /** @type {import('webpack').Configuration} */
module.exports = { module.exports = {
@ -39,12 +39,12 @@ module.exports = {
}, },
output: { output: {
libraryTarget: 'var', libraryTarget: 'var',
libraryExport: 'default',
library: 'pwExport', library: 'pwExport',
libraryExport: 'default',
filename: 'recorderSource.js', filename: 'recorderSource.js',
path: path.resolve(__dirname, '../../../lib/server/injected/packed') path: path.resolve(__dirname, '../../../lib/server/injected/packed')
}, },
plugins: [ plugins: [
new InlineSource(path.join(__dirname, '..', '..', 'generated', 'recorderSource.ts')), new InlineSource(path.join(__dirname, '..', '..', '..', 'generated', 'recorderSource.ts')),
] ]
}; };

Просмотреть файл

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import type InjectedScript from '../../server/injected/injectedScript'; import type InjectedScript from '../../injected/injectedScript';
export function generateSelector(injectedScript: InjectedScript, targetElement: Element): { selector: string, elements: Element[] } { export function generateSelector(injectedScript: InjectedScript, targetElement: Element): { selector: string, elements: Element[] } {
const path: SelectorToken[] = []; const path: SelectorToken[] = [];

Просмотреть файл

@ -14,18 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
import { BrowserContext, ContextListener, contextListeners } from '../server/browserContext'; import { BrowserContext, ContextListener, contextListeners } from '../browserContext';
import { isDebugMode } from '../utils/utils'; import { isDebugMode } from '../../utils/utils';
import * as consoleApiSource from '../generated/consoleApiSource';
export function installDebugController() { export function installInspectorController() {
contextListeners.add(new DebugController()); contextListeners.add(new InspectorController());
} }
class DebugController implements ContextListener { class InspectorController implements ContextListener {
async onContextCreated(context: BrowserContext): Promise<void> { async onContextCreated(context: BrowserContext): Promise<void> {
if (isDebugMode()) if (isDebugMode())
context.extendInjectedScript(consoleApiSource.source); context.exposeConsoleApi();
} }
async onContextWillDestroy(context: BrowserContext): Promise<void> {} async onContextWillDestroy(context: BrowserContext): Promise<void> {}
async onContextDidDestroy(context: BrowserContext): Promise<void> {} async onContextDidDestroy(context: BrowserContext): Promise<void> {}

Просмотреть файл

@ -82,26 +82,3 @@ it('exposeBindingHandle should work', async ({context}) => {
expect(await target.evaluate(x => x.foo)).toBe(42); expect(await target.evaluate(x => x.foo)).toBe(42);
expect(result).toEqual(17); expect(result).toEqual(17);
}); });
it('extendInjectedScript should work', async ({ context, server }) => {
await (context as any)._extendInjectedScript(`var pwExport = (() => {
class Foo {
constructor() {
window._counter = (window._counter || 0) + 1;
}
}
return Foo;
})()`);
const page = await context.newPage();
await page.waitForFunction(() => (window as any)._counter === 1);
await page.goto(server.EMPTY_PAGE);
await page.waitForFunction(() => (window as any)._counter === 1);
await Promise.all([
page.waitForNavigation(),
page.evaluate(() => history.pushState({}, '', '/url.html'))
]);
expect(await page.evaluate(() => (window as any)._counter)).toBe(1);
});

Просмотреть файл

@ -16,11 +16,10 @@
import { folio } from './fixtures'; import { folio } from './fixtures';
import type { Page, Frame } from '..'; import type { Page, Frame } from '..';
import { source } from '../src/generated/consoleApiSource';
const fixtures = folio.extend(); const fixtures = folio.extend();
fixtures.context.override(async ({ context }, run) => { fixtures.context.override(async ({ context }, run) => {
await (context as any)._extendInjectedScript(source); await (context as any)._exposeConsoleApi();
await run(context); await run(context);
}); });
const { describe, it, expect } = fixtures.build(); const { describe, it, expect } = fixtures.build();

Просмотреть файл

@ -68,8 +68,8 @@ function runBuild() {
const webPackFiles = [ const webPackFiles = [
'src/server/injected/injectedScript.webpack.config.js', 'src/server/injected/injectedScript.webpack.config.js',
'src/server/injected/utilityScript.webpack.config.js', 'src/server/injected/utilityScript.webpack.config.js',
'src/debug/injected/consoleApi.webpack.config.js', 'src/server/inspector/injected/consoleApi.webpack.config.js',
'src/cli/injected/recorder.webpack.config.js', 'src/server/inspector/injected/recorder.webpack.config.js',
'src/cli/traceViewer/web/web.webpack.config.js', 'src/cli/traceViewer/web/web.webpack.config.js',
]; ];
for (const file of webPackFiles) { for (const file of webPackFiles) {

Просмотреть файл

@ -131,6 +131,7 @@ DEPS['src/server/'] = [
DEPS['src/server/common/'] = []; DEPS['src/server/common/'] = [];
// Strict dependencies for injected code. // Strict dependencies for injected code.
DEPS['src/server/injected/'] = ['src/server/common/']; DEPS['src/server/injected/'] = ['src/server/common/'];
DEPS['src/server/inspector/injected/'] = ['src/server/common/', 'src/cli/codegen/', 'src/server/injected/'];
// Electron and Clank use chromium internally. // Electron and Clank use chromium internally.
DEPS['src/server/android/'] = [...DEPS['src/server/'], 'src/server/chromium/', 'src/protocol/']; DEPS['src/server/android/'] = [...DEPS['src/server/'], 'src/server/chromium/', 'src/protocol/'];
@ -142,11 +143,8 @@ DEPS['src/cli/driver.ts'] = DEPS['src/inprocess.ts'] = DEPS['src/browserServerIm
// Tracing is a client/server plugin, nothing should depend on it. // Tracing is a client/server plugin, nothing should depend on it.
DEPS['src/trace/'] = ['src/utils/', 'src/client/**', 'src/server/**']; DEPS['src/trace/'] = ['src/utils/', 'src/client/**', 'src/server/**'];
// Debug is a server plugin, nothing should depend on it.
DEPS['src/debug/'] = ['src/utils/', 'src/generated/', 'src/server/**', 'src/debug/**'];
// The service is a cross-cutting feature, and so it depends on a bunch of things. // The service is a cross-cutting feature, and so it depends on a bunch of things.
DEPS['src/remote/'] = ['src/client/', 'src/debug/', 'src/dispatchers/', 'src/server/', 'src/server/electron/', 'src/trace/']; DEPS['src/remote/'] = ['src/client/', 'src/debug/', 'src/dispatchers/', 'src/server/', 'src/server/inspector/', 'src/server/electron/', 'src/trace/'];
DEPS['src/service.ts'] = ['src/remote/']; DEPS['src/service.ts'] = ['src/remote/'];
// CLI should only use client-side features. // CLI should only use client-side features.