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
JavaScript
/**
* 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