parent
ecd147ce43
commit
6cfcbe0d6d
|
@ -140,6 +140,7 @@ export class Recorder implements InstrumentationListener, IRecorder {
|
|||
this._contextRecorder.on(ContextRecorder.Events.Change, (data: { sources: Source[], actions: actions.ActionInContext[] }) => {
|
||||
this._recorderSources = data.sources;
|
||||
recorderApp.setActions(data.actions, data.sources);
|
||||
recorderApp.setRunningFile(undefined);
|
||||
this._pushAllSources();
|
||||
});
|
||||
|
||||
|
@ -299,7 +300,7 @@ export class Recorder implements InstrumentationListener, IRecorder {
|
|||
}
|
||||
this._pushAllSources();
|
||||
if (fileToSelect)
|
||||
this._recorderApp?.setFile(fileToSelect);
|
||||
this._recorderApp?.setRunningFile(fileToSelect);
|
||||
}
|
||||
|
||||
private _pushAllSources() {
|
||||
|
|
|
@ -34,7 +34,7 @@ export class EmptyRecorderApp extends EventEmitter implements IRecorderApp {
|
|||
async close(): Promise<void> {}
|
||||
async setPaused(paused: boolean): Promise<void> {}
|
||||
async setMode(mode: Mode): Promise<void> {}
|
||||
async setFile(file: string): Promise<void> {}
|
||||
async setRunningFile(file: string | undefined): Promise<void> {}
|
||||
async setSelector(selector: string, userGesture?: boolean): Promise<void> {}
|
||||
async updateCallLogs(callLogs: CallLog[]): Promise<void> {}
|
||||
async setSources(sources: Source[]): Promise<void> {}
|
||||
|
@ -131,9 +131,9 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
|||
}).toString(), { isFunction: true }, mode).catch(() => {});
|
||||
}
|
||||
|
||||
async setFile(file: string): Promise<void> {
|
||||
async setRunningFile(file: string | undefined): Promise<void> {
|
||||
await this._page.mainFrame().evaluateExpression(((file: string) => {
|
||||
window.playwrightSetFile(file);
|
||||
window.playwrightSetRunningFile(file);
|
||||
}).toString(), { isFunction: true }, file).catch(() => {});
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ export interface IRecorderApp extends EventEmitter {
|
|||
close(): Promise<void>;
|
||||
setPaused(paused: boolean): Promise<void>;
|
||||
setMode(mode: Mode): Promise<void>;
|
||||
setFile(file: string): Promise<void>;
|
||||
setRunningFile(file: string | undefined): Promise<void>;
|
||||
setSelector(selector: string, userGesture?: boolean): Promise<void>;
|
||||
updateCallLogs(callLogs: CallLog[]): Promise<void>;
|
||||
setSources(sources: Source[]): Promise<void>;
|
||||
|
|
|
@ -66,8 +66,8 @@ export class RecorderInTraceViewer extends EventEmitter implements IRecorderApp
|
|||
this._transport.deliverEvent('setMode', { mode });
|
||||
}
|
||||
|
||||
async setFile(file: string): Promise<void> {
|
||||
this._transport.deliverEvent('setFileIfNeeded', { file });
|
||||
async setRunningFile(file: string | undefined): Promise<void> {
|
||||
this._transport.deliverEvent('setRunningFile', { file });
|
||||
}
|
||||
|
||||
async setSelector(selector: string, userGesture?: boolean): Promise<void> {
|
||||
|
|
|
@ -41,13 +41,11 @@ export const Recorder: React.FC<RecorderProps> = ({
|
|||
log,
|
||||
mode,
|
||||
}) => {
|
||||
const [fileId, setFileId] = React.useState<string | undefined>();
|
||||
const [selectedFileId, setSelectedFileId] = React.useState<string | undefined>();
|
||||
const [runningFileId, setRunningFileId] = React.useState<string | undefined>();
|
||||
const [selectedTab, setSelectedTab] = React.useState<string>('log');
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!fileId && sources.length > 0)
|
||||
setFileId(sources[0].id);
|
||||
}, [fileId, sources]);
|
||||
const fileId = selectedFileId || runningFileId || sources[0]?.id;
|
||||
|
||||
const source = React.useMemo(() => {
|
||||
if (fileId) {
|
||||
|
@ -66,7 +64,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
|||
setLocator(asLocator(language, selector));
|
||||
};
|
||||
|
||||
window.playwrightSetFile = setFileId;
|
||||
window.playwrightSetRunningFile = setRunningFileId;
|
||||
|
||||
const messagesEndRef = React.useRef<HTMLDivElement>(null);
|
||||
React.useLayoutEffect(() => {
|
||||
|
@ -134,19 +132,19 @@ export const Recorder: React.FC<RecorderProps> = ({
|
|||
<ToolbarButton icon='files' title='Copy' disabled={!source || !source.text} onClick={() => {
|
||||
copy(source.text);
|
||||
}}></ToolbarButton>
|
||||
<ToolbarButton icon='debug-continue' title='Resume (F8)' disabled={!paused} onClick={() => {
|
||||
<ToolbarButton icon='debug-continue' title='Resume (F8)' ariaLabel='Resume' disabled={!paused} onClick={() => {
|
||||
window.dispatch({ event: 'resume' });
|
||||
}}></ToolbarButton>
|
||||
<ToolbarButton icon='debug-pause' title='Pause (F8)' disabled={paused} onClick={() => {
|
||||
<ToolbarButton icon='debug-pause' title='Pause (F8)' ariaLabel='Pause' disabled={paused} onClick={() => {
|
||||
window.dispatch({ event: 'pause' });
|
||||
}}></ToolbarButton>
|
||||
<ToolbarButton icon='debug-step-over' title='Step over (F10)' disabled={!paused} onClick={() => {
|
||||
<ToolbarButton icon='debug-step-over' title='Step over (F10)' ariaLabel='Step over' disabled={!paused} onClick={() => {
|
||||
window.dispatch({ event: 'step' });
|
||||
}}></ToolbarButton>
|
||||
<div style={{ flex: 'auto' }}></div>
|
||||
<div>Target:</div>
|
||||
<SourceChooser fileId={fileId} sources={sources} setFileId={fileId => {
|
||||
setFileId(fileId);
|
||||
setSelectedFileId(fileId);
|
||||
window.dispatch({ event: 'fileChanged', params: { file: fileId } });
|
||||
}} />
|
||||
<ToolbarButton icon='clear-all' title='Clear' disabled={!source || !source.text} onClick={() => {
|
||||
|
|
|
@ -96,7 +96,7 @@ declare global {
|
|||
playwrightSetSources: (sources: Source[]) => void;
|
||||
playwrightSetOverlayVisible: (visible: boolean) => void;
|
||||
playwrightUpdateLogs: (callLogs: CallLog[]) => void;
|
||||
playwrightSetFile: (file: string) => void;
|
||||
playwrightSetRunningFile: (file: string | undefined) => void;
|
||||
playwrightSetSelector: (selector: string, focus?: boolean) => void;
|
||||
playwrightSourcesEchoForTest: Source[];
|
||||
dispatch(data: any): Promise<void>;
|
||||
|
|
|
@ -22,7 +22,7 @@ export const SourceChooser: React.FC<{
|
|||
fileId: string | undefined,
|
||||
setFileId: (fileId: string) => void,
|
||||
}> = ({ sources, fileId, setFileId }) => {
|
||||
return <select className='source-chooser' hidden={!sources.length} value={fileId} onChange={event => {
|
||||
return <select className='source-chooser' hidden={!sources.length} title='Source chooser' value={fileId} onChange={event => {
|
||||
setFileId(event.target.selectedOptions[0].value);
|
||||
}}>{renderSourceOptions(sources)}</select>;
|
||||
};
|
||||
|
@ -33,17 +33,21 @@ function renderSourceOptions(sources: Source[]): React.ReactNode {
|
|||
<option key={source.id} value={source.id}>{transformTitle(source.label)}</option>
|
||||
);
|
||||
|
||||
const hasGroup = sources.some(s => s.group);
|
||||
if (hasGroup) {
|
||||
const groups = new Set(sources.map(s => s.group));
|
||||
return [...groups].filter(Boolean).map(group => (
|
||||
<optgroup label={group} key={group}>
|
||||
{sources.filter(s => s.group === group).map(source => renderOption(source))}
|
||||
</optgroup>
|
||||
));
|
||||
const sourcesByGroups = new Map<string, Source[]>();
|
||||
for (const source of sources) {
|
||||
let list = sourcesByGroups.get(source.group || 'Debugger');
|
||||
if (!list) {
|
||||
list = [];
|
||||
sourcesByGroups.set(source.group || 'Debugger', list);
|
||||
}
|
||||
list.push(source);
|
||||
}
|
||||
|
||||
return sources.map(source => renderOption(source));
|
||||
return [...sourcesByGroups.entries()].map(([group, sources]) => (
|
||||
<optgroup label={group} key={group}>
|
||||
{sources.filter(s => (s.group || 'Debugger') === group).map(source => renderOption(source))}
|
||||
</optgroup>
|
||||
));
|
||||
}
|
||||
|
||||
export function emptySource(): Source {
|
||||
|
|
|
@ -28,6 +28,7 @@ export interface ToolbarButtonProps {
|
|||
style?: React.CSSProperties,
|
||||
testId?: string,
|
||||
className?: string,
|
||||
ariaLabel?: string,
|
||||
}
|
||||
|
||||
export const ToolbarButton: React.FC<React.PropsWithChildren<ToolbarButtonProps>> = ({
|
||||
|
@ -40,6 +41,7 @@ export const ToolbarButton: React.FC<React.PropsWithChildren<ToolbarButtonProps>
|
|||
style,
|
||||
testId,
|
||||
className,
|
||||
ariaLabel,
|
||||
}) => {
|
||||
return <button
|
||||
className={clsx(className, 'toolbar-button', icon, toggled && 'toggled')}
|
||||
|
@ -50,6 +52,7 @@ export const ToolbarButton: React.FC<React.PropsWithChildren<ToolbarButtonProps>
|
|||
disabled={!!disabled}
|
||||
style={style}
|
||||
data-testid={testId}
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{icon && <span className={`codicon codicon-${icon}`} style={children ? { marginRight: 5 } : {}}></span>}
|
||||
{children}
|
||||
|
|
|
@ -103,6 +103,7 @@ it.describe('pause', () => {
|
|||
await page.pause();
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await expect(recorderPage.getByRole('combobox', { name: 'Source chooser' })).toHaveValue(/pause\.spec\.ts/);
|
||||
const source = await recorderPage.textContent('.source-line-paused');
|
||||
expect(source).toContain('page.pause()');
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
|
@ -480,6 +481,21 @@ it.describe('pause', () => {
|
|||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should record from debugger', async ({ page, recorderPageGetter }) => {
|
||||
const scriptPromise = (async () => {
|
||||
await page.pause();
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await expect(recorderPage.getByRole('combobox', { name: 'Source chooser' })).toHaveValue(/pause\.spec\.ts/);
|
||||
await expect(recorderPage.locator('.source-line-paused')).toHaveText(/await page\.pause\(\)/);
|
||||
await recorderPage.getByRole('button', { name: 'Record' }).click();
|
||||
await page.locator('body').click();
|
||||
await expect(recorderPage.getByRole('combobox', { name: 'Source chooser' })).toHaveValue('javascript');
|
||||
await expect(recorderPage.locator('.cm-wrapper')).toContainText(`await page.locator('body').click();`);
|
||||
await recorderPage.getByRole('button', { name: 'Resume' }).click();
|
||||
await scriptPromise;
|
||||
});
|
||||
});
|
||||
|
||||
async function sanitizeLog(recorderPage: Page): Promise<string[]> {
|
||||
|
|
Loading…
Reference in New Issue