chore: make trace server work over http (#23561)

This commit is contained in:
Pavel Feldman 2023-06-06 18:36:05 -07:00 committed by GitHub
parent 2e327c9d0c
commit 0b30f2017c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 96 additions and 63 deletions

View File

@ -94,9 +94,9 @@ async function startTraceViewerServer(traceUrls: string[], options?: Options): P
});
const params = traceUrls.map(t => `trace=${t}`);
const transport = options?.transport || (options?.isServer ? new StdinServer() : undefined);
if (options?.transport) {
const transport = options?.transport;
if (transport) {
const guid = createGuid();
params.push('ws=' + guid);
const wss = new wsServer({ server: server.server(), path: '/' + guid });
@ -135,7 +135,6 @@ async function startTraceViewerServer(traceUrls: string[], options?: Options): P
}
export async function openTraceViewerApp(traceUrls: string[], browserName: string, options?: Options): Promise<Page> {
const stdinServer = options?.isServer ? new StdinServer() : undefined;
const { url } = await startTraceViewerServer(traceUrls, options);
const traceViewerPlaywright = createPlaywright({ sdkLanguage: 'javascript', isInternalPlaywright: true });
const traceViewerBrowser = isUnderTest() ? 'chromium' : browserName;
@ -176,7 +175,6 @@ export async function openTraceViewerApp(traceUrls: string[], browserName: strin
page.on('close', () => process.exit());
await page.mainFrame().goto(serverSideCallMetadata(), url);
stdinServer?.setPage(page);
return page;
}
@ -187,7 +185,7 @@ async function openTraceInBrowser(traceUrls: string[], options?: Options) {
await open(url, { wait: true }).catch(() => {});
}
class StdinServer {
class StdinServer implements Transport {
private _pollTimer: NodeJS.Timeout | undefined;
private _traceUrl: string | undefined;
private _page: Page | undefined;
@ -197,7 +195,6 @@ class StdinServer {
const url = data.toString().trim();
if (url === this._traceUrl)
return;
this._traceUrl = url;
if (url.endsWith('.json'))
this._pollLoadTrace(url);
else
@ -206,15 +203,24 @@ class StdinServer {
process.stdin.on('close', () => this._selfDestruct());
}
setPage(page: Page) {
this._page = page;
if (this._traceUrl)
this._loadTrace(this._traceUrl);
async dispatch(method: string, params: any) {
if (method === 'ready') {
if (this._traceUrl)
this._loadTrace(this._traceUrl);
}
}
onclose() {
this._selfDestruct();
}
sendEvent?: (method: string, params: any) => void;
close?: () => void;
private _loadTrace(url: string) {
this._traceUrl = url;
clearTimeout(this._pollTimer);
this._page?.mainFrame().evaluateExpression(`window.setTraceURL(${JSON.stringify(url)})`).catch(() => {});
this.sendEvent?.('loadTrace', { url });
}
private _pollLoadTrace(url: string) {

View File

@ -87,6 +87,9 @@ class UIMode {
this._transport = {
dispatch: async (method, params) => {
if (method === 'ping')
return;
if (method === 'exit') {
exitPromise.resolve();
return;

View File

@ -37,11 +37,14 @@ import { toggleTheme } from '@web/theme';
import { artifactsFolderName } from '@testIsomorphic/folders';
import { msToString, settings, useSetting } from '@web/uiUtils';
import type { ActionTraceEvent } from '@trace/trace';
import { connect } from './wsPort';
let updateRootSuite: (config: FullConfig, rootSuite: Suite, loadErrors: TestError[], progress: Progress | undefined) => void = () => {};
let runWatchedTests = (fileNames: string[]) => {};
let xtermSize = { cols: 80, rows: 24 };
let sendMessage: (method: string, params?: any) => Promise<any> = async () => {};
const xtermDataSource: XtermDataSource = {
pending: [],
clear: () => {},
@ -96,7 +99,10 @@ export const UIModeView: React.FC<{}> = ({
React.useEffect(() => {
inputRef.current?.focus();
setIsLoading(true);
initWebSocket(() => setIsDisconnected(true)).then(() => reloadTests());
connect({ onEvent: dispatchEvent, onClose: () => setIsDisconnected(true) }).then(send => {
sendMessage = send;
reloadTests();
});
}, [reloadTests]);
updateRootSuite = React.useCallback((config: FullConfig, rootSuite: Suite, loadErrors: TestError[], newProgress: Progress | undefined) => {
@ -639,43 +645,6 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
return sendMessage('list', {});
};
let lastId = 0;
let _ws: WebSocket;
const callbacks = new Map<number, { resolve: (arg: any) => void, reject: (arg: Error) => void }>();
const initWebSocket = async (onClose: () => void) => {
const guid = new URLSearchParams(window.location.search).get('ws');
const ws = new WebSocket(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.hostname}:${window.location.port}/${guid}`);
await new Promise(f => ws.addEventListener('open', f));
ws.addEventListener('close', onClose);
ws.addEventListener('message', event => {
const message = JSON.parse(event.data);
const { id, result, error, method, params } = message;
if (id) {
const callback = callbacks.get(id);
if (!callback)
return;
callbacks.delete(id);
if (error)
callback.reject(new Error(error));
else
callback.resolve(result);
} else {
dispatchMessage(method, params);
}
});
_ws = ws;
};
const sendMessage = async (method: string, params: any): Promise<any> => {
const id = ++lastId;
const message = { id, method, params };
_ws.send(JSON.stringify(message));
return new Promise((resolve, reject) => {
callbacks.set(id, { resolve, reject });
});
};
const sendMessageNoReply = (method: string, params?: any) => {
if ((window as any)._overrideProtocolForTest) {
(window as any)._overrideProtocolForTest({ method, params }).catch(() => {});
@ -687,7 +656,7 @@ const sendMessageNoReply = (method: string, params?: any) => {
});
};
const dispatchMessage = (method: string, params?: any) => {
const dispatchEvent = (method: string, params?: any) => {
if (method === 'listChanged') {
refreshRootSuite(false).catch(() => {});
return;

View File

@ -21,6 +21,7 @@ import { MultiTraceModel } from './modelUtil';
import './workbench.css';
import { toggleTheme } from '@web/theme';
import { Workbench } from './workbench';
import { connect } from './wsPort';
export const WorkbenchLoader: React.FunctionComponent<{
}> = () => {
@ -82,13 +83,19 @@ export const WorkbenchLoader: React.FunctionComponent<{
}
}
(window as any).setTraceURL = (url: string) => {
setTraceURLs([url]);
setDragOver(false);
setProcessingErrorMessage(null);
};
if (earlyTraceURL) {
(window as any).setTraceURL(earlyTraceURL);
if (params.has('isServer')) {
connect({
onEvent(method: string, params?: any) {
if (method === 'loadTrace') {
setTraceURLs([params!.url]);
setDragOver(false);
setProcessingErrorMessage(null);
}
},
onClose() {}
}).then(sendMessage => {
sendMessage('ready');
});
} else if (!newTraceURLs.some(url => url.startsWith('blob:'))) {
// Don't re-use blob file URLs on page load (results in Fetch error)
setTraceURLs(newTraceURLs);
@ -176,9 +183,3 @@ export const WorkbenchLoader: React.FunctionComponent<{
};
export const emptyModel = new MultiTraceModel([]);
let earlyTraceURL: string | undefined = undefined;
(window as any).setTraceURL = (url: string) => {
earlyTraceURL = url;
};

View File

@ -0,0 +1,54 @@
/**
* 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.
*/
let lastId = 0;
let _ws: WebSocket;
const callbacks = new Map<number, { resolve: (arg: any) => void, reject: (arg: Error) => void }>();
export async function connect(options: { onEvent: (method: string, params?: any) => void, onClose: () => void }): Promise<(method: string, params?: any) => Promise<any>> {
const guid = new URLSearchParams(window.location.search).get('ws');
const ws = new WebSocket(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.hostname}:${window.location.port}/${guid}`);
await new Promise(f => ws.addEventListener('open', f));
ws.addEventListener('close', options.onClose);
ws.addEventListener('message', event => {
const message = JSON.parse(event.data);
const { id, result, error, method, params } = message;
if (id) {
const callback = callbacks.get(id);
if (!callback)
return;
callbacks.delete(id);
if (error)
callback.reject(new Error(error));
else
callback.resolve(result);
} else {
options.onEvent(method, params);
}
});
_ws = ws;
setInterval(() => sendMessage('ping').catch(() => {}), 30000);
return sendMessage;
}
const sendMessage = async (method: string, params?: any): Promise<any> => {
const id = ++lastId;
const message = { id, method, params };
_ws.send(JSON.stringify(message));
return new Promise((resolve, reject) => {
callbacks.set(id, { resolve, reject });
});
};