Skip to content

Commit 0b30f20

Browse files
authored
chore: make trace server work over http (#23561)
1 parent 2e327c9 commit 0b30f20

File tree

5 files changed

+96
-63
lines changed

5 files changed

+96
-63
lines changed

packages/playwright-core/src/server/trace/viewer/traceViewer.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ async function startTraceViewerServer(traceUrls: string[], options?: Options): P
9494
});
9595

9696
const params = traceUrls.map(t => `trace=${t}`);
97+
const transport = options?.transport || (options?.isServer ? new StdinServer() : undefined);
9798

98-
if (options?.transport) {
99-
const transport = options?.transport;
99+
if (transport) {
100100
const guid = createGuid();
101101
params.push('ws=' + guid);
102102
const wss = new wsServer({ server: server.server(), path: '/' + guid });
@@ -135,7 +135,6 @@ async function startTraceViewerServer(traceUrls: string[], options?: Options): P
135135
}
136136

137137
export async function openTraceViewerApp(traceUrls: string[], browserName: string, options?: Options): Promise<Page> {
138-
const stdinServer = options?.isServer ? new StdinServer() : undefined;
139138
const { url } = await startTraceViewerServer(traceUrls, options);
140139
const traceViewerPlaywright = createPlaywright({ sdkLanguage: 'javascript', isInternalPlaywright: true });
141140
const traceViewerBrowser = isUnderTest() ? 'chromium' : browserName;
@@ -176,7 +175,6 @@ export async function openTraceViewerApp(traceUrls: string[], browserName: strin
176175
page.on('close', () => process.exit());
177176

178177
await page.mainFrame().goto(serverSideCallMetadata(), url);
179-
stdinServer?.setPage(page);
180178
return page;
181179
}
182180

@@ -187,7 +185,7 @@ async function openTraceInBrowser(traceUrls: string[], options?: Options) {
187185
await open(url, { wait: true }).catch(() => {});
188186
}
189187

190-
class StdinServer {
188+
class StdinServer implements Transport {
191189
private _pollTimer: NodeJS.Timeout | undefined;
192190
private _traceUrl: string | undefined;
193191
private _page: Page | undefined;
@@ -197,7 +195,6 @@ class StdinServer {
197195
const url = data.toString().trim();
198196
if (url === this._traceUrl)
199197
return;
200-
this._traceUrl = url;
201198
if (url.endsWith('.json'))
202199
this._pollLoadTrace(url);
203200
else
@@ -206,15 +203,24 @@ class StdinServer {
206203
process.stdin.on('close', () => this._selfDestruct());
207204
}
208205

209-
setPage(page: Page) {
210-
this._page = page;
211-
if (this._traceUrl)
212-
this._loadTrace(this._traceUrl);
206+
async dispatch(method: string, params: any) {
207+
if (method === 'ready') {
208+
if (this._traceUrl)
209+
this._loadTrace(this._traceUrl);
210+
}
211+
}
212+
213+
onclose() {
214+
this._selfDestruct();
213215
}
214216

217+
sendEvent?: (method: string, params: any) => void;
218+
close?: () => void;
219+
215220
private _loadTrace(url: string) {
221+
this._traceUrl = url;
216222
clearTimeout(this._pollTimer);
217-
this._page?.mainFrame().evaluateExpression(`window.setTraceURL(${JSON.stringify(url)})`).catch(() => {});
223+
this.sendEvent?.('loadTrace', { url });
218224
}
219225

220226
private _pollLoadTrace(url: string) {

packages/playwright-test/src/runner/uiMode.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ class UIMode {
8787

8888
this._transport = {
8989
dispatch: async (method, params) => {
90+
if (method === 'ping')
91+
return;
92+
9093
if (method === 'exit') {
9194
exitPromise.resolve();
9295
return;

packages/trace-viewer/src/ui/uiModeView.tsx

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,14 @@ import { toggleTheme } from '@web/theme';
3737
import { artifactsFolderName } from '@testIsomorphic/folders';
3838
import { msToString, settings, useSetting } from '@web/uiUtils';
3939
import type { ActionTraceEvent } from '@trace/trace';
40+
import { connect } from './wsPort';
4041

4142
let updateRootSuite: (config: FullConfig, rootSuite: Suite, loadErrors: TestError[], progress: Progress | undefined) => void = () => {};
4243
let runWatchedTests = (fileNames: string[]) => {};
4344
let xtermSize = { cols: 80, rows: 24 };
4445

46+
let sendMessage: (method: string, params?: any) => Promise<any> = async () => {};
47+
4548
const xtermDataSource: XtermDataSource = {
4649
pending: [],
4750
clear: () => {},
@@ -96,7 +99,10 @@ export const UIModeView: React.FC<{}> = ({
9699
React.useEffect(() => {
97100
inputRef.current?.focus();
98101
setIsLoading(true);
99-
initWebSocket(() => setIsDisconnected(true)).then(() => reloadTests());
102+
connect({ onEvent: dispatchEvent, onClose: () => setIsDisconnected(true) }).then(send => {
103+
sendMessage = send;
104+
reloadTests();
105+
});
100106
}, [reloadTests]);
101107

102108
updateRootSuite = React.useCallback((config: FullConfig, rootSuite: Suite, loadErrors: TestError[], newProgress: Progress | undefined) => {
@@ -639,43 +645,6 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
639645
return sendMessage('list', {});
640646
};
641647

642-
let lastId = 0;
643-
let _ws: WebSocket;
644-
const callbacks = new Map<number, { resolve: (arg: any) => void, reject: (arg: Error) => void }>();
645-
646-
const initWebSocket = async (onClose: () => void) => {
647-
const guid = new URLSearchParams(window.location.search).get('ws');
648-
const ws = new WebSocket(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.hostname}:${window.location.port}/${guid}`);
649-
await new Promise(f => ws.addEventListener('open', f));
650-
ws.addEventListener('close', onClose);
651-
ws.addEventListener('message', event => {
652-
const message = JSON.parse(event.data);
653-
const { id, result, error, method, params } = message;
654-
if (id) {
655-
const callback = callbacks.get(id);
656-
if (!callback)
657-
return;
658-
callbacks.delete(id);
659-
if (error)
660-
callback.reject(new Error(error));
661-
else
662-
callback.resolve(result);
663-
} else {
664-
dispatchMessage(method, params);
665-
}
666-
});
667-
_ws = ws;
668-
};
669-
670-
const sendMessage = async (method: string, params: any): Promise<any> => {
671-
const id = ++lastId;
672-
const message = { id, method, params };
673-
_ws.send(JSON.stringify(message));
674-
return new Promise((resolve, reject) => {
675-
callbacks.set(id, { resolve, reject });
676-
});
677-
};
678-
679648
const sendMessageNoReply = (method: string, params?: any) => {
680649
if ((window as any)._overrideProtocolForTest) {
681650
(window as any)._overrideProtocolForTest({ method, params }).catch(() => {});
@@ -687,7 +656,7 @@ const sendMessageNoReply = (method: string, params?: any) => {
687656
});
688657
};
689658

690-
const dispatchMessage = (method: string, params?: any) => {
659+
const dispatchEvent = (method: string, params?: any) => {
691660
if (method === 'listChanged') {
692661
refreshRootSuite(false).catch(() => {});
693662
return;

packages/trace-viewer/src/ui/workbenchLoader.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { MultiTraceModel } from './modelUtil';
2121
import './workbench.css';
2222
import { toggleTheme } from '@web/theme';
2323
import { Workbench } from './workbench';
24+
import { connect } from './wsPort';
2425

2526
export const WorkbenchLoader: React.FunctionComponent<{
2627
}> = () => {
@@ -82,13 +83,19 @@ export const WorkbenchLoader: React.FunctionComponent<{
8283
}
8384
}
8485

85-
(window as any).setTraceURL = (url: string) => {
86-
setTraceURLs([url]);
87-
setDragOver(false);
88-
setProcessingErrorMessage(null);
89-
};
90-
if (earlyTraceURL) {
91-
(window as any).setTraceURL(earlyTraceURL);
86+
if (params.has('isServer')) {
87+
connect({
88+
onEvent(method: string, params?: any) {
89+
if (method === 'loadTrace') {
90+
setTraceURLs([params!.url]);
91+
setDragOver(false);
92+
setProcessingErrorMessage(null);
93+
}
94+
},
95+
onClose() {}
96+
}).then(sendMessage => {
97+
sendMessage('ready');
98+
});
9299
} else if (!newTraceURLs.some(url => url.startsWith('blob:'))) {
93100
// Don't re-use blob file URLs on page load (results in Fetch error)
94101
setTraceURLs(newTraceURLs);
@@ -176,9 +183,3 @@ export const WorkbenchLoader: React.FunctionComponent<{
176183
};
177184

178185
export const emptyModel = new MultiTraceModel([]);
179-
180-
let earlyTraceURL: string | undefined = undefined;
181-
182-
(window as any).setTraceURL = (url: string) => {
183-
earlyTraceURL = url;
184-
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
let lastId = 0;
18+
let _ws: WebSocket;
19+
const callbacks = new Map<number, { resolve: (arg: any) => void, reject: (arg: Error) => void }>();
20+
21+
export async function connect(options: { onEvent: (method: string, params?: any) => void, onClose: () => void }): Promise<(method: string, params?: any) => Promise<any>> {
22+
const guid = new URLSearchParams(window.location.search).get('ws');
23+
const ws = new WebSocket(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.hostname}:${window.location.port}/${guid}`);
24+
await new Promise(f => ws.addEventListener('open', f));
25+
ws.addEventListener('close', options.onClose);
26+
ws.addEventListener('message', event => {
27+
const message = JSON.parse(event.data);
28+
const { id, result, error, method, params } = message;
29+
if (id) {
30+
const callback = callbacks.get(id);
31+
if (!callback)
32+
return;
33+
callbacks.delete(id);
34+
if (error)
35+
callback.reject(new Error(error));
36+
else
37+
callback.resolve(result);
38+
} else {
39+
options.onEvent(method, params);
40+
}
41+
});
42+
_ws = ws;
43+
setInterval(() => sendMessage('ping').catch(() => {}), 30000);
44+
return sendMessage;
45+
}
46+
47+
const sendMessage = async (method: string, params?: any): Promise<any> => {
48+
const id = ++lastId;
49+
const message = { id, method, params };
50+
_ws.send(JSON.stringify(message));
51+
return new Promise((resolve, reject) => {
52+
callbacks.set(id, { resolve, reject });
53+
});
54+
};

0 commit comments

Comments
 (0)