cherry-pick(#12020): chore: headless mode for codegen

This commit is contained in:
Pavel Feldman 2022-02-10 21:23:16 -08:00
parent 55be85284c
commit 73d78f5988
2 changed files with 59 additions and 11 deletions

View file

@ -38,7 +38,18 @@ declare global {
} }
} }
export class RecorderApp extends EventEmitter { export interface IRecorderApp extends EventEmitter {
close(): Promise<void>;
setPaused(paused: boolean): Promise<void>;
setMode(mode: 'none' | 'recording' | 'inspecting'): Promise<void>;
setFile(file: string): Promise<void>;
setSelector(selector: string, focus?: boolean): Promise<void>;
updateCallLogs(callLogs: CallLog[]): Promise<void>;
bringToFront(): void;
setSources(sources: Source[]): Promise<void>;
}
export class RecorderApp extends EventEmitter implements IRecorderApp {
private _page: Page; private _page: Page;
readonly wsEndpoint: string | undefined; readonly wsEndpoint: string | undefined;
@ -85,7 +96,9 @@ export class RecorderApp extends EventEmitter {
await mainFrame.goto(internalCallMetadata(), 'https://playwright/index.html'); await mainFrame.goto(internalCallMetadata(), 'https://playwright/index.html');
} }
static async open(sdkLanguage: string, headed: boolean): Promise<RecorderApp> { static async open(sdkLanguage: string, headed: boolean): Promise<IRecorderApp> {
if (process.env.PW_CODEGEN_NO_INSPECTOR)
return new HeadlessRecorderApp();
const recorderPlaywright = (require('../../playwright').createPlaywright as typeof import('../../playwright').createPlaywright)('javascript', true); const recorderPlaywright = (require('../../playwright').createPlaywright as typeof import('../../playwright').createPlaywright)('javascript', true);
const args = [ const args = [
'--app=data:text/html,', '--app=data:text/html,',
@ -163,3 +176,14 @@ export class RecorderApp extends EventEmitter {
await this._page.bringToFront(); await this._page.bringToFront();
} }
} }
class HeadlessRecorderApp extends EventEmitter implements IRecorderApp {
async close(): Promise<void> {}
async setPaused(paused: boolean): Promise<void> {}
async setMode(mode: 'none' | 'recording' | 'inspecting'): Promise<void> {}
async setFile(file: string): Promise<void> {}
async setSelector(selector: string, focus?: boolean): Promise<void> {}
async updateCallLogs(callLogs: CallLog[]): Promise<void> {}
bringToFront(): void {}
async setSources(sources: Source[]): Promise<void> {}
}

View file

@ -28,7 +28,7 @@ import { CSharpLanguageGenerator } from './recorder/csharp';
import { PythonLanguageGenerator } from './recorder/python'; import { PythonLanguageGenerator } from './recorder/python';
import * as recorderSource from '../../generated/recorderSource'; import * as recorderSource from '../../generated/recorderSource';
import * as consoleApiSource from '../../generated/consoleApiSource'; import * as consoleApiSource from '../../generated/consoleApiSource';
import { RecorderApp } from './recorder/recorderApp'; import { IRecorderApp, RecorderApp } from './recorder/recorderApp';
import { CallMetadata, InstrumentationListener, SdkObject } from '../instrumentation'; import { CallMetadata, InstrumentationListener, SdkObject } from '../instrumentation';
import { Point } from '../../common/types'; import { Point } from '../../common/types';
import { CallLog, CallLogStatus, EventData, Mode, Source, UIState } from './recorder/recorderTypes'; import { CallLog, CallLogStatus, EventData, Mode, Source, UIState } from './recorder/recorderTypes';
@ -46,7 +46,7 @@ export class RecorderSupplement implements InstrumentationListener {
private _context: BrowserContext; private _context: BrowserContext;
private _mode: Mode; private _mode: Mode;
private _highlightedSelector = ''; private _highlightedSelector = '';
private _recorderApp: RecorderApp | null = null; private _recorderApp: IRecorderApp | null = null;
private _currentCallsMetadata = new Map<CallMetadata, SdkObject>(); private _currentCallsMetadata = new Map<CallMetadata, SdkObject>();
private _recorderSources: Source[] = []; private _recorderSources: Source[] = [];
private _userSources = new Map<string, Source>(); private _userSources = new Map<string, Source>();
@ -317,7 +317,7 @@ class ContextRecorder extends EventEmitter {
this._recorderSources = []; this._recorderSources = [];
const generator = new CodeGenerator(context._browser.options.name, !!params.startRecording, params.launchOptions || {}, params.contextOptions || {}, params.device, params.saveStorage); const generator = new CodeGenerator(context._browser.options.name, !!params.startRecording, params.launchOptions || {}, params.contextOptions || {}, params.device, params.saveStorage);
let text = ''; const throttledOutputFile = params.outputFile ? new ThrottledFile(params.outputFile) : null;
generator.on('change', () => { generator.on('change', () => {
this._recorderSources = []; this._recorderSources = [];
for (const languageGenerator of orderedLanguages) { for (const languageGenerator of orderedLanguages) {
@ -330,21 +330,19 @@ class ContextRecorder extends EventEmitter {
source.revealLine = source.text.split('\n').length - 1; source.revealLine = source.text.split('\n').length - 1;
this._recorderSources.push(source); this._recorderSources.push(source);
if (languageGenerator === orderedLanguages[0]) if (languageGenerator === orderedLanguages[0])
text = source.text; throttledOutputFile?.setContent(source.text);
} }
this.emit(ContextRecorder.Events.Change, { this.emit(ContextRecorder.Events.Change, {
sources: this._recorderSources, sources: this._recorderSources,
primaryFileName: primaryLanguage.fileName primaryFileName: primaryLanguage.fileName
}); });
}); });
if (params.outputFile) { if (throttledOutputFile) {
context.on(BrowserContext.Events.BeforeClose, () => { context.on(BrowserContext.Events.BeforeClose, () => {
fs.writeFileSync(params.outputFile!, text); throttledOutputFile.flush();
text = '';
}); });
process.on('exit', () => { process.on('exit', () => {
if (text) throttledOutputFile.flush();
fs.writeFileSync(params.outputFile!, text);
}); });
} }
this._generator = generator; this._generator = generator;
@ -590,3 +588,29 @@ function languageForFile(file: string) {
return 'csharp'; return 'csharp';
return 'javascript'; return 'javascript';
} }
class ThrottledFile {
private _file: string;
private _timer: NodeJS.Timeout | undefined;
private _text: string | undefined;
constructor(file: string) {
this._file = file;
}
setContent(text: string) {
this._text = text;
if (!this._timer)
this._timer = setTimeout(() => this.flush(), 1000);
}
flush(): void {
if (this._timer) {
clearTimeout(this._timer);
this._timer = undefined;
}
if (this._text)
fs.writeFileSync(this._file, this._text);
this._text = undefined;
}
}