mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-12 00:52:05 +03:00
chore(recorder): explore using codemirror (#18529)
This commit is contained in:
parent
45aa82242d
commit
17c8554255
@ -49,3 +49,13 @@
|
||||
.recorder .selector-input {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.recorder .toolbar .cm-wrapper {
|
||||
margin-left: 8px;
|
||||
display: flex;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.recorder .toolbar .CodeMirror {
|
||||
height: auto;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import type { CallLog, Mode, Source } from './recorderTypes';
|
||||
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
|
||||
import { Source as SourceView } from '@web/components/source';
|
||||
import { SplitView } from '@web/components/splitView';
|
||||
import { Toolbar } from '@web/components/toolbar';
|
||||
@ -45,14 +46,6 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||
log,
|
||||
mode,
|
||||
}) => {
|
||||
const [locator, setLocator] = React.useState('');
|
||||
const [focusSelectorInput, setFocusSelectorInput] = React.useState(false);
|
||||
window.playwrightSetSelector = (selector: string, focus?: boolean) => {
|
||||
const language = sources[0]?.language || 'javascript';
|
||||
setLocator(asLocator(language, selector));
|
||||
setFocusSelectorInput(!!focus);
|
||||
};
|
||||
|
||||
const [fileId, setFileId] = React.useState<string | undefined>();
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -68,6 +61,13 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||
label: '',
|
||||
highlight: []
|
||||
};
|
||||
|
||||
const [locator, setLocator] = React.useState('');
|
||||
window.playwrightSetSelector = (selector: string, focus?: boolean) => {
|
||||
const language = source.language;
|
||||
setLocator(asLocator(language, selector));
|
||||
};
|
||||
|
||||
window.playwrightSetFileIfNeeded = (value: string) => {
|
||||
const newSource = sources.find(s => s.id === value);
|
||||
// Do not forcefully switch between two recorded sources, because
|
||||
@ -81,14 +81,6 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||
messagesEndRef.current?.scrollIntoView({ block: 'center', inline: 'nearest' });
|
||||
}, [messagesEndRef]);
|
||||
|
||||
const selectorInputRef = React.createRef<HTMLInputElement>();
|
||||
React.useLayoutEffect(() => {
|
||||
if (focusSelectorInput && selectorInputRef.current) {
|
||||
selectorInputRef.current.select();
|
||||
selectorInputRef.current.focus();
|
||||
setFocusSelectorInput(false);
|
||||
}
|
||||
}, [focusSelectorInput, selectorInputRef]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
@ -145,12 +137,12 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||
<ToolbarButton icon='microscope' title='Explore' toggled={mode === 'inspecting'} onClick={() => {
|
||||
window.dispatch({ event: 'setMode', params: { mode: mode === 'inspecting' ? 'none' : 'inspecting' } }).catch(() => { });
|
||||
}}>Explore</ToolbarButton>
|
||||
<input ref={selectorInputRef} className='selector-input' placeholder='Playwright Selector' spellCheck='false' value={locator} disabled={mode !== 'none'} onChange={event => {
|
||||
setLocator(event.target.value);
|
||||
window.dispatch({ event: 'selectorUpdated', params: { selector: event.target.value } });
|
||||
}} />
|
||||
<CodeMirrorWrapper text={locator} language={source.language} readOnly={false} focusOnChange={true} wrapLines={true} onChange={text => {
|
||||
setLocator(text);
|
||||
window.dispatch({ event: 'selectorUpdated', params: { selector: text } });
|
||||
}}></CodeMirrorWrapper>
|
||||
<ToolbarButton icon='files' title='Copy' onClick={() => {
|
||||
copy(selectorInputRef.current?.value || '');
|
||||
copy(locator);
|
||||
}}></ToolbarButton>
|
||||
</Toolbar>
|
||||
<CallLogView language={source.language} log={Array.from(log.values())}/>
|
||||
|
127
packages/web/src/components/codeMirrorWrapper.css
Normal file
127
packages/web/src/components/codeMirrorWrapper.css
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
@import '../third_party/vscode/colors.css';
|
||||
|
||||
.cm-wrapper, .cm-wrapper > div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-meta {
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-number {
|
||||
color: var(--vscode-debugTokenExpression-number);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-keyword {
|
||||
color: var(--vscode-debugTokenExpression-name);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-operator {
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-string {
|
||||
color: var(--vscode-debugTokenExpression-string);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-string-2 {
|
||||
color: var(--vscode-debugTokenExpression-string);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-error {
|
||||
color: var(--vscode-errorForeground);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-def, .CodeMirror span.cm-tag {
|
||||
color: #0070c1;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-comment, .CodeMirror span.cm-link {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-variable, .CodeMirror span.cm-variable-2, .CodeMirror span.cm-atom {
|
||||
color: #0070c1;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-property, .CodeMirror span.cm-qualifier, .CodeMirror span.cm-attribute {
|
||||
color: #001080;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-variable-3,
|
||||
.CodeMirror span.cm-type {
|
||||
color: #267f99;
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: dark) {
|
||||
|
||||
.CodeMirror span.cm-def, .CodeMirror span.cm-tag {
|
||||
color: var(--vscode-debugView-valueChangedHighlight);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-comment, .CodeMirror span.cm-link {
|
||||
color: #6a9955;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-variable, .CodeMirror span.cm-variable-2, .CodeMirror span.cm-atom {
|
||||
color: #4fc1ff;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-property, .CodeMirror span.cm-qualifier, .CodeMirror span.cm-attribute {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-variable-3,
|
||||
.CodeMirror span.cm-type {
|
||||
color: #4ec9b0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-bracket {
|
||||
color: var(--vscode-editorBracketHighlight-foreground3);
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid #bebebe;
|
||||
}
|
||||
|
||||
.CodeMirror div.CodeMirror-selected {
|
||||
background: var(--vscode-terminal-inactiveSelectionBackground);
|
||||
}
|
||||
|
||||
.CodeMirror .CodeMirror-gutters {
|
||||
background: var(--vscode-editor-background);
|
||||
border-right: 1px solid var(--vscode-editorGroup-border);
|
||||
color: var(--vscode-editorLineNumber-foreground);
|
||||
}
|
||||
|
||||
.CodeMirror .CodeMirror-matchingbracket {
|
||||
background-color: var(--vscode-editorBracketPairGuide-background1);
|
||||
color: var(--vscode-editorBracketHighlight-foreground1) !important;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
font-family: var(--vscode-editor-font-family) !important;
|
||||
color: var(--vscode-editor-foreground) !important;
|
||||
background-color: var(--vscode-editor-background) !important;
|
||||
font-weight: var(--vscode-editor-font-weight) !important;
|
||||
font-size: var(--vscode-editor-font-size) !important;
|
||||
}
|
109
packages/web/src/components/codeMirrorWrapper.tsx
Normal file
109
packages/web/src/components/codeMirrorWrapper.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import './source.css';
|
||||
import * as React from 'react';
|
||||
import CodeMirror from 'codemirror';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/mode/python/python';
|
||||
import 'codemirror/mode/clike/clike';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
|
||||
export type SourceHighlight = {
|
||||
line: number;
|
||||
type: 'running' | 'paused' | 'error';
|
||||
};
|
||||
|
||||
export interface SourceProps {
|
||||
text: string;
|
||||
language: string;
|
||||
readOnly: boolean;
|
||||
// 1-based
|
||||
highlight?: SourceHighlight[];
|
||||
revealLine?: number;
|
||||
lineNumbers?: boolean;
|
||||
focusOnChange?: boolean;
|
||||
wrapLines?: boolean;
|
||||
onChange?: (text: string) => void;
|
||||
}
|
||||
|
||||
export const CodeMirrorWrapper: React.FC<SourceProps> = ({
|
||||
text,
|
||||
language,
|
||||
readOnly,
|
||||
highlight = [],
|
||||
revealLine,
|
||||
lineNumbers,
|
||||
focusOnChange,
|
||||
wrapLines,
|
||||
onChange,
|
||||
}) => {
|
||||
const codemirrorElement = React.createRef<HTMLDivElement>();
|
||||
const [codemirror, setCodemirror] = React.useState<CodeMirror.Editor>();
|
||||
|
||||
React.useEffect(() => {
|
||||
let mode;
|
||||
if (language === 'javascript')
|
||||
mode = 'javascript';
|
||||
if (language === 'python')
|
||||
mode = 'python';
|
||||
if (language === 'java')
|
||||
mode = 'text/x-java';
|
||||
if (language === 'csharp')
|
||||
mode = 'text/x-csharp';
|
||||
|
||||
if (codemirror && codemirror.getOption('mode') === mode)
|
||||
return;
|
||||
|
||||
if (!codemirrorElement.current)
|
||||
return;
|
||||
if (codemirror)
|
||||
codemirror.getWrapperElement().remove();
|
||||
|
||||
const cm = CodeMirror(codemirrorElement.current, {
|
||||
value: '',
|
||||
mode,
|
||||
readOnly,
|
||||
lineNumbers,
|
||||
lineWrapping: wrapLines,
|
||||
});
|
||||
if (onChange)
|
||||
cm.on('change', () => onChange(cm.getValue()));
|
||||
setCodemirror(cm);
|
||||
updateEditor(cm, text, highlight, revealLine, focusOnChange);
|
||||
}, [codemirror, codemirrorElement, text, language, highlight, revealLine, focusOnChange, lineNumbers, wrapLines, readOnly, onChange]);
|
||||
|
||||
if (codemirror)
|
||||
updateEditor(codemirror, text, highlight, revealLine, focusOnChange);
|
||||
|
||||
return <div className='cm-wrapper' ref={codemirrorElement}></div>;
|
||||
};
|
||||
|
||||
function updateEditor(cm: CodeMirror.Editor, text: string, highlight: SourceHighlight[], revealLine?: number, focusOnChange?: boolean) {
|
||||
if (cm.getValue() !== text) {
|
||||
cm.setValue(text);
|
||||
if (focusOnChange) {
|
||||
cm.execCommand('selectAll');
|
||||
cm.focus();
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < cm.lineCount(); ++i)
|
||||
cm.removeLineClass(i, 'wrap');
|
||||
for (const h of highlight)
|
||||
cm.addLineClass(h.line - 1, 'wrap', `source-line-${h.type}`);
|
||||
if (revealLine)
|
||||
cm.scrollIntoView({ line: revealLine - 1, ch: 0 }, 50);
|
||||
}
|
@ -46,118 +46,3 @@
|
||||
outline: 1px solid #ff5656;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.cm-wrapper, .cm-wrapper > div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-meta {
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-number {
|
||||
color: var(--vscode-debugTokenExpression-number);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-keyword {
|
||||
color: var(--vscode-debugTokenExpression-name);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-operator {
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-string {
|
||||
color: var(--vscode-debugTokenExpression-string);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-string-2 {
|
||||
color: var(--vscode-debugTokenExpression-string);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-error {
|
||||
color: var(--vscode-errorForeground);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-def, .CodeMirror span.cm-tag {
|
||||
color: #0070c1;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-comment, .CodeMirror span.cm-link {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-variable, .CodeMirror span.cm-variable-2, .CodeMirror span.cm-atom {
|
||||
color: #0070c1;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-property, .CodeMirror span.cm-qualifier, .CodeMirror span.cm-attribute {
|
||||
color: #001080;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-variable-3,
|
||||
.CodeMirror span.cm-type {
|
||||
color: #267f99;
|
||||
}
|
||||
|
||||
@media(prefers-color-scheme: dark) {
|
||||
|
||||
.CodeMirror span.cm-def, .CodeMirror span.cm-tag {
|
||||
color: var(--vscode-debugView-valueChangedHighlight);
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-comment, .CodeMirror span.cm-link {
|
||||
color: #6a9955;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-variable, .CodeMirror span.cm-variable-2, .CodeMirror span.cm-atom {
|
||||
color: #4fc1ff;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-property, .CodeMirror span.cm-qualifier, .CodeMirror span.cm-attribute {
|
||||
color: #9cdcfe;
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-variable-3,
|
||||
.CodeMirror span.cm-type {
|
||||
color: #4ec9b0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.CodeMirror span.cm-bracket {
|
||||
color: var(--vscode-editorBracketHighlight-foreground3);
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid #bebebe;
|
||||
}
|
||||
|
||||
.CodeMirror div.CodeMirror-selected {
|
||||
background: var(--vscode-terminal-inactiveSelectionBackground);
|
||||
}
|
||||
|
||||
.CodeMirror .CodeMirror-gutters {
|
||||
background: var(--vscode-editor-background);
|
||||
border-right: 1px solid var(--vscode-editorGroup-border);
|
||||
color: var(--vscode-editorLineNumber-foreground);
|
||||
}
|
||||
|
||||
.CodeMirror .CodeMirror-matchingbracket {
|
||||
background-color: var(--vscode-editorBracketPairGuide-background1);
|
||||
color: var(--vscode-editorBracketHighlight-foreground1) !important;
|
||||
}
|
||||
|
||||
/* .CodeMirror-line,
|
||||
.CodeMirror-line-like {
|
||||
color: var(--vscode-editor-foreground) !important;
|
||||
} */
|
||||
|
||||
.CodeMirror {
|
||||
font-family: var(--vscode-editor-font-family) !important;
|
||||
color: var(--vscode-editor-foreground) !important;
|
||||
background-color: var(--vscode-editor-background) !important;
|
||||
font-weight: var(--vscode-editor-font-weight) !important;
|
||||
font-size: var(--vscode-editor-font-size) !important;
|
||||
}
|
||||
|
@ -14,13 +14,9 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import './source.css';
|
||||
import * as React from 'react';
|
||||
import CodeMirror from 'codemirror';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/mode/python/python';
|
||||
import 'codemirror/mode/clike/clike';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import './codeMirrorWrapper.css';
|
||||
import { CodeMirrorWrapper } from './codeMirrorWrapper';
|
||||
|
||||
export type SourceHighlight = {
|
||||
line: number;
|
||||
@ -41,51 +37,5 @@ export const Source: React.FC<SourceProps> = ({
|
||||
highlight = [],
|
||||
revealLine
|
||||
}) => {
|
||||
const codemirrorElement = React.createRef<HTMLDivElement>();
|
||||
const [codemirror, setCodemirror] = React.useState<CodeMirror.Editor>();
|
||||
|
||||
React.useEffect(() => {
|
||||
let mode;
|
||||
if (language === 'javascript')
|
||||
mode = 'javascript';
|
||||
if (language === 'python')
|
||||
mode = 'python';
|
||||
if (language === 'java')
|
||||
mode = 'text/x-java';
|
||||
if (language === 'csharp')
|
||||
mode = 'text/x-csharp';
|
||||
|
||||
if (codemirror && codemirror.getOption('mode') === mode)
|
||||
return;
|
||||
|
||||
if (!codemirrorElement.current)
|
||||
return;
|
||||
if (codemirror)
|
||||
codemirror.getWrapperElement().remove();
|
||||
|
||||
const cm = CodeMirror(codemirrorElement.current, {
|
||||
value: '',
|
||||
mode,
|
||||
readOnly: true,
|
||||
lineNumbers: true,
|
||||
});
|
||||
setCodemirror(cm);
|
||||
updateEditor(cm, text, highlight, revealLine);
|
||||
}, [codemirror, codemirrorElement, text, language, highlight, revealLine]);
|
||||
|
||||
if (codemirror)
|
||||
updateEditor(codemirror, text, highlight, revealLine);
|
||||
|
||||
return <div className='cm-wrapper' ref={codemirrorElement}></div>;
|
||||
return <CodeMirrorWrapper text={text} language={language} readOnly={true} highlight={highlight} revealLine={revealLine} lineNumbers={true}></CodeMirrorWrapper>;
|
||||
};
|
||||
|
||||
function updateEditor(cm: CodeMirror.Editor, text: string, highlight: SourceHighlight[], revealLine: number | undefined) {
|
||||
if (cm.getValue() !== text)
|
||||
cm.setValue(text);
|
||||
for (let i = 0; i < cm.lineCount(); ++i)
|
||||
cm.removeLineClass(i, 'wrap');
|
||||
for (const h of highlight)
|
||||
cm.addLineClass(h.line - 1, 'wrap', `source-line-${h.type}`);
|
||||
if (revealLine)
|
||||
cm.scrollIntoView({ line: revealLine - 1, ch: 0 }, 50);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
box-shadow: var(--box-shadow);
|
||||
background-color: var(--vscode-sideBar-background);
|
||||
color: var(--vscode-sideBarTitle-foreground);
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
align-items: center;
|
||||
padding-right: 10px;
|
||||
flex: none;
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
.toolbar-button {
|
||||
flex: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--vscode-sideBarTitle-foreground);
|
||||
|
@ -359,10 +359,12 @@ it.describe('pause', () => {
|
||||
await page.pause();
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
const [box1] = await Promise.all([
|
||||
waitForTestLog<Box>(page, 'Highlight box for test: '),
|
||||
recorderPage.fill('input[placeholder="Playwright Selector"]', 'text=Submit'),
|
||||
]);
|
||||
|
||||
const box1Promise = waitForTestLog<Box>(page, 'Highlight box for test: ');
|
||||
await recorderPage.click('.toolbar .CodeMirror');
|
||||
await recorderPage.keyboard.type('getByText(\'Submit\')');
|
||||
const box1 = await box1Promise;
|
||||
|
||||
const button = await page.$('text=Submit');
|
||||
const box2 = await button.boundingBox();
|
||||
expect(roundBox(box1)).toEqual(roundBox(box2));
|
||||
|
Loading…
Reference in New Issue
Block a user