From 3fb33e7144de86180c539ad59ea9085554419415 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 23 Aug 2024 14:58:34 +0200 Subject: [PATCH] chore(ui): decouple TestServerConnection from websocket transport (#32274) Preparation for https://github.com/microsoft/playwright/issues/32076. --- .../src/isomorphic/testServerConnection.ts | 68 +++++++++++++++---- packages/trace-viewer/src/ui/uiModeView.tsx | 4 +- .../trace-viewer/src/ui/workbenchLoader.tsx | 4 +- .../test-server-connection.spec.ts | 4 +- 4 files changed, 61 insertions(+), 19 deletions(-) diff --git a/packages/playwright/src/isomorphic/testServerConnection.ts b/packages/playwright/src/isomorphic/testServerConnection.ts index 34ce0e31ef..00dafdf20b 100644 --- a/packages/playwright/src/isomorphic/testServerConnection.ts +++ b/packages/playwright/src/isomorphic/testServerConnection.ts @@ -19,6 +19,48 @@ import * as events from './events'; // -- Reuse boundary -- Everything below this line is reused in the vscode extension. +export interface TestServerTransport { + onmessage(listener: (message: string) => void): void; + onopen(listener: () => void): void; + onerror(listener: () => void): void; + onclose(listener: () => void): void; + + send(data: string): void; + close(): void; +} + +export class WebSocketTestServerTransport implements TestServerTransport { + private _ws: WebSocket; + + constructor(url: string | URL) { + this._ws = new WebSocket(url); + } + + onmessage(listener: (message: string) => void) { + this._ws.addEventListener('message', event => listener(event.data)); + } + + onopen(listener: () => void) { + this._ws.addEventListener('open', listener); + } + + onerror(listener: () => void) { + this._ws.addEventListener('error', listener); + } + + onclose(listener: () => void) { + this._ws.addEventListener('close', listener); + } + + send(data: string) { + this._ws.send(data); + } + + close() { + this._ws.close(); + } +} + export class TestServerConnection implements TestServerInterface, TestServerInterfaceEvents { readonly onClose: events.Event; readonly onReport: events.Event; @@ -33,21 +75,21 @@ export class TestServerConnection implements TestServerInterface, TestServerInte private _onLoadTraceRequestedEmitter = new events.EventEmitter<{ traceUrl: string }>(); private _lastId = 0; - private _ws: WebSocket; + private _transport: TestServerTransport; private _callbacks = new Map void, reject: (arg: Error) => void }>(); private _connectedPromise: Promise; private _isClosed = false; - constructor(wsURL: string) { + constructor(transport: TestServerTransport) { this.onClose = this._onCloseEmitter.event; this.onReport = this._onReportEmitter.event; this.onStdio = this._onStdioEmitter.event; this.onTestFilesChanged = this._onTestFilesChangedEmitter.event; this.onLoadTraceRequested = this._onLoadTraceRequestedEmitter.event; - this._ws = new WebSocket(wsURL); - this._ws.addEventListener('message', event => { - const message = JSON.parse(String(event.data)); + this._transport = transport; + this._transport.onmessage(data => { + const message = JSON.parse(data); const { id, result, error, method, params } = message; if (id) { const callback = this._callbacks.get(id); @@ -62,12 +104,12 @@ export class TestServerConnection implements TestServerInterface, TestServerInte this._dispatchEvent(method, params); } }); - const pingInterval = setInterval(() => this._sendMessage('ping').catch(() => {}), 30000); + const pingInterval = setInterval(() => this._sendMessage('ping').catch(() => { }), 30000); this._connectedPromise = new Promise((f, r) => { - this._ws.addEventListener('open', () => f()); - this._ws.addEventListener('error', r); + this._transport.onopen(f); + this._transport.onerror(r); }); - this._ws.addEventListener('close', () => { + this._transport.onclose(() => { this._isClosed = true; this._onCloseEmitter.fire(); clearInterval(pingInterval); @@ -85,14 +127,14 @@ export class TestServerConnection implements TestServerInterface, TestServerInte await this._connectedPromise; const id = ++this._lastId; const message = { id, method, params }; - this._ws.send(JSON.stringify(message)); + this._transport.send(JSON.stringify(message)); return new Promise((resolve, reject) => { this._callbacks.set(id, { resolve, reject }); }); } private _sendMessageNoReply(method: string, params?: any) { - this._sendMessage(method, params).catch(() => {}); + this._sendMessage(method, params).catch(() => { }); } private _dispatchEvent(method: string, params?: any) { @@ -200,8 +242,8 @@ export class TestServerConnection implements TestServerInterface, TestServerInte close() { try { - this._ws.close(); + this._transport.close(); } catch { } } -} +} \ No newline at end of file diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 4a73575a11..0f799b2035 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -32,7 +32,7 @@ import { useDarkModeSetting } from '@web/theme'; import { clsx, settings, useSetting } from '@web/uiUtils'; import { statusEx, TestTree } from '@testIsomorphic/testTree'; import type { TreeItem } from '@testIsomorphic/testTree'; -import { TestServerConnection } from '@testIsomorphic/testServerConnection'; +import { TestServerConnection, WebSocketTestServerTransport } from '@testIsomorphic/testServerConnection'; import { FiltersView } from './uiModeFiltersView'; import { TestListView } from './uiModeTestListView'; import { TraceView } from './uiModeTraceView'; @@ -134,7 +134,7 @@ export const UIModeView: React.FC<{}> = ({ const inputRef = React.useRef(null); const reloadTests = React.useCallback(() => { - setTestServerConnection(new TestServerConnection(wsURL.toString())); + setTestServerConnection(new TestServerConnection(new WebSocketTestServerTransport(wsURL))); }, []); // Load tests on startup. diff --git a/packages/trace-viewer/src/ui/workbenchLoader.tsx b/packages/trace-viewer/src/ui/workbenchLoader.tsx index e7f94e969a..4764b3d6a8 100644 --- a/packages/trace-viewer/src/ui/workbenchLoader.tsx +++ b/packages/trace-viewer/src/ui/workbenchLoader.tsx @@ -21,7 +21,7 @@ import { MultiTraceModel } from './modelUtil'; import './workbenchLoader.css'; import { toggleTheme } from '@web/theme'; import { Workbench } from './workbench'; -import { TestServerConnection } from '@testIsomorphic/testServerConnection'; +import { TestServerConnection, WebSocketTestServerTransport } from '@testIsomorphic/testServerConnection'; export const WorkbenchLoader: React.FunctionComponent<{ }> = () => { @@ -102,7 +102,7 @@ export const WorkbenchLoader: React.FunctionComponent<{ const guid = new URLSearchParams(window.location.search).get('ws'); const wsURL = new URL(`../${guid}`, window.location.toString()); wsURL.protocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:'); - const testServerConnection = new TestServerConnection(wsURL.toString()); + const testServerConnection = new TestServerConnection(new WebSocketTestServerTransport(wsURL)); testServerConnection.onLoadTraceRequested(async params => { setTraceURLs(params.traceUrl ? [params.traceUrl] : []); setDragOver(false); diff --git a/tests/playwright-test/test-server-connection.spec.ts b/tests/playwright-test/test-server-connection.spec.ts index af5f0223e5..aef2b63483 100644 --- a/tests/playwright-test/test-server-connection.spec.ts +++ b/tests/playwright-test/test-server-connection.spec.ts @@ -15,13 +15,13 @@ */ import { test as baseTest, expect } from './ui-mode-fixtures'; -import { TestServerConnection } from '../../packages/playwright/lib/isomorphic/testServerConnection'; +import { TestServerConnection, WebSocketTestServerTransport } from '../../packages/playwright/lib/isomorphic/testServerConnection'; class TestServerConnectionUnderTest extends TestServerConnection { events: [string, any][] = []; constructor(wsUrl: string) { - super(wsUrl); + super(new WebSocketTestServerTransport(wsUrl)); this.onTestFilesChanged(params => this.events.push(['testFilesChanged', params])); this.onStdio(params => this.events.push(['stdio', params])); this.onLoadTraceRequested(params => this.events.push(['loadTraceRequested', params]));