fix(codegen): escape tricky characters (#8350)
This commit is contained in:
parent
026426227d
commit
60829f8909
|
|
@ -18,7 +18,7 @@ import type { BrowserContextOptions } from '../../../..';
|
||||||
import { LanguageGenerator, LanguageGeneratorOptions, sanitizeDeviceOptions, toSignalMap } from './language';
|
import { LanguageGenerator, LanguageGeneratorOptions, sanitizeDeviceOptions, toSignalMap } from './language';
|
||||||
import { ActionInContext } from './codeGenerator';
|
import { ActionInContext } from './codeGenerator';
|
||||||
import { actionTitle, Action } from './recorderActions';
|
import { actionTitle, Action } from './recorderActions';
|
||||||
import { MouseClickOptions, toModifiers } from './utils';
|
import { escapeWithQuotes, MouseClickOptions, toModifiers } from './utils';
|
||||||
import deviceDescriptors from '../../deviceDescriptors';
|
import deviceDescriptors from '../../deviceDescriptors';
|
||||||
|
|
||||||
export class CSharpLanguageGenerator implements LanguageGenerator {
|
export class CSharpLanguageGenerator implements LanguageGenerator {
|
||||||
|
|
@ -266,5 +266,5 @@ class CSharpFormatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
function quote(text: string) {
|
function quote(text: string) {
|
||||||
return `"${text.replace(/["]/g, '\\"')}"`;
|
return escapeWithQuotes(text, '\"');
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ import type { BrowserContextOptions } from '../../../..';
|
||||||
import { LanguageGenerator, LanguageGeneratorOptions, toSignalMap } from './language';
|
import { LanguageGenerator, LanguageGeneratorOptions, toSignalMap } from './language';
|
||||||
import { ActionInContext } from './codeGenerator';
|
import { ActionInContext } from './codeGenerator';
|
||||||
import { Action, actionTitle } from './recorderActions';
|
import { Action, actionTitle } from './recorderActions';
|
||||||
import { MouseClickOptions, toModifiers } from './utils';
|
import { escapeWithQuotes, MouseClickOptions, toModifiers } from './utils';
|
||||||
import deviceDescriptors from '../../deviceDescriptors';
|
import deviceDescriptors from '../../deviceDescriptors';
|
||||||
import { JavaScriptFormatter } from './javascript';
|
import { JavaScriptFormatter } from './javascript';
|
||||||
|
|
||||||
|
|
@ -228,12 +228,6 @@ function formatClickOptions(options: MouseClickOptions, isPage: boolean) {
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function quote(text: string, char: string = '\"') {
|
function quote(text: string) {
|
||||||
if (char === '\'')
|
return escapeWithQuotes(text, '\"');
|
||||||
return char + text.replace(/[']/g, '\\\'') + char;
|
|
||||||
if (char === '"')
|
|
||||||
return char + text.replace(/["]/g, '\\"') + char;
|
|
||||||
if (char === '`')
|
|
||||||
return char + text.replace(/[`]/g, '\\`') + char;
|
|
||||||
throw new Error('Invalid escape char');
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import type { BrowserContextOptions } from '../../../..';
|
||||||
import { LanguageGenerator, LanguageGeneratorOptions, sanitizeDeviceOptions, toSignalMap } from './language';
|
import { LanguageGenerator, LanguageGeneratorOptions, sanitizeDeviceOptions, toSignalMap } from './language';
|
||||||
import { ActionInContext } from './codeGenerator';
|
import { ActionInContext } from './codeGenerator';
|
||||||
import { Action, actionTitle } from './recorderActions';
|
import { Action, actionTitle } from './recorderActions';
|
||||||
import { MouseClickOptions, toModifiers } from './utils';
|
import { escapeWithQuotes, MouseClickOptions, toModifiers } from './utils';
|
||||||
import deviceDescriptors from '../../deviceDescriptors';
|
import deviceDescriptors from '../../deviceDescriptors';
|
||||||
|
|
||||||
export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
export class JavaScriptLanguageGenerator implements LanguageGenerator {
|
||||||
|
|
@ -275,12 +275,6 @@ export class JavaScriptFormatter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function quote(text: string, char: string = '\'') {
|
function quote(text: string) {
|
||||||
if (char === '\'')
|
return escapeWithQuotes(text, '\'');
|
||||||
return char + text.replace(/[']/g, '\\\'') + char;
|
|
||||||
if (char === '"')
|
|
||||||
return char + text.replace(/["]/g, '\\"') + char;
|
|
||||||
if (char === '`')
|
|
||||||
return char + text.replace(/[`]/g, '\\`') + char;
|
|
||||||
throw new Error('Invalid escape char');
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import type { BrowserContextOptions } from '../../../..';
|
||||||
import { LanguageGenerator, LanguageGeneratorOptions, sanitizeDeviceOptions, toSignalMap } from './language';
|
import { LanguageGenerator, LanguageGeneratorOptions, sanitizeDeviceOptions, toSignalMap } from './language';
|
||||||
import { ActionInContext } from './codeGenerator';
|
import { ActionInContext } from './codeGenerator';
|
||||||
import { actionTitle, Action } from './recorderActions';
|
import { actionTitle, Action } from './recorderActions';
|
||||||
import { MouseClickOptions, toModifiers } from './utils';
|
import { escapeWithQuotes, MouseClickOptions, toModifiers } from './utils';
|
||||||
import deviceDescriptors from '../../deviceDescriptors';
|
import deviceDescriptors from '../../deviceDescriptors';
|
||||||
|
|
||||||
export class PythonLanguageGenerator implements LanguageGenerator {
|
export class PythonLanguageGenerator implements LanguageGenerator {
|
||||||
|
|
@ -264,12 +264,6 @@ class PythonFormatter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function quote(text: string, char: string = '\"') {
|
function quote(text: string) {
|
||||||
if (char === '\'')
|
return escapeWithQuotes(text, '\"');
|
||||||
return char + text.replace(/[']/g, '\\\'') + char;
|
|
||||||
if (char === '"')
|
|
||||||
return char + text.replace(/["]/g, '\\"') + char;
|
|
||||||
if (char === '`')
|
|
||||||
return char + text.replace(/[`]/g, '\\`') + char;
|
|
||||||
throw new Error('Invalid escape char');
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,3 +56,15 @@ export function describeFrame(frame: Frame): { frameName?: string, frameUrl: str
|
||||||
return { isMainFrame: false, frameUrl: frame.url(), frameName: frame.name() };
|
return { isMainFrame: false, frameUrl: frame.url(), frameName: frame.name() };
|
||||||
return { isMainFrame: false, frameUrl: frame.url() };
|
return { isMainFrame: false, frameUrl: frame.url() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function escapeWithQuotes(text: string, char: string = '\'') {
|
||||||
|
const stringified = JSON.stringify(text);
|
||||||
|
const escapedText = stringified.substring(1, stringified.length - 1).replace(/\\"/g, '"');
|
||||||
|
if (char === '\'')
|
||||||
|
return char + escapedText.replace(/[']/g, '\\\'') + char;
|
||||||
|
if (char === '"')
|
||||||
|
return char + escapedText.replace(/["]/g, '\\"') + char;
|
||||||
|
if (char === '`')
|
||||||
|
return char + escapedText.replace(/[`]/g, '`') + char;
|
||||||
|
throw new Error('Invalid escape char');
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -645,4 +645,40 @@ test.describe('cli codegen', () => {
|
||||||
await cli.exited;
|
await cli.exited;
|
||||||
expect(fs.existsSync(traceFileName)).toBeTruthy();
|
expect(fs.existsSync(traceFileName)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should fill tricky characters', async ({ page, openRecorder }) => {
|
||||||
|
const recorder = await openRecorder();
|
||||||
|
|
||||||
|
await recorder.setContentAndWait(`<textarea id="textarea" name="name" oninput="console.log(textarea.value)"></textarea>`);
|
||||||
|
const selector = await recorder.focusElement('textarea');
|
||||||
|
expect(selector).toBe('textarea[name="name"]');
|
||||||
|
|
||||||
|
const [message, sources] = await Promise.all([
|
||||||
|
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
||||||
|
recorder.waitForOutput('JavaScript', 'fill'),
|
||||||
|
page.fill('textarea', 'Hello\'\"\`\nWorld')
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
|
// Fill textarea[name="name"]
|
||||||
|
await page.fill('textarea[name="name"]', 'Hello\\'"\`\\nWorld');`);
|
||||||
|
expect(sources.get('Java').text).toContain(`
|
||||||
|
// Fill textarea[name="name"]
|
||||||
|
page.fill("textarea[name=\\\"name\\\"]", "Hello'\\"\`\\nWorld");`);
|
||||||
|
|
||||||
|
expect(sources.get('Python').text).toContain(`
|
||||||
|
# Fill textarea[name="name"]
|
||||||
|
page.fill(\"textarea[name=\\\"name\\\"]\", \"Hello'\\"\`\\nWorld\")`);
|
||||||
|
|
||||||
|
expect(sources.get('Python Async').text).toContain(`
|
||||||
|
# Fill textarea[name="name"]
|
||||||
|
await page.fill(\"textarea[name=\\\"name\\\"]\", \"Hello'\\"\`\\nWorld\")`);
|
||||||
|
|
||||||
|
expect(sources.get('C#').text).toContain(`
|
||||||
|
// Fill textarea[name="name"]
|
||||||
|
await page.FillAsync(\"textarea[name=\\\"name\\\"]\", \"Hello'\\"\`\\nWorld\");`);
|
||||||
|
|
||||||
|
expect(message.text()).toBe('Hello\'\"\`\nWorld');
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue