diff --git a/src/client/browserContext.ts b/src/client/browserContext.ts index 03f819e867..5b2bb29e0e 100644 --- a/src/client/browserContext.ts +++ b/src/client/browserContext.ts @@ -66,11 +66,21 @@ export class BrowserContext extends ChannelOwner this._onRoute(network.Route.from(route), network.Request.from(request))); this._stdout = process.stdout; this._stderr = process.stderr; - this._channel.on('stdout', ({ data }) => this._stdout.write(Buffer.from(data, 'base64'))); - this._channel.on('stderr', ({ data }) => this._stderr.write(Buffer.from(data, 'base64'))); + this._channel.on('stdout', ({ data }) => { + this._stdout.write(Buffer.from(data, 'base64')); + this._pushTerminalSize(); + }); + this._channel.on('stderr', ({ data }) => { + this._stderr.write(Buffer.from(data, 'base64')); + this._pushTerminalSize(); + }); this._closedPromise = new Promise(f => this.once(Events.BrowserContext.Close, f)); } + private _pushTerminalSize() { + this._channel.setTerminalSizeNoReply({ rows: process.stdout.rows, columns: process.stdout.columns }).catch(() => {}); + } + private _onPage(page: Page): void { this._pages.add(page); this.emit(Events.BrowserContext.Page, page); @@ -279,6 +289,7 @@ export class BrowserContext extends ChannelOwner { + this._context.terminalSize = params; + } } diff --git a/src/protocol/channels.ts b/src/protocol/channels.ts index 0a72f8868f..a142d46d6b 100644 --- a/src/protocol/channels.ts +++ b/src/protocol/channels.ts @@ -562,6 +562,7 @@ export interface BrowserContextChannel extends Channel { pause(params?: BrowserContextPauseParams, metadata?: Metadata): Promise; recorderSupplementEnable(params: BrowserContextRecorderSupplementEnableParams, metadata?: Metadata): Promise; crNewCDPSession(params: BrowserContextCrNewCDPSessionParams, metadata?: Metadata): Promise; + setTerminalSizeNoReply(params: BrowserContextSetTerminalSizeNoReplyParams, metadata?: Metadata): Promise; } export type BrowserContextBindingCallEvent = { binding: BindingCallChannel, @@ -741,6 +742,15 @@ export type BrowserContextCrNewCDPSessionOptions = { export type BrowserContextCrNewCDPSessionResult = { session: CDPSessionChannel, }; +export type BrowserContextSetTerminalSizeNoReplyParams = { + rows?: number, + columns?: number, +}; +export type BrowserContextSetTerminalSizeNoReplyOptions = { + rows?: number, + columns?: number, +}; +export type BrowserContextSetTerminalSizeNoReplyResult = void; // ----------- Page ----------- export type PageInitializer = { diff --git a/src/protocol/protocol.yml b/src/protocol/protocol.yml index 8cd3171aa0..8fb910c40b 100644 --- a/src/protocol/protocol.yml +++ b/src/protocol/protocol.yml @@ -624,6 +624,11 @@ BrowserContext: returns: session: CDPSession + setTerminalSizeNoReply: + parameters: + rows: number? + columns: number? + events: bindingCall: diff --git a/src/protocol/validator.ts b/src/protocol/validator.ts index 4978c65c6a..2252c96ec9 100644 --- a/src/protocol/validator.ts +++ b/src/protocol/validator.ts @@ -351,6 +351,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { scheme.BrowserContextCrNewCDPSessionParams = tObject({ page: tChannel('Page'), }); + scheme.BrowserContextSetTerminalSizeNoReplyParams = tObject({ + rows: tOptional(tNumber), + columns: tOptional(tNumber), + }); scheme.PageSetDefaultNavigationTimeoutNoReplyParams = tObject({ timeout: tNumber, }); diff --git a/src/server/browserContext.ts b/src/server/browserContext.ts index 53dd515a06..db1e93aa0d 100644 --- a/src/server/browserContext.ts +++ b/src/server/browserContext.ts @@ -119,6 +119,7 @@ export abstract class BrowserContext extends EventEmitter { private _selectors?: Selectors; readonly _actionListeners = new Set(); private _origins = new Set(); + terminalSize: { rows?: number, columns?: number } = {}; constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) { super(); diff --git a/src/server/supplements/injected/recorder.ts b/src/server/supplements/injected/recorder.ts index f9fc47c91a..dee5a48f40 100644 --- a/src/server/supplements/injected/recorder.ts +++ b/src/server/supplements/injected/recorder.ts @@ -29,6 +29,7 @@ declare global { playwrightRecorderSetUIState: (state: SetUIState) => Promise; playwrightRecorderResume: () => Promise; playwrightRecorderShowRecorderPage: () => Promise; + playwrightRecorderPrintSelector: (text: string) => Promise; } } @@ -276,8 +277,10 @@ export class Recorder { private _onClick(event: MouseEvent) { if (this._state.uiState.mode === 'inspecting' && !this._isInToolbar(event.target as HTMLElement)) { - if (this._hoveredModel) + if (this._hoveredModel) { copy(this._hoveredModel.selector); + window.playwrightRecorderPrintSelector(this._hoveredModel.selector); + } } if (this._shouldIgnoreMouseEvent(event)) return; diff --git a/src/server/supplements/recorder/outputs.ts b/src/server/supplements/recorder/outputs.ts index 7550dfd502..93acb2c878 100644 --- a/src/server/supplements/recorder/outputs.ts +++ b/src/server/supplements/recorder/outputs.ts @@ -26,6 +26,7 @@ export interface RecorderOutput { export interface Writable { write(data: string): void; + columns(): number; } export class OutputMultiplexer implements RecorderOutput { @@ -156,7 +157,7 @@ export class TerminalOutput implements RecorderOutput { } popLn(text: string) { - const terminalWidth = process.stdout.columns || 80; + const terminalWidth = this._output.columns(); for (const line of text.split('\n')) { const terminalLines = ((line.length - 1) / terminalWidth | 0) + 1; for (let i = 0; i < terminalLines; ++i) diff --git a/src/server/supplements/recorderSupplement.ts b/src/server/supplements/recorderSupplement.ts index e8bde2ea14..ecfd16dafb 100644 --- a/src/server/supplements/recorderSupplement.ts +++ b/src/server/supplements/recorderSupplement.ts @@ -82,7 +82,8 @@ export class RecorderSupplement { highlighterType = 'python'; const writable: Writable = { - write: (text: string) => context.emit(BrowserContext.Events.StdOut, text) + write: (text: string) => context.emit(BrowserContext.Events.StdOut, text), + columns: () => context.terminalSize.columns || 80 }; const outputs: RecorderOutput[] = [params.terminal ? new TerminalOutput(writable, highlighterType) : new FlushingTerminalOutput(writable)]; this._highlighterType = highlighterType; @@ -142,7 +143,11 @@ export class RecorderSupplement { }).catch(e => console.error(e)); }); - await this._context.exposeBinding('playwrightRecorderState', false, ({ page }) => { + await this._context.exposeBinding('playwrightRecorderPrintSelector', false, (_, text) => { + this._context.emit(BrowserContext.Events.StdOut, `Selector: \x1b[38;5;130m${text}\x1b[0m\n`); + }); + + await this._context.exposeBinding('playwrightRecorderState', false, () => { const state: State = { uiState: this._recorderUIState, canResume: this._app === 'pause', diff --git a/src/web/components/source.css b/src/web/components/source.css index dc28021770..70968ace74 100644 --- a/src/web/components/source.css +++ b/src/web/components/source.css @@ -40,6 +40,10 @@ user-select: none; } +.source-line-number { + flex: none; +} + .source-line-highlighted { background-color: #ffc0cb7f; } \ No newline at end of file