UNPKG

ultimate-mcp-server

Version:

The definitive all-in-one Model Context Protocol server for AI-assisted coding across 30+ platforms

321 lines 11.7 kB
/** * Browser Automation Manager * * Provides unified interface for both Playwright and Puppeteer * with automatic fallback and optimal selection */ import { chromium, firefox, webkit } from 'playwright'; import puppeteer from 'puppeteer'; export class BrowserManager { playwrightBrowser; puppeteerBrowser; currentEngine; isInitialized = false; /** * Initialize browser with specified engine */ async initialize(options = {}) { const engine = options.engine || this.selectOptimalEngine(); try { if (engine === 'playwright') { await this.initializePlaywright(options); } else { await this.initializePuppeteer(options); } this.currentEngine = engine; this.isInitialized = true; } catch (error) { console.error(`Failed to initialize ${engine}, trying fallback...`); // Try fallback engine const fallbackEngine = engine === 'playwright' ? 'puppeteer' : 'playwright'; if (fallbackEngine === 'playwright') { await this.initializePlaywright(options); } else { await this.initializePuppeteer(options); } this.currentEngine = fallbackEngine; this.isInitialized = true; } } /** * Create a new page with unified interface */ async newPage() { if (!this.isInitialized) { await this.initialize(); } if (this.currentEngine === 'playwright' && this.playwrightBrowser) { const page = await this.playwrightBrowser.newPage(); return this.wrapPlaywrightPage(page); } else if (this.puppeteerBrowser) { const page = await this.puppeteerBrowser.newPage(); return this.wrapPuppeteerPage(page); } throw new Error('No browser initialized'); } /** * Take screenshot with automatic tiling for large pages */ async captureScreenshot(url, options = {}) { const page = await this.newPage(); try { // Set viewport if specified if (options.viewport && page.setViewport) { await page.setViewport(options.viewport); } await page.goto(url); await page.waitForSelector('body', { timeout: 30000 }); // Wait for images to load await page.evaluate(() => { const doc = globalThis.document; if (!doc) return Promise.resolve(); return Promise.all(Array.from(doc.images) .filter((img) => !img.complete) .map((img) => new Promise(resolve => { img.onload = img.onerror = resolve; }))); }); if (options.fullPage && options.tileSize) { return await this.captureScreenshotTiles(page, options.tileSize); } else { const screenshot = await page.screenshot({ fullPage: options.fullPage || false, type: 'png' }); return [screenshot]; } } finally { await page.close(); } } /** * Execute automation script */ async executeAutomation(script) { const page = await this.newPage(); const results = []; try { await page.goto(script.url); for (const action of script.actions) { switch (action.type) { case 'click': if (action.selector) { await page.waitForSelector(action.selector); await page.click(action.selector); results.push({ type: 'click', success: true }); } break; case 'type': if (action.selector && action.text) { await page.waitForSelector(action.selector); await page.type(action.selector, action.text); results.push({ type: 'type', success: true }); } break; case 'wait': await new Promise(resolve => setTimeout(resolve, action.duration || 1000)); results.push({ type: 'wait', success: true }); break; case 'screenshot': const screenshot = await page.screenshot({ fullPage: true }); results.push({ type: 'screenshot', data: screenshot }); break; case 'evaluate': if (action.code) { const fn = new Function('return ' + action.code); const result = await page.evaluate(fn); results.push({ type: 'evaluate', data: result }); } break; } } return results; } finally { await page.close(); } } /** * Extract structured data from page */ async extractData(url, selectors) { const page = await this.newPage(); try { await page.goto(url); await page.waitForSelector('body'); const data = {}; for (const [key, selector] of Object.entries(selectors)) { try { data[key] = await page.evaluate((sel) => { const doc = globalThis.document; if (!doc) return null; const element = doc.querySelector(sel); if (!element) return null; // Try different extraction methods if (globalThis.HTMLImageElement && element instanceof globalThis.HTMLImageElement) { return element.src; } else if (globalThis.HTMLAnchorElement && element instanceof globalThis.HTMLAnchorElement) { return { text: element.textContent?.trim(), href: element.href }; } else { return element.textContent?.trim(); } }, selector); } catch (error) { data[key] = null; } } return data; } finally { await page.close(); } } /** * Close all browsers */ async close() { if (this.playwrightBrowser) { await this.playwrightBrowser.close(); this.playwrightBrowser = undefined; } if (this.puppeteerBrowser) { await this.puppeteerBrowser.close(); this.puppeteerBrowser = undefined; } this.isInitialized = false; this.currentEngine = undefined; } /** * Initialize Playwright */ async initializePlaywright(options) { const browserType = options.browserType || 'chromium'; const launcher = browserType === 'firefox' ? firefox : browserType === 'webkit' ? webkit : chromium; this.playwrightBrowser = await launcher.launch({ headless: options.headless !== false, timeout: options.timeout || 30000 }); } /** * Initialize Puppeteer */ async initializePuppeteer(options) { this.puppeteerBrowser = await puppeteer.launch({ headless: options.headless !== false, defaultViewport: options.viewport || { width: 1280, height: 720 }, timeout: options.timeout || 30000 }); } /** * Select optimal engine based on availability and features */ selectOptimalEngine() { // Playwright is preferred for its better API and multi-browser support try { require.resolve('playwright'); return 'playwright'; } catch { return 'puppeteer'; } } /** * Wrap Playwright page with unified interface */ wrapPlaywrightPage(page) { return { goto: async (url) => { await page.goto(url); }, screenshot: async (options) => { const result = await page.screenshot(options); return result; }, evaluate: (fn, ...args) => page.evaluate(fn, ...args), click: (selector) => page.click(selector), type: (selector, text) => page.type(selector, text), waitForSelector: async (selector, options) => { await page.waitForSelector(selector, options); }, close: () => page.close(), content: () => page.content(), title: () => page.title(), url: () => page.url(), setViewport: (viewport) => page.setViewportSize(viewport) }; } /** * Wrap Puppeteer page with unified interface */ wrapPuppeteerPage(page) { return { goto: async (url) => { await page.goto(url); }, screenshot: async (options) => { const result = await page.screenshot(options); return result; }, evaluate: (fn, ...args) => page.evaluate(fn, ...args), click: (selector) => page.click(selector), type: (selector, text) => page.type(selector, text), waitForSelector: async (selector, options) => { await page.waitForSelector(selector, options); }, close: () => page.close(), content: () => page.content(), title: () => page.title(), url: () => page.url(), setViewport: (viewport) => page.setViewport(viewport) }; } /** * Capture screenshot tiles for large pages */ async captureScreenshotTiles(page, tileSize) { const tiles = []; // Get page dimensions const dimensions = await page.evaluate(() => { const doc = globalThis.document; if (!doc) return { width: 1920, height: 1080 }; return { width: doc.documentElement.scrollWidth, height: doc.documentElement.scrollHeight }; }); const cols = Math.ceil(dimensions.width / tileSize); const rows = Math.ceil(dimensions.height / tileSize); for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { const x = col * tileSize; const y = row * tileSize; const width = Math.min(tileSize, dimensions.width - x); const height = Math.min(tileSize, dimensions.height - y); const tile = await page.screenshot({ clip: { x, y, width, height }, type: 'png' }); tiles.push(tile); } } return tiles; } } //# sourceMappingURL=browser-manager.js.map