mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-05 19:04:43 +03:00
chore: split code mirror and xterm modules (#21415)
This commit is contained in:
parent
99e736afc8
commit
b6ff3bad98
@ -26,10 +26,10 @@ import { createPlaywright } from '../../playwright';
|
||||
import { ProgressController } from '../../progress';
|
||||
import type { Page } from '../../page';
|
||||
|
||||
type Options = { headless?: boolean, host?: string, port?: number, watchMode?: boolean };
|
||||
type Options = { app?: string, headless?: boolean, host?: string, port?: number };
|
||||
|
||||
export async function showTraceViewer(traceUrls: string[], browserName: string, options?: Options): Promise<Page> {
|
||||
const { headless = false, host, port, watchMode } = options || {};
|
||||
const { headless = false, host, port, app } = options || {};
|
||||
for (const traceUrl of traceUrls) {
|
||||
if (!traceUrl.startsWith('http://') && !traceUrl.startsWith('https://') && !fs.existsSync(traceUrl)) {
|
||||
// eslint-disable-next-line no-console
|
||||
@ -89,8 +89,6 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
|
||||
await syncLocalStorageWithSettings(page, 'traceviewer');
|
||||
|
||||
const params = traceUrls.map(t => `trace=${t}`);
|
||||
if (watchMode)
|
||||
params.push('watchMode=true');
|
||||
if (isUnderTest()) {
|
||||
params.push('isUnderTest=true');
|
||||
page.on('close', () => context.close(serverSideCallMetadata()).catch(() => {}));
|
||||
@ -99,6 +97,6 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
|
||||
}
|
||||
|
||||
const searchQuery = params.length ? '?' + params.join('&') : '';
|
||||
await page.mainFrame().goto(serverSideCallMetadata(), urlPrefix + `/trace/index.html${searchQuery}`);
|
||||
await page.mainFrame().goto(serverSideCallMetadata(), urlPrefix + `/trace/${app || 'index.html'}${searchQuery}`);
|
||||
return page;
|
||||
}
|
||||
|
@ -281,12 +281,18 @@ class HtmlBuilder {
|
||||
if (this._hasTraces) {
|
||||
const traceViewerFolder = path.join(require.resolve('playwright-core'), '..', 'lib', 'webpack', 'traceViewer');
|
||||
const traceViewerTargetFolder = path.join(this._reportFolder, 'trace');
|
||||
fs.mkdirSync(traceViewerTargetFolder, { recursive: true });
|
||||
const traceViewerAssetsTargetFolder = path.join(traceViewerTargetFolder, 'assets');
|
||||
fs.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true });
|
||||
for (const file of fs.readdirSync(traceViewerFolder)) {
|
||||
if (file.endsWith('.map'))
|
||||
if (file.endsWith('.map') || file.includes('watch') || file.includes('assets'))
|
||||
continue;
|
||||
await copyFileAndMakeWritable(path.join(traceViewerFolder, file), path.join(traceViewerTargetFolder, file));
|
||||
}
|
||||
for (const file of fs.readdirSync(path.join(traceViewerFolder, 'assets'))) {
|
||||
if (file.endsWith('.map') || file.includes('xTermModule'))
|
||||
continue;
|
||||
await copyFileAndMakeWritable(path.join(traceViewerFolder, 'assets', file), path.join(traceViewerAssetsTargetFolder, file));
|
||||
}
|
||||
}
|
||||
|
||||
// Inline report data.
|
||||
|
@ -65,7 +65,7 @@ class UIMode {
|
||||
}
|
||||
|
||||
async showUI() {
|
||||
this._page = await showTraceViewer([], 'chromium', { watchMode: true });
|
||||
this._page = await showTraceViewer([], 'chromium', { app: 'watch.html' });
|
||||
const exitPromise = new ManualPromise();
|
||||
this._page.on('close', () => exitPromise.resolve());
|
||||
this._page.exposeBinding('sendMessage', false, async (source, data) => {
|
||||
|
30
packages/trace-viewer/public/watch.webmanifest
Normal file
30
packages/trace-viewer/public/watch.webmanifest
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"theme_color": "#000",
|
||||
"background_color": "#fff",
|
||||
"display": "browser",
|
||||
"start_url": "watch.html",
|
||||
"name": "Playwright Test",
|
||||
"short_name": "Trace Viewer",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icon-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
@ -19,17 +19,8 @@ import { applyTheme } from '@web/theme';
|
||||
import '@web/third_party/vscode/codicon.css';
|
||||
import React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { WatchModeView } from './ui/watchMode';
|
||||
import { WorkbenchLoader } from './ui/workbench';
|
||||
|
||||
export const RootView: React.FC<{}> = ({
|
||||
}) => {
|
||||
if (window.location.href.includes('watchMode=true'))
|
||||
return <WatchModeView />;
|
||||
else
|
||||
return <WorkbenchLoader/>;
|
||||
};
|
||||
|
||||
(async () => {
|
||||
applyTheme();
|
||||
if (window.location.protocol !== 'file:') {
|
||||
@ -46,5 +37,5 @@ export const RootView: React.FC<{}> = ({
|
||||
setInterval(function() { fetch('ping'); }, 10000);
|
||||
}
|
||||
|
||||
ReactDOM.render(<RootView></RootView>, document.querySelector('#root'));
|
||||
ReactDOM.render(<WorkbenchLoader/>, document.querySelector('#root'));
|
||||
})();
|
||||
|
41
packages/trace-viewer/src/watch.tsx
Normal file
41
packages/trace-viewer/src/watch.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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 React from 'react';
|
||||
import '@web/common.css';
|
||||
import { applyTheme } from '@web/theme';
|
||||
import '@web/third_party/vscode/codicon.css';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { WatchModeView } from './ui/watchMode';
|
||||
|
||||
(async () => {
|
||||
applyTheme();
|
||||
if (window.location.protocol !== 'file:') {
|
||||
if (window.location.href.includes('isUnderTest=true'))
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
navigator.serviceWorker.register('sw.bundle.js');
|
||||
if (!navigator.serviceWorker.controller) {
|
||||
await new Promise<void>(f => {
|
||||
navigator.serviceWorker.oncontrollerchange = () => f();
|
||||
});
|
||||
}
|
||||
|
||||
// Keep SW running.
|
||||
setInterval(function() { fetch('ping'); }, 10000);
|
||||
}
|
||||
|
||||
ReactDOM.render(<WatchModeView></WatchModeView>, document.querySelector('#root'));
|
||||
})();
|
@ -43,6 +43,7 @@ export default defineConfig({
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: path.resolve(__dirname, 'index.html'),
|
||||
watch: path.resolve(__dirname, 'watch.html'),
|
||||
popout: path.resolve(__dirname, 'popout.html'),
|
||||
},
|
||||
output: {
|
||||
|
30
packages/trace-viewer/watch.html
Normal file
30
packages/trace-viewer/watch.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/icon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/icon-16x16.png">
|
||||
<link rel="manifest" href="/watch.webmanifest">
|
||||
<title>Playwright Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/watch.tsx"></script>
|
||||
</body>
|
||||
</html>
|
24
packages/web/src/components/codeMirrorModule.tsx
Normal file
24
packages/web/src/components/codeMirrorModule.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
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 codemirror from 'codemirror';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/mode/python/python';
|
||||
import 'codemirror/mode/clike/clike';
|
||||
|
||||
export type CodeMirror = typeof codemirror;
|
||||
export default codemirror;
|
@ -16,11 +16,7 @@
|
||||
|
||||
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 type { CodeMirror } from './codeMirrorModule';
|
||||
|
||||
export type SourceHighlight = {
|
||||
line: number;
|
||||
@ -54,42 +50,53 @@ export const CodeMirrorWrapper: React.FC<SourceProps> = ({
|
||||
onChange,
|
||||
}) => {
|
||||
const codemirrorElement = React.createRef<HTMLDivElement>();
|
||||
const [modulePromise] = React.useState<Promise<CodeMirror>>(import('./codeMirrorModule').then(m => m.default));
|
||||
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';
|
||||
(async () => {
|
||||
// Always load the module first.
|
||||
const CodeMirror = await modulePromise;
|
||||
|
||||
if (codemirror && codemirror.getOption('mode') === mode && codemirror.isReadOnly() === readOnly)
|
||||
return;
|
||||
const element = codemirrorElement.current;
|
||||
if (!element)
|
||||
return;
|
||||
|
||||
if (!codemirrorElement.current)
|
||||
return;
|
||||
if (codemirror)
|
||||
codemirror.getWrapperElement().remove();
|
||||
let mode = 'javascript';
|
||||
if (language === 'python')
|
||||
mode = 'python';
|
||||
if (language === 'java')
|
||||
mode = 'text/x-java';
|
||||
if (language === 'csharp')
|
||||
mode = 'text/x-csharp';
|
||||
|
||||
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
|
||||
&& mode === codemirror.getOption('mode')
|
||||
&& readOnly === codemirror.getOption('readOnly')
|
||||
&& lineNumbers === codemirror.getOption('lineNumbers')
|
||||
&& wrapLines === codemirror.getOption('lineWrapping')) {
|
||||
updateEditor(codemirror, text, highlight, revealLine, focusOnChange);
|
||||
return;
|
||||
}
|
||||
|
||||
if (codemirror)
|
||||
updateEditor(codemirror, text, highlight, revealLine, focusOnChange);
|
||||
// Either configuration is different or we don't have a codemirror yet.
|
||||
if (codemirror)
|
||||
codemirror.getWrapperElement().remove();
|
||||
|
||||
const cm = CodeMirror(element, {
|
||||
value: '',
|
||||
mode,
|
||||
readOnly,
|
||||
lineNumbers,
|
||||
lineWrapping: wrapLines,
|
||||
});
|
||||
setCodemirror(cm);
|
||||
if (onChange)
|
||||
cm.on('change', () => onChange(cm.getValue()));
|
||||
updateEditor(cm, text, highlight, revealLine, focusOnChange);
|
||||
return cm;
|
||||
})();
|
||||
}, [modulePromise, codemirror, codemirrorElement, text, language, highlight, revealLine, focusOnChange, lineNumbers, wrapLines, readOnly, onChange]);
|
||||
|
||||
return <div className='cm-wrapper' ref={codemirrorElement}></div>;
|
||||
};
|
||||
|
27
packages/web/src/components/xTermModule.tsx
Normal file
27
packages/web/src/components/xTermModule.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
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 'xterm/css/xterm.css';
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
|
||||
export type XTermModule = {
|
||||
Terminal: typeof Terminal;
|
||||
FitAddon: typeof FitAddon;
|
||||
};
|
||||
|
||||
export default { Terminal, FitAddon };
|
@ -15,10 +15,9 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Terminal } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import 'xterm/css/xterm.css';
|
||||
import './xtermWrapper.css';
|
||||
import type { Terminal } from 'xterm';
|
||||
import type { XTermModule } from './xtermModule';
|
||||
|
||||
export type XTermDataSource = {
|
||||
pending: (string | Uint8Array)[];
|
||||
@ -30,29 +29,37 @@ export const XTermWrapper: React.FC<{ source: XTermDataSource }> = ({
|
||||
source
|
||||
}) => {
|
||||
const xtermElement = React.createRef<HTMLDivElement>();
|
||||
const [modulePromise] = React.useState<Promise<XTermModule>>(import('./xTermModule').then(m => m.default));
|
||||
const [terminal, setTerminal] = React.useState<Terminal>();
|
||||
React.useEffect(() => {
|
||||
if (terminal)
|
||||
return;
|
||||
if (!xtermElement.current)
|
||||
return;
|
||||
const newTerminal = new Terminal({ convertEol: true });
|
||||
const fitAddon = new FitAddon();
|
||||
newTerminal.loadAddon(fitAddon);
|
||||
for (const p of source.pending)
|
||||
newTerminal.write(p);
|
||||
source.write = (data => {
|
||||
newTerminal.write(data);
|
||||
});
|
||||
newTerminal.open(xtermElement.current);
|
||||
setTerminal(newTerminal);
|
||||
fitAddon.fit();
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
source.resize(newTerminal.cols, newTerminal.rows);
|
||||
(async () => {
|
||||
// Always load the module first.
|
||||
const { Terminal, FitAddon } = await modulePromise;
|
||||
const element = xtermElement.current;
|
||||
if (!element)
|
||||
return;
|
||||
|
||||
if (terminal)
|
||||
return;
|
||||
|
||||
const newTerminal = new Terminal({ convertEol: true });
|
||||
const fitAddon = new FitAddon();
|
||||
newTerminal.loadAddon(fitAddon);
|
||||
for (const p of source.pending)
|
||||
newTerminal.write(p);
|
||||
source.write = (data => {
|
||||
newTerminal.write(data);
|
||||
});
|
||||
newTerminal.open(element);
|
||||
fitAddon.fit();
|
||||
});
|
||||
resizeObserver.observe(xtermElement.current);
|
||||
}, [terminal, xtermElement, source]);
|
||||
setTerminal(newTerminal);
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
source.resize(newTerminal.cols, newTerminal.rows);
|
||||
fitAddon.fit();
|
||||
});
|
||||
resizeObserver.observe(element);
|
||||
})();
|
||||
}, [modulePromise, terminal, xtermElement, source]);
|
||||
return <div className='xterm-wrapper' style={{ flex: 'auto' }} ref={xtermElement}>
|
||||
</div>;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user