import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
import { createServer } from '../src/server.js';
import { closeBrowser } from '../src/browser.js';
import { closeAllTabs } from '../src/tabs.js';
describe('Puppeteer MCP Server', () => {
let client: Client;
beforeAll(async () => {
const server = createServer();
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
client = new Client({ name: 'test-client', version: '1.0.0' });
await client.connect(clientTransport);
}, 30000);
afterAll(async () => {
await closeAllTabs();
await closeBrowser();
}, 30000);
describe('Tab Management', () => {
it('should list tabs', async () => {
const result = await client.callTool({ name: 'list_tabs', arguments: {} });
expect(result.content).toBeDefined();
expect(result.content[0]).toHaveProperty('text');
const data = JSON.parse((result.content[0] as { text: string }).text);
expect(data).toHaveProperty('tabs');
expect(Array.isArray(data.tabs)).toBe(true);
});
it('should create a new tab', async () => {
const result = await client.callTool({ name: 'new_tab', arguments: {} });
expect(result.content).toBeDefined();
const data = JSON.parse((result.content[0] as { text: string }).text);
expect(data).toHaveProperty('id');
expect(data.id).toMatch(/^tab_/);
});
it('should create a new tab with URL', async () => {
const result = await client.callTool({
name: 'new_tab',
arguments: { url: 'https://example.com' },
});
expect(result.content).toBeDefined();
const data = JSON.parse((result.content[0] as { text: string }).text);
expect(data).toHaveProperty('id');
expect(data.url).toContain('example.com');
}, 30000);
});
describe('Navigation', () => {
it('should navigate to a URL', async () => {
const result = await client.callTool({
name: 'navigate',
arguments: { url: 'https://example.com' },
});
expect(result.content).toBeDefined();
const data = JSON.parse((result.content[0] as { text: string }).text);
expect(data.url).toContain('example.com');
expect(data).toHaveProperty('title');
}, 30000);
it('should reload the page', async () => {
// First navigate to a page
await client.callTool({
name: 'navigate',
arguments: { url: 'https://example.com' },
});
const result = await client.callTool({
name: 'reload',
arguments: {},
});
expect(result.content).toBeDefined();
const data = JSON.parse((result.content[0] as { text: string }).text);
expect(data.url).toContain('example.com');
}, 30000);
});
describe('Content', () => {
it('should evaluate JavaScript', async () => {
await client.callTool({
name: 'navigate',
arguments: { url: 'https://example.com' },
});
const result = await client.callTool({
name: 'evaluate',
arguments: { script: 'return 1 + 1' },
});
expect(result.content).toBeDefined();
const data = JSON.parse((result.content[0] as { text: string }).text);
expect(data.result).toBe(2);
}, 30000);
it('should get page content', async () => {
await client.callTool({
name: 'navigate',
arguments: { url: 'https://example.com' },
});
const result = await client.callTool({
name: 'get_content',
arguments: { type: 'text' },
});
expect(result.content).toBeDefined();
const data = JSON.parse((result.content[0] as { text: string }).text);
expect(data).toHaveProperty('content');
expect(data.content).toContain('Example Domain');
}, 30000);
it('should query selector', async () => {
await client.callTool({
name: 'navigate',
arguments: { url: 'https://example.com' },
});
const result = await client.callTool({
name: 'query_selector',
arguments: { selector: 'h1' },
});
expect(result.content).toBeDefined();
const data = JSON.parse((result.content[0] as { text: string }).text);
expect(data.exists).toBe(true);
expect(data.tagName).toBe('h1');
}, 30000);
});
describe('Media', () => {
it('should take a screenshot', async () => {
await client.callTool({
name: 'navigate',
arguments: { url: 'https://example.com' },
});
const result = await client.callTool({
name: 'screenshot',
arguments: { format: 'png' },
});
expect(result.content).toBeDefined();
// Screenshot returns image content block
const imageBlock = result.content[0] as { type: string; data: string; mimeType: string };
expect(imageBlock.type).toBe('image');
expect(imageBlock.mimeType).toBe('image/png');
expect(imageBlock.data).toBeDefined();
expect(imageBlock.data.length).toBeGreaterThan(0);
}, 30000);
});
describe('Waiting', () => {
it('should wait for a selector', async () => {
await client.callTool({
name: 'navigate',
arguments: { url: 'https://example.com' },
});
const result = await client.callTool({
name: 'wait_for_selector',
arguments: { selector: 'h1' },
});
expect(result.content).toBeDefined();
const data = JSON.parse((result.content[0] as { text: string }).text);
expect(data.found).toBe(true);
}, 30000);
it('should wait for specified time', async () => {
const startTime = Date.now();
const result = await client.callTool({
name: 'wait',
arguments: { ms: 100 },
});
const endTime = Date.now();
expect(result.content).toBeDefined();
const data = JSON.parse((result.content[0] as { text: string }).text);
expect(data.waited).toBe(100);
expect(endTime - startTime).toBeGreaterThanOrEqual(100);
});
});
describe('Error Handling', () => {
it('should return error for non-existent selector', async () => {
await client.callTool({
name: 'navigate',
arguments: { url: 'https://example.com' },
});
const result = await client.callTool({
name: 'click',
arguments: { selector: '#non-existent-element-12345', timeout: 1000 },
});
expect(result.content).toBeDefined();
expect(result.isError).toBe(true);
const data = JSON.parse((result.content[0] as { text: string }).text);
expect(data.error).toBe('E101'); // SELECTOR_NOT_FOUND
}, 30000);
it('should return error for invalid tab ID', async () => {
const result = await client.callTool({
name: 'switch_tab',
arguments: { tabId: 'non-existent-tab' },
});
expect(result.content).toBeDefined();
expect(result.isError).toBe(true);
const data = JSON.parse((result.content[0] as { text: string }).text);
expect(data.error).toBe('E003'); // TAB_NOT_FOUND
});
});
});