mirror of
https://github.com/microsoft/playwright-mcp.git
synced 2025-07-26 08:32:26 +08:00
chore: save downloads to outputDir (#310)
This commit is contained in:
parent
23ce973377
commit
a15f0f301b
@ -20,10 +20,9 @@ import os from 'os';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { devices } from 'playwright';
|
import { devices } from 'playwright';
|
||||||
|
|
||||||
import { sanitizeForFilePath } from './tools/utils.js';
|
|
||||||
|
|
||||||
import type { Config, ToolCapability } from '../config.js';
|
import type { Config, ToolCapability } from '../config.js';
|
||||||
import type { BrowserContextOptions, LaunchOptions } from 'playwright';
|
import type { BrowserContextOptions, LaunchOptions } from 'playwright';
|
||||||
|
import { sanitizeForFilePath } from './tools/utils.js';
|
||||||
|
|
||||||
export type CLIOptions = {
|
export type CLIOptions = {
|
||||||
browser?: string;
|
browser?: string;
|
||||||
|
@ -23,6 +23,7 @@ import { Tab } from './tab.js';
|
|||||||
import type { ImageContent, TextContent } from '@modelcontextprotocol/sdk/types.js';
|
import type { ImageContent, TextContent } from '@modelcontextprotocol/sdk/types.js';
|
||||||
import type { ModalState, Tool, ToolActionResult } from './tools/tool.js';
|
import type { ModalState, Tool, ToolActionResult } from './tools/tool.js';
|
||||||
import type { Config } from '../config.js';
|
import type { Config } from '../config.js';
|
||||||
|
import { outputFile } from './config.js';
|
||||||
|
|
||||||
type PendingAction = {
|
type PendingAction = {
|
||||||
dialogShown: ManualPromise<void>;
|
dialogShown: ManualPromise<void>;
|
||||||
@ -38,6 +39,7 @@ export class Context {
|
|||||||
private _currentTab: Tab | undefined;
|
private _currentTab: Tab | undefined;
|
||||||
private _modalStates: (ModalState & { tab: Tab })[] = [];
|
private _modalStates: (ModalState & { tab: Tab })[] = [];
|
||||||
private _pendingAction: PendingAction | undefined;
|
private _pendingAction: PendingAction | undefined;
|
||||||
|
private _downloads: { download: playwright.Download, finished: boolean, outputFile: string }[] = [];
|
||||||
|
|
||||||
constructor(tools: Tool[], config: Config) {
|
constructor(tools: Tool[], config: Config) {
|
||||||
this.tools = tools;
|
this.tools = tools;
|
||||||
@ -164,6 +166,17 @@ ${code.join('\n')}
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._downloads.length) {
|
||||||
|
result.push('', '### Downloads');
|
||||||
|
for (const entry of this._downloads) {
|
||||||
|
if (entry.finished)
|
||||||
|
result.push(`- Downloaded file ${entry.download.suggestedFilename()} to ${entry.outputFile}`);
|
||||||
|
else
|
||||||
|
result.push(`- Downloading file ${entry.download.suggestedFilename()} ...`);
|
||||||
|
}
|
||||||
|
result.push('');
|
||||||
|
}
|
||||||
|
|
||||||
if (this.tabs().length > 1)
|
if (this.tabs().length > 1)
|
||||||
result.push(await this.listTabsMarkdown(), '');
|
result.push(await this.listTabsMarkdown(), '');
|
||||||
|
|
||||||
@ -228,6 +241,17 @@ ${code.join('\n')}
|
|||||||
this._pendingAction?.dialogShown.resolve();
|
this._pendingAction?.dialogShown.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async downloadStarted(tab: Tab, download: playwright.Download) {
|
||||||
|
const entry = {
|
||||||
|
download,
|
||||||
|
finished: false,
|
||||||
|
outputFile: await outputFile(this.config, download.suggestedFilename())
|
||||||
|
};
|
||||||
|
this._downloads.push(entry);
|
||||||
|
await download.saveAs(entry.outputFile);
|
||||||
|
entry.finished = true;
|
||||||
|
}
|
||||||
|
|
||||||
private _onPageCreated(page: playwright.Page) {
|
private _onPageCreated(page: playwright.Page) {
|
||||||
const tab = new Tab(this, page, tab => this._onPageClosed(tab));
|
const tab = new Tab(this, page, tab => this._onPageClosed(tab));
|
||||||
this._tabs.push(tab);
|
this._tabs.push(tab);
|
||||||
|
@ -48,6 +48,9 @@ export class Tab {
|
|||||||
}, this);
|
}, this);
|
||||||
});
|
});
|
||||||
page.on('dialog', dialog => this.context.dialogShown(this, dialog));
|
page.on('dialog', dialog => this.context.dialogShown(this, dialog));
|
||||||
|
page.on('download', download => {
|
||||||
|
void this.context.downloadStarted(this, download);
|
||||||
|
});
|
||||||
page.setDefaultNavigationTimeout(60000);
|
page.setDefaultNavigationTimeout(60000);
|
||||||
page.setDefaultTimeout(5000);
|
page.setDefaultTimeout(5000);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ const pdf = defineTool({
|
|||||||
|
|
||||||
handle: async context => {
|
handle: async context => {
|
||||||
const tab = context.currentTabOrDie();
|
const tab = context.currentTabOrDie();
|
||||||
const fileName = await outputFile(context.config, `page-${new Date().toISOString()}'.pdf'`);
|
const fileName = await outputFile(context.config, `page-${new Date().toISOString()}.pdf`);
|
||||||
|
|
||||||
const code = [
|
const code = [
|
||||||
`// Save page as ${fileName}`,
|
`// Save page as ${fileName}`,
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import { test, expect } from './fixtures.js';
|
import { test, expect } from './fixtures.js';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
test('browser_file_upload', async ({ client }) => {
|
test('browser_file_upload', async ({ client }) => {
|
||||||
expect(await client.callTool({
|
expect(await client.callTool({
|
||||||
@ -96,3 +97,27 @@ The tool "browser_file_upload" can only be used when there is related modal stat
|
|||||||
- [File chooser]: can be handled by the "browser_file_upload" tool`);
|
- [File chooser]: can be handled by the "browser_file_upload" tool`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('clicking on download link emits download', async ({ startClient }, testInfo) => {
|
||||||
|
const outputDir = testInfo.outputPath('output');
|
||||||
|
const client = await startClient({
|
||||||
|
config: { outputDir },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await client.callTool({
|
||||||
|
name: 'browser_navigate',
|
||||||
|
arguments: {
|
||||||
|
url: 'data:text/html,<a href="data:text/plain,Hello world!" download="test.txt">Download</a>',
|
||||||
|
},
|
||||||
|
})).toContainTextContent('- link "Download" [ref=s1e3]');
|
||||||
|
|
||||||
|
await expect.poll(() => client.callTool({
|
||||||
|
name: 'browser_click',
|
||||||
|
arguments: {
|
||||||
|
element: 'Download link',
|
||||||
|
ref: 's1e3',
|
||||||
|
},
|
||||||
|
})).toContainTextContent(`
|
||||||
|
### Downloads
|
||||||
|
- Downloaded file test.txt to ${path.join(outputDir, 'test-txt')}`);
|
||||||
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user