chore: language specific dropdowns in codegen (#16452)

This commit is contained in:
Max Schmitt 2022-08-15 19:44:46 +02:00 committed by GitHub
parent bd06d1604f
commit 13596b7be3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 78 additions and 34 deletions

View File

@ -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);

View File

@ -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 {

View File

@ -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 {

View File

@ -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;
}

View File

@ -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;

View File

@ -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 ' : '';

View File

@ -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;
};

View File

@ -53,22 +53,27 @@ export const Recorder: React.FC<RecorderProps> = ({
setFocusSelectorInput(!!focus);
};
const [f, setFile] = React.useState<string | undefined>();
const file = f || sources[0]?.file;
const [fileId, setFileId] = React.useState<string | undefined>();
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<HTMLDivElement>();
@ -125,15 +130,9 @@ export const Recorder: React.FC<RecorderProps> = ({
}}></ToolbarButton>
<div style={{ flex: 'auto' }}></div>
<div>Target:</div>
<select className='recorder-chooser' hidden={!sources.length} value={file} onChange={event => {
setFile(event.target.selectedOptions[0].value);
}}>{
sources.map(s => {
const title = s.file.replace(/.*[/\\]([^/\\]+)/, '$1');
return <option key={s.file} value={s.file}>{title}</option>;
})
}
</select>
<select className='recorder-chooser' hidden={!sources.length} value={fileId} onChange={event => {
setFileId(event.target.selectedOptions[0].value);
}}>{renderSourceOptions(sources)}</select>
<ToolbarButton icon='clear-all' title='Clear' disabled={!source || !source.text} onClick={() => {
window.dispatch({ event: 'clear' });
}}></ToolbarButton>
@ -159,6 +158,25 @@ export const Recorder: React.FC<RecorderProps> = ({
</div>;
};
function renderSourceOptions(sources: Source[]): React.ReactNode {
const transformTitle = (title: string): string => title.replace(/.*[/\\]([^/\\]+)/, '$1');
const renderOption = (source: 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 Array.from(groups).map(group => (
<optgroup label={group} key={group}>
{sources.filter(s => s.group === group).map(source => renderOption(source))}
</optgroup>
));
}
return sources.map(source => renderOption(source));
}
function copy(text: string) {
const textArea = document.createElement('textarea');
textArea.style.position = 'absolute';

View File

@ -28,6 +28,17 @@ type CLITestArgs = {
runCLI: (args: string[], options?: { noAutoExit?: boolean }) => CLIMock;
};
const codegenLang2Id: Map<string, string> = 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<CLITestArgs>({
@ -115,14 +126,19 @@ class Recorder {
}
async waitForOutput(file: string, text: string): Promise<Map<string, Source>> {
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;
}