💬 Commit message: Update 2026-02-06 21:50:58, 15 files, 983 lines

📁 Files changed: 15
📝 Lines changed: 983

  • .gitignore
  • browser.ts.html
  • clover.xml
  • coverage-final.json
  • index.html
  • logger.ts.html
  • server.ts.html
  • types.ts.html
  • package.json
  • browser.integration.test.ts
  • logger.test.ts
  • server.test.ts
  • vitest.all.config.ts
  • vitest.config.ts
  • vitest.integration.config.ts
This commit is contained in:
Adam Ladachowski
2026-02-06 21:50:58 +01:00
parent 880e24877d
commit 4a85e0087c
15 changed files with 635 additions and 462 deletions
+203
View File
@@ -0,0 +1,203 @@
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { ClaudeBrowser } from './browser.js';
describe('ClaudeBrowser Integration', () => {
let browser: ClaudeBrowser;
beforeAll(async () => {
browser = new ClaudeBrowser({ headless: true });
await browser.launch();
}, 30000);
afterAll(async () => {
await browser.close();
});
describe('navigation', () => {
it('navigates to a URL and returns title', async () => {
const result = await browser.goto('https://example.com');
expect(result.url).toContain('example.com');
expect(result.title).toBe('Example Domain');
});
it('gets current URL and title', async () => {
const result = await browser.getUrl();
expect(result.url).toContain('example.com');
expect(result.title).toBe('Example Domain');
});
it('gets page HTML', async () => {
const html = await browser.getHtml();
expect(html.toLowerCase()).toContain('<!doctype html>');
expect(html).toContain('Example Domain');
});
it('gets full page HTML', async () => {
const html = await browser.getHtml(true);
expect(html.toLowerCase()).toContain('<!doctype html>');
expect(html.length).toBeGreaterThan(100);
});
it('reloads the page', async () => {
const result = await browser.reload();
expect(result.url).toContain('example.com');
});
});
describe('DOM interaction', () => {
it('queries elements', async () => {
const elements = await browser.query('h1');
expect(elements.length).toBe(1);
expect(elements[0].tag).toBe('h1');
expect(elements[0].text).toContain('Example Domain');
});
it('queries multiple elements', async () => {
const elements = await browser.query('p');
expect(elements.length).toBeGreaterThan(0);
expect(elements[0].tag).toBe('p');
});
it('clicks an element', async () => {
await browser.goto('https://example.com');
const result = await browser.click('a');
expect(result.url).toBeDefined();
});
});
describe('screenshots', () => {
it('takes a screenshot', async () => {
await browser.goto('https://example.com');
const result = await browser.screenshot('screenshots/test-integration.png');
expect(result.path).toContain('test-integration.png');
expect(result.buffer).toBeDefined();
});
it('takes a full page screenshot', async () => {
const result = await browser.screenshot('screenshots/test-full.png', true);
expect(result.path).toContain('test-full.png');
});
});
describe('wait', () => {
it('waits for specified time', async () => {
const start = Date.now();
await browser.wait(100);
const elapsed = Date.now() - start;
expect(elapsed).toBeGreaterThanOrEqual(90);
});
});
describe('eval', () => {
it('evaluates JavaScript', async () => {
await browser.goto('https://example.com');
const result = await browser.eval('document.title');
expect(result).toBe('Example Domain');
});
it('evaluates expressions', async () => {
const result = await browser.eval('1 + 1');
expect(result).toBe(2);
});
it('evaluates complex expressions', async () => {
const result = await browser.eval('document.querySelectorAll("p").length');
expect(result).toBeGreaterThan(0);
});
});
describe('pages', () => {
it('creates a new page', async () => {
await browser.newPage();
const result = await browser.getUrl();
expect(result.url).toBe('about:blank');
});
it('navigates in new page', async () => {
const result = await browser.goto('https://example.com');
expect(result.title).toBe('Example Domain');
});
});
describe('executeCommand', () => {
it('handles goto command', async () => {
const result = await browser.executeCommand({ cmd: 'goto', url: 'https://example.com' });
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.title).toBe('Example Domain');
}
});
it('handles query command', async () => {
const result = await browser.executeCommand({ cmd: 'query', selector: 'h1' });
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.count).toBe(1);
}
});
it('handles url command', async () => {
const result = await browser.executeCommand({ cmd: 'url' });
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.url).toContain('example.com');
}
});
it('handles html command', async () => {
const result = await browser.executeCommand({ cmd: 'html' });
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.html).toContain('Example');
}
});
it('handles wait command', async () => {
const result = await browser.executeCommand({ cmd: 'wait', ms: 50 });
expect(result.ok).toBe(true);
});
it('handles eval command', async () => {
const result = await browser.executeCommand({ cmd: 'eval', script: '2+2' });
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.result).toBe(4);
}
});
it('handles screenshot command', async () => {
const result = await browser.executeCommand({
cmd: 'screenshot',
path: 'screenshots/cmd-test.png',
});
expect(result.ok).toBe(true);
});
it('handles reload command', async () => {
const result = await browser.executeCommand({ cmd: 'reload' });
expect(result.ok).toBe(true);
});
it('handles click command', async () => {
await browser.executeCommand({ cmd: 'goto', url: 'https://example.com' });
const result = await browser.executeCommand({ cmd: 'click', selector: 'a' });
expect(result.ok).toBe(true);
});
it('handles newpage command', async () => {
const result = await browser.executeCommand({ cmd: 'newpage' });
expect(result.ok).toBe(true);
});
it('handles back command', async () => {
await browser.executeCommand({ cmd: 'goto', url: 'https://example.com' });
const result = await browser.executeCommand({ cmd: 'back' });
expect(result.ok).toBe(true);
});
it('handles forward command', async () => {
const result = await browser.executeCommand({ cmd: 'forward' });
expect(result.ok).toBe(true);
});
});
});
+61
View File
@@ -66,6 +66,36 @@ describe('getCommandDetail', () => {
const result = getCommandDetail({ cmd: 'wait', ms: 500 });
expect(result).toContain('500');
});
it('returns default ms for wait command without ms', () => {
const result = getCommandDetail({ cmd: 'wait' });
expect(result).toContain('1000');
});
it('returns script for eval command', () => {
const result = getCommandDetail({ cmd: 'eval', script: 'document.title' });
expect(result).toContain('document.title');
});
it('truncates long eval scripts', () => {
const longScript = 'a'.repeat(100);
const result = getCommandDetail({ cmd: 'eval', script: longScript });
expect(result).toContain('...');
});
it('returns (full) for html command with full=true', () => {
const result = getCommandDetail({ cmd: 'html', full: true });
expect(result).toContain('full');
});
it('returns undefined for html command with full=false', () => {
expect(getCommandDetail({ cmd: 'html', full: false })).toBeUndefined();
});
it('returns selector for query command', () => {
const result = getCommandDetail({ cmd: 'query', selector: '.items' });
expect(result).toContain('.items');
});
});
describe('formatCommand', () => {
@@ -97,6 +127,37 @@ describe('formatResult', () => {
const result = formatResult({ cmd: 'query' }, { ok: true, count: 5 });
expect(result).toContain('5');
});
it('formats click result with url', () => {
const result = formatResult({ cmd: 'click' }, { ok: true, url: '/page' });
expect(result).toContain('/page');
});
it('formats screenshot result with path', () => {
const result = formatResult({ cmd: 'screenshot' }, { ok: true, path: 'test.png' });
expect(result).toContain('Saved to test.png');
});
it('formats url result', () => {
const result = formatResult({ cmd: 'url' }, { ok: true, url: 'https://example.com' });
expect(result).toContain('https://example.com');
});
it('formats html result with length', () => {
const result = formatResult({ cmd: 'html' }, { ok: true, html: '<html></html>' });
expect(result).toContain('13 chars');
});
it('formats eval result', () => {
const result = formatResult({ cmd: 'eval' }, { ok: true, result: { foo: 'bar' } });
expect(result).toContain('foo');
expect(result).toContain('bar');
});
it('formats result without formatter', () => {
const result = formatResult({ cmd: 'wait' }, { ok: true });
expect(result).toBeDefined();
});
});
describe('createLogger', () => {
+69 -1
View File
@@ -7,7 +7,7 @@ vi.mock('./browser.js', () => ({
ClaudeBrowser: class MockClaudeBrowser {
async launch() {}
async close() {}
async executeCommand(cmd: { cmd: string; url?: string }) {
async executeCommand(cmd: { cmd: string; url?: string; path?: string }) {
switch (cmd.cmd) {
case 'goto':
return { ok: true, url: cmd.url, title: 'Test Page' };
@@ -21,6 +21,22 @@ vi.mock('./browser.js', () => ({
return { ok: true, url: 'https://example.com', title: 'Example' };
case 'html':
return { ok: true, html: '<html></html>' };
case 'back':
return { ok: true, url: '/previous' };
case 'forward':
return { ok: true, url: '/next' };
case 'reload':
return { ok: true, url: '/current' };
case 'wait':
return { ok: true };
case 'screenshot':
return { ok: true, path: cmd.path || 'screenshot.png' };
case 'eval':
return { ok: true, result: 2 };
case 'newpage':
return { ok: true };
case 'error':
throw new Error('Test error');
default:
return { ok: true };
}
@@ -119,5 +135,57 @@ describe('BrowserServer', () => {
expect(res.body.ok).toBe(true);
});
it('handles back command', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'back' }).expect(200);
expect(res.body.ok).toBe(true);
});
it('handles forward command', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'forward' }).expect(200);
expect(res.body.ok).toBe(true);
});
it('handles reload command', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'reload' }).expect(200);
expect(res.body.ok).toBe(true);
});
it('handles wait command', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'wait', ms: 100 }).expect(200);
expect(res.body.ok).toBe(true);
});
it('handles screenshot command', async () => {
const app = server.getApp();
const res = await request(app)
.post('/')
.send({ cmd: 'screenshot', path: 'test.png' })
.expect(200);
expect(res.body.ok).toBe(true);
});
it('handles eval command', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'eval', script: '1+1' }).expect(200);
expect(res.body.ok).toBe(true);
});
it('handles newpage command', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'newpage' }).expect(200);
expect(res.body.ok).toBe(true);
});
it('handles errors and returns 500', async () => {
const app = server.getApp();
const res = await request(app).post('/').send({ cmd: 'error' }).expect(500);
expect(res.body.ok).toBe(false);
expect(res.body.error).toBe('Test error');
});
});
});