Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
"native-keymap": "^3.3.5",
"node-pty": "^1.2.0-beta.10",
"open": "^10.1.2",
"playwright-core": "^1.58.2",
"playwright-core": "1.59.0-alpha-2026-02-20",
"tas-client": "0.3.1",
"undici": "^7.18.2",
"v8-inspect-profiler": "^0.1.1",
Expand Down Expand Up @@ -245,4 +245,4 @@
"optionalDependencies": {
"windows-foreground-love": "0.6.1"
}
}
}
14 changes: 13 additions & 1 deletion src/vs/platform/browserView/common/cdp/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,19 @@ export class CDPBrowserProxy extends Disposable implements ICDPConnection {
await this.attachToTarget(targetInfo.targetId, true);
}
});
this._targets.onDidUnregisterTarget(async ({ targetInfo }) => {
this._targets.onDidUnregisterTarget(({ targetInfo }) => {
// Close any sessions attached to the destroyed target. Snapshot first
// to avoid mutating _sessions while iterating (onClose fires synchronously).
const toDispose: ICDPConnection[] = [];
for (const [, connection] of this._sessions) {
if (this._sessionTargetIds.get(connection) === targetInfo.targetId) {
toDispose.push(connection);
}
}
for (const connection of toDispose) {
connection.dispose();
}

if (this._discover) {
this.sendBrowserEvent('Target.targetDestroyed', { targetId: targetInfo.targetId });
}
Expand Down
86 changes: 84 additions & 2 deletions src/vs/platform/browserView/common/playwrightService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,98 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Event } from '../../../base/common/event.js';
import { VSBuffer } from '../../../base/common/buffer.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';

export const IPlaywrightService = createDecorator<IPlaywrightService>('playwrightService');

/**
* A service for using Playwright to connect to and automate the integrated browser.
*
* Pages must be explicitly tracked via {@link startTrackingPage} (or implicitly via
* {@link openPage}) before they can be interacted with.
Comment thread
kycutler marked this conversation as resolved.
*/
export interface IPlaywrightService {
readonly _serviceBrand: undefined;

// TODO@kycutler: define a more specific API.
initialize(): Promise<void>;
/**
* Fires when the set of tracked pages changes.
* The event value is the full list of currently tracked view IDs.
*/
readonly onDidChangeTrackedPages: Event<readonly string[]>;

/**
* Start tracking an existing browser view so that agent
* tools can interact with it.
* @param viewId The browser view identifier.
*/
startTrackingPage(viewId: string): Promise<void>;

/**
* Stop tracking a browser view.
* @param viewId The browser view identifier.
*/
stopTrackingPage(viewId: string): Promise<void>;

/**
* Whether the given page is currently tracked by the service.
*/
isPageTracked(viewId: string): Promise<boolean>;

/**
* Get the list of currently tracked page IDs.
*/
getTrackedPages(): Promise<readonly string[]>;

/**
* Opens a new page in the browser and returns its associated view ID.
* The page is automatically added to the tracked pages.
* @param url The URL to open in the new page.
* @returns An object containing the new page's view ID and a summary of its initial state.
*/
openPage(url: string): Promise<{ pageId: string; summary: string }>;

/**
* Gets a summary of the page's current state, including its DOM and visual representation.
* @param pageId The browser view ID identifying the page to read.
* @returns The summary of the page's current state.
*/
getSummary(pageId: string): Promise<string>;

/**
* Run a function with access to a Playwright page.
* The first function argument is always the Playwright `page` object, and additional arguments can be passed after.
* @param pageId The browser view ID identifying the page to operate on.
* @param fnDef The function code to execute. Should contain the function definition but not its invocation, e.g. `async (page, arg1, arg2) => { ... }`.
* @param args Additional arguments to pass to the function after the `page` object.
* @returns The result of the function execution, including a page summary.
*/
invokeFunction(pageId: string, fnDef: string, ...args: unknown[]): Promise<{ result: unknown; summary: string }>;

/**
* Takes a screenshot of the current page viewport and returns it as a VSBuffer.
* @param pageId The browser view ID identifying the page to capture.
* @param selector Optional Playwright selector to capture a specific element instead of the viewport.
* @param fullPage Whether to capture the full scrollable page instead of just the viewport.
Comment thread
kycutler marked this conversation as resolved.
* @returns The screenshot image data.
*/
captureScreenshot(pageId: string, selector?: string, fullPage?: boolean): Promise<VSBuffer>;

/**
* Responds to a file chooser dialog on the given page.
* @param pageId The browser view ID identifying the page.
* @param files The list of files to select in the file chooser. Empty to dismiss the dialog without selecting files.
* @returns An object with the page summary afterwards.
*/
replyToFileChooser(pageId: string, files: string[]): Promise<{ summary: string }>;

/**
* Responds to a dialog (alert, confirm, prompt) on the given page.
* @param pageId The browser view ID identifying the page.
* @param accept Whether to accept or dismiss the dialog.
* @param promptText Optional text to enter into a prompt dialog.
* @returns An object with the page summary afterwards.
*/
replyToDialog(pageId: string, accept: boolean, promptText?: string): Promise<{ summary: string }>;
}
4 changes: 4 additions & 0 deletions src/vs/platform/browserView/electron-main/browserViewGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ export class BrowserViewGroup extends Disposable implements ICDPBrowserTarget, I
async removeView(viewId: string): Promise<void> {
const view = this.views.get(viewId);
if (view && this.views.delete(viewId)) {
// If no remaining views belong to the view's context, and we don't own the context, remove it from known contexts
if (!this.ownedContextIds.has(view.session.id) && ![...this.views.values()].some(v => v.session.id === view.session.id)) {
this.knownContextIds.delete(view.session.id);
}
this._onDidRemoveView.fire({ viewId: view.id });
this._onTargetDestroyed.fire(view);
}
Expand Down
Loading
Loading