From c105de4436cc47b54a243bca2b44d12aa3a82d77 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 26 Sep 2024 14:50:09 -0700 Subject: [PATCH] chore: move actions types to recorder (#32839) --- .../src/server/codegen/csharp.ts | 9 ++++---- .../src/server/codegen/java.ts | 7 ++++--- .../src/server/codegen/javascript.ts | 7 ++++--- .../src/server/codegen/jsonl.ts | 5 +++-- .../src/server/codegen/language.ts | 6 +++--- .../src/server/codegen/python.ts | 7 ++++--- .../src/server/codegen/types.ts | 16 ++------------ .../injected/recorder/pollingRecorder.ts | 2 +- .../src/server/injected/recorder/recorder.ts | 2 +- .../playwright-core/src/server/recorder.ts | 4 +++- .../src/server/recorder/contextRecorder.ts | 21 +++++++++++-------- .../src/server/recorder/recorderApp.ts | 21 ++++++------------- .../src/server/recorder/recorderCollection.ts | 12 +++++------ .../src/server/recorder/recorderFrontend.ts | 2 ++ .../server/recorder/recorderInTraceViewer.ts | 5 +++++ .../src/server/recorder/recorderRunner.ts | 5 ++--- .../src/server/recorder/recorderUtils.ts | 19 ++++++++--------- .../src/actions.ts} | 14 ++++++++++++- packages/trace-viewer/src/types/entries.ts | 2 ++ packages/trace-viewer/src/ui/modelUtil.ts | 5 ++--- packages/trace-viewer/src/ui/recorderView.tsx | 17 ++++++++++----- packages/web/src/uiUtils.ts | 2 +- 22 files changed, 102 insertions(+), 88 deletions(-) rename packages/{playwright-core/src/server/recorder/recorderActions.ts => recorder/src/actions.ts} (93%) diff --git a/packages/playwright-core/src/server/codegen/csharp.ts b/packages/playwright-core/src/server/codegen/csharp.ts index 13a5f17be8..8e6561f04e 100644 --- a/packages/playwright-core/src/server/codegen/csharp.ts +++ b/packages/playwright-core/src/server/codegen/csharp.ts @@ -15,7 +15,8 @@ */ import type { BrowserContextOptions } from '../../../types/types'; -import type { ActionInContext, Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import type * as actions from '@recorder/actions'; import { sanitizeDeviceOptions, toClickOptionsForSourceCode, toKeyboardModifiers, toSignalMap } from './language'; import { escapeWithQuotes, asLocator } from '../../utils'; import { deviceDescriptors } from '../deviceDescriptors'; @@ -45,14 +46,14 @@ export class CSharpLanguageGenerator implements LanguageGenerator { this._mode = mode; } - generateAction(actionInContext: ActionInContext): string { + generateAction(actionInContext: actions.ActionInContext): string { const action = this._generateActionInner(actionInContext); if (action) return action; return ''; } - _generateActionInner(actionInContext: ActionInContext): string { + _generateActionInner(actionInContext: actions.ActionInContext): string { const action = actionInContext.action; if (this._mode !== 'library' && (action.name === 'openPage' || action.name === 'closePage')) return ''; @@ -101,7 +102,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator { return formatter.format(); } - private _generateActionCall(subject: string, actionInContext: ActionInContext): string { + private _generateActionCall(subject: string, actionInContext: actions.ActionInContext): string { const action = actionInContext.action; switch (action.name) { case 'openPage': diff --git a/packages/playwright-core/src/server/codegen/java.ts b/packages/playwright-core/src/server/codegen/java.ts index 3a640d36e2..507a040bce 100644 --- a/packages/playwright-core/src/server/codegen/java.ts +++ b/packages/playwright-core/src/server/codegen/java.ts @@ -16,7 +16,8 @@ import type { BrowserContextOptions } from '../../../types/types'; import type * as types from '../types'; -import type { ActionInContext, Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import type * as actions from '@recorder/actions'; +import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; import { toClickOptionsForSourceCode, toKeyboardModifiers, toSignalMap } from './language'; import { deviceDescriptors } from '../deviceDescriptors'; import { JavaScriptFormatter } from './javascript'; @@ -44,7 +45,7 @@ export class JavaLanguageGenerator implements LanguageGenerator { this._mode = mode; } - generateAction(actionInContext: ActionInContext): string { + generateAction(actionInContext: actions.ActionInContext): string { const action = actionInContext.action; const pageAlias = actionInContext.frame.pageAlias; const offset = this._mode === 'junit' ? 4 : 6; @@ -90,7 +91,7 @@ export class JavaLanguageGenerator implements LanguageGenerator { return formatter.format(); } - private _generateActionCall(subject: string, actionInContext: ActionInContext, inFrameLocator: boolean): string { + private _generateActionCall(subject: string, actionInContext: actions.ActionInContext, inFrameLocator: boolean): string { const action = actionInContext.action; switch (action.name) { case 'openPage': diff --git a/packages/playwright-core/src/server/codegen/javascript.ts b/packages/playwright-core/src/server/codegen/javascript.ts index c3ed05d4d4..17f627b601 100644 --- a/packages/playwright-core/src/server/codegen/javascript.ts +++ b/packages/playwright-core/src/server/codegen/javascript.ts @@ -15,7 +15,8 @@ */ import type { BrowserContextOptions } from '../../../types/types'; -import type { ActionInContext, Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import type * as actions from '@recorder/actions'; import { sanitizeDeviceOptions, toSignalMap, toKeyboardModifiers, toClickOptionsForSourceCode } from './language'; import { deviceDescriptors } from '../deviceDescriptors'; import { escapeWithQuotes, asLocator } from '../../utils'; @@ -33,7 +34,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { this._isTest = isTest; } - generateAction(actionInContext: ActionInContext): string { + generateAction(actionInContext: actions.ActionInContext): string { const action = actionInContext.action; if (this._isTest && (action.name === 'openPage' || action.name === 'closePage')) return ''; @@ -74,7 +75,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { return formatter.format(); } - private _generateActionCall(subject: string, actionInContext: ActionInContext): string { + private _generateActionCall(subject: string, actionInContext: actions.ActionInContext): string { const action = actionInContext.action; switch (action.name) { case 'openPage': diff --git a/packages/playwright-core/src/server/codegen/jsonl.ts b/packages/playwright-core/src/server/codegen/jsonl.ts index 78485297b6..8f506433f0 100644 --- a/packages/playwright-core/src/server/codegen/jsonl.ts +++ b/packages/playwright-core/src/server/codegen/jsonl.ts @@ -15,7 +15,8 @@ */ import { asLocator } from '../../utils'; -import type { ActionInContext, Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import type * as actions from '@recorder/actions'; +import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; export class JsonlLanguageGenerator implements LanguageGenerator { id = 'jsonl'; @@ -23,7 +24,7 @@ export class JsonlLanguageGenerator implements LanguageGenerator { name = 'JSONL'; highlighter = 'javascript' as Language; - generateAction(actionInContext: ActionInContext): string { + generateAction(actionInContext: actions.ActionInContext): string { const locator = (actionInContext.action as any).selector ? JSON.parse(asLocator('jsonl', (actionInContext.action as any).selector)) : undefined; const entry = { ...actionInContext.action, diff --git a/packages/playwright-core/src/server/codegen/language.ts b/packages/playwright-core/src/server/codegen/language.ts index 7ee775b18b..b38959b89b 100644 --- a/packages/playwright-core/src/server/codegen/language.ts +++ b/packages/playwright-core/src/server/codegen/language.ts @@ -15,11 +15,11 @@ */ import type { BrowserContextOptions } from '../../..'; -import type * as actions from '../recorder/recorderActions'; +import type * as actions from '@recorder/actions'; import type * as types from '../types'; -import type { ActionInContext, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import type { LanguageGenerator, LanguageGeneratorOptions } from './types'; -export function generateCode(actions: ActionInContext[], languageGenerator: LanguageGenerator, options: LanguageGeneratorOptions) { +export function generateCode(actions: actions.ActionInContext[], languageGenerator: LanguageGenerator, options: LanguageGeneratorOptions) { const header = languageGenerator.generateHeader(options); const footer = languageGenerator.generateFooter(options.saveStorage); const actionTexts = actions.map(a => languageGenerator.generateAction(a)).filter(Boolean); diff --git a/packages/playwright-core/src/server/codegen/python.ts b/packages/playwright-core/src/server/codegen/python.ts index aadd455b3c..38894695bc 100644 --- a/packages/playwright-core/src/server/codegen/python.ts +++ b/packages/playwright-core/src/server/codegen/python.ts @@ -15,7 +15,8 @@ */ import type { BrowserContextOptions } from '../../../types/types'; -import type { ActionInContext, Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './types'; +import type * as actions from '@recorder/actions'; import { sanitizeDeviceOptions, toSignalMap, toKeyboardModifiers, toClickOptionsForSourceCode } from './language'; import { escapeWithQuotes, toSnakeCase, asLocator } from '../../utils'; import { deviceDescriptors } from '../deviceDescriptors'; @@ -40,7 +41,7 @@ export class PythonLanguageGenerator implements LanguageGenerator { this._asyncPrefix = isAsync ? 'async ' : ''; } - generateAction(actionInContext: ActionInContext): string { + generateAction(actionInContext: actions.ActionInContext): string { const action = actionInContext.action; if (this._isPyTest && (action.name === 'openPage' || action.name === 'closePage')) return ''; @@ -83,7 +84,7 @@ export class PythonLanguageGenerator implements LanguageGenerator { return formatter.format(); } - private _generateActionCall(subject: string, actionInContext: ActionInContext): string { + private _generateActionCall(subject: string, actionInContext: actions.ActionInContext): string { const action = actionInContext.action; switch (action.name) { case 'openPage': diff --git a/packages/playwright-core/src/server/codegen/types.ts b/packages/playwright-core/src/server/codegen/types.ts index 48c1141a3e..81cd59948b 100644 --- a/packages/playwright-core/src/server/codegen/types.ts +++ b/packages/playwright-core/src/server/codegen/types.ts @@ -15,7 +15,7 @@ */ import type { BrowserContextOptions, LaunchOptions } from '../../../types/types'; -import type * as actions from '../recorder/recorderActions'; +import type * as actions from '@recorder/actions'; import type { Language } from '../../utils'; export type { Language } from '../../utils'; @@ -27,24 +27,12 @@ export type LanguageGeneratorOptions = { saveStorage?: string; }; -export type FrameDescription = { - pageAlias: string; - framePath: string[]; -}; - -export type ActionInContext = { - frame: FrameDescription; - description?: string; - action: actions.Action; - timestamp: number; -}; - export interface LanguageGenerator { id: string; groupName: string; name: string; highlighter: Language; generateHeader(options: LanguageGeneratorOptions): string; - generateAction(actionInContext: ActionInContext): string; + generateAction(actionInContext: actions.ActionInContext): string; generateFooter(saveStorage: string | undefined): string; } diff --git a/packages/playwright-core/src/server/injected/recorder/pollingRecorder.ts b/packages/playwright-core/src/server/injected/recorder/pollingRecorder.ts index 57627f3723..9a96d8f25d 100644 --- a/packages/playwright-core/src/server/injected/recorder/pollingRecorder.ts +++ b/packages/playwright-core/src/server/injected/recorder/pollingRecorder.ts @@ -15,7 +15,7 @@ */ import type { Mode, OverlayState, UIState } from '@recorder/recorderTypes'; -import type * as actions from '../../recorder/recorderActions'; +import type * as actions from '@recorder/actions'; import type { InjectedScript } from '../injectedScript'; import { Recorder } from './recorder'; import type { RecorderDelegate } from './recorder'; diff --git a/packages/playwright-core/src/server/injected/recorder/recorder.ts b/packages/playwright-core/src/server/injected/recorder/recorder.ts index defe73192f..cdc29a1050 100644 --- a/packages/playwright-core/src/server/injected/recorder/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder/recorder.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type * as actions from '../../recorder/recorderActions'; +import type * as actions from '@recorder/actions'; import type { InjectedScript } from '../injectedScript'; import type { Point } from '../../../common/types'; import type { Mode, OverlayState, UIState } from '@recorder/recorderTypes'; diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 19776c8a29..d6bc0331f9 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -28,6 +28,7 @@ import type { CallMetadata, InstrumentationListener, SdkObject } from './instrum import { ContextRecorder, generateFrameSelector } from './recorder/contextRecorder'; import type { IRecorderAppFactory, IRecorderApp, IRecorder } from './recorder/recorderFrontend'; import { buildFullSelector, metadataToCallLog } from './recorder/recorderUtils'; +import type * as actions from '@recorder/actions'; const recorderSymbol = Symbol('recorderSymbol'); @@ -135,8 +136,9 @@ export class Recorder implements InstrumentationListener, IRecorder { this._context.instrumentation.removeListener(this); this._recorderApp?.close().catch(() => {}); }); - this._contextRecorder.on(ContextRecorder.Events.Change, (data: { sources: Source[] }) => { + this._contextRecorder.on(ContextRecorder.Events.Change, (data: { sources: Source[], actions: actions.ActionInContext[] }) => { this._recorderSources = data.sources; + recorderApp.setActions(data.actions); this._pushAllSources(); }); diff --git a/packages/playwright-core/src/server/recorder/contextRecorder.ts b/packages/playwright-core/src/server/recorder/contextRecorder.ts index a02ea9f5b4..ad6b9d0f87 100644 --- a/packages/playwright-core/src/server/recorder/contextRecorder.ts +++ b/packages/playwright-core/src/server/recorder/contextRecorder.ts @@ -21,12 +21,12 @@ import * as recorderSource from '../../generated/pollingRecorderSource'; import { eventsHelper, monotonicTime, quoteCSSAttributeValue, type RegisteredListener } from '../../utils'; import { raceAgainstDeadline } from '../../utils/timeoutRunner'; import { BrowserContext } from '../browserContext'; -import type { ActionInContext, FrameDescription, LanguageGeneratorOptions, Language, LanguageGenerator } from '../codegen/types'; +import type { LanguageGeneratorOptions, Language, LanguageGenerator } from '../codegen/types'; import { languageSet } from '../codegen/languages'; import type { Dialog } from '../dialog'; import { Frame } from '../frames'; import { Page } from '../page'; -import type * as actions from './recorderActions'; +import type * as actions from '@recorder/actions'; import { ThrottledFile } from './throttledFile'; import { RecorderCollection } from './recorderCollection'; import { generateCode } from '../codegen/language'; @@ -34,7 +34,7 @@ import { generateCode } from '../codegen/language'; type BindingSource = { frame: Frame, page: Page }; export interface ContextRecorderDelegate { - rewriteActionInContext?(pageAliases: Map, actionInContext: ActionInContext): Promise; + rewriteActionInContext?(pageAliases: Map, actionInContext: actions.ActionInContext): Promise; } export class ContextRecorder extends EventEmitter { @@ -76,7 +76,7 @@ export class ContextRecorder extends EventEmitter { }; this._collection = new RecorderCollection(codegenMode, context, this._pageAliases); - this._collection.on('change', (actions: ActionInContext[]) => { + this._collection.on('change', (actions: actions.ActionInContext[]) => { this._recorderSources = []; for (const languageGenerator of this._orderedLanguages) { const { header, footer, actionTexts, text } = generateCode(actions, languageGenerator, languageGeneratorOptions); @@ -97,7 +97,10 @@ export class ContextRecorder extends EventEmitter { if (languageGenerator === this._orderedLanguages[0]) this._throttledOutputFile?.setContent(source.text); } - this.emit(ContextRecorder.Events.Change, { sources: this._recorderSources }); + this.emit(ContextRecorder.Events.Change, { + sources: this._recorderSources, + actions + }); }); context.on(BrowserContext.Events.BeforeClose, () => { this._throttledOutputFile?.flush(); @@ -205,14 +208,14 @@ export class ContextRecorder extends EventEmitter { } } - private _describeMainFrame(page: Page): FrameDescription { + private _describeMainFrame(page: Page): actions.FrameDescription { return { pageAlias: this._pageAliases.get(page)!, framePath: [], }; } - private async _describeFrame(frame: Frame): Promise { + private async _describeFrame(frame: Frame): Promise { return { pageAlias: this._pageAliases.get(frame._page)!, framePath: await generateFrameSelector(frame), @@ -223,9 +226,9 @@ export class ContextRecorder extends EventEmitter { return this._params.testIdAttributeName || this._context.selectors().testIdAttributeName() || 'data-testid'; } - private async _createActionInContext(frame: Frame, action: actions.Action): Promise { + private async _createActionInContext(frame: Frame, action: actions.Action): Promise { const frameDescription = await this._describeFrame(frame); - const actionInContext: ActionInContext = { + const actionInContext: actions.ActionInContext = { frame: frameDescription, action, description: undefined, diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 41c92a3198..fa7327c480 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -20,27 +20,14 @@ import type { Page } from '../page'; import { ProgressController } from '../progress'; import { EventEmitter } from 'events'; import { serverSideCallMetadata } from '../instrumentation'; -import type { CallLog, EventData, Mode, Source } from '@recorder/recorderTypes'; +import type { CallLog, Mode, Source } from '@recorder/recorderTypes'; import { isUnderTest } from '../../utils'; import { mime } from '../../utilsBundle'; import { syncLocalStorageWithSettings } from '../launchApp'; import type { BrowserContext } from '../browserContext'; import { launchApp } from '../launchApp'; import type { IRecorder, IRecorderApp, IRecorderAppFactory } from './recorderFrontend'; - -declare global { - interface Window { - playwrightSetFile: (file: string) => void; - playwrightSetMode: (mode: Mode) => void; - playwrightSetPaused: (paused: boolean) => void; - playwrightSetSources: (sources: Source[]) => void; - playwrightSetOverlayVisible: (visible: boolean) => void; - playwrightSetSelector: (selector: string, focus?: boolean) => void; - playwrightUpdateLogs: (callLogs: CallLog[]) => void; - dispatch(data: EventData): Promise; - saveSettings?(): Promise; - } -} +import type * as actions from '@recorder/actions'; export class EmptyRecorderApp extends EventEmitter implements IRecorderApp { wsEndpointForTest: undefined; @@ -51,6 +38,7 @@ export class EmptyRecorderApp extends EventEmitter implements IRecorderApp { async setSelector(selector: string, userGesture?: boolean): Promise {} async updateCallLogs(callLogs: CallLog[]): Promise {} async setSources(sources: Source[]): Promise {} + async setActions(actions: actions.ActionInContext[]): Promise {} } export class RecorderApp extends EventEmitter implements IRecorderApp { @@ -167,6 +155,9 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { } } + async setActions(actions: actions.ActionInContext[]): Promise { + } + async setSelector(selector: string, userGesture?: boolean): Promise { if (userGesture) { if (this._recorder?.mode() === 'inspecting') { diff --git a/packages/playwright-core/src/server/recorder/recorderCollection.ts b/packages/playwright-core/src/server/recorder/recorderCollection.ts index 1706de39ee..ad1c65f6d9 100644 --- a/packages/playwright-core/src/server/recorder/recorderCollection.ts +++ b/packages/playwright-core/src/server/recorder/recorderCollection.ts @@ -17,8 +17,8 @@ import { EventEmitter } from 'events'; import type { Frame } from '../frames'; import type { Page } from '../page'; -import type { Signal } from './recorderActions'; -import type { ActionInContext } from '../codegen/types'; +import type { Signal } from '../../../../recorder/src/actions'; +import type * as actions from '@recorder/actions'; import { monotonicTime } from '../../utils/time'; import { callMetadataForAction, collapseActions, traceEventsToAction } from './recorderUtils'; import { serializeError } from '../errors'; @@ -28,7 +28,7 @@ import { isUnderTest } from '../../utils/debug'; import type { BrowserContext } from '../browserContext'; export class RecorderCollection extends EventEmitter { - private _actions: ActionInContext[] = []; + private _actions: actions.ActionInContext[] = []; private _enabled = false; private _pageAliases: Map; private _context: BrowserContext; @@ -55,13 +55,13 @@ export class RecorderCollection extends EventEmitter { this._enabled = enabled; } - async performAction(actionInContext: ActionInContext) { + async performAction(actionInContext: actions.ActionInContext) { await this._addAction(actionInContext, async callMetadata => { await performAction(callMetadata, this._pageAliases, actionInContext); }); } - addRecordedAction(actionInContext: ActionInContext) { + addRecordedAction(actionInContext: actions.ActionInContext) { if (['openPage', 'closePage'].includes(actionInContext.action.name)) { this._actions.push(actionInContext); this._fireChange(); @@ -70,7 +70,7 @@ export class RecorderCollection extends EventEmitter { this._addAction(actionInContext).catch(() => {}); } - private async _addAction(actionInContext: ActionInContext, callback?: (callMetadata: CallMetadata) => Promise) { + private async _addAction(actionInContext: actions.ActionInContext, callback?: (callMetadata: CallMetadata) => Promise) { if (!this._enabled) return; if (actionInContext.action.name === 'openPage' || actionInContext.action.name === 'closePage') { diff --git a/packages/playwright-core/src/server/recorder/recorderFrontend.ts b/packages/playwright-core/src/server/recorder/recorderFrontend.ts index d2cdffdca4..4c456d24ab 100644 --- a/packages/playwright-core/src/server/recorder/recorderFrontend.ts +++ b/packages/playwright-core/src/server/recorder/recorderFrontend.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import type * as actions from '@recorder/actions'; import type { CallLog, Mode, Source } from '@recorder/recorderTypes'; import type { EventEmitter } from 'events'; @@ -31,6 +32,7 @@ export interface IRecorderApp extends EventEmitter { setSelector(selector: string, userGesture?: boolean): Promise; updateCallLogs(callLogs: CallLog[]): Promise; setSources(sources: Source[]): Promise; + setActions(actions: actions.ActionInContext[]): Promise; } export type IRecorderAppFactory = (recorder: IRecorder) => Promise; diff --git a/packages/playwright-core/src/server/recorder/recorderInTraceViewer.ts b/packages/playwright-core/src/server/recorder/recorderInTraceViewer.ts index 4c84547ade..32dcb8954b 100644 --- a/packages/playwright-core/src/server/recorder/recorderInTraceViewer.ts +++ b/packages/playwright-core/src/server/recorder/recorderInTraceViewer.ts @@ -24,6 +24,7 @@ import type { BrowserContext } from '../browserContext'; import type { HttpServer, Transport } from '../../utils/httpServer'; import type { Page } from '../page'; import { ManualPromise } from '../../utils/manualPromise'; +import type * as actions from '@recorder/actions'; export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp { readonly wsEndpointForTest: string | undefined; @@ -84,6 +85,10 @@ export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp this.close(); } } + + async setActions(actions: actions.ActionInContext[]): Promise { + this._transport.deliverEvent('setActions', { actions }); + } } async function openApp(trace: string, options?: TraceViewerServerOptions & { headless?: boolean }): Promise<{ wsEndpointForTest: string | undefined, tracePage: Page, traceServer: HttpServer }> { diff --git a/packages/playwright-core/src/server/recorder/recorderRunner.ts b/packages/playwright-core/src/server/recorder/recorderRunner.ts index f5358d6097..381d9b6fc5 100644 --- a/packages/playwright-core/src/server/recorder/recorderRunner.ts +++ b/packages/playwright-core/src/server/recorder/recorderRunner.ts @@ -16,14 +16,13 @@ import { serializeExpectedTextValues } from '../../utils'; import { toKeyboardModifiers } from '../codegen/language'; -import type { ActionInContext } from '../codegen/types'; import type { CallMetadata } from '../instrumentation'; import type { Page } from '../page'; -import type * as actions from './recorderActions'; +import type * as actions from '@recorder/actions'; import type * as types from '../types'; import { buildFullSelector, mainFrameForAction } from './recorderUtils'; -export async function performAction(callMetadata: CallMetadata, pageAliases: Map, actionInContext: ActionInContext) { +export async function performAction(callMetadata: CallMetadata, pageAliases: Map, actionInContext: actions.ActionInContext) { const mainFrame = mainFrameForAction(pageAliases, actionInContext); const { action } = actionInContext; diff --git a/packages/playwright-core/src/server/recorder/recorderUtils.ts b/packages/playwright-core/src/server/recorder/recorderUtils.ts index 3cde57052a..9605d21ba6 100644 --- a/packages/playwright-core/src/server/recorder/recorderUtils.ts +++ b/packages/playwright-core/src/server/recorder/recorderUtils.ts @@ -17,9 +17,8 @@ import type { CallMetadata } from '../instrumentation'; import type { CallLog, CallLogStatus } from '@recorder/recorderTypes'; import type { Page } from '../page'; -import type { ActionInContext } from '../codegen/types'; import type { Frame } from '../frames'; -import type * as actions from './recorderActions'; +import type * as actions from '@recorder/actions'; import type * as channels from '@protocol/channels'; import type * as trace from '@trace/trace'; import { fromKeyboardModifiers, toKeyboardModifiers } from '../codegen/language'; @@ -60,7 +59,7 @@ export function buildFullSelector(framePath: string[], selector: string) { return [...framePath, selector].join(' >> internal:control=enter-frame >> '); } -export function mainFrameForAction(pageAliases: Map, actionInContext: ActionInContext): Frame { +export function mainFrameForAction(pageAliases: Map, actionInContext: actions.ActionInContext): Frame { const pageAlias = actionInContext.frame.pageAlias; const page = [...pageAliases.entries()].find(([, alias]) => pageAlias === alias)?.[0]; if (!page) @@ -68,7 +67,7 @@ export function mainFrameForAction(pageAliases: Map, actionInConte return page.mainFrame(); } -export async function frameForAction(pageAliases: Map, actionInContext: ActionInContext, action: actions.ActionWithSelector): Promise { +export async function frameForAction(pageAliases: Map, actionInContext: actions.ActionInContext, action: actions.ActionWithSelector): Promise { const pageAlias = actionInContext.frame.pageAlias; const page = [...pageAliases.entries()].find(([, alias]) => pageAlias === alias)?.[0]; if (!page) @@ -80,7 +79,7 @@ export async function frameForAction(pageAliases: Map, actionInCon return result.frame; } -export function traceParamsForAction(actionInContext: ActionInContext): { method: string, params: any } { +export function traceParamsForAction(actionInContext: actions.ActionInContext): { method: string, params: any } { const { action } = actionInContext; switch (action.name) { @@ -191,7 +190,7 @@ export function traceParamsForAction(actionInContext: ActionInContext): { method } } -export function callMetadataForAction(pageAliases: Map, actionInContext: ActionInContext): { callMetadata: CallMetadata, mainFrame: Frame } { +export function callMetadataForAction(pageAliases: Map, actionInContext: actions.ActionInContext): { callMetadata: CallMetadata, mainFrame: Frame } { const mainFrame = mainFrameForAction(pageAliases, actionInContext); const { method, params } = traceParamsForAction(actionInContext); @@ -212,8 +211,8 @@ export function callMetadataForAction(pageAliases: Map, actionInCo return { callMetadata, mainFrame }; } -export function traceEventsToAction(events: trace.TraceEvent[]): ActionInContext[] { - const result: ActionInContext[] = []; +export function traceEventsToAction(events: trace.TraceEvent[]): actions.ActionInContext[] { + const result: actions.ActionInContext[] = []; const pageAliases = new Map(); let lastDownloadOrdinal = 0; let lastDialogOrdinal = 0; @@ -487,8 +486,8 @@ export function traceEventsToAction(events: trace.TraceEvent[]): ActionInContext return result; } -export function collapseActions(actions: ActionInContext[]): ActionInContext[] { - const result: ActionInContext[] = []; +export function collapseActions(actions: actions.ActionInContext[]): actions.ActionInContext[] { + const result: actions.ActionInContext[] = []; for (const action of actions) { const lastAction = result[result.length - 1]; const isSameAction = lastAction && lastAction.action.name === action.action.name && lastAction.frame.pageAlias === action.frame.pageAlias && lastAction.frame.framePath.join('|') === action.frame.framePath.join('|'); diff --git a/packages/playwright-core/src/server/recorder/recorderActions.ts b/packages/recorder/src/actions.ts similarity index 93% rename from packages/playwright-core/src/server/recorder/recorderActions.ts rename to packages/recorder/src/actions.ts index 9447f32457..c416684b2a 100644 --- a/packages/playwright-core/src/server/recorder/recorderActions.ts +++ b/packages/recorder/src/actions.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Point } from '../../common/types'; +type Point = { x: number, y: number }; export type ActionName = 'check' | @@ -143,3 +143,15 @@ export type DialogSignal = BaseSignal & { }; export type Signal = NavigationSignal | PopupSignal | DownloadSignal | DialogSignal; + +export type FrameDescription = { + pageAlias: string; + framePath: string[]; +}; + +export type ActionInContext = { + frame: FrameDescription; + description?: string; + action: Action; + timestamp: number; +}; diff --git a/packages/trace-viewer/src/types/entries.ts b/packages/trace-viewer/src/types/entries.ts index df6dc7972e..e7b7a0b977 100644 --- a/packages/trace-viewer/src/types/entries.ts +++ b/packages/trace-viewer/src/types/entries.ts @@ -18,6 +18,8 @@ import type { Language } from 'playwright-core/src/utils/isomorphic/locatorGener import type { ResourceSnapshot } from '@trace/snapshot'; import type * as trace from '@trace/trace'; +// *Entry structures are used to pass the trace between the sw and the page. + export type ContextEntry = { origin: 'testRunner'|'library'; traceUrl: string; diff --git a/packages/trace-viewer/src/ui/modelUtil.ts b/packages/trace-viewer/src/ui/modelUtil.ts index 92dfdb1e8f..b639a4c03d 100644 --- a/packages/trace-viewer/src/ui/modelUtil.ts +++ b/packages/trace-viewer/src/ui/modelUtil.ts @@ -18,7 +18,7 @@ import type { Language } from '@isomorphic/locatorGenerators'; import type { ResourceSnapshot } from '@trace/snapshot'; import type * as trace from '@trace/trace'; import type { ActionTraceEvent } from '@trace/trace'; -import type { ContextEntry, PageEntry } from '../types/entries'; +import type { ActionEntry, ContextEntry, PageEntry } from '../types/entries'; import type { StackFrame } from '@protocol/channels'; const contextSymbol = Symbol('context'); @@ -39,9 +39,8 @@ export type SourceModel = { content: string | undefined; }; -export type ActionTraceEventInContext = ActionTraceEvent & { +export type ActionTraceEventInContext = ActionEntry & { context: ContextEntry; - log: { time: number, message: string }[]; }; export type ActionTreeItem = { diff --git a/packages/trace-viewer/src/ui/recorderView.tsx b/packages/trace-viewer/src/ui/recorderView.tsx index 23c223eede..329deb5568 100644 --- a/packages/trace-viewer/src/ui/recorderView.tsx +++ b/packages/trace-viewer/src/ui/recorderView.tsx @@ -37,6 +37,7 @@ import { Toolbar } from '@web/components/toolbar'; import { ToolbarButton, ToolbarSeparator } from '@web/components/toolbarButton'; import { toggleTheme } from '@web/theme'; import { SourceChooser } from '@web/components/sourceChooser'; +import type * as actions from '@recorder/actions'; const searchParams = new URLSearchParams(window.location.search); const guid = searchParams.get('ws'); @@ -45,6 +46,7 @@ const traceLocation = searchParams.get('trace') + '.json'; export const RecorderView: React.FunctionComponent = () => { const [connection, setConnection] = React.useState(null); const [sources, setSources] = React.useState([]); + const [, setActions] = React.useState([]); const [model, setModel] = React.useState<{ model: MultiTraceModel, isLive: boolean, sha1: string } | undefined>(); const [mode, setMode] = React.useState('none'); const [counter, setCounter] = React.useState(0); @@ -54,7 +56,7 @@ export const RecorderView: React.FunctionComponent = () => { const wsURL = new URL(`../${guid}`, window.location.toString()); wsURL.protocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:'); const webSocket = new WebSocket(wsURL.toString()); - setConnection(new Connection(webSocket, { setMode, setSources })); + setConnection(new Connection(webSocket, { setMode, setSources, setActions })); return () => { webSocket.close(); }; @@ -336,8 +338,9 @@ const SnapshotContainer: React.FunctionComponent<{ }; type ConnectionOptions = { - setSources: (sources: Source[]) => void; setMode: (mode: Mode) => void; + setSources: (sources: Source[]) => void; + setActions: (actions: actions.ActionInContext[]) => void; }; class Connection { @@ -387,14 +390,18 @@ class Connection { } private _dispatchEvent(method: string, params?: any) { + if (method === 'setMode') { + const { mode } = params as { mode: Mode }; + this._options.setMode(mode); + } if (method === 'setSources') { const { sources } = params as { sources: Source[] }; this._options.setSources(sources); window.playwrightSourcesEchoForTest = sources; } - if (method === 'setMode') { - const { mode } = params as { mode: Mode }; - this._options.setMode(mode); + if (method === 'setActions') { + const { actions } = params as { actions: actions.ActionInContext[] }; + this._options.setActions(actions); } } } diff --git a/packages/web/src/uiUtils.ts b/packages/web/src/uiUtils.ts index df344df394..2697177c6f 100644 --- a/packages/web/src/uiUtils.ts +++ b/packages/web/src/uiUtils.ts @@ -162,7 +162,7 @@ export function useSetting(name: string | undefined, defaultValue: S): [S, Re declare global { interface Window { - saveSettings?(): Promise; + saveSettings?(): void; } }