run-via-mcp.jsā¢10.6 kB
// MCP-driven Google search: navigate to Google, accept/dismiss cookies if present,
// perform a search for "Playwright", verify results contain the word, take a
// full-page screenshot and save it to the project root as `google-playwright-search.png`.
(async () => {
try {
const fs = await import('fs');
const path = await import('path');
const { Client } = await import('@modelcontextprotocol/sdk/client/index.js');
const { StdioClientTransport } = await import('@modelcontextprotocol/sdk/client/stdio.js');
const client = new Client({ name: 'mcp-runner', version: '1.0.0' });
// If an MCP server is already running on localhost:8931, reuse it. Otherwise attempt to spawn one.
const serverUrl = 'http://localhost:8931/';
let child = null;
let serverUp = false;
try {
const res = await (globalThis.fetch || fetch)(serverUrl, { method: 'GET' });
if (res && (res.status === 200 || res.status === 404 || res.status === 405)) {
serverUp = true;
console.log('Found existing Playwright MCP server at http://localhost:8931');
}
} catch (e) {
// not running
}
if (!serverUp) {
const { spawn } = await import('child_process');
const serverCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
const serverArgs = ['@playwright/mcp@latest', '--port', '8931', '--caps=vision,pdf,testing,tracing', '--output-dir=playwright-mcp-output'];
console.log('Starting Playwright MCP server (HTTP) as child process...');
child = spawn(serverCmd, serverArgs, { stdio: ['ignore', 'pipe', 'pipe'] });
// Wait for the server to print the listening line
await new Promise((resolve, reject) => {
const onData = (data) => {
const text = String(data);
process.stdout.write(text);
if (text.includes('Listening on')) {
cleanup();
resolve();
}
};
const onErr = (data) => {
const text = String(data);
process.stderr.write(text);
};
const onExit = (code) => {
cleanup();
reject(new Error('MCP server exited early with code ' + code));
};
const cleanup = () => {
child.stdout.removeListener('data', onData);
child.stderr.removeListener('data', onErr);
child.removeListener('exit', onExit);
};
child.stdout.on('data', onData);
child.stderr.on('data', onErr);
child.on('exit', onExit);
});
serverUp = true;
}
// Connect to the server via Streamable HTTP transport
const { StreamableHTTPClientTransport } = await import('@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.js');
const transport = new StreamableHTTPClientTransport('http://localhost:8931/mcp', { fetch: globalThis.fetch });
console.log('Connecting to MCP server via HTTP transport...');
await client.connect(transport);
console.log('Connected to MCP server (HTTP)');
// Ensure browser is installed (idempotent)
console.log('Calling tool: browser_install');
try {
await client.callTool({ name: 'browser_install', arguments: {} });
} catch (err) {
console.warn('browser_install failed or was unnecessary:', err?.message || err);
}
// 1) Navigate to Google
console.log('Navigating to https://www.google.com');
await client.callTool({ name: 'browser_navigate', arguments: { url: 'https://www.google.com' } });
// 2) Try to accept/dismiss cookie dialogs by running a small evaluation in the page.
console.log('Attempting to dismiss cookie dialog if present');
try {
const clickResult = await client.callTool({
name: 'browser_evaluate',
arguments: {
function: `() => {
const selectors = [
'#L2AGLb',
'button[aria-label="Accept all"]',
'button[id*="accept"]',
'button[jsname="higCR"]',
'button:contains("I agree")',
'button:contains("Accept")',
'form button'
];
for (const s of selectors) {
try {
const el = document.querySelector(s);
if (el) { el.click(); return { clicked: true, selector: s }; }
} catch (e) { /* ignore invalid selectors */ }
}
// Try common cookie button text search
const textButtons = Array.from(document.querySelectorAll('button'));
for (const b of textButtons) {
const t = (b.innerText || '').trim().toLowerCase();
if (t.includes('agree') || t.includes('accept') || t.includes('consent')) { b.click(); return { clicked: true, text: t }; }
}
return { clicked: false };
}`
}
});
console.log('cookie dismiss result:', JSON.stringify(clickResult));
} catch (err) {
console.warn('cookie dismiss attempt failed (continuing):', err?.message || err);
}
// 3) Type "Playwright" into the search box and press Enter
console.log('Typing query and submitting');
try {
await client.callTool({ name: 'browser_type', arguments: { selector: 'input[name="q"]', text: 'Playwright' } });
await client.callTool({ name: 'browser_press_key', arguments: { key: 'Enter' } });
} catch (err) {
// Fallback: use evaluate to set the value and dispatch Enter
console.warn('browser_type/browser_press_key not available, falling back to evaluate:', err?.message || err);
await client.callTool({
name: 'browser_evaluate',
arguments: {
function: `() => {
const q = document.querySelector('input[name="q"]');
if (!q) return { ok: false };
q.focus();
q.value = 'Playwright';
q.dispatchEvent(new Event('input', { bubbles: true }));
const form = q.form;
if (form) { form.submit(); return { ok: true, method: 'form.submit' }; }
q.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', keyCode: 13, which: 13, bubbles: true }));
return { ok: true, method: 'keydown' };
}`
}
});
}
// 4) Wait for search results to load. Prefer waiting for '#search' or result snippets.
console.log('Waiting for search results to appear');
let found = false;
try {
// Try the dedicated wait tool first
await client.callTool({ name: 'browser_wait_for', arguments: { selector: '#search', timeout: 10000 } });
found = true;
} catch (err) {
// Fallback: poll the page for the presence of the word "Playwright" in body text
const start = Date.now();
const timeout = 15000;
while (Date.now() - start < timeout) {
const r = await client.callTool({ name: 'browser_evaluate', arguments: { function: '() => document.body && document.body.innerText ? document.body.innerText : ""' } });
let body = '';
if (r && r.structuredContent) body = r.structuredContent;
else if (Array.isArray(r?.content) && r.content.length) body = r.content[0].text || '';
if (typeof body === 'string' && body.toLowerCase().includes('playwright')) { found = true; break; }
await new Promise((res) => setTimeout(res, 500));
}
}
if (!found) throw new Error('Search results did not appear or did not contain "Playwright" in time');
// 5) Verify the results page contains the word "Playwright"
console.log('Verifying results contain the word "Playwright"');
const verify = await client.callTool({ name: 'browser_evaluate', arguments: { function: '() => (document.body && document.body.innerText || "").includes("Playwright")' } });
let verified = false;
if (verify && typeof verify.structuredContent === 'boolean') verified = verify.structuredContent;
else if (Array.isArray(verify?.content) && verify.content.length) {
const txt = verify.content[0].text;
verified = String(txt).toLowerCase().includes('playwright');
}
if (!verified) throw new Error('Verification failed: results page does not contain the word "Playwright"');
console.log('Verification succeeded');
// 6) Take a full-page screenshot and save it locally in project root
console.log('Taking full-page screenshot via MCP tool');
const ss = await client.callTool({ name: 'browser_take_screenshot', arguments: { fullPage: true } });
// Helper to locate base64 payload in tool result
function extractBase64(res) {
if (!res) return null;
if (typeof res === 'string') return res;
if (res.structuredContent && typeof res.structuredContent === 'string') return res.structuredContent;
if (Array.isArray(res.content)) {
for (const c of res.content) {
if (c && typeof c.text === 'string' && c.text.length > 100) return c.text;
if (c && typeof c.data === 'string') return c.data;
}
}
if (res.content && typeof res.content === 'string') return res.content;
return null;
}
let base64 = extractBase64(ss);
if (!base64 && ss && ss.structuredContent && ss.structuredContent.data) base64 = ss.structuredContent.data;
if (!base64) {
console.log('No base64 found in response; saving fallback server-side filename if present');
// If server saved the file already (older behavior), try to return path info from tool result
const fallbackPath = path.resolve(process.cwd(), 'playwright-mcp-output', 'google-playwright-search.png');
console.log('Fallback path (server-side):', fallbackPath);
} else {
// Data may be a data URL
if (base64.startsWith('data:')) {
const parts = base64.split(',');
base64 = parts[1];
}
const outPath = path.resolve(process.cwd(), 'google-playwright-search.png');
fs.writeFileSync(outPath, Buffer.from(base64, 'base64'));
console.log('Saved screenshot to:', outPath);
}
// Disconnect / close transport (terminate spawned child process)
try {
if (typeof client.disconnect === 'function') {
await client.disconnect();
} else if (transport && typeof transport.close === 'function') {
transport.close();
}
} catch (e) {
console.warn('Error while disconnecting client:', e?.message || e);
}
try { child.kill(); } catch {}
console.log('Disconnected and server stopped');
} catch (err) {
console.error('Error running via MCP:', err && err.message ? err.message : err);
process.exit(1);
}
})();