mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: move actions types to recorder (#32839)
This commit is contained in:
parent
0d79291604
commit
c105de4436
@ -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':
|
||||
|
@ -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':
|
||||
|
@ -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':
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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':
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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<Page, string>, actionInContext: ActionInContext): Promise<void>;
|
||||
rewriteActionInContext?(pageAliases: Map<Page, string>, actionInContext: actions.ActionInContext): Promise<void>;
|
||||
}
|
||||
|
||||
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<FrameDescription> {
|
||||
private async _describeFrame(frame: Frame): Promise<actions.FrameDescription> {
|
||||
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<ActionInContext> {
|
||||
private async _createActionInContext(frame: Frame, action: actions.Action): Promise<actions.ActionInContext> {
|
||||
const frameDescription = await this._describeFrame(frame);
|
||||
const actionInContext: ActionInContext = {
|
||||
const actionInContext: actions.ActionInContext = {
|
||||
frame: frameDescription,
|
||||
action,
|
||||
description: undefined,
|
||||
|
@ -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<void>;
|
||||
saveSettings?(): Promise<void>;
|
||||
}
|
||||
}
|
||||
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<void> {}
|
||||
async updateCallLogs(callLogs: CallLog[]): Promise<void> {}
|
||||
async setSources(sources: Source[]): Promise<void> {}
|
||||
async setActions(actions: actions.ActionInContext[]): Promise<void> {}
|
||||
}
|
||||
|
||||
export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||
@ -167,6 +155,9 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||
}
|
||||
}
|
||||
|
||||
async setActions(actions: actions.ActionInContext[]): Promise<void> {
|
||||
}
|
||||
|
||||
async setSelector(selector: string, userGesture?: boolean): Promise<void> {
|
||||
if (userGesture) {
|
||||
if (this._recorder?.mode() === 'inspecting') {
|
||||
|
@ -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<Page, string>;
|
||||
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<void>) {
|
||||
private async _addAction(actionInContext: actions.ActionInContext, callback?: (callMetadata: CallMetadata) => Promise<void>) {
|
||||
if (!this._enabled)
|
||||
return;
|
||||
if (actionInContext.action.name === 'openPage' || actionInContext.action.name === 'closePage') {
|
||||
|
@ -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<void>;
|
||||
updateCallLogs(callLogs: CallLog[]): Promise<void>;
|
||||
setSources(sources: Source[]): Promise<void>;
|
||||
setActions(actions: actions.ActionInContext[]): Promise<void>;
|
||||
}
|
||||
|
||||
export type IRecorderAppFactory = (recorder: IRecorder) => Promise<IRecorderApp>;
|
||||
|
@ -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<void> {
|
||||
this._transport.deliverEvent('setActions', { actions });
|
||||
}
|
||||
}
|
||||
|
||||
async function openApp(trace: string, options?: TraceViewerServerOptions & { headless?: boolean }): Promise<{ wsEndpointForTest: string | undefined, tracePage: Page, traceServer: HttpServer }> {
|
||||
|
@ -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<Page, string>, actionInContext: ActionInContext) {
|
||||
export async function performAction(callMetadata: CallMetadata, pageAliases: Map<Page, string>, actionInContext: actions.ActionInContext) {
|
||||
const mainFrame = mainFrameForAction(pageAliases, actionInContext);
|
||||
const { action } = actionInContext;
|
||||
|
||||
|
@ -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<Page, string>, actionInContext: ActionInContext): Frame {
|
||||
export function mainFrameForAction(pageAliases: Map<Page, string>, 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<Page, string>, actionInConte
|
||||
return page.mainFrame();
|
||||
}
|
||||
|
||||
export async function frameForAction(pageAliases: Map<Page, string>, actionInContext: ActionInContext, action: actions.ActionWithSelector): Promise<Frame> {
|
||||
export async function frameForAction(pageAliases: Map<Page, string>, actionInContext: actions.ActionInContext, action: actions.ActionWithSelector): Promise<Frame> {
|
||||
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<Page, string>, 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<Page, string>, actionInContext: ActionInContext): { callMetadata: CallMetadata, mainFrame: Frame } {
|
||||
export function callMetadataForAction(pageAliases: Map<Page, string>, 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<Page, string>, 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<string, string>();
|
||||
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('|');
|
||||
|
@ -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;
|
||||
};
|
@ -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;
|
||||
|
@ -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 = {
|
||||
|
@ -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<Connection | null>(null);
|
||||
const [sources, setSources] = React.useState<Source[]>([]);
|
||||
const [, setActions] = React.useState<actions.ActionInContext[]>([]);
|
||||
const [model, setModel] = React.useState<{ model: MultiTraceModel, isLive: boolean, sha1: string } | undefined>();
|
||||
const [mode, setMode] = React.useState<Mode>('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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ export function useSetting<S>(name: string | undefined, defaultValue: S): [S, Re
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
saveSettings?(): Promise<void>;
|
||||
saveSettings?(): void;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user