mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-01 08:34:02 +03:00
chore(tv): render error in-line (#21586)
This commit is contained in:
parent
21950bc8ce
commit
e45a496850
12
package-lock.json
generated
12
package-lock.json
generated
@ -6203,14 +6203,12 @@
|
||||
"version": "0.0.0"
|
||||
},
|
||||
"packages/trace-viewer": {
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"ansi-to-html": "^0.7.2"
|
||||
}
|
||||
"version": "0.0.0"
|
||||
},
|
||||
"packages/web": {
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"codemirror": "^5.65.9",
|
||||
"xterm": "^5.1.0",
|
||||
"xterm-addon-fit": "^0.7.0"
|
||||
@ -9704,10 +9702,7 @@
|
||||
}
|
||||
},
|
||||
"trace-viewer": {
|
||||
"version": "file:packages/trace-viewer",
|
||||
"requires": {
|
||||
"ansi-to-html": "^0.7.2"
|
||||
}
|
||||
"version": "file:packages/trace-viewer"
|
||||
},
|
||||
"tree-kill": {
|
||||
"version": "1.2.2",
|
||||
@ -9886,6 +9881,7 @@
|
||||
"web": {
|
||||
"version": "file:packages/web",
|
||||
"requires": {
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"codemirror": "^5.65.9",
|
||||
"xterm": "^5.1.0",
|
||||
"xterm-addon-fit": "^0.7.0"
|
||||
|
@ -56,14 +56,14 @@
|
||||
}
|
||||
|
||||
.call-log-call.error {
|
||||
background-color: #fff0f0;
|
||||
background-color: var(--vscode-inputValidation-errorBackground);
|
||||
border-top: 1px solid var(--vscode-panel-border);
|
||||
}
|
||||
|
||||
.call-log-call.error .call-log-call-header,
|
||||
.call-log-message.error,
|
||||
.call-log .codicon-error {
|
||||
color: red;
|
||||
color: var(--vscode-errorForeground);
|
||||
}
|
||||
|
||||
.call-log-details {
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
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';
|
||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||
@ -134,7 +133,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||
<ToolbarButton icon='color-mode' title='Toggle color mode' toggled={false} onClick={() => toggleTheme()}></ToolbarButton>
|
||||
</Toolbar>
|
||||
<SplitView sidebarSize={200} sidebarHidden={mode === 'recording'}>
|
||||
<SourceView text={source.text} language={source.language} highlight={source.highlight} revealLine={source.revealLine}></SourceView>
|
||||
<CodeMirrorWrapper text={source.text} language={source.language} highlight={source.highlight} revealLine={source.revealLine} readOnly={true} lineNumbers={true}/>
|
||||
<div className='vbox'>
|
||||
<Toolbar>
|
||||
<ToolbarButton icon='microscope' title='Pick locator' toggled={mode === 'inspecting'} onClick={() => {
|
||||
@ -143,7 +142,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||
<CodeMirrorWrapper text={locator} language={source.language} readOnly={false} focusOnChange={true} wrapLines={true} onChange={text => {
|
||||
setLocator(text);
|
||||
window.dispatch({ event: 'selectorUpdated', params: { selector: text, language: source.language } });
|
||||
}}></CodeMirrorWrapper>
|
||||
}} />
|
||||
<ToolbarButton icon='files' title='Copy' onClick={() => {
|
||||
copy(locator);
|
||||
}}></ToolbarButton>
|
||||
|
@ -7,8 +7,5 @@
|
||||
"build": "vite build && tsc",
|
||||
"build-sw": "vite --config vite.sw.config.ts build && tsc",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-to-html": "^0.7.2"
|
||||
}
|
||||
}
|
||||
|
@ -45,11 +45,11 @@
|
||||
}
|
||||
|
||||
.action-icons:hover {
|
||||
border-bottom: 1px solid white;
|
||||
border-bottom: 1px solid var(--vscode-sideBarTitle-foreground);
|
||||
}
|
||||
|
||||
.action-error {
|
||||
color: red;
|
||||
color: var(--vscode-errorForeground);
|
||||
position: relative;
|
||||
margin-right: 2px;
|
||||
flex: none;
|
||||
|
@ -28,7 +28,7 @@
|
||||
}
|
||||
|
||||
.call-error .codicon {
|
||||
color: red;
|
||||
color: var(--vscode-errorForeground);
|
||||
position: relative;
|
||||
top: 2px;
|
||||
margin-right: 2px;
|
||||
|
@ -22,7 +22,7 @@ import './callTab.css';
|
||||
import { CopyToClipboard } from './copyToClipboard';
|
||||
import { asLocator } from '@isomorphic/locatorGenerators';
|
||||
import type { Language } from '@isomorphic/locatorGenerators';
|
||||
import { ErrorMessage } from './errorMessage';
|
||||
import { ErrorMessage } from '@web/components/errorMessage';
|
||||
|
||||
export const CallTab: React.FunctionComponent<{
|
||||
action: ActionTraceEvent | undefined,
|
||||
|
@ -33,16 +33,16 @@
|
||||
}
|
||||
|
||||
.console-line.error {
|
||||
background: #fff0f0;
|
||||
border-top-color: #ffd6d6;
|
||||
border-bottom-color: #ffd6d6;
|
||||
color: red;
|
||||
background: var(--vscode-inputValidation-errorBackground);
|
||||
border-top-color: var(--vscode-inputValidation-errorBorder);
|
||||
border-bottom-color: var(--vscode-inputValidation-errorBorder);
|
||||
color: var(--vscode-errorForeground);
|
||||
}
|
||||
|
||||
.console-line.warning {
|
||||
background: #fffbe5;
|
||||
border-top-color: #fff5c2;
|
||||
border-bottom-color: #fff5c2;
|
||||
background: var(--vscode-inputValidation-warningBackground);
|
||||
border-top-color: var(--vscode-inputValidation-warningBorder);
|
||||
border-bottom-color: var(--vscode-inputValidation-warningBorder);
|
||||
}
|
||||
|
||||
.console-line .codicon {
|
||||
|
@ -16,14 +16,14 @@
|
||||
|
||||
import type { StackFrame } from '@protocol/channels';
|
||||
import type { ActionTraceEvent } from '@trace/trace';
|
||||
import { Source as SourceView } from '@web/components/source';
|
||||
import { SplitView } from '@web/components/splitView';
|
||||
import * as React from 'react';
|
||||
import { useAsyncMemo } from './helpers';
|
||||
import './sourceTab.css';
|
||||
import { StackTraceView } from './stackTrace';
|
||||
import { CodeMirrorWrapper } from '@web/components/codeMirrorWrapper';
|
||||
|
||||
type StackInfo = string | {
|
||||
type StackInfo = {
|
||||
frames: StackFrame[];
|
||||
fileContent: Map<string, string>;
|
||||
};
|
||||
@ -33,17 +33,15 @@ export const SourceTab: React.FunctionComponent<{
|
||||
}> = ({ action }) => {
|
||||
const [lastAction, setLastAction] = React.useState<ActionTraceEvent | undefined>();
|
||||
const [selectedFrame, setSelectedFrame] = React.useState<number>(0);
|
||||
const [needReveal, setNeedReveal] = React.useState<boolean>(false);
|
||||
|
||||
if (lastAction !== action) {
|
||||
setLastAction(action);
|
||||
setSelectedFrame(0);
|
||||
setNeedReveal(true);
|
||||
}
|
||||
|
||||
const stackInfo = React.useMemo<StackInfo>(() => {
|
||||
if (!action)
|
||||
return '';
|
||||
return { frames: [], fileContent: new Map() };
|
||||
const frames = action.stack || [];
|
||||
return {
|
||||
frames,
|
||||
@ -52,34 +50,20 @@ export const SourceTab: React.FunctionComponent<{
|
||||
}, [action]);
|
||||
|
||||
const content = useAsyncMemo<string>(async () => {
|
||||
let value: string;
|
||||
if (typeof stackInfo === 'string') {
|
||||
value = stackInfo;
|
||||
} else {
|
||||
const filePath = stackInfo.frames[selectedFrame]?.file;
|
||||
if (!filePath)
|
||||
return '';
|
||||
if (!stackInfo.fileContent.has(filePath)) {
|
||||
const sha1 = await calculateSha1(filePath);
|
||||
stackInfo.fileContent.set(filePath, await fetch(`sha1/src@${sha1}.txt`).then(response => response.text()).catch(e => `<Unable to read "${filePath}">`));
|
||||
stackInfo.fileContent.set(filePath, await fetch(`sha1/src@${sha1}.txt`).then(response => response.text()).catch(() => `<Unable to read "${filePath}">`));
|
||||
}
|
||||
value = stackInfo.fileContent.get(filePath)!;
|
||||
}
|
||||
return value;
|
||||
return stackInfo.fileContent.get(filePath)!;
|
||||
}, [stackInfo, selectedFrame], '');
|
||||
|
||||
const targetLine = typeof stackInfo === 'string' ? 0 : stackInfo.frames[selectedFrame]?.line || 0;
|
||||
|
||||
const targetLineRef = React.useRef<HTMLDivElement>(null);
|
||||
React.useLayoutEffect(() => {
|
||||
if (needReveal && targetLineRef.current) {
|
||||
targetLineRef.current.scrollIntoView({ block: 'center', inline: 'nearest' });
|
||||
setNeedReveal(false);
|
||||
}
|
||||
}, [needReveal, targetLineRef]);
|
||||
|
||||
const targetLine = stackInfo.frames[selectedFrame]?.line || 0;
|
||||
const error = action?.error?.message;
|
||||
return <SplitView sidebarSize={200} orientation='horizontal'>
|
||||
<SourceView text={content} language='javascript' highlight={[{ line: targetLine, type: 'running' }]} revealLine={targetLine}></SourceView>
|
||||
<CodeMirrorWrapper text={content} language='javascript' highlight={[{ line: targetLine, type: error ? 'error' : 'running', message: error }]} revealLine={targetLine} readOnly={true} lineNumbers={true}></CodeMirrorWrapper>
|
||||
<StackTraceView action={action} selectedFrame={selectedFrame} setSelectedFrame={setSelectedFrame}></StackTraceView>
|
||||
</SplitView>;
|
||||
};
|
||||
|
@ -49,9 +49,9 @@ export const Workbench: React.FunctionComponent<{
|
||||
|
||||
const tabs: TabbedPaneTabModel[] = [
|
||||
{ id: 'call', title: 'Call', render: () => <CallTab action={activeAction} sdkLanguage={sdkLanguage} /> },
|
||||
{ id: 'source', title: 'Source', count: 0, render: () => <SourceTab action={activeAction} /> },
|
||||
{ id: 'console', title: 'Console', count: consoleCount, render: () => <ConsoleTab action={activeAction} /> },
|
||||
{ id: 'network', title: 'Network', count: networkCount, render: () => <NetworkTab action={activeAction} /> },
|
||||
{ id: 'source', title: 'Source', count: 0, render: () => <SourceTab action={activeAction} /> },
|
||||
];
|
||||
|
||||
if (output)
|
||||
|
@ -4,6 +4,7 @@
|
||||
"version": "0.0.0",
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"codemirror": "^5.65.9",
|
||||
"xterm": "^5.1.0",
|
||||
"xterm-addon-fit": "^0.7.0"
|
||||
|
@ -5,7 +5,7 @@
|
||||
[expandable.spec.tsx]
|
||||
***
|
||||
|
||||
[source.spec.tsx]
|
||||
[codeMirrorWrapper.spec.tsx]
|
||||
***
|
||||
|
||||
[splitView.spec.tsx]
|
||||
|
@ -118,6 +118,7 @@ body.dark-mode .CodeMirror span.cm-type {
|
||||
|
||||
.CodeMirror .CodeMirror-gutters {
|
||||
z-index: 0;
|
||||
background: var(--vscode-editorGutter-background);
|
||||
}
|
||||
|
||||
.CodeMirror .CodeMirror-gutterwrapper {
|
||||
@ -138,3 +139,31 @@ body.dark-mode .CodeMirror span.cm-type {
|
||||
font-weight: var(--vscode-editor-font-weight) !important;
|
||||
font-size: var(--vscode-editor-font-size) !important;
|
||||
}
|
||||
|
||||
.CodeMirror .source-line-running {
|
||||
background-color: #b3dbff7f;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.CodeMirror .source-line-paused {
|
||||
background-color: #b3dbff7f;
|
||||
outline: 1px solid #008aff;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.CodeMirror .source-line-error {
|
||||
/* Intentionally empty. */
|
||||
}
|
||||
|
||||
.CodeMirror .source-line-error-underline {
|
||||
text-decoration: underline wavy var(--vscode-errorForeground);
|
||||
position: relative;
|
||||
top: -12px;
|
||||
}
|
||||
|
||||
.CodeMirror .source-line-error-widget {
|
||||
background-color: var(--vscode-inputValidation-errorBackground);
|
||||
white-space: pre-wrap;
|
||||
margin: 3px 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { expect, test } from '@playwright/experimental-ct-react';
|
||||
import { Source } from './source';
|
||||
import { CodeMirrorWrapper } from './codeMirrorWrapper';
|
||||
|
||||
test.use({ viewport: { width: 500, height: 500 } });
|
||||
|
||||
@ -70,31 +70,31 @@ class Program
|
||||
`;
|
||||
|
||||
test('highlight JavaScript', async ({ mount }) => {
|
||||
const component = await mount(<Source text={javascriptSnippet} language='javascript'></Source>);
|
||||
const component = await mount(<CodeMirrorWrapper text={javascriptSnippet} language='javascript' />);
|
||||
await expect(component.locator('text="async"').first()).toHaveClass('cm-keyword');
|
||||
});
|
||||
|
||||
test('highlight Python', async ({ mount }) => {
|
||||
const component = await mount(<Source text={pythonSnippet} language='python'></Source>);
|
||||
const component = await mount(<CodeMirrorWrapper text={pythonSnippet} language='python' />);
|
||||
await expect(component.locator('text="async"').first()).toHaveClass('cm-keyword');
|
||||
});
|
||||
|
||||
test('highlight Java', async ({ mount }) => {
|
||||
const component = await mount(<Source text={javaSnippet} language='java'></Source>);
|
||||
const component = await mount(<CodeMirrorWrapper text={javaSnippet} language='java' />);
|
||||
await expect(component.locator('text="public"').first()).toHaveClass('cm-keyword');
|
||||
});
|
||||
|
||||
test('highlight C#', async ({ mount }) => {
|
||||
const component = await mount(<Source text={csharpSnippet} language='csharp'></Source>);
|
||||
const component = await mount(<CodeMirrorWrapper text={csharpSnippet} language='csharp' />);
|
||||
await expect(component.locator('text="public"').first()).toHaveClass('cm-keyword');
|
||||
});
|
||||
|
||||
test('highlight lines', async ({ mount }) => {
|
||||
const component = await mount(<Source text={javascriptSnippet} language='javascript' highlight={[
|
||||
const component = await mount(<CodeMirrorWrapper text={javascriptSnippet} language='javascript' highlight={[
|
||||
{ line: 4, type: 'running' },
|
||||
{ line: 5, type: 'paused' },
|
||||
{ line: 6, type: 'error' },
|
||||
]}></Source>);
|
||||
]} />);
|
||||
await expect(component.locator('.source-line-running')).toContainText('goto');
|
||||
await expect(component.locator('.source-line-paused')).toContainText('title');
|
||||
await expect(component.locator('.source-line-error')).toContainText('expect');
|
@ -14,13 +14,15 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import './source.css';
|
||||
import './codeMirrorWrapper.css';
|
||||
import * as React from 'react';
|
||||
import type { CodeMirror } from './codeMirrorModule';
|
||||
import { ansi2htmlMarkup } from './errorMessage';
|
||||
|
||||
export type SourceHighlight = {
|
||||
line: number;
|
||||
type: 'running' | 'paused' | 'error';
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export type Language = 'javascript' | 'python' | 'java' | 'csharp';
|
||||
@ -28,7 +30,7 @@ export type Language = 'javascript' | 'python' | 'java' | 'csharp';
|
||||
export interface SourceProps {
|
||||
text: string;
|
||||
language: Language;
|
||||
readOnly: boolean;
|
||||
readOnly?: boolean;
|
||||
// 1-based
|
||||
highlight?: SourceHighlight[];
|
||||
revealLine?: number;
|
||||
@ -51,7 +53,7 @@ export const CodeMirrorWrapper: React.FC<SourceProps> = ({
|
||||
}) => {
|
||||
const codemirrorElement = React.useRef<HTMLDivElement>(null);
|
||||
const [modulePromise] = React.useState<Promise<CodeMirror>>(import('./codeMirrorModule').then(m => m.default));
|
||||
const codemirrorRef = React.useRef<CodeMirror.Editor|null>(null);
|
||||
const codemirrorRef = React.useRef<{ cm: CodeMirror.Editor, highlight: SourceHighlight[], widgets: CodeMirror.LineWidget[] } | null>(null);
|
||||
const [codemirror, setCodemirror] = React.useState<CodeMirror.Editor>();
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -72,25 +74,25 @@ export const CodeMirrorWrapper: React.FC<SourceProps> = ({
|
||||
mode = 'text/x-csharp';
|
||||
|
||||
if (codemirrorRef.current
|
||||
&& mode === codemirrorRef.current.getOption('mode')
|
||||
&& readOnly === codemirrorRef.current.getOption('readOnly')
|
||||
&& lineNumbers === codemirrorRef.current.getOption('lineNumbers')
|
||||
&& wrapLines === codemirrorRef.current.getOption('lineWrapping')) {
|
||||
&& mode === codemirrorRef.current.cm.getOption('mode')
|
||||
&& !!readOnly === codemirrorRef.current.cm.getOption('readOnly')
|
||||
&& lineNumbers === codemirrorRef.current.cm.getOption('lineNumbers')
|
||||
&& wrapLines === codemirrorRef.current.cm.getOption('lineWrapping')) {
|
||||
// No need to re-create codemirror.
|
||||
return;
|
||||
}
|
||||
|
||||
// Either configuration is different or we don't have a codemirror yet.
|
||||
codemirrorRef.current?.getWrapperElement().remove();
|
||||
codemirrorRef.current?.cm?.getWrapperElement().remove();
|
||||
|
||||
const cm = CodeMirror(element, {
|
||||
value: '',
|
||||
mode,
|
||||
readOnly,
|
||||
readOnly: !!readOnly,
|
||||
lineNumbers,
|
||||
lineWrapping: wrapLines,
|
||||
});
|
||||
codemirrorRef.current = cm;
|
||||
codemirrorRef.current = { cm, highlight: [], widgets: [] };
|
||||
setCodemirror(cm);
|
||||
return cm;
|
||||
})();
|
||||
@ -113,10 +115,37 @@ export const CodeMirrorWrapper: React.FC<SourceProps> = ({
|
||||
codemirror.focus();
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < codemirror.lineCount(); ++i)
|
||||
codemirror.removeLineClass(i, 'wrap');
|
||||
|
||||
// Line highlight.
|
||||
for (const h of codemirrorRef.current!.highlight)
|
||||
codemirror.removeLineClass(h.line - 1, 'wrap');
|
||||
for (const h of highlight || [])
|
||||
codemirror.addLineClass(h.line - 1, 'wrap', `source-line-${h.type}`);
|
||||
codemirrorRef.current!.highlight = highlight || [];
|
||||
|
||||
// Error widgets.
|
||||
for (const w of codemirrorRef.current!.widgets)
|
||||
codemirror.removeLineWidget(w);
|
||||
const widgets: CodeMirror.LineWidget[] = [];
|
||||
for (const h of highlight || []) {
|
||||
if (h.type !== 'error')
|
||||
continue;
|
||||
|
||||
const line = codemirrorRef.current?.cm.getLine(h.line - 1);
|
||||
if (line) {
|
||||
const underlineWidgetElement = document.createElement('div');
|
||||
underlineWidgetElement.className = 'source-line-error-underline';
|
||||
underlineWidgetElement.innerHTML = ' '.repeat(line.length || 1);
|
||||
widgets.push(codemirror.addLineWidget(h.line, underlineWidgetElement, { above: true, coverGutter: false }));
|
||||
}
|
||||
|
||||
const errorWidgetElement = document.createElement('div');
|
||||
errorWidgetElement.innerHTML = ansi2htmlMarkup(h.message || '');
|
||||
errorWidgetElement.className = 'source-line-error-widget';
|
||||
widgets.push(codemirror.addLineWidget(h.line, errorWidgetElement, { above: true, coverGutter: false }));
|
||||
}
|
||||
codemirrorRef.current!.widgets = widgets;
|
||||
|
||||
if (revealLine)
|
||||
codemirror.scrollIntoView({ line: revealLine - 1, ch: 0 }, 50);
|
||||
}, [codemirror, text, highlight, revealLine, focusOnChange, onChange]);
|
||||
|
@ -21,16 +21,18 @@ import './errorMessage.css';
|
||||
export const ErrorMessage: React.FC<{
|
||||
error: string;
|
||||
}> = ({ error }) => {
|
||||
const html = React.useMemo(() => {
|
||||
const html = React.useMemo(() => ansi2htmlMarkup(error), [error]);
|
||||
return <div className='error-message' dangerouslySetInnerHTML={{ __html: html || '' }}></div>;
|
||||
};
|
||||
|
||||
export function ansi2htmlMarkup(text: string) {
|
||||
const config: any = {
|
||||
bg: 'var(--vscode-panel-background)',
|
||||
fg: 'var(--vscode-foreground)',
|
||||
};
|
||||
config.colors = ansiColors;
|
||||
return new ansi2html(config).toHtml(escapeHTML(error));
|
||||
}, [error]);
|
||||
return <div className='error-message' dangerouslySetInnerHTML={{ __html: html || '' }}></div>;
|
||||
};
|
||||
return new ansi2html(config).toHtml(escapeHTML(text));
|
||||
}
|
||||
|
||||
const ansiColors = {
|
||||
0: '#000',
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
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';
|
||||
|
||||
.source {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
flex-direction: column;
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
user-select: text;
|
||||
font-family: var(--vscode-editor-font-family);
|
||||
font-weight: var(--vscode-editor-font-weight);
|
||||
line-height: 19px;
|
||||
background: var(--vscode-editor-background);
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
.source-line-running {
|
||||
background-color: #b3dbff7f;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.source-line-paused {
|
||||
background-color: #b3dbff7f;
|
||||
outline: 1px solid #008aff;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.source-line-error {
|
||||
background-color: #fff0f0;
|
||||
outline: 1px solid #ff5656;
|
||||
z-index: 2;
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
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 * as React from 'react';
|
||||
import './codeMirrorWrapper.css';
|
||||
import type { Language } from './codeMirrorWrapper';
|
||||
import { CodeMirrorWrapper } from './codeMirrorWrapper';
|
||||
|
||||
export type SourceHighlight = {
|
||||
line: number;
|
||||
type: 'running' | 'paused' | 'error';
|
||||
};
|
||||
|
||||
export interface SourceProps {
|
||||
text: string;
|
||||
language: Language;
|
||||
// 1-based
|
||||
highlight?: SourceHighlight[];
|
||||
revealLine?: number;
|
||||
}
|
||||
|
||||
export const Source: React.FC<SourceProps> = ({
|
||||
text,
|
||||
language,
|
||||
highlight = [],
|
||||
revealLine
|
||||
}) => {
|
||||
return <CodeMirrorWrapper text={text} language={language} readOnly={true} highlight={highlight} revealLine={revealLine} lineNumbers={true}></CodeMirrorWrapper>;
|
||||
};
|
@ -294,7 +294,7 @@ it.describe('pause', () => {
|
||||
})().catch(e => e);
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await recorderPage.waitForSelector('.source-line-error');
|
||||
await recorderPage.waitForSelector('.source-line-error-underline');
|
||||
expect(await sanitizeLog(recorderPage)).toEqual([
|
||||
'page.pause- XXms',
|
||||
'page.getByRole(\'button\').isChecked()- XXms',
|
||||
|
Loading…
Reference in New Issue
Block a user