💬 Commit message: Update 2026-02-11 04:16:38, 12 files, 588 lines
📁 Files changed: 12 📝 Lines changed: 588 • TODO.md • browser.d.ts • browser.d.ts.map • browser.js • browser.js.map • mcp.js • mcp.js.map • types.d.ts • types.d.ts.map • browser.ts • mcp.ts • types.ts
This commit is contained in:
@@ -5,9 +5,9 @@
|
|||||||
- [ ] Step 1.2: Add Page Errors Command
|
- [ ] Step 1.2: Add Page Errors Command
|
||||||
|
|
||||||
## Phase 2: Network Monitoring
|
## Phase 2: Network Monitoring
|
||||||
- [ ] Step 2.1: Add Network Logging
|
- [x] Step 2.1: Add Network Logging
|
||||||
- [ ] Step 2.2: Add Failed Requests Filter
|
- [x] Step 2.2: Add Failed Requests Filter
|
||||||
- [ ] Step 2.3: Add Request Interception
|
- [x] Step 2.3: Add Request Interception
|
||||||
|
|
||||||
## Phase 3: Performance & Metrics
|
## Phase 3: Performance & Metrics
|
||||||
- [ ] Step 3.1: Add Performance Metrics
|
- [ ] Step 3.1: Add Performance Metrics
|
||||||
|
|||||||
Vendored
+13
-1
@@ -1,14 +1,17 @@
|
|||||||
import { type BrowserContext, type Page } from 'playwright';
|
import { type BrowserContext, type Page } from 'playwright';
|
||||||
import type { BrowserCommand, BrowserOptions, CommandResponse, ConsoleMessage, ElementInfo } from './types.js';
|
import type { BrowserCommand, BrowserOptions, CommandResponse, ConsoleMessage, ElementInfo, NetworkEntry } from './types.js';
|
||||||
export declare class ClaudeBrowser {
|
export declare class ClaudeBrowser {
|
||||||
private browser;
|
private browser;
|
||||||
private context;
|
private context;
|
||||||
private page;
|
private page;
|
||||||
private options;
|
private options;
|
||||||
private consoleMessages;
|
private consoleMessages;
|
||||||
|
private networkEntries;
|
||||||
|
private interceptPatterns;
|
||||||
constructor(options?: BrowserOptions);
|
constructor(options?: BrowserOptions);
|
||||||
launch(): Promise<void>;
|
launch(): Promise<void>;
|
||||||
private setupConsoleListener;
|
private setupConsoleListener;
|
||||||
|
private setupNetworkListener;
|
||||||
close(): Promise<void>;
|
close(): Promise<void>;
|
||||||
private ensurePage;
|
private ensurePage;
|
||||||
/** Get the current page instance (for advanced usage) */
|
/** Get the current page instance (for advanced usage) */
|
||||||
@@ -47,6 +50,15 @@ export declare class ClaudeBrowser {
|
|||||||
eval(script: string): Promise<unknown>;
|
eval(script: string): Promise<unknown>;
|
||||||
getConsole(level?: string, clear?: boolean): ConsoleMessage[];
|
getConsole(level?: string, clear?: boolean): ConsoleMessage[];
|
||||||
clearConsole(): void;
|
clearConsole(): void;
|
||||||
|
getNetwork(filter?: string, clear?: boolean): NetworkEntry[];
|
||||||
|
clearNetwork(): void;
|
||||||
|
addIntercept(pattern: string, action: 'block' | 'mock', response?: {
|
||||||
|
status?: number;
|
||||||
|
body?: string;
|
||||||
|
contentType?: string;
|
||||||
|
}): Promise<void>;
|
||||||
|
clearIntercepts(): Promise<void>;
|
||||||
|
getInterceptPatterns(): string[];
|
||||||
executeCommand(cmd: BrowserCommand): Promise<CommandResponse>;
|
executeCommand(cmd: BrowserCommand): Promise<CommandResponse>;
|
||||||
}
|
}
|
||||||
//# sourceMappingURL=browser.d.ts.map
|
//# sourceMappingURL=browser.d.ts.map
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,cAAc,EAAE,KAAK,IAAI,EAAU,MAAM,YAAY,CAAC;AAElF,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,eAAe,EACf,cAAc,EACd,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,eAAe,CAAwB;gBAEnC,OAAO,GAAE,cAAmB;IAQlC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAY7B,OAAO,CAAC,oBAAoB;IAYtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAS5B,OAAO,CAAC,UAAU;IAOlB,yDAAyD;IACzD,OAAO,IAAI,IAAI,GAAG,IAAI;IAItB,gEAAgE;IAChE,UAAU,IAAI,cAAc,GAAG,IAAI;IAI7B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAM1D,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAOjD,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnD,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAiB/C,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAOvF,MAAM,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAKjD,OAAO,CAAC,IAAI,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAMtC,IAAI,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAMhC,OAAO,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAMnC,MAAM,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAMlC,IAAI,CAAC,EAAE,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAQxB,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK5C,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,cAAc,EAAE;IAW3D,YAAY,IAAI,IAAI;IAId,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CA0IpE"}
|
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,cAAc,EAAE,KAAK,IAAI,EAAU,MAAM,YAAY,CAAC;AAElF,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,eAAe,EACf,cAAc,EACd,WAAW,EACX,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,eAAe,CAAwB;IAC/C,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,iBAAiB,CAA6H;gBAE1I,OAAO,GAAE,cAAmB;IAQlC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAa7B,OAAO,CAAC,oBAAoB;IAY5B,OAAO,CAAC,oBAAoB;IAoDtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAS5B,OAAO,CAAC,UAAU;IAOlB,yDAAyD;IACzD,OAAO,IAAI,IAAI,GAAG,IAAI;IAItB,gEAAgE;IAChE,UAAU,IAAI,cAAc,GAAG,IAAI;IAI7B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAM1D,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAOjD,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnD,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAiB/C,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAOvF,MAAM,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAKjD,OAAO,CAAC,IAAI,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAMtC,IAAI,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAMhC,OAAO,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAMnC,MAAM,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAMlC,IAAI,CAAC,EAAE,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IASxB,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK5C,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,cAAc,EAAE;IAW3D,YAAY,IAAI,IAAI;IAIpB,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,YAAY,EAAE;IAe1D,YAAY,IAAI,IAAI;IAId,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,OAAO,GAAG,MAAM,EACxB,QAAQ,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAClE,OAAO,CAAC,IAAI,CAAC;IAyBV,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAQtC,oBAAoB,IAAI,MAAM,EAAE;IAI1B,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CA6JpE"}
|
||||||
Vendored
+124
@@ -7,6 +7,8 @@ export class ClaudeBrowser {
|
|||||||
page = null;
|
page = null;
|
||||||
options;
|
options;
|
||||||
consoleMessages = [];
|
consoleMessages = [];
|
||||||
|
networkEntries = [];
|
||||||
|
interceptPatterns = new Map();
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
this.options = {
|
this.options = {
|
||||||
headless: options.headless ?? true,
|
headless: options.headless ?? true,
|
||||||
@@ -24,6 +26,7 @@ export class ClaudeBrowser {
|
|||||||
});
|
});
|
||||||
this.page = await this.context.newPage();
|
this.page = await this.context.newPage();
|
||||||
this.setupConsoleListener(this.page);
|
this.setupConsoleListener(this.page);
|
||||||
|
this.setupNetworkListener(this.page);
|
||||||
}
|
}
|
||||||
setupConsoleListener(page) {
|
setupConsoleListener(page) {
|
||||||
page.on('console', (msg) => {
|
page.on('console', (msg) => {
|
||||||
@@ -36,6 +39,55 @@ export class ClaudeBrowser {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
setupNetworkListener(page) {
|
||||||
|
const pendingRequests = new Map();
|
||||||
|
page.on('request', (request) => {
|
||||||
|
const entry = {
|
||||||
|
url: request.url(),
|
||||||
|
method: request.method(),
|
||||||
|
resourceType: request.resourceType(),
|
||||||
|
requestHeaders: request.headers(),
|
||||||
|
timing: { startTime: Date.now() },
|
||||||
|
};
|
||||||
|
pendingRequests.set(request.url() + request.method(), entry);
|
||||||
|
});
|
||||||
|
page.on('response', async (response) => {
|
||||||
|
const request = response.request();
|
||||||
|
const key = request.url() + request.method();
|
||||||
|
const entry = pendingRequests.get(key);
|
||||||
|
if (entry) {
|
||||||
|
entry.status = response.status();
|
||||||
|
entry.statusText = response.statusText();
|
||||||
|
entry.responseHeaders = response.headers();
|
||||||
|
if (entry.timing) {
|
||||||
|
entry.timing.endTime = Date.now();
|
||||||
|
entry.timing.duration = entry.timing.endTime - entry.timing.startTime;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const body = await response.body();
|
||||||
|
entry.size = body.length;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// Body may not be available for some responses
|
||||||
|
}
|
||||||
|
this.networkEntries.push(entry);
|
||||||
|
pendingRequests.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
page.on('requestfailed', (request) => {
|
||||||
|
const key = request.url() + request.method();
|
||||||
|
const entry = pendingRequests.get(key);
|
||||||
|
if (entry) {
|
||||||
|
entry.error = request.failure()?.errorText || 'Request failed';
|
||||||
|
if (entry.timing) {
|
||||||
|
entry.timing.endTime = Date.now();
|
||||||
|
entry.timing.duration = entry.timing.endTime - entry.timing.startTime;
|
||||||
|
}
|
||||||
|
this.networkEntries.push(entry);
|
||||||
|
pendingRequests.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
async close() {
|
async close() {
|
||||||
if (this.browser) {
|
if (this.browser) {
|
||||||
await this.browser.close();
|
await this.browser.close();
|
||||||
@@ -127,6 +179,7 @@ export class ClaudeBrowser {
|
|||||||
}
|
}
|
||||||
this.page = await this.context.newPage();
|
this.page = await this.context.newPage();
|
||||||
this.setupConsoleListener(this.page);
|
this.setupConsoleListener(this.page);
|
||||||
|
this.setupNetworkListener(this.page);
|
||||||
}
|
}
|
||||||
async eval(script) {
|
async eval(script) {
|
||||||
const page = this.ensurePage();
|
const page = this.ensurePage();
|
||||||
@@ -145,6 +198,58 @@ export class ClaudeBrowser {
|
|||||||
clearConsole() {
|
clearConsole() {
|
||||||
this.consoleMessages = [];
|
this.consoleMessages = [];
|
||||||
}
|
}
|
||||||
|
getNetwork(filter, clear = false) {
|
||||||
|
let entries = this.networkEntries;
|
||||||
|
if (filter && filter !== 'all') {
|
||||||
|
if (filter === 'failed') {
|
||||||
|
entries = entries.filter((e) => e.error || (e.status && e.status >= 400));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entries = entries.filter((e) => e.resourceType === filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (clear) {
|
||||||
|
this.networkEntries = [];
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
clearNetwork() {
|
||||||
|
this.networkEntries = [];
|
||||||
|
}
|
||||||
|
async addIntercept(pattern, action, response) {
|
||||||
|
const page = this.ensurePage();
|
||||||
|
this.interceptPatterns.set(pattern, { action, response });
|
||||||
|
await page.route(pattern, async (route) => {
|
||||||
|
const config = this.interceptPatterns.get(pattern);
|
||||||
|
if (!config) {
|
||||||
|
await route.continue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (config.action === 'block') {
|
||||||
|
await route.abort();
|
||||||
|
}
|
||||||
|
else if (config.action === 'mock' && config.response) {
|
||||||
|
await route.fulfill({
|
||||||
|
status: config.response.status || 200,
|
||||||
|
contentType: config.response.contentType || 'application/json',
|
||||||
|
body: config.response.body || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await route.continue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async clearIntercepts() {
|
||||||
|
const page = this.ensurePage();
|
||||||
|
for (const pattern of this.interceptPatterns.keys()) {
|
||||||
|
await page.unroute(pattern);
|
||||||
|
}
|
||||||
|
this.interceptPatterns.clear();
|
||||||
|
}
|
||||||
|
getInterceptPatterns() {
|
||||||
|
return Array.from(this.interceptPatterns.keys());
|
||||||
|
}
|
||||||
async executeCommand(cmd) {
|
async executeCommand(cmd) {
|
||||||
try {
|
try {
|
||||||
switch (cmd.cmd) {
|
switch (cmd.cmd) {
|
||||||
@@ -208,6 +313,25 @@ export class ClaudeBrowser {
|
|||||||
const messages = this.getConsole(cmd.level, cmd.clear);
|
const messages = this.getConsole(cmd.level, cmd.clear);
|
||||||
return { ok: true, count: messages.length, messages };
|
return { ok: true, count: messages.length, messages };
|
||||||
}
|
}
|
||||||
|
case 'network': {
|
||||||
|
const requests = this.getNetwork(cmd.filter, cmd.clear);
|
||||||
|
return { ok: true, count: requests.length, requests };
|
||||||
|
}
|
||||||
|
case 'intercept': {
|
||||||
|
if (cmd.action === 'list') {
|
||||||
|
const patterns = this.getInterceptPatterns();
|
||||||
|
return { ok: true, count: patterns.length, patterns };
|
||||||
|
}
|
||||||
|
if (cmd.action === 'clear') {
|
||||||
|
await this.clearIntercepts();
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
if (!cmd.pattern) {
|
||||||
|
return { ok: false, error: 'Pattern required for block/mock actions' };
|
||||||
|
}
|
||||||
|
await this.addIntercept(cmd.pattern, cmd.action, cmd.response);
|
||||||
|
return { ok: true, patterns: this.getInterceptPatterns() };
|
||||||
|
}
|
||||||
case 'favicon': {
|
case 'favicon': {
|
||||||
const result = await image.createFavicon(cmd.input, cmd.outputDir);
|
const result = await image.createFavicon(cmd.input, cmd.outputDir);
|
||||||
return { ok: true, files: result.files, outputDir: result.outputDir };
|
return { ok: true, files: result.files, outputDir: result.outputDir };
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+94
@@ -119,6 +119,49 @@ server.tool('console', 'Get captured console messages (log, warn, error, etc.) f
|
|||||||
const messages = browser.getConsole(level, clear);
|
const messages = browser.getConsole(level, clear);
|
||||||
return textResult(JSON.stringify({ ok: true, count: messages.length, messages }));
|
return textResult(JSON.stringify({ ok: true, count: messages.length, messages }));
|
||||||
}));
|
}));
|
||||||
|
// Network monitoring
|
||||||
|
server.tool('network', 'Get captured network requests and responses', {
|
||||||
|
filter: z
|
||||||
|
.enum(['all', 'failed', 'xhr', 'fetch', 'document', 'script', 'stylesheet', 'image'])
|
||||||
|
.optional()
|
||||||
|
.default('all')
|
||||||
|
.describe('Filter by request type or status'),
|
||||||
|
clear: z.boolean().optional().default(false).describe('Clear entries after retrieving'),
|
||||||
|
}, withLogging('network', async ({ filter, clear }) => {
|
||||||
|
await ensureLaunched();
|
||||||
|
const requests = browser.getNetwork(filter, clear);
|
||||||
|
return textResult(JSON.stringify({ ok: true, count: requests.length, requests }));
|
||||||
|
}));
|
||||||
|
server.tool('intercept', 'Block or mock network requests matching a pattern', {
|
||||||
|
action: z.enum(['block', 'mock', 'list', 'clear']).describe('Action to perform'),
|
||||||
|
pattern: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe('URL pattern to match (glob syntax, e.g., "**/api/*" or "**/*.png")'),
|
||||||
|
status: z.number().optional().describe('HTTP status code for mock response'),
|
||||||
|
body: z.string().optional().describe('Response body for mock'),
|
||||||
|
contentType: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default('application/json')
|
||||||
|
.describe('Content-Type for mock response'),
|
||||||
|
}, withLogging('intercept', async ({ action, pattern, status, body, contentType }) => {
|
||||||
|
await ensureLaunched();
|
||||||
|
if (action === 'list') {
|
||||||
|
const patterns = browser.getInterceptPatterns();
|
||||||
|
return textResult(JSON.stringify({ ok: true, count: patterns.length, patterns }));
|
||||||
|
}
|
||||||
|
if (action === 'clear') {
|
||||||
|
await browser.clearIntercepts();
|
||||||
|
return textResult(JSON.stringify({ ok: true, message: 'All intercepts cleared' }));
|
||||||
|
}
|
||||||
|
if (!pattern) {
|
||||||
|
return textResult(JSON.stringify({ ok: false, error: 'Pattern required for block/mock' }));
|
||||||
|
}
|
||||||
|
const response = action === 'mock' ? { status, body, contentType } : undefined;
|
||||||
|
await browser.addIntercept(pattern, action, response);
|
||||||
|
return textResult(JSON.stringify({ ok: true, action, pattern, patterns: browser.getInterceptPatterns() }));
|
||||||
|
}));
|
||||||
// Utility
|
// Utility
|
||||||
server.tool('wait', 'Wait for a specified time in milliseconds', { ms: z.number().optional().default(1000) }, withLogging('wait', async ({ ms }) => {
|
server.tool('wait', 'Wait for a specified time in milliseconds', { ms: z.number().optional().default(1000) }, withLogging('wait', async ({ ms }) => {
|
||||||
await ensureLaunched();
|
await ensureLaunched();
|
||||||
@@ -374,6 +417,57 @@ server.resource('Console Messages', 'browser://console', { description: 'Console
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
// Resource: browser://network - All captured network requests
|
||||||
|
server.resource('Network Requests', 'browser://network', { description: 'All network requests captured from the browser', mimeType: 'application/json' }, async () => {
|
||||||
|
if (!launched) {
|
||||||
|
return {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
uri: 'browser://network',
|
||||||
|
mimeType: 'application/json',
|
||||||
|
text: JSON.stringify({ launched: false, requests: [] }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const requests = browser.getNetwork('all', false);
|
||||||
|
return {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
uri: 'browser://network',
|
||||||
|
mimeType: 'application/json',
|
||||||
|
text: JSON.stringify({ launched: true, count: requests.length, requests }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// Resource: browser://network/failed - Failed network requests only
|
||||||
|
server.resource('Failed Requests', 'browser://network/failed', {
|
||||||
|
description: 'Failed network requests (errors and 4xx/5xx status codes)',
|
||||||
|
mimeType: 'application/json',
|
||||||
|
}, async () => {
|
||||||
|
if (!launched) {
|
||||||
|
return {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
uri: 'browser://network/failed',
|
||||||
|
mimeType: 'application/json',
|
||||||
|
text: JSON.stringify({ launched: false, requests: [] }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const requests = browser.getNetwork('failed', false);
|
||||||
|
return {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
uri: 'browser://network/failed',
|
||||||
|
mimeType: 'application/json',
|
||||||
|
text: JSON.stringify({ launched: true, count: requests.length, requests }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
// Resource: browser://screenshot - Current page screenshot (base64)
|
// Resource: browser://screenshot - Current page screenshot (base64)
|
||||||
server.resource('Page Screenshot', 'browser://screenshot', { description: 'Screenshot of the current page as base64 PNG', mimeType: 'image/png' }, async () => {
|
server.resource('Page Screenshot', 'browser://screenshot', { description: 'Screenshot of the current page as base64 PNG', mimeType: 'image/png' }, async () => {
|
||||||
if (!launched) {
|
if (!launched) {
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+34
-1
@@ -111,7 +111,38 @@ export interface ConsoleMessage {
|
|||||||
timestamp: number;
|
timestamp: number;
|
||||||
location?: string;
|
location?: string;
|
||||||
}
|
}
|
||||||
export type BrowserCommand = GotoCommand | ClickCommand | TypeCommand | QueryCommand | ScreenshotCommand | UrlCommand | HtmlCommand | BackCommand | ForwardCommand | ReloadCommand | WaitCommand | NewPageCommand | CloseCommand | EvalCommand | FaviconCommand | ConvertCommand | ResizeCommand | CropCommand | CompressCommand | ThumbnailCommand | ConsoleCommand;
|
export interface NetworkEntry {
|
||||||
|
url: string;
|
||||||
|
method: string;
|
||||||
|
resourceType: string;
|
||||||
|
status?: number;
|
||||||
|
statusText?: string;
|
||||||
|
requestHeaders?: Record<string, string>;
|
||||||
|
responseHeaders?: Record<string, string>;
|
||||||
|
timing?: {
|
||||||
|
startTime: number;
|
||||||
|
endTime?: number;
|
||||||
|
duration?: number;
|
||||||
|
};
|
||||||
|
size?: number;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
export interface NetworkCommand {
|
||||||
|
cmd: 'network';
|
||||||
|
clear?: boolean;
|
||||||
|
filter?: 'all' | 'failed' | 'xhr' | 'fetch' | 'document' | 'script' | 'stylesheet' | 'image';
|
||||||
|
}
|
||||||
|
export interface InterceptCommand {
|
||||||
|
cmd: 'intercept';
|
||||||
|
action: 'block' | 'mock' | 'list' | 'clear';
|
||||||
|
pattern?: string;
|
||||||
|
response?: {
|
||||||
|
status?: number;
|
||||||
|
body?: string;
|
||||||
|
contentType?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export type BrowserCommand = GotoCommand | ClickCommand | TypeCommand | QueryCommand | ScreenshotCommand | UrlCommand | HtmlCommand | BackCommand | ForwardCommand | ReloadCommand | WaitCommand | NewPageCommand | CloseCommand | EvalCommand | FaviconCommand | ConvertCommand | ResizeCommand | CropCommand | CompressCommand | ThumbnailCommand | ConsoleCommand | NetworkCommand | InterceptCommand;
|
||||||
export interface SuccessResponse {
|
export interface SuccessResponse {
|
||||||
ok: true;
|
ok: true;
|
||||||
url?: string;
|
url?: string;
|
||||||
@@ -128,6 +159,8 @@ export interface SuccessResponse {
|
|||||||
format?: string;
|
format?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
messages?: ConsoleMessage[];
|
messages?: ConsoleMessage[];
|
||||||
|
requests?: NetworkEntry[];
|
||||||
|
patterns?: string[];
|
||||||
}
|
}
|
||||||
export interface ErrorResponse {
|
export interface ErrorResponse {
|
||||||
ok: false;
|
ok: false;
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAGD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,OAAO,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,YAAY,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,KAAK,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,SAAS,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,QAAQ,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,SAAS,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,OAAO,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAGD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,SAAS,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,SAAS,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CAC1C;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,QAAQ,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;CAC3D;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,UAAU,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,WAAW,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,SAAS,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;CAC7D;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,MAAM,CAAC,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,SAAS,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,YAAY,GAAG,OAAO,CAAC;CAC9F;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,WAAW,CAAC;IACjB,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,YAAY,GACZ,WAAW,GACX,YAAY,GACZ,iBAAiB,GACjB,UAAU,GACV,WAAW,GACX,WAAW,GACX,cAAc,GACd,aAAa,GACb,WAAW,GACX,cAAc,GACd,YAAY,GACZ,WAAW,GACX,cAAc,GACd,cAAc,GACd,aAAa,GACb,WAAW,GACX,eAAe,GACf,gBAAgB,GAChB,cAAc,GACd,cAAc,GACd,gBAAgB,CAAC;AAGrB,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAE5B,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG,aAAa,CAAC"}
|
||||||
+145
-1
@@ -1,5 +1,5 @@
|
|||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
import { type Browser, type BrowserContext, type Page, webkit } from 'playwright';
|
import { type Browser, type BrowserContext, type Page, type Route, webkit } from 'playwright';
|
||||||
import * as image from './image.js';
|
import * as image from './image.js';
|
||||||
import type {
|
import type {
|
||||||
BrowserCommand,
|
BrowserCommand,
|
||||||
@@ -7,6 +7,7 @@ import type {
|
|||||||
CommandResponse,
|
CommandResponse,
|
||||||
ConsoleMessage,
|
ConsoleMessage,
|
||||||
ElementInfo,
|
ElementInfo,
|
||||||
|
NetworkEntry,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
|
||||||
export class ClaudeBrowser {
|
export class ClaudeBrowser {
|
||||||
@@ -15,6 +16,14 @@ export class ClaudeBrowser {
|
|||||||
private page: Page | null = null;
|
private page: Page | null = null;
|
||||||
private options: Required<BrowserOptions>;
|
private options: Required<BrowserOptions>;
|
||||||
private consoleMessages: ConsoleMessage[] = [];
|
private consoleMessages: ConsoleMessage[] = [];
|
||||||
|
private networkEntries: NetworkEntry[] = [];
|
||||||
|
private interceptPatterns: Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
action: 'block' | 'mock';
|
||||||
|
response?: { status?: number; body?: string; contentType?: string };
|
||||||
|
}
|
||||||
|
> = new Map();
|
||||||
|
|
||||||
constructor(options: BrowserOptions = {}) {
|
constructor(options: BrowserOptions = {}) {
|
||||||
this.options = {
|
this.options = {
|
||||||
@@ -34,6 +43,7 @@ export class ClaudeBrowser {
|
|||||||
});
|
});
|
||||||
this.page = await this.context.newPage();
|
this.page = await this.context.newPage();
|
||||||
this.setupConsoleListener(this.page);
|
this.setupConsoleListener(this.page);
|
||||||
|
this.setupNetworkListener(this.page);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupConsoleListener(page: Page): void {
|
private setupConsoleListener(page: Page): void {
|
||||||
@@ -48,6 +58,58 @@ export class ClaudeBrowser {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setupNetworkListener(page: Page): void {
|
||||||
|
const pendingRequests = new Map<string, NetworkEntry>();
|
||||||
|
|
||||||
|
page.on('request', (request) => {
|
||||||
|
const entry: NetworkEntry = {
|
||||||
|
url: request.url(),
|
||||||
|
method: request.method(),
|
||||||
|
resourceType: request.resourceType(),
|
||||||
|
requestHeaders: request.headers(),
|
||||||
|
timing: { startTime: Date.now() },
|
||||||
|
};
|
||||||
|
pendingRequests.set(request.url() + request.method(), entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
page.on('response', async (response) => {
|
||||||
|
const request = response.request();
|
||||||
|
const key = request.url() + request.method();
|
||||||
|
const entry = pendingRequests.get(key);
|
||||||
|
if (entry) {
|
||||||
|
entry.status = response.status();
|
||||||
|
entry.statusText = response.statusText();
|
||||||
|
entry.responseHeaders = response.headers();
|
||||||
|
if (entry.timing) {
|
||||||
|
entry.timing.endTime = Date.now();
|
||||||
|
entry.timing.duration = entry.timing.endTime - entry.timing.startTime;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const body = await response.body();
|
||||||
|
entry.size = body.length;
|
||||||
|
} catch {
|
||||||
|
// Body may not be available for some responses
|
||||||
|
}
|
||||||
|
this.networkEntries.push(entry);
|
||||||
|
pendingRequests.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
page.on('requestfailed', (request) => {
|
||||||
|
const key = request.url() + request.method();
|
||||||
|
const entry = pendingRequests.get(key);
|
||||||
|
if (entry) {
|
||||||
|
entry.error = request.failure()?.errorText || 'Request failed';
|
||||||
|
if (entry.timing) {
|
||||||
|
entry.timing.endTime = Date.now();
|
||||||
|
entry.timing.duration = entry.timing.endTime - entry.timing.startTime;
|
||||||
|
}
|
||||||
|
this.networkEntries.push(entry);
|
||||||
|
pendingRequests.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
if (this.browser) {
|
if (this.browser) {
|
||||||
await this.browser.close();
|
await this.browser.close();
|
||||||
@@ -156,6 +218,7 @@ export class ClaudeBrowser {
|
|||||||
}
|
}
|
||||||
this.page = await this.context.newPage();
|
this.page = await this.context.newPage();
|
||||||
this.setupConsoleListener(this.page);
|
this.setupConsoleListener(this.page);
|
||||||
|
this.setupNetworkListener(this.page);
|
||||||
}
|
}
|
||||||
|
|
||||||
async eval(script: string): Promise<unknown> {
|
async eval(script: string): Promise<unknown> {
|
||||||
@@ -178,6 +241,68 @@ export class ClaudeBrowser {
|
|||||||
this.consoleMessages = [];
|
this.consoleMessages = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNetwork(filter?: string, clear = false): NetworkEntry[] {
|
||||||
|
let entries = this.networkEntries;
|
||||||
|
if (filter && filter !== 'all') {
|
||||||
|
if (filter === 'failed') {
|
||||||
|
entries = entries.filter((e) => e.error || (e.status && e.status >= 400));
|
||||||
|
} else {
|
||||||
|
entries = entries.filter((e) => e.resourceType === filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (clear) {
|
||||||
|
this.networkEntries = [];
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearNetwork(): void {
|
||||||
|
this.networkEntries = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async addIntercept(
|
||||||
|
pattern: string,
|
||||||
|
action: 'block' | 'mock',
|
||||||
|
response?: { status?: number; body?: string; contentType?: string }
|
||||||
|
): Promise<void> {
|
||||||
|
const page = this.ensurePage();
|
||||||
|
this.interceptPatterns.set(pattern, { action, response });
|
||||||
|
await page.route(pattern, (route) => this.handleIntercept(pattern, route));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleIntercept(pattern: string, route: Route): Promise<void> {
|
||||||
|
const config = this.interceptPatterns.get(pattern);
|
||||||
|
if (!config) {
|
||||||
|
await route.continue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (config.action === 'block') {
|
||||||
|
await route.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (config.action === 'mock' && config.response) {
|
||||||
|
await route.fulfill({
|
||||||
|
status: config.response.status || 200,
|
||||||
|
contentType: config.response.contentType || 'application/json',
|
||||||
|
body: config.response.body || '',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await route.continue();
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearIntercepts(): Promise<void> {
|
||||||
|
const page = this.ensurePage();
|
||||||
|
for (const pattern of this.interceptPatterns.keys()) {
|
||||||
|
await page.unroute(pattern);
|
||||||
|
}
|
||||||
|
this.interceptPatterns.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
getInterceptPatterns(): string[] {
|
||||||
|
return Array.from(this.interceptPatterns.keys());
|
||||||
|
}
|
||||||
|
|
||||||
async executeCommand(cmd: BrowserCommand): Promise<CommandResponse> {
|
async executeCommand(cmd: BrowserCommand): Promise<CommandResponse> {
|
||||||
try {
|
try {
|
||||||
switch (cmd.cmd) {
|
switch (cmd.cmd) {
|
||||||
@@ -241,6 +366,25 @@ export class ClaudeBrowser {
|
|||||||
const messages = this.getConsole(cmd.level, cmd.clear);
|
const messages = this.getConsole(cmd.level, cmd.clear);
|
||||||
return { ok: true, count: messages.length, messages };
|
return { ok: true, count: messages.length, messages };
|
||||||
}
|
}
|
||||||
|
case 'network': {
|
||||||
|
const requests = this.getNetwork(cmd.filter, cmd.clear);
|
||||||
|
return { ok: true, count: requests.length, requests };
|
||||||
|
}
|
||||||
|
case 'intercept': {
|
||||||
|
if (cmd.action === 'list') {
|
||||||
|
const patterns = this.getInterceptPatterns();
|
||||||
|
return { ok: true, count: patterns.length, patterns };
|
||||||
|
}
|
||||||
|
if (cmd.action === 'clear') {
|
||||||
|
await this.clearIntercepts();
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
if (!cmd.pattern) {
|
||||||
|
return { ok: false, error: 'Pattern required for block/mock actions' };
|
||||||
|
}
|
||||||
|
await this.addIntercept(cmd.pattern, cmd.action, cmd.response);
|
||||||
|
return { ok: true, patterns: this.getInterceptPatterns() };
|
||||||
|
}
|
||||||
case 'favicon': {
|
case 'favicon': {
|
||||||
const result = await image.createFavicon(cmd.input, cmd.outputDir);
|
const result = await image.createFavicon(cmd.input, cmd.outputDir);
|
||||||
return { ok: true, files: result.files, outputDir: result.outputDir };
|
return { ok: true, files: result.files, outputDir: result.outputDir };
|
||||||
|
|||||||
+120
@@ -202,6 +202,63 @@ server.tool(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Network monitoring
|
||||||
|
server.tool(
|
||||||
|
'network',
|
||||||
|
'Get captured network requests and responses',
|
||||||
|
{
|
||||||
|
filter: z
|
||||||
|
.enum(['all', 'failed', 'xhr', 'fetch', 'document', 'script', 'stylesheet', 'image'])
|
||||||
|
.optional()
|
||||||
|
.default('all')
|
||||||
|
.describe('Filter by request type or status'),
|
||||||
|
clear: z.boolean().optional().default(false).describe('Clear entries after retrieving'),
|
||||||
|
},
|
||||||
|
withLogging('network', async ({ filter, clear }) => {
|
||||||
|
await ensureLaunched();
|
||||||
|
const requests = browser.getNetwork(filter, clear);
|
||||||
|
return textResult(JSON.stringify({ ok: true, count: requests.length, requests }));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
'intercept',
|
||||||
|
'Block or mock network requests matching a pattern',
|
||||||
|
{
|
||||||
|
action: z.enum(['block', 'mock', 'list', 'clear']).describe('Action to perform'),
|
||||||
|
pattern: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe('URL pattern to match (glob syntax, e.g., "**/api/*" or "**/*.png")'),
|
||||||
|
status: z.number().optional().describe('HTTP status code for mock response'),
|
||||||
|
body: z.string().optional().describe('Response body for mock'),
|
||||||
|
contentType: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default('application/json')
|
||||||
|
.describe('Content-Type for mock response'),
|
||||||
|
},
|
||||||
|
withLogging('intercept', async ({ action, pattern, status, body, contentType }) => {
|
||||||
|
await ensureLaunched();
|
||||||
|
if (action === 'list') {
|
||||||
|
const patterns = browser.getInterceptPatterns();
|
||||||
|
return textResult(JSON.stringify({ ok: true, count: patterns.length, patterns }));
|
||||||
|
}
|
||||||
|
if (action === 'clear') {
|
||||||
|
await browser.clearIntercepts();
|
||||||
|
return textResult(JSON.stringify({ ok: true, message: 'All intercepts cleared' }));
|
||||||
|
}
|
||||||
|
if (!pattern) {
|
||||||
|
return textResult(JSON.stringify({ ok: false, error: 'Pattern required for block/mock' }));
|
||||||
|
}
|
||||||
|
const response = action === 'mock' ? { status, body, contentType } : undefined;
|
||||||
|
await browser.addIntercept(pattern, action, response);
|
||||||
|
return textResult(
|
||||||
|
JSON.stringify({ ok: true, action, pattern, patterns: browser.getInterceptPatterns() })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Utility
|
// Utility
|
||||||
server.tool(
|
server.tool(
|
||||||
'wait',
|
'wait',
|
||||||
@@ -561,6 +618,69 @@ server.resource(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Resource: browser://network - All captured network requests
|
||||||
|
server.resource(
|
||||||
|
'Network Requests',
|
||||||
|
'browser://network',
|
||||||
|
{ description: 'All network requests captured from the browser', mimeType: 'application/json' },
|
||||||
|
async () => {
|
||||||
|
if (!launched) {
|
||||||
|
return {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
uri: 'browser://network',
|
||||||
|
mimeType: 'application/json',
|
||||||
|
text: JSON.stringify({ launched: false, requests: [] }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const requests = browser.getNetwork('all', false);
|
||||||
|
return {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
uri: 'browser://network',
|
||||||
|
mimeType: 'application/json',
|
||||||
|
text: JSON.stringify({ launched: true, count: requests.length, requests }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Resource: browser://network/failed - Failed network requests only
|
||||||
|
server.resource(
|
||||||
|
'Failed Requests',
|
||||||
|
'browser://network/failed',
|
||||||
|
{
|
||||||
|
description: 'Failed network requests (errors and 4xx/5xx status codes)',
|
||||||
|
mimeType: 'application/json',
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
if (!launched) {
|
||||||
|
return {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
uri: 'browser://network/failed',
|
||||||
|
mimeType: 'application/json',
|
||||||
|
text: JSON.stringify({ launched: false, requests: [] }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const requests = browser.getNetwork('failed', false);
|
||||||
|
return {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
uri: 'browser://network/failed',
|
||||||
|
mimeType: 'application/json',
|
||||||
|
text: JSON.stringify({ launched: true, count: requests.length, requests }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Resource: browser://screenshot - Current page screenshot (base64)
|
// Resource: browser://screenshot - Current page screenshot (base64)
|
||||||
server.resource(
|
server.resource(
|
||||||
'Page Screenshot',
|
'Page Screenshot',
|
||||||
|
|||||||
+40
-1
@@ -137,6 +137,40 @@ export interface ConsoleMessage {
|
|||||||
location?: string;
|
location?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NetworkEntry {
|
||||||
|
url: string;
|
||||||
|
method: string;
|
||||||
|
resourceType: string;
|
||||||
|
status?: number;
|
||||||
|
statusText?: string;
|
||||||
|
requestHeaders?: Record<string, string>;
|
||||||
|
responseHeaders?: Record<string, string>;
|
||||||
|
timing?: {
|
||||||
|
startTime: number;
|
||||||
|
endTime?: number;
|
||||||
|
duration?: number;
|
||||||
|
};
|
||||||
|
size?: number;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NetworkCommand {
|
||||||
|
cmd: 'network';
|
||||||
|
clear?: boolean;
|
||||||
|
filter?: 'all' | 'failed' | 'xhr' | 'fetch' | 'document' | 'script' | 'stylesheet' | 'image';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InterceptCommand {
|
||||||
|
cmd: 'intercept';
|
||||||
|
action: 'block' | 'mock' | 'list' | 'clear';
|
||||||
|
pattern?: string;
|
||||||
|
response?: {
|
||||||
|
status?: number;
|
||||||
|
body?: string;
|
||||||
|
contentType?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type BrowserCommand =
|
export type BrowserCommand =
|
||||||
| GotoCommand
|
| GotoCommand
|
||||||
| ClickCommand
|
| ClickCommand
|
||||||
@@ -158,7 +192,9 @@ export type BrowserCommand =
|
|||||||
| CropCommand
|
| CropCommand
|
||||||
| CompressCommand
|
| CompressCommand
|
||||||
| ThumbnailCommand
|
| ThumbnailCommand
|
||||||
| ConsoleCommand;
|
| ConsoleCommand
|
||||||
|
| NetworkCommand
|
||||||
|
| InterceptCommand;
|
||||||
|
|
||||||
// Response types
|
// Response types
|
||||||
export interface SuccessResponse {
|
export interface SuccessResponse {
|
||||||
@@ -179,6 +215,9 @@ export interface SuccessResponse {
|
|||||||
size?: number;
|
size?: number;
|
||||||
// Console fields
|
// Console fields
|
||||||
messages?: ConsoleMessage[];
|
messages?: ConsoleMessage[];
|
||||||
|
// Network fields
|
||||||
|
requests?: NetworkEntry[];
|
||||||
|
patterns?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ErrorResponse {
|
export interface ErrorResponse {
|
||||||
|
|||||||
Reference in New Issue
Block a user