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 os from 'os';
import * as fs from 'fs';
import * as consoleApiSource from '../generated/consoleApiSource';
import { OutputMultiplexer, TerminalOutput, FileOutput } from './codegen/outputs';
import { CodeGenerator, CodeGeneratorOutput } from './codegen/codeGenerator';
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) {
const { context } = await launchContext(options, false);
(context as any)._extendInjectedScript(consoleApiSource.source);
(context as any)._exposeConsoleApi();
await openPage(context, url);
if (process.env.PWCLI_EXIT_FOR_TEST)
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);
new RecorderController(context, generator);
(context as any)._extendInjectedScript(consoleApiSource.source);
(context as any)._exposeConsoleApi();
await openPage(context, url);
if (process.env.PWCLI_EXIT_FOR_TEST)
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 { CodeGenerator, ActionInContext } from './codeGenerator';
import { toClickOptions, toModifiers } from './utils';
import * as recorderSource from '../../generated/recorderSource';
type BindingSource = { frame: Frame, page: Page };
@ -30,7 +29,7 @@ export class RecorderController {
private _timers = new Set<NodeJS.Timeout>();
constructor(context: BrowserContext, generator: CodeGenerator) {
(context as any)._extendInjectedScript(recorderSource.source);
(context as any)._enableRecorder();
this._generator = generator;

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

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

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

@ -29,7 +29,6 @@ import { Waiter } from './waiter';
import { URLMatch, Headers, WaitForEventOptions, BrowserContextOptions, StorageState } from './types';
import { isUnderTest, headersObjectToArray, mkdirIfNeeded } from '../utils/utils';
import { isSafeCloseError } from '../utils/errors';
import { serializeArgument } from './jsHandle';
import * as api from '../../types/types';
import * as structs from '../../types/structs';
@ -255,8 +254,12 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
}
}
async _extendInjectedScript<Arg>(source: string, arg?: Arg) {
await this._channel.extendInjectedScript({ source, arg: serializeArgument(arg) });
async _exposeConsoleApi() {
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 { CRBrowserContext } from '../server/chromium/crBrowser';
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
import { parseArgument } from './jsHandleDispatcher';
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer> implements channels.BrowserContextChannel {
private _context: BrowserContext;
@ -126,8 +125,12 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
await this._context.close();
}
async extendInjectedScript(params: channels.BrowserContextExtendInjectedScriptParams): Promise<void> {
await this._context.extendInjectedScript(params.source, parseArgument(params.arg));
async exposeConsoleApi(): Promise<void> {
await this._context.exposeConsoleApi();
}
async enableRecorder(): Promise<void> {
await this._context.enableRecorder();
}
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 { Connection } from './client/connection';
import { BrowserServerLauncherImpl } from './browserServerImpl';
import { installDebugController } from './debug/debugController';
import { installInspectorController } from './server/inspector/inspectorController';
import { installTracer } from './trace/tracer';
import { installHarTracer } from './trace/harTracer';
import * as path from 'path';
@ -28,7 +28,7 @@ import * as path from 'path';
function setupInProcess(): PlaywrightAPI {
const playwright = new PlaywrightImpl(path.join(__dirname, '..'), require('../browsers.json')['browsers']);
installDebugController();
installInspectorController();
installTracer();
installHarTracer();

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -15,7 +15,7 @@
*/
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} */
module.exports = {
@ -45,6 +45,6 @@ module.exports = {
path: path.resolve(__dirname, '../../../lib/server/injected/packed')
},
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.
*/
import type * as actions from '../codegen/recorderActions';
import type InjectedScript from '../../server/injected/injectedScript';
import { generateSelector } from '../../debug/injected/selectorGenerator';
import type * as actions from '../../../cli/codegen/recorderActions';
import type InjectedScript from '../../injected/injectedScript';
import { generateSelector } from './selectorGenerator';
import { html } from './html';
declare global {

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

@ -15,7 +15,7 @@
*/
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} */
module.exports = {
@ -39,12 +39,12 @@ module.exports = {
},
output: {
libraryTarget: 'var',
libraryExport: 'default',
library: 'pwExport',
libraryExport: 'default',
filename: 'recorderSource.js',
path: path.resolve(__dirname, '../../../lib/server/injected/packed')
},
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.
*/
import type InjectedScript from '../../server/injected/injectedScript';
import type InjectedScript from '../../injected/injectedScript';
export function generateSelector(injectedScript: InjectedScript, targetElement: Element): { selector: string, elements: Element[] } {
const path: SelectorToken[] = [];

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

@ -14,18 +14,17 @@
* limitations under the License.
*/
import { BrowserContext, ContextListener, contextListeners } from '../server/browserContext';
import { isDebugMode } from '../utils/utils';
import * as consoleApiSource from '../generated/consoleApiSource';
import { BrowserContext, ContextListener, contextListeners } from '../browserContext';
import { isDebugMode } from '../../utils/utils';
export function installDebugController() {
contextListeners.add(new DebugController());
export function installInspectorController() {
contextListeners.add(new InspectorController());
}
class DebugController implements ContextListener {
class InspectorController implements ContextListener {
async onContextCreated(context: BrowserContext): Promise<void> {
if (isDebugMode())
context.extendInjectedScript(consoleApiSource.source);
context.exposeConsoleApi();
}
async onContextWillDestroy(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(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 type { Page, Frame } from '..';
import { source } from '../src/generated/consoleApiSource';
const fixtures = folio.extend();
fixtures.context.override(async ({ context }, run) => {
await (context as any)._extendInjectedScript(source);
await (context as any)._exposeConsoleApi();
await run(context);
});
const { describe, it, expect } = fixtures.build();

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

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

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

@ -131,6 +131,7 @@ DEPS['src/server/'] = [
DEPS['src/server/common/'] = [];
// Strict dependencies for injected code.
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.
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.
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.
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/'];
// CLI should only use client-side features.