mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: make trace server work over http (#23561)
This commit is contained in:
parent
2e327c9d0c
commit
0b30f2017c
@ -94,9 +94,9 @@ async function startTraceViewerServer(traceUrls: string[], options?: Options): P
|
|||||||
});
|
});
|
||||||
|
|
||||||
const params = traceUrls.map(t => `trace=${t}`);
|
const params = traceUrls.map(t => `trace=${t}`);
|
||||||
|
const transport = options?.transport || (options?.isServer ? new StdinServer() : undefined);
|
||||||
|
|
||||||
if (options?.transport) {
|
if (transport) {
|
||||||
const transport = options?.transport;
|
|
||||||
const guid = createGuid();
|
const guid = createGuid();
|
||||||
params.push('ws=' + guid);
|
params.push('ws=' + guid);
|
||||||
const wss = new wsServer({ server: server.server(), path: '/' + 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> {
|
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 { url } = await startTraceViewerServer(traceUrls, options);
|
||||||
const traceViewerPlaywright = createPlaywright({ sdkLanguage: 'javascript', isInternalPlaywright: true });
|
const traceViewerPlaywright = createPlaywright({ sdkLanguage: 'javascript', isInternalPlaywright: true });
|
||||||
const traceViewerBrowser = isUnderTest() ? 'chromium' : browserName;
|
const traceViewerBrowser = isUnderTest() ? 'chromium' : browserName;
|
||||||
@ -176,7 +175,6 @@ export async function openTraceViewerApp(traceUrls: string[], browserName: strin
|
|||||||
page.on('close', () => process.exit());
|
page.on('close', () => process.exit());
|
||||||
|
|
||||||
await page.mainFrame().goto(serverSideCallMetadata(), url);
|
await page.mainFrame().goto(serverSideCallMetadata(), url);
|
||||||
stdinServer?.setPage(page);
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,7 +185,7 @@ async function openTraceInBrowser(traceUrls: string[], options?: Options) {
|
|||||||
await open(url, { wait: true }).catch(() => {});
|
await open(url, { wait: true }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
class StdinServer {
|
class StdinServer implements Transport {
|
||||||
private _pollTimer: NodeJS.Timeout | undefined;
|
private _pollTimer: NodeJS.Timeout | undefined;
|
||||||
private _traceUrl: string | undefined;
|
private _traceUrl: string | undefined;
|
||||||
private _page: Page | undefined;
|
private _page: Page | undefined;
|
||||||
@ -197,7 +195,6 @@ class StdinServer {
|
|||||||
const url = data.toString().trim();
|
const url = data.toString().trim();
|
||||||
if (url === this._traceUrl)
|
if (url === this._traceUrl)
|
||||||
return;
|
return;
|
||||||
this._traceUrl = url;
|
|
||||||
if (url.endsWith('.json'))
|
if (url.endsWith('.json'))
|
||||||
this._pollLoadTrace(url);
|
this._pollLoadTrace(url);
|
||||||
else
|
else
|
||||||
@ -206,15 +203,24 @@ class StdinServer {
|
|||||||
process.stdin.on('close', () => this._selfDestruct());
|
process.stdin.on('close', () => this._selfDestruct());
|
||||||
}
|
}
|
||||||
|
|
||||||
setPage(page: Page) {
|
async dispatch(method: string, params: any) {
|
||||||
this._page = page;
|
if (method === 'ready') {
|
||||||
if (this._traceUrl)
|
if (this._traceUrl)
|
||||||
this._loadTrace(this._traceUrl);
|
this._loadTrace(this._traceUrl);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onclose() {
|
||||||
|
this._selfDestruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEvent?: (method: string, params: any) => void;
|
||||||
|
close?: () => void;
|
||||||
|
|
||||||
private _loadTrace(url: string) {
|
private _loadTrace(url: string) {
|
||||||
|
this._traceUrl = url;
|
||||||
clearTimeout(this._pollTimer);
|
clearTimeout(this._pollTimer);
|
||||||
this._page?.mainFrame().evaluateExpression(`window.setTraceURL(${JSON.stringify(url)})`).catch(() => {});
|
this.sendEvent?.('loadTrace', { url });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _pollLoadTrace(url: string) {
|
private _pollLoadTrace(url: string) {
|
||||||
|
@ -87,6 +87,9 @@ class UIMode {
|
|||||||
|
|
||||||
this._transport = {
|
this._transport = {
|
||||||
dispatch: async (method, params) => {
|
dispatch: async (method, params) => {
|
||||||
|
if (method === 'ping')
|
||||||
|
return;
|
||||||
|
|
||||||
if (method === 'exit') {
|
if (method === 'exit') {
|
||||||
exitPromise.resolve();
|
exitPromise.resolve();
|
||||||
return;
|
return;
|
||||||
|
@ -37,11 +37,14 @@ import { toggleTheme } from '@web/theme';
|
|||||||
import { artifactsFolderName } from '@testIsomorphic/folders';
|
import { artifactsFolderName } from '@testIsomorphic/folders';
|
||||||
import { msToString, settings, useSetting } from '@web/uiUtils';
|
import { msToString, settings, useSetting } from '@web/uiUtils';
|
||||||
import type { ActionTraceEvent } from '@trace/trace';
|
import type { ActionTraceEvent } from '@trace/trace';
|
||||||
|
import { connect } from './wsPort';
|
||||||
|
|
||||||
let updateRootSuite: (config: FullConfig, rootSuite: Suite, loadErrors: TestError[], progress: Progress | undefined) => void = () => {};
|
let updateRootSuite: (config: FullConfig, rootSuite: Suite, loadErrors: TestError[], progress: Progress | undefined) => void = () => {};
|
||||||
let runWatchedTests = (fileNames: string[]) => {};
|
let runWatchedTests = (fileNames: string[]) => {};
|
||||||
let xtermSize = { cols: 80, rows: 24 };
|
let xtermSize = { cols: 80, rows: 24 };
|
||||||
|
|
||||||
|
let sendMessage: (method: string, params?: any) => Promise<any> = async () => {};
|
||||||
|
|
||||||
const xtermDataSource: XtermDataSource = {
|
const xtermDataSource: XtermDataSource = {
|
||||||
pending: [],
|
pending: [],
|
||||||
clear: () => {},
|
clear: () => {},
|
||||||
@ -96,7 +99,10 @@ export const UIModeView: React.FC<{}> = ({
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
initWebSocket(() => setIsDisconnected(true)).then(() => reloadTests());
|
connect({ onEvent: dispatchEvent, onClose: () => setIsDisconnected(true) }).then(send => {
|
||||||
|
sendMessage = send;
|
||||||
|
reloadTests();
|
||||||
|
});
|
||||||
}, [reloadTests]);
|
}, [reloadTests]);
|
||||||
|
|
||||||
updateRootSuite = React.useCallback((config: FullConfig, rootSuite: Suite, loadErrors: TestError[], newProgress: Progress | undefined) => {
|
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', {});
|
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) => {
|
const sendMessageNoReply = (method: string, params?: any) => {
|
||||||
if ((window as any)._overrideProtocolForTest) {
|
if ((window as any)._overrideProtocolForTest) {
|
||||||
(window as any)._overrideProtocolForTest({ method, params }).catch(() => {});
|
(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') {
|
if (method === 'listChanged') {
|
||||||
refreshRootSuite(false).catch(() => {});
|
refreshRootSuite(false).catch(() => {});
|
||||||
return;
|
return;
|
||||||
|
@ -21,6 +21,7 @@ import { MultiTraceModel } from './modelUtil';
|
|||||||
import './workbench.css';
|
import './workbench.css';
|
||||||
import { toggleTheme } from '@web/theme';
|
import { toggleTheme } from '@web/theme';
|
||||||
import { Workbench } from './workbench';
|
import { Workbench } from './workbench';
|
||||||
|
import { connect } from './wsPort';
|
||||||
|
|
||||||
export const WorkbenchLoader: React.FunctionComponent<{
|
export const WorkbenchLoader: React.FunctionComponent<{
|
||||||
}> = () => {
|
}> = () => {
|
||||||
@ -82,13 +83,19 @@ export const WorkbenchLoader: React.FunctionComponent<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(window as any).setTraceURL = (url: string) => {
|
if (params.has('isServer')) {
|
||||||
setTraceURLs([url]);
|
connect({
|
||||||
|
onEvent(method: string, params?: any) {
|
||||||
|
if (method === 'loadTrace') {
|
||||||
|
setTraceURLs([params!.url]);
|
||||||
setDragOver(false);
|
setDragOver(false);
|
||||||
setProcessingErrorMessage(null);
|
setProcessingErrorMessage(null);
|
||||||
};
|
}
|
||||||
if (earlyTraceURL) {
|
},
|
||||||
(window as any).setTraceURL(earlyTraceURL);
|
onClose() {}
|
||||||
|
}).then(sendMessage => {
|
||||||
|
sendMessage('ready');
|
||||||
|
});
|
||||||
} else if (!newTraceURLs.some(url => url.startsWith('blob:'))) {
|
} else if (!newTraceURLs.some(url => url.startsWith('blob:'))) {
|
||||||
// Don't re-use blob file URLs on page load (results in Fetch error)
|
// Don't re-use blob file URLs on page load (results in Fetch error)
|
||||||
setTraceURLs(newTraceURLs);
|
setTraceURLs(newTraceURLs);
|
||||||
@ -176,9 +183,3 @@ export const WorkbenchLoader: React.FunctionComponent<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const emptyModel = new MultiTraceModel([]);
|
export const emptyModel = new MultiTraceModel([]);
|
||||||
|
|
||||||
let earlyTraceURL: string | undefined = undefined;
|
|
||||||
|
|
||||||
(window as any).setTraceURL = (url: string) => {
|
|
||||||
earlyTraceURL = url;
|
|
||||||
};
|
|
||||||
|
54
packages/trace-viewer/src/ui/wsPort.ts
Normal file
54
packages/trace-viewer/src/ui/wsPort.ts
Normal 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 });
|
||||||
|
});
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user