mirror of
https://github.com/microsoft/playwright-mcp.git
synced 2025-07-26 08:32:26 +08:00
chore(extension): propagate errors to the client (#736)
This commit is contained in:
parent
468c84eb8f
commit
70862ce456
@ -59,7 +59,6 @@ class TabShareExtension {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const connection = new RelayConnection(socket);
|
const connection = new RelayConnection(socket);
|
||||||
connection.setConnectedTabId(tabId);
|
|
||||||
const connectionClosed = (m: string) => {
|
const connectionClosed = (m: string) => {
|
||||||
debugLog(m);
|
debugLog(m);
|
||||||
if (this._activeConnection === connection) {
|
if (this._activeConnection === connection) {
|
||||||
@ -71,6 +70,7 @@ class TabShareExtension {
|
|||||||
socket.onerror = error => connectionClosed(`WebSocket error: ${error}`);
|
socket.onerror = error => connectionClosed(`WebSocket error: ${error}`);
|
||||||
this._activeConnection = connection;
|
this._activeConnection = connection;
|
||||||
|
|
||||||
|
connection.setConnectedTabId(tabId);
|
||||||
await this._setConnectedTabId(tabId);
|
await this._setConnectedTabId(tabId);
|
||||||
debugLog(`Tab ${tabId} connected successfully`);
|
debugLog(`Tab ${tabId} connected successfully`);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
@ -147,8 +147,8 @@ export class CDPRelayServer {
|
|||||||
try {
|
try {
|
||||||
const message = JSON.parse(data.toString());
|
const message = JSON.parse(data.toString());
|
||||||
await this._handlePlaywrightMessage(message);
|
await this._handlePlaywrightMessage(message);
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
debugLogger('Error parsing Playwright message:', error);
|
debugLogger(`Error while handling Playwright message\n${data.toString()}\n`, error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ws.on('close', () => {
|
ws.on('close', () => {
|
||||||
@ -205,91 +205,65 @@ export class CDPRelayServer {
|
|||||||
|
|
||||||
private async _handlePlaywrightMessage(message: CDPCommand): Promise<void> {
|
private async _handlePlaywrightMessage(message: CDPCommand): Promise<void> {
|
||||||
debugLogger('← Playwright:', `${message.method} (id=${message.id})`);
|
debugLogger('← Playwright:', `${message.method} (id=${message.id})`);
|
||||||
if (!this._extensionConnection) {
|
const { id, sessionId, method, params } = message;
|
||||||
debugLogger('Extension not connected, sending error to Playwright');
|
|
||||||
this._sendToPlaywright({
|
|
||||||
id: message.id,
|
|
||||||
error: { message: 'Extension not connected' }
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (await this._interceptCDPCommand(message))
|
|
||||||
return;
|
|
||||||
await this._forwardToExtension(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _interceptCDPCommand(message: CDPCommand): Promise<boolean> {
|
|
||||||
switch (message.method) {
|
|
||||||
case 'Browser.getVersion': {
|
|
||||||
this._sendToPlaywright({
|
|
||||||
id: message.id,
|
|
||||||
result: {
|
|
||||||
protocolVersion: '1.3',
|
|
||||||
product: 'Chrome/Extension-Bridge',
|
|
||||||
userAgent: 'CDP-Bridge-Server/1.0.0',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case 'Browser.setDownloadBehavior': {
|
|
||||||
this._sendToPlaywright({
|
|
||||||
id: message.id
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case 'Target.setAutoAttach': {
|
|
||||||
// Simulate auto-attach behavior with real target info
|
|
||||||
if (!message.sessionId) {
|
|
||||||
this._connectedTabInfo = await this._extensionConnection!.send('attachToTab');
|
|
||||||
debugLogger('Simulating auto-attach for target:', message);
|
|
||||||
this._sendToPlaywright({
|
|
||||||
method: 'Target.attachedToTarget',
|
|
||||||
params: {
|
|
||||||
sessionId: this._connectedTabInfo!.sessionId,
|
|
||||||
targetInfo: {
|
|
||||||
...this._connectedTabInfo!.targetInfo,
|
|
||||||
attached: true,
|
|
||||||
},
|
|
||||||
waitingForDebugger: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this._sendToPlaywright({
|
|
||||||
id: message.id
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await this._forwardToExtension(message);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case 'Target.getTargetInfo': {
|
|
||||||
debugLogger('Target.getTargetInfo', message);
|
|
||||||
this._sendToPlaywright({
|
|
||||||
id: message.id,
|
|
||||||
result: this._connectedTabInfo?.targetInfo
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _forwardToExtension(message: CDPCommand): Promise<void> {
|
|
||||||
try {
|
try {
|
||||||
if (!this._extensionConnection)
|
const result = await this._handleCDPCommand(method, params, sessionId);
|
||||||
throw new Error('Extension not connected');
|
|
||||||
const { id, sessionId, method, params } = message;
|
|
||||||
const result = await this._extensionConnection.send('forwardCDPCommand', { sessionId, method, params });
|
|
||||||
this._sendToPlaywright({ id, sessionId, result });
|
this._sendToPlaywright({ id, sessionId, result });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugLogger('Error in the extension:', e);
|
debugLogger('Error in the extension:', e);
|
||||||
this._sendToPlaywright({
|
this._sendToPlaywright({
|
||||||
id: message.id,
|
id,
|
||||||
sessionId: message.sessionId,
|
sessionId,
|
||||||
error: { message: (e as Error).message }
|
error: { message: (e as Error).message }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _handleCDPCommand(method: string, params: any, sessionId: string | undefined): Promise<any> {
|
||||||
|
switch (method) {
|
||||||
|
case 'Browser.getVersion': {
|
||||||
|
return {
|
||||||
|
protocolVersion: '1.3',
|
||||||
|
product: 'Chrome/Extension-Bridge',
|
||||||
|
userAgent: 'CDP-Bridge-Server/1.0.0',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'Browser.setDownloadBehavior': {
|
||||||
|
return { };
|
||||||
|
}
|
||||||
|
case 'Target.setAutoAttach': {
|
||||||
|
// Forward child session handling.
|
||||||
|
if (sessionId)
|
||||||
|
break;
|
||||||
|
// Simulate auto-attach behavior with real target info
|
||||||
|
this._connectedTabInfo = await this._extensionConnection!.send('attachToTab');
|
||||||
|
debugLogger('Simulating auto-attach');
|
||||||
|
this._sendToPlaywright({
|
||||||
|
method: 'Target.attachedToTarget',
|
||||||
|
params: {
|
||||||
|
sessionId: this._connectedTabInfo!.sessionId,
|
||||||
|
targetInfo: {
|
||||||
|
...this._connectedTabInfo!.targetInfo,
|
||||||
|
attached: true,
|
||||||
|
},
|
||||||
|
waitingForDebugger: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { };
|
||||||
|
}
|
||||||
|
case 'Target.getTargetInfo': {
|
||||||
|
return this._connectedTabInfo?.targetInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await this._forwardToExtension(method, params, sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _forwardToExtension(method: string, params: any, sessionId: string | undefined): Promise<any> {
|
||||||
|
if (!this._extensionConnection)
|
||||||
|
throw new Error('Extension not connected');
|
||||||
|
return await this._extensionConnection.send('forwardCDPCommand', { sessionId, method, params });
|
||||||
|
}
|
||||||
|
|
||||||
private _sendToPlaywright(message: CDPResponse): void {
|
private _sendToPlaywright(message: CDPResponse): void {
|
||||||
debugLogger('→ Playwright:', `${message.method ?? `response(id=${message.id})`}`);
|
debugLogger('→ Playwright:', `${message.method ?? `response(id=${message.id})`}`);
|
||||||
this._playwrightConnection?.send(JSON.stringify(message));
|
this._playwrightConnection?.send(JSON.stringify(message));
|
||||||
@ -329,9 +303,17 @@ export async function startCDPRelayServer(port: number, browserChannel: string)
|
|||||||
return new ExtensionContextFactory(cdpRelayServer);
|
return new ExtensionContextFactory(cdpRelayServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExtensionResponse = {
|
||||||
|
id?: number;
|
||||||
|
method?: string;
|
||||||
|
params?: any;
|
||||||
|
result?: any;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
|
||||||
class ExtensionConnection {
|
class ExtensionConnection {
|
||||||
private readonly _ws: WebSocket;
|
private readonly _ws: WebSocket;
|
||||||
private readonly _callbacks = new Map<number, { resolve: (o: any) => void, reject: (e: Error) => void }>();
|
private readonly _callbacks = new Map<number, { resolve: (o: any) => void, reject: (e: Error) => void, error: Error }>();
|
||||||
private _lastId = 0;
|
private _lastId = 0;
|
||||||
|
|
||||||
onmessage?: (method: string, params: any) => void;
|
onmessage?: (method: string, params: any) => void;
|
||||||
@ -349,8 +331,9 @@ class ExtensionConnection {
|
|||||||
throw new Error(`Unexpected WebSocket state: ${this._ws.readyState}`);
|
throw new Error(`Unexpected WebSocket state: ${this._ws.readyState}`);
|
||||||
const id = ++this._lastId;
|
const id = ++this._lastId;
|
||||||
this._ws.send(JSON.stringify({ id, method, params, sessionId }));
|
this._ws.send(JSON.stringify({ id, method, params, sessionId }));
|
||||||
|
const error = new Error(`Protocol error: ${method}`);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this._callbacks.set(id, { resolve, reject });
|
this._callbacks.set(id, { resolve, reject, error });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,18 +361,21 @@ class ExtensionConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleParsedMessage(object: any) {
|
private _handleParsedMessage(object: ExtensionResponse) {
|
||||||
if (object.id && this._callbacks.has(object.id)) {
|
if (object.id && this._callbacks.has(object.id)) {
|
||||||
const callback = this._callbacks.get(object.id)!;
|
const callback = this._callbacks.get(object.id)!;
|
||||||
this._callbacks.delete(object.id);
|
this._callbacks.delete(object.id);
|
||||||
if (object.error)
|
if (object.error) {
|
||||||
callback.reject(new Error(object.error.message));
|
const error = callback.error;
|
||||||
else
|
error.message = object.error;
|
||||||
|
callback.reject(error);
|
||||||
|
} else {
|
||||||
callback.resolve(object.result);
|
callback.resolve(object.result);
|
||||||
|
}
|
||||||
} else if (object.id) {
|
} else if (object.id) {
|
||||||
debugLogger('← Extension: unexpected response', object);
|
debugLogger('← Extension: unexpected response', object);
|
||||||
} else {
|
} else {
|
||||||
this.onmessage?.(object.method, object.params);
|
this.onmessage?.(object.method!, object.params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user