feat: respond with action and generated locator (#181)

Closes https://github.com/microsoft/playwright-mcp/issues/163
This commit is contained in:
Simon Knott 2025-04-15 19:55:20 +02:00 committed by GitHub
parent 4d59e06184
commit 4a19e18999
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 126 additions and 52 deletions

View file

@ -207,36 +207,52 @@ class Tab {
await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => {});
}
async run(callback: (tab: Tab) => Promise<void>, options?: RunOptions): Promise<ToolResult> {
async run(callback: (tab: Tab) => Promise<void | string>, options?: RunOptions): Promise<ToolResult> {
let actionCode: string | undefined;
try {
if (!options?.noClearFileChooser)
this._fileChooser = undefined;
if (options?.waitForCompletion)
await waitForCompletion(this.page, () => callback(this));
actionCode = await waitForCompletion(this.page, () => callback(this)) ?? undefined;
else
await callback(this);
actionCode = await callback(this) ?? undefined;
} finally {
if (options?.captureSnapshot)
this._snapshot = await PageSnapshot.create(this.page);
}
const tabList = this.context.tabs().length > 1 ? await this.context.listTabs() + '\n\nCurrent tab:' + '\n' : '';
const snapshot = this._snapshot?.text({ status: options?.status, hasFileChooser: !!this._fileChooser }) ?? options?.status ?? '';
const result: string[] = [];
if (options?.status)
result.push(options.status, '');
if (this.context.tabs().length > 1)
result.push(await this.context.listTabs(), '');
if (actionCode)
result.push('- Action: ' + actionCode, '');
if (this._snapshot) {
if (this.context.tabs().length > 1)
result.push('Current tab:');
result.push(this._snapshot.text({ hasFileChooser: !!this._fileChooser }));
}
return {
content: [{
type: 'text',
text: tabList + snapshot,
text: result.join('\n'),
}],
};
}
async runAndWait(callback: (tab: Tab) => Promise<void>, options?: RunOptions): Promise<ToolResult> {
async runAndWait(callback: (tab: Tab) => Promise<void | string>, options?: RunOptions): Promise<ToolResult> {
return await this.run(callback, {
waitForCompletion: true,
...options,
});
}
async runAndWaitWithSnapshot(callback: (snapshot: PageSnapshot) => Promise<void>, options?: RunOptions): Promise<ToolResult> {
async runAndWaitWithSnapshot(callback: (snapshot: PageSnapshot) => Promise<void | string>, options?: RunOptions): Promise<ToolResult> {
return await this.run(tab => callback(tab.lastSnapshot()), {
captureSnapshot: true,
waitForCompletion: true,
@ -275,13 +291,9 @@ class PageSnapshot {
return snapshot;
}
text(options?: { status?: string, hasFileChooser?: boolean }): string {
text(options: { hasFileChooser: boolean }): string {
const results: string[] = [];
if (options?.status) {
results.push(options.status);
results.push('');
}
if (options?.hasFileChooser) {
if (options.hasFileChooser) {
results.push('- There is a file chooser visible that requires browser_file_upload to be called');
results.push('');
}
@ -359,3 +371,7 @@ class PageSnapshot {
return frame.locator(`aria-ref=${ref}`);
}
}
export async function generateLocator(locator: playwright.Locator): Promise<string> {
return (locator as any)._generateLocatorString();
}