feat(inspector): allow selecting file (#5483)
This commit is contained in:
parent
8f3a6c6b45
commit
b2227c1bcf
|
|
@ -94,7 +94,7 @@ export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageActi
|
|||
|
||||
export type BaseSignal = {
|
||||
isAsync?: boolean,
|
||||
}
|
||||
};
|
||||
|
||||
export type NavigationSignal = BaseSignal & {
|
||||
name: 'navigation',
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ const readFileAsync = util.promisify(fs.readFile);
|
|||
|
||||
declare global {
|
||||
interface Window {
|
||||
playwrightSetFile: (file: string) => void;
|
||||
playwrightSetMode: (mode: Mode) => void;
|
||||
playwrightSetPaused: (paused: boolean) => void;
|
||||
playwrightSetSources: (sources: Source[]) => void;
|
||||
|
|
@ -123,6 +124,12 @@ export class RecorderApp extends EventEmitter {
|
|||
}).toString(), true, mode, 'main').catch(() => {});
|
||||
}
|
||||
|
||||
async setFile(file: string): Promise<void> {
|
||||
await this._page.mainFrame()._evaluateExpression(((file: string) => {
|
||||
window.playwrightSetFile(file);
|
||||
}).toString(), true, file, 'main').catch(() => {});
|
||||
}
|
||||
|
||||
async setPaused(paused: boolean): Promise<void> {
|
||||
await this._page.mainFrame()._evaluateExpression(((paused: boolean) => {
|
||||
window.playwrightSetPaused(paused);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export class RecorderSupplement {
|
|||
private _params: channels.BrowserContextRecorderSupplementEnableParams;
|
||||
private _currentCallsMetadata = new Map<CallMetadata, SdkObject>();
|
||||
private _pausedCallsMetadata = new Map<CallMetadata, () => void>();
|
||||
private _pauseOnNextStatement = true;
|
||||
private _pauseOnNextStatement = false;
|
||||
private _recorderSources: Source[];
|
||||
private _userSources = new Map<string, Source>();
|
||||
|
||||
|
|
@ -104,6 +104,7 @@ export class RecorderSupplement {
|
|||
text = source.text;
|
||||
}
|
||||
this._pushAllSources();
|
||||
this._recorderApp?.setFile(primaryLanguage.fileName);
|
||||
});
|
||||
if (params.outputFile) {
|
||||
context.on(BrowserContext.Events.BeforeClose, () => {
|
||||
|
|
@ -216,12 +217,12 @@ export class RecorderSupplement {
|
|||
|
||||
private async _resume(step: boolean) {
|
||||
this._pauseOnNextStatement = step;
|
||||
this._recorderApp?.setPaused(false);
|
||||
|
||||
for (const callback of this._pausedCallsMetadata.values())
|
||||
callback();
|
||||
this._pausedCallsMetadata.clear();
|
||||
|
||||
this._recorderApp?.setPaused(false);
|
||||
this._updateUserSources();
|
||||
this.updateCallLog([...this._currentCallsMetadata.keys()]);
|
||||
}
|
||||
|
|
@ -369,6 +370,7 @@ export class RecorderSupplement {
|
|||
}
|
||||
|
||||
// Apply new decorations.
|
||||
let fileToSelect = undefined;
|
||||
for (const metadata of this._currentCallsMetadata.keys()) {
|
||||
if (!metadata.stack || !metadata.stack[0])
|
||||
continue;
|
||||
|
|
@ -381,11 +383,13 @@ export class RecorderSupplement {
|
|||
if (line) {
|
||||
const paused = this._pausedCallsMetadata.has(metadata);
|
||||
source.highlight.push({ line, type: metadata.error ? 'error' : (paused ? 'paused' : 'running') });
|
||||
if (paused)
|
||||
source.revealLine = line;
|
||||
source.revealLine = line;
|
||||
fileToSelect = source.file;
|
||||
}
|
||||
}
|
||||
this._pushAllSources();
|
||||
if (fileToSelect)
|
||||
this._recorderApp?.setFile(fileToSelect);
|
||||
}
|
||||
|
||||
private _pushAllSources() {
|
||||
|
|
|
|||
|
|
@ -51,12 +51,12 @@
|
|||
|
||||
.source-line-paused {
|
||||
background-color: #b3dbff7f;
|
||||
outline: 1px solid #009aff;
|
||||
outline: 1px solid #008aff;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.source-line-error {
|
||||
background-color: #fff0f0;
|
||||
outline: 1px solid #ffd6d6;
|
||||
outline: 1px solid #ff5656;
|
||||
z-index: 2;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import { Story, Meta } from '@storybook/react/types-6-0';
|
||||
import React from 'react';
|
||||
import { Source, SourceProps } from './source';
|
||||
import { exampleText } from './exampleText';
|
||||
import { exampleText } from './source.example';
|
||||
|
||||
export default {
|
||||
title: 'Components/Source',
|
||||
|
|
@ -37,9 +37,29 @@ Primary.args = {
|
|||
text: exampleText()
|
||||
};
|
||||
|
||||
export const HighlightLine = Template.bind({});
|
||||
HighlightLine.args = {
|
||||
export const RunningOnLine = Template.bind({});
|
||||
RunningOnLine.args = {
|
||||
language: 'javascript',
|
||||
text: exampleText(),
|
||||
highlightedLine: 11
|
||||
highlight: [
|
||||
{ line: 15, type: 'running' },
|
||||
]
|
||||
};
|
||||
|
||||
export const PausedOnLine = Template.bind({});
|
||||
PausedOnLine.args = {
|
||||
language: 'javascript',
|
||||
text: exampleText(),
|
||||
highlight: [
|
||||
{ line: 15, type: 'paused' },
|
||||
]
|
||||
};
|
||||
|
||||
export const ErrorOnLine = Template.bind({});
|
||||
ErrorOnLine.args = {
|
||||
language: 'javascript',
|
||||
text: exampleText(),
|
||||
highlight: [
|
||||
{ line: 15, type: 'error' },
|
||||
]
|
||||
};
|
||||
|
|
|
|||
78
src/web/recorder/callLog.css
Normal file
78
src/web/recorder/callLog.css
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
.call-log {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: auto;
|
||||
line-height: 20px;
|
||||
white-space: pre;
|
||||
background: white;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.call-log-message {
|
||||
flex: none;
|
||||
padding: 3px 0 3px 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.call-log-header {
|
||||
color: var(--toolbar-color);
|
||||
box-shadow: var(--box-shadow);
|
||||
background-color: var(--toolbar-bg-color);
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 9px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.call-log-call {
|
||||
display: flex;
|
||||
flex: none;
|
||||
flex-direction: column;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.call-log-call-header {
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 2px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.call-log-call .codicon {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.call-log .codicon-check {
|
||||
color: #21a945;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.call-log-call.error {
|
||||
background-color: #fff0f0;
|
||||
border-top: 1px solid #ffd6d6;
|
||||
}
|
||||
|
||||
.call-log-call.error .call-log-call-header,
|
||||
.call-log-message.error,
|
||||
.call-log .codicon-error {
|
||||
color: red;
|
||||
}
|
||||
62
src/web/recorder/callLog.example.ts
Normal file
62
src/web/recorder/callLog.example.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
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 { CallLog } from '../../server/supplements/recorder/recorderTypes';
|
||||
|
||||
export function exampleCallLog(): CallLog[] {
|
||||
return [
|
||||
{
|
||||
'id': 3,
|
||||
'messages': [],
|
||||
'title': 'newPage',
|
||||
'status': 'done'
|
||||
},
|
||||
{
|
||||
'id': 4,
|
||||
'messages': [
|
||||
'navigating to "https://github.com/microsoft", waiting until "load"',
|
||||
],
|
||||
'title': 'goto',
|
||||
'status': 'done'
|
||||
},
|
||||
{
|
||||
'id': 5,
|
||||
'messages': [
|
||||
'waiting for selector "input[aria-label="Find a repository…"]"',
|
||||
' selector resolved to visible <input name="q" value=" type="search" autocomplete="of…/>',
|
||||
'attempting click action',
|
||||
' waiting for element to be visible, enabled and stable',
|
||||
' element is visible, enabled and stable',
|
||||
' scrolling into view if needed',
|
||||
' done scrolling',
|
||||
' checking that element receives pointer events at (351.6,291)',
|
||||
' element does receive pointer events',
|
||||
' performing click action'
|
||||
],
|
||||
'title': 'click',
|
||||
'status': 'paused'
|
||||
},
|
||||
{
|
||||
'id': 5,
|
||||
'messages': [
|
||||
'navigating to "https://github.com/microsoft", waiting until "load"',
|
||||
],
|
||||
'error': 'Error occured',
|
||||
'title': 'error',
|
||||
'status': 'error'
|
||||
},
|
||||
];
|
||||
}
|
||||
37
src/web/recorder/callLog.stories.tsx
Normal file
37
src/web/recorder/callLog.stories.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
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 { Story, Meta } from '@storybook/react/types-6-0';
|
||||
import React from 'react';
|
||||
import { CallLogProps, CallLogView } from './callLog';
|
||||
import { exampleCallLog } from './callLog.example';
|
||||
|
||||
export default {
|
||||
title: 'Recorder/CallLog',
|
||||
component: CallLogView,
|
||||
parameters: {
|
||||
viewport: {
|
||||
defaultViewport: 'recorder'
|
||||
}
|
||||
}
|
||||
} as Meta;
|
||||
|
||||
const Template: Story<CallLogProps> = args => <CallLogView {...args} />;
|
||||
|
||||
export const Primary = Template.bind({});
|
||||
Primary.args = {
|
||||
log: exampleCallLog()
|
||||
};
|
||||
63
src/web/recorder/callLog.tsx
Normal file
63
src/web/recorder/callLog.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
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 './callLog.css';
|
||||
import * as React from 'react';
|
||||
import type { CallLog } from '../../server/supplements/recorder/recorderTypes';
|
||||
|
||||
export interface CallLogProps {
|
||||
log: CallLog[]
|
||||
}
|
||||
|
||||
export const CallLogView: React.FC<CallLogProps> = ({
|
||||
log
|
||||
}) => {
|
||||
const messagesEndRef = React.createRef<HTMLDivElement>();
|
||||
React.useLayoutEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ block: 'center', inline: 'nearest' });
|
||||
}, [messagesEndRef]);
|
||||
|
||||
return <div className='vbox'>
|
||||
<div className='call-log-header' style={{flex: 'none'}}>Log</div>
|
||||
<div className='call-log' style={{flex: 'auto'}}>
|
||||
{log.map(callLog => {
|
||||
return <div className={`call-log-call ${callLog.status}`} key={callLog.id}>
|
||||
<div className='call-log-call-header'>
|
||||
<span className={'codicon ' + iconClass(callLog)}></span>{ callLog.title }
|
||||
</div>
|
||||
{ callLog.messages.map((message, i) => {
|
||||
return <div className='call-log-message' key={i}>
|
||||
{ message.trim() }
|
||||
</div>;
|
||||
})}
|
||||
{ callLog.error ? <div className='call-log-message error'>
|
||||
{ callLog.error }
|
||||
</div> : undefined }
|
||||
</div>
|
||||
})}
|
||||
<div ref={messagesEndRef}></div>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
function iconClass(callLog: CallLog): string {
|
||||
switch (callLog.status) {
|
||||
case 'done': return 'codicon-check';
|
||||
case 'in-progress': return 'codicon-clock';
|
||||
case 'paused': return 'codicon-debug-pause';
|
||||
case 'error': return 'codicon-error';
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ import * as React from 'react';
|
|||
import * as ReactDOM from 'react-dom';
|
||||
import { applyTheme } from '../theme';
|
||||
import '../common.css';
|
||||
import { Recorder } from './recorder';
|
||||
import { Main } from './main';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
@ -28,5 +28,5 @@ declare global {
|
|||
|
||||
(async () => {
|
||||
applyTheme();
|
||||
ReactDOM.render(<Recorder/>, document.querySelector('#root'));
|
||||
ReactDOM.render(<Main/>, document.querySelector('#root'));
|
||||
})();
|
||||
|
|
|
|||
52
src/web/recorder/main.tsx
Normal file
52
src/web/recorder/main.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
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 './recorder.css';
|
||||
import * as React from 'react';
|
||||
import type { CallLog, Mode, Source } from '../../server/supplements/recorder/recorderTypes';
|
||||
import { Recorder } from './recorder';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
playwrightSetMode: (mode: Mode) => void;
|
||||
playwrightSetPaused: (paused: boolean) => void;
|
||||
playwrightSetSources: (sources: Source[]) => void;
|
||||
playwrightUpdateLogs: (callLogs: CallLog[]) => void;
|
||||
dispatch(data: any): Promise<void>;
|
||||
playwrightSourcesEchoForTest: Source[];
|
||||
}
|
||||
}
|
||||
|
||||
export const Main: React.FC = ({
|
||||
}) => {
|
||||
const [sources, setSources] = React.useState<Source[]>([]);
|
||||
const [paused, setPaused] = React.useState(false);
|
||||
const [log, setLog] = React.useState(new Map<number, CallLog>());
|
||||
const [mode, setMode] = React.useState<Mode>('none');
|
||||
|
||||
window.playwrightSetMode = setMode;
|
||||
window.playwrightSetSources = setSources;
|
||||
window.playwrightSetPaused = setPaused;
|
||||
window.playwrightUpdateLogs = callLogs => {
|
||||
const newLog = new Map<number, CallLog>(log);
|
||||
for (const callLog of callLogs)
|
||||
newLog.set(callLog.id, callLog);
|
||||
setLog(newLog);
|
||||
};
|
||||
|
||||
window.playwrightSourcesEchoForTest = sources;
|
||||
return <Recorder sources={sources} paused={paused} log={log} mode={mode}/>;
|
||||
};
|
||||
|
|
@ -30,65 +30,10 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.recorder-log {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: auto;
|
||||
line-height: 20px;
|
||||
white-space: pre;
|
||||
background: white;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.recorder-log-message {
|
||||
flex: none;
|
||||
padding: 3px 0 3px 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.recorder-log-header {
|
||||
.recorder-chooser {
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
color: var(--toolbar-color);
|
||||
box-shadow: var(--box-shadow);
|
||||
background-color: var(--toolbar-bg-color);
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 9px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.recorder-log-call {
|
||||
display: flex;
|
||||
flex: none;
|
||||
flex-direction: column;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.recorder-log-call-header {
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 2px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.recorder-log-call .codicon {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.recorder-log .codicon-check {
|
||||
color: #21a945;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.recorder-log-call.error {
|
||||
background-color: #fff0f0;
|
||||
border-top: 1px solid #ffd6d6;
|
||||
}
|
||||
|
||||
.recorder-log-call.error .recorder-log-call-header,
|
||||
.recorder-log-message.error,
|
||||
.recorder-log .codicon-error {
|
||||
color: red;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
import { Story, Meta } from '@storybook/react/types-6-0';
|
||||
import React from 'react';
|
||||
import { exampleCallLog } from './callLog.example';
|
||||
import { Recorder, RecorderProps } from './recorder';
|
||||
|
||||
export default {
|
||||
|
|
@ -32,4 +33,53 @@ const Template: Story<RecorderProps> = args => <Recorder {...args} />;
|
|||
|
||||
export const Primary = Template.bind({});
|
||||
Primary.args = {
|
||||
sources: [],
|
||||
paused: false,
|
||||
log: [],
|
||||
mode: 'none'
|
||||
};
|
||||
|
||||
export const OneSource = Template.bind({});
|
||||
OneSource.args = {
|
||||
sources: [
|
||||
{
|
||||
file: '<one>',
|
||||
text: '// Text One',
|
||||
language: 'javascript',
|
||||
highlight: [],
|
||||
},
|
||||
],
|
||||
paused: false,
|
||||
log: [],
|
||||
mode: 'none'
|
||||
};
|
||||
|
||||
export const TwoSources = Template.bind({});
|
||||
TwoSources.args = {
|
||||
sources: [
|
||||
{
|
||||
file: '<one>',
|
||||
text: '// Text One',
|
||||
language: 'javascript',
|
||||
highlight: [],
|
||||
},
|
||||
{
|
||||
file: '<two>',
|
||||
text: '// Text Two',
|
||||
language: 'javascript',
|
||||
highlight: [],
|
||||
},
|
||||
],
|
||||
paused: false,
|
||||
log: [],
|
||||
mode: 'none'
|
||||
};
|
||||
|
||||
export const WithLog = Template.bind({});
|
||||
WithLog.args = {
|
||||
sources: [
|
||||
],
|
||||
paused: false,
|
||||
log: exampleCallLog(),
|
||||
mode: 'none'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,50 +21,35 @@ import { ToolbarButton } from '../components/toolbarButton';
|
|||
import { Source as SourceView } from '../components/source';
|
||||
import type { CallLog, Mode, Source } from '../../server/supplements/recorder/recorderTypes';
|
||||
import { SplitView } from '../components/splitView';
|
||||
import { CallLogView } from './callLog';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
playwrightSetMode: (mode: Mode) => void;
|
||||
playwrightSetPaused: (paused: boolean) => void;
|
||||
playwrightSetSources: (sources: Source[]) => void;
|
||||
playwrightUpdateLogs: (callLogs: CallLog[]) => void;
|
||||
dispatch(data: any): Promise<void>;
|
||||
playwrightSourcesEchoForTest: Source[];
|
||||
playwrightSetFile: (file: string) => void;
|
||||
}
|
||||
}
|
||||
|
||||
export interface RecorderProps {
|
||||
sources: Source[],
|
||||
paused: boolean,
|
||||
log: Map<number, CallLog>,
|
||||
mode: Mode
|
||||
}
|
||||
|
||||
export const Recorder: React.FC<RecorderProps> = ({
|
||||
sources,
|
||||
paused,
|
||||
log,
|
||||
mode
|
||||
}) => {
|
||||
const [sources, setSources] = React.useState<Source[]>([]);
|
||||
const [paused, setPaused] = React.useState(false);
|
||||
const [log, setLog] = React.useState(new Map<number, CallLog>());
|
||||
const [mode, setMode] = React.useState<Mode>('none');
|
||||
const [f, setFile] = React.useState<string | undefined>();
|
||||
window.playwrightSetFile = setFile;
|
||||
const file = f || sources[0]?.file;
|
||||
|
||||
window.playwrightSetMode = setMode;
|
||||
window.playwrightSetSources = setSources;
|
||||
window.playwrightSetPaused = setPaused;
|
||||
window.playwrightUpdateLogs = callLogs => {
|
||||
const newLog = new Map<number, CallLog>(log);
|
||||
for (const callLog of callLogs)
|
||||
newLog.set(callLog.id, callLog);
|
||||
setLog(newLog);
|
||||
};
|
||||
|
||||
window.playwrightSourcesEchoForTest = sources;
|
||||
const source = sources.find(source => {
|
||||
let s = sources.find(s => s.revealLine);
|
||||
if (!s)
|
||||
s = sources.find(s => s.file === source.file);
|
||||
if (!s)
|
||||
s = sources[0];
|
||||
return s;
|
||||
}) || {
|
||||
file: 'untitled',
|
||||
const source = sources.find(s => s.file === file) || {
|
||||
text: '',
|
||||
language: 'javascript',
|
||||
file: '',
|
||||
highlight: []
|
||||
};
|
||||
|
||||
|
|
@ -81,48 +66,35 @@ export const Recorder: React.FC<RecorderProps> = ({
|
|||
<ToolbarButton icon='question' title='Inspect' toggled={mode == 'inspecting'} onClick={() => {
|
||||
window.dispatch({ event: 'setMode', params: { mode: mode === 'inspecting' ? 'none' : 'inspecting' }}).catch(() => { });
|
||||
}}></ToolbarButton>
|
||||
<ToolbarButton icon='files' title='Copy' disabled={!source.text} onClick={() => {
|
||||
<ToolbarButton icon='files' title='Copy' disabled={!source || !source.text} onClick={() => {
|
||||
copy(source.text);
|
||||
}}></ToolbarButton>
|
||||
<ToolbarButton icon='debug-continue' title='Resume' disabled={!paused} onClick={() => {
|
||||
setPaused(false);
|
||||
window.dispatch({ event: 'resume' }).catch(() => {});
|
||||
}}></ToolbarButton>
|
||||
<ToolbarButton icon='debug-pause' title='Pause' disabled={paused} onClick={() => {
|
||||
window.dispatch({ event: 'pause' }).catch(() => {});
|
||||
}}></ToolbarButton>
|
||||
<ToolbarButton icon='debug-step-over' title='Step over' disabled={!paused} onClick={() => {
|
||||
setPaused(false);
|
||||
window.dispatch({ event: 'step' }).catch(() => {});
|
||||
}}></ToolbarButton>
|
||||
<select className='recorder-chooser' hidden={!sources.length} 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} selected={s.file === file}>{title}</option>;
|
||||
})
|
||||
}
|
||||
</select>
|
||||
<div style={{flex: 'auto'}}></div>
|
||||
<ToolbarButton icon='clear-all' title='Clear' disabled={!source.text} onClick={() => {
|
||||
<ToolbarButton icon='clear-all' title='Clear' disabled={!source || !source.text} onClick={() => {
|
||||
window.dispatch({ event: 'clear' }).catch(() => {});
|
||||
}}></ToolbarButton>
|
||||
</Toolbar>
|
||||
<SplitView sidebarSize={200}>
|
||||
<SourceView text={source.text} language={source.language} highlight={source.highlight} revealLine={source.revealLine}></SourceView>
|
||||
<div className='vbox'>
|
||||
<div className='recorder-log-header' style={{flex: 'none'}}>Log</div>
|
||||
<div className='recorder-log' style={{flex: 'auto'}}>
|
||||
{[...log.values()].map(callLog => {
|
||||
return <div className={`recorder-log-call ${callLog.status}`} key={callLog.id}>
|
||||
<div className='recorder-log-call-header'>
|
||||
<span className={'codicon ' + iconClass(callLog)}></span>{ callLog.title }
|
||||
</div>
|
||||
{ callLog.messages.map((message, i) => {
|
||||
return <div className='recorder-log-message' key={i}>
|
||||
{ message.trim() }
|
||||
</div>;
|
||||
})}
|
||||
{ callLog.error ? <div className='recorder-log-message error'>
|
||||
{ callLog.error }
|
||||
</div> : undefined }
|
||||
</div>
|
||||
})}
|
||||
<div ref={messagesEndRef}></div>
|
||||
</div>
|
||||
</div>
|
||||
<CallLogView log={[...log.values()]}/>
|
||||
</SplitView>
|
||||
</div>;
|
||||
};
|
||||
|
|
@ -137,12 +109,3 @@ function copy(text: string) {
|
|||
document.execCommand('copy');
|
||||
textArea.remove();
|
||||
}
|
||||
|
||||
function iconClass(callLog: CallLog): string {
|
||||
switch (callLog.status) {
|
||||
case 'done': return 'codicon-check';
|
||||
case 'in-progress': return 'codicon-clock';
|
||||
case 'paused': return 'codicon-debug-pause';
|
||||
case 'error': return 'codicon-error';
|
||||
}
|
||||
}
|
||||
|
|
@ -226,7 +226,7 @@ describe('pause', (suite, { mode }) => {
|
|||
});
|
||||
|
||||
async function sanitizeLog(recorderPage: Page): Promise<string[]> {
|
||||
const text = await recorderPage.innerText('.recorder-log');
|
||||
const text = await recorderPage.innerText('.call-log');
|
||||
return text.split('\n').filter(l => {
|
||||
return l !== 'element is not stable - waiting...';
|
||||
}).map(l => {
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ DEPS['src/cli/driver.ts'] = DEPS['src/inprocess.ts'] = DEPS['src/browserServerIm
|
|||
|
||||
// Tracing is a client/server plugin, nothing should depend on it.
|
||||
DEPS['src/trace/'] = ['src/common/', 'src/utils/', 'src/client/**', 'src/server/**'];
|
||||
DEPS['src/web/recorder/'] = ['src/common/', 'src/web/', 'src/web/components/'];
|
||||
DEPS['src/web/recorder/'] = ['src/common/', 'src/web/', 'src/web/components/', 'src/server/supplements/recorder/recorderTypes.ts'];
|
||||
DEPS['src/web/traceViewer/'] = ['src/common/', 'src/web/', 'src/cli/traceViewer/'];
|
||||
DEPS['src/web/traceViewer/ui/'] = ['src/common/', 'src/web/traceViewer/', 'src/web/', 'src/cli/traceViewer/', 'src/trace/'];
|
||||
// The service is a cross-cutting feature, and so it depends on a bunch of things.
|
||||
|
|
@ -156,7 +156,6 @@ DEPS['src/service.ts'] = ['src/remote/'];
|
|||
DEPS['src/cli/'] = ['src/cli/**', 'src/client/**', 'src/install/**', 'src/generated/', 'src/server/injected/', 'src/debug/injected/', 'src/trace/**', 'src/utils/**'];
|
||||
|
||||
DEPS['src/server/supplements/recorder/recorderApp.ts'] = ['src/common/', 'src/utils/', 'src/server/', 'src/server/chromium/'];
|
||||
DEPS['src/web/recorder/recorder.tsx'] = ['src/server/supplements/recorder/recorderTypes.ts'];
|
||||
DEPS['src/utils/'] = ['src/common/'];
|
||||
|
||||
checkDeps().catch(e => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue