From 13596b7be35689ac486c7312537e5a794352eb8a Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 15 Aug 2022 19:44:46 +0200 Subject: [PATCH] chore: language specific dropdowns in codegen (#16452) --- .../playwright-core/src/server/recorder.ts | 12 +++-- .../src/server/recorder/csharp.ts | 3 +- .../src/server/recorder/java.ts | 3 +- .../src/server/recorder/javascript.ts | 5 +- .../src/server/recorder/language.ts | 3 +- .../src/server/recorder/python.ts | 7 +-- .../src/server/recorder/recorderTypes.ts | 5 +- packages/recorder/src/recorder.tsx | 48 +++++++++++++------ tests/library/inspector/inspectorTest.ts | 26 ++++++++-- 9 files changed, 78 insertions(+), 34 deletions(-) diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 457c108dd1..a5f2808164 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -251,14 +251,14 @@ export class Recorder implements InstrumentationListener { const { file, line } = metadata.stack[0]; let source = this._userSources.get(file); if (!source) { - source = { isRecorded: false, file, text: this._readSource(file), highlight: [], language: languageForFile(file) }; + source = { isRecorded: false, label: file, id: file, text: this._readSource(file), highlight: [], language: languageForFile(file) }; this._userSources.set(file, source); } if (line) { const paused = this._debugger.isPaused(metadata); source.highlight.push({ line, type: metadata.error ? 'error' : (paused ? 'paused' : 'running') }); source.revealLine = line; - fileToSelect = source.file; + fileToSelect = source.id; } } this._pushAllSources(); @@ -333,7 +333,9 @@ class ContextRecorder extends EventEmitter { for (const languageGenerator of this._orderedLanguages) { const source: Source = { isRecorded: true, - file: languageGenerator.fileName, + label: languageGenerator.name, + group: languageGenerator.groupName, + id: languageGenerator.id, text: generator.generateText(languageGenerator), language: languageGenerator.highlighter, highlight: [] @@ -345,7 +347,7 @@ class ContextRecorder extends EventEmitter { } this.emit(ContextRecorder.Events.Change, { sources: this._recorderSources, - primaryFileName: this._orderedLanguages[0].fileName + primaryFileName: this._orderedLanguages[0].id }); }); context.on(BrowserContext.Events.BeforeClose, () => { @@ -362,9 +364,9 @@ class ContextRecorder extends EventEmitter { new JavaLanguageGenerator(), new JavaScriptLanguageGenerator(false), new JavaScriptLanguageGenerator(true), + new PythonLanguageGenerator(false, true), new PythonLanguageGenerator(false, false), new PythonLanguageGenerator(true, false), - new PythonLanguageGenerator(false, true), new CSharpLanguageGenerator(), ]); const primaryLanguage = [...languages].find(l => l.id === language); diff --git a/packages/playwright-core/src/server/recorder/csharp.ts b/packages/playwright-core/src/server/recorder/csharp.ts index ecc62868c0..ccb0b47547 100644 --- a/packages/playwright-core/src/server/recorder/csharp.ts +++ b/packages/playwright-core/src/server/recorder/csharp.ts @@ -27,7 +27,8 @@ import deviceDescriptors from '../deviceDescriptors'; export class CSharpLanguageGenerator implements LanguageGenerator { id = 'csharp'; - fileName = 'C#'; + groupName = '.NET'; + name = 'Library C#'; highlighter = 'csharp'; generateAction(actionInContext: ActionInContext): string { diff --git a/packages/playwright-core/src/server/recorder/java.ts b/packages/playwright-core/src/server/recorder/java.ts index af08e8af96..c0221a69ce 100644 --- a/packages/playwright-core/src/server/recorder/java.ts +++ b/packages/playwright-core/src/server/recorder/java.ts @@ -28,7 +28,8 @@ import { escapeWithQuotes } from '../../utils/isomorphic/stringUtils'; export class JavaLanguageGenerator implements LanguageGenerator { id = 'java'; - fileName = 'Java'; + groupName = 'Java'; + name = 'Library'; highlighter = 'java'; generateAction(actionInContext: ActionInContext): string { diff --git a/packages/playwright-core/src/server/recorder/javascript.ts b/packages/playwright-core/src/server/recorder/javascript.ts index 3182c7cdf8..af5118d5a1 100644 --- a/packages/playwright-core/src/server/recorder/javascript.ts +++ b/packages/playwright-core/src/server/recorder/javascript.ts @@ -27,13 +27,14 @@ import { escapeWithQuotes } from '../../utils/isomorphic/stringUtils'; export class JavaScriptLanguageGenerator implements LanguageGenerator { id: string; - fileName: string; + groupName = 'Node.js'; + name: string; highlighter = 'javascript'; private _isTest: boolean; constructor(isTest: boolean) { this.id = isTest ? 'test' : 'javascript'; - this.fileName = isTest ? 'Playwright Test' : 'JavaScript'; + this.name = isTest ? 'Test Runner' : 'Library'; this._isTest = isTest; } diff --git a/packages/playwright-core/src/server/recorder/language.ts b/packages/playwright-core/src/server/recorder/language.ts index 2d93fbbed7..b646c76807 100644 --- a/packages/playwright-core/src/server/recorder/language.ts +++ b/packages/playwright-core/src/server/recorder/language.ts @@ -28,7 +28,8 @@ export type LanguageGeneratorOptions = { export interface LanguageGenerator { id: string; - fileName: string; + groupName: string; + name: string; highlighter: string; generateHeader(options: LanguageGeneratorOptions): string; generateAction(actionInContext: ActionInContext): string; diff --git a/packages/playwright-core/src/server/recorder/python.ts b/packages/playwright-core/src/server/recorder/python.ts index 21fcced3cc..32aa101a44 100644 --- a/packages/playwright-core/src/server/recorder/python.ts +++ b/packages/playwright-core/src/server/recorder/python.ts @@ -26,8 +26,9 @@ import { escapeWithQuotes } from '../../utils/isomorphic/stringUtils'; import deviceDescriptors from '../deviceDescriptors'; export class PythonLanguageGenerator implements LanguageGenerator { - id = 'python'; - fileName = 'Python'; + id: string; + groupName = 'Python'; + name: string; highlighter = 'python'; private _awaitPrefix: '' | 'await '; @@ -37,7 +38,7 @@ export class PythonLanguageGenerator implements LanguageGenerator { constructor(isAsync: boolean, isPyTest: boolean) { this.id = isPyTest ? 'pytest' : (isAsync ? 'python-async' : 'python'); - this.fileName = isPyTest ? 'Pytest' : (isAsync ? 'Python Async' : 'Python'); + this.name = isPyTest ? 'Pytest' : (isAsync ? 'Library Async' : 'Library'); this._isAsync = isAsync; this._isPyTest = isPyTest; this._awaitPrefix = isAsync ? 'await ' : ''; diff --git a/packages/playwright-core/src/server/recorder/recorderTypes.ts b/packages/playwright-core/src/server/recorder/recorderTypes.ts index 6e3e3931ec..7fa30477d7 100644 --- a/packages/playwright-core/src/server/recorder/recorderTypes.ts +++ b/packages/playwright-core/src/server/recorder/recorderTypes.ts @@ -53,9 +53,12 @@ export type SourceHighlight = { export type Source = { isRecorded: boolean; - file: string; + id: string; + label: string; text: string; language: string; highlight: SourceHighlight[]; revealLine?: number; + // used to group the language generators + group?: string; }; diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index ff20979e95..0d23de2639 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -53,22 +53,27 @@ export const Recorder: React.FC = ({ setFocusSelectorInput(!!focus); }; - const [f, setFile] = React.useState(); - const file = f || sources[0]?.file; + const [fileId, setFileId] = React.useState(); - const source = sources.find(s => s.file === file) || { + React.useEffect(() => { + if (!fileId && sources.length > 0) + setFileId(sources[0].id); + }, [fileId, sources]); + + const source: Source = sources.find(s => s.id === fileId) || { + id: 'default', isRecorded: false, text: '', language: 'javascript', - file: '', + label: '', highlight: [] }; window.playwrightSetFileIfNeeded = (value: string) => { - const newSource = sources.find(s => s.file === value); + const newSource = sources.find(s => s.id === value); // Do not forcefully switch between two recorded sources, because // user did explicitly choose one. if (newSource && !newSource.isRecorded || !source.isRecorded) - setFile(value); + setFileId(value); }; const messagesEndRef = React.createRef(); @@ -125,15 +130,9 @@ export const Recorder: React.FC = ({ }}>
Target:
- + { window.dispatch({ event: 'clear' }); }}> @@ -159,6 +158,25 @@ export const Recorder: React.FC = ({ ; }; +function renderSourceOptions(sources: Source[]): React.ReactNode { + const transformTitle = (title: string): string => title.replace(/.*[/\\]([^/\\]+)/, '$1'); + const renderOption = (source: Source): React.ReactNode => ( + + ); + + const hasGroup = sources.some(s => s.group); + if (hasGroup) { + const groups = new Set(sources.map(s => s.group)); + return Array.from(groups).map(group => ( + + {sources.filter(s => s.group === group).map(source => renderOption(source))} + + )); + } + + return sources.map(source => renderOption(source)); +} + function copy(text: string) { const textArea = document.createElement('textarea'); textArea.style.position = 'absolute'; diff --git a/tests/library/inspector/inspectorTest.ts b/tests/library/inspector/inspectorTest.ts index fe88e18d6d..cc952207eb 100644 --- a/tests/library/inspector/inspectorTest.ts +++ b/tests/library/inspector/inspectorTest.ts @@ -28,6 +28,17 @@ type CLITestArgs = { runCLI: (args: string[], options?: { noAutoExit?: boolean }) => CLIMock; }; +const codegenLang2Id: Map = new Map([ + ['JavaScript', 'javascript'], + ['Java', 'java'], + ['Python', 'python'], + ['Python Async', 'python-async'], + ['Pytest', 'pytest'], + ['C#', 'csharp'], + ['Playwright Test', 'test'], +]); +const codegenLangId2lang = new Map([...codegenLang2Id.entries()].map(([lang, langId]) => [langId, lang])); + const playwrightToAutomateInspector = require('../../../packages/playwright-core/lib/inProcessFactory').createInProcessPlaywright(); export const test = contextTest.extend({ @@ -115,14 +126,19 @@ class Recorder { } async waitForOutput(file: string, text: string): Promise> { - const handle = await this.recorderPage.waitForFunction((params: { text: string, file: string }) => { + if (!codegenLang2Id.has(file)) + throw new Error(`Unknown language: ${file}`); + const handle = await this.recorderPage.waitForFunction((params: { text: string, languageId: string }) => { const w = window as any; - const source = (w.playwrightSourcesEchoForTest || []).find((s: Source) => s.file === params.file); + const source = (w.playwrightSourcesEchoForTest || []).find((s: Source) => s.id === params.languageId); return source && source.text.includes(params.text) ? w.playwrightSourcesEchoForTest : null; - }, { text, file }, { timeout: 8000, polling: 300 }); + }, { text, languageId: codegenLang2Id.get(file) }, { timeout: 8000, polling: 300 }); const sources: Source[] = await handle.jsonValue(); - for (const source of sources) - this._sources.set(source.file, source); + for (const source of sources) { + if (!codegenLangId2lang.has(source.id)) + throw new Error(`Unknown language: ${source.id}`); + this._sources.set(codegenLangId2lang.get(source.id), source); + } return this._sources; }