tests
This commit is contained in:
+5
-2
@@ -13,7 +13,9 @@
|
|||||||
"dev": "tsx src/index.ts",
|
"dev": "tsx src/index.ts",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"format": "prettier --write 'src/**/*.ts'",
|
"format": "prettier --write 'src/**/*.ts'",
|
||||||
"format:check": "prettier --check 'src/**/*.ts'"
|
"format:check": "prettier --check 'src/**/*.ts'",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"browser",
|
"browser",
|
||||||
@@ -33,6 +35,7 @@
|
|||||||
"@types/node": "^20.10.0",
|
"@types/node": "^20.10.0",
|
||||||
"prettier": "^3.7.4",
|
"prettier": "^3.7.4",
|
||||||
"tsx": "^4.6.0",
|
"tsx": "^4.6.0",
|
||||||
"typescript": "^5.3.0"
|
"typescript": "^5.3.0",
|
||||||
|
"vitest": "^4.0.16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||||
|
import { BrowserManager } from './browser.js';
|
||||||
|
|
||||||
|
describe('BrowserManager', () => {
|
||||||
|
let browser: BrowserManager;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
browser = new BrowserManager();
|
||||||
|
await browser.launch({ headless: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('launch and close', () => {
|
||||||
|
it('should report as launched', () => {
|
||||||
|
expect(browser.isLaunched()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a page', () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
expect(page).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('navigation', () => {
|
||||||
|
it('should navigate to URL', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.goto('https://example.com');
|
||||||
|
expect(page.url()).toBe('https://example.com/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get page title', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const title = await page.title();
|
||||||
|
expect(title).toBe('Example Domain');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('element interaction', () => {
|
||||||
|
it('should find element by selector', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const heading = await page.locator('h1').textContent();
|
||||||
|
expect(heading).toBe('Example Domain');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check element visibility', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const isVisible = await page.locator('h1').isVisible();
|
||||||
|
expect(isVisible).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should count elements', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const count = await page.locator('p').count();
|
||||||
|
expect(count).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('screenshots', () => {
|
||||||
|
it('should take screenshot as buffer', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const buffer = await page.screenshot();
|
||||||
|
expect(buffer).toBeInstanceOf(Buffer);
|
||||||
|
expect(buffer.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('evaluate', () => {
|
||||||
|
it('should evaluate JavaScript', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const result = await page.evaluate(() => document.title);
|
||||||
|
expect(result).toBe('Example Domain');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should evaluate with arguments', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const result = await page.evaluate((x: number) => x * 2, 5);
|
||||||
|
expect(result).toBe(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('tabs', () => {
|
||||||
|
it('should create new tab', async () => {
|
||||||
|
const result = await browser.newTab();
|
||||||
|
expect(result.index).toBe(1);
|
||||||
|
expect(result.total).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list tabs', async () => {
|
||||||
|
const tabs = await browser.listTabs();
|
||||||
|
expect(tabs.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should close tab', async () => {
|
||||||
|
// Switch to second tab and close it
|
||||||
|
const page = browser.getPage();
|
||||||
|
const tabs = await browser.listTabs();
|
||||||
|
if (tabs.length > 1) {
|
||||||
|
const result = await browser.closeTab(1);
|
||||||
|
expect(result.remaining).toBe(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('context operations', () => {
|
||||||
|
it('should get cookies from context', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const cookies = await page.context().cookies();
|
||||||
|
expect(Array.isArray(cookies)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set and get cookies', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const context = page.context();
|
||||||
|
await context.addCookies([{ name: 'test', value: 'value', url: 'https://example.com' }]);
|
||||||
|
const cookies = await context.cookies();
|
||||||
|
const testCookie = cookies.find((c) => c.name === 'test');
|
||||||
|
expect(testCookie?.value).toBe('value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear cookies', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const context = page.context();
|
||||||
|
await context.clearCookies();
|
||||||
|
const cookies = await context.cookies();
|
||||||
|
expect(cookies.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('storage via evaluate', () => {
|
||||||
|
it('should set and get localStorage', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.evaluate(() => localStorage.setItem('testKey', 'testValue'));
|
||||||
|
const value = await page.evaluate(() => localStorage.getItem('testKey'));
|
||||||
|
expect(value).toBe('testValue');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear localStorage', async () => {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.evaluate(() => localStorage.clear());
|
||||||
|
const value = await page.evaluate(() => localStorage.getItem('testKey'));
|
||||||
|
expect(value).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('viewport', () => {
|
||||||
|
it('should set viewport', async () => {
|
||||||
|
await browser.setViewport(1920, 1080);
|
||||||
|
const page = browser.getPage();
|
||||||
|
const size = page.viewportSize();
|
||||||
|
expect(size?.width).toBe(1920);
|
||||||
|
expect(size?.height).toBe(1080);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { parseCommand } from './protocol.js';
|
||||||
|
|
||||||
|
// Helper to create command JSON string
|
||||||
|
const cmd = (obj: object) => JSON.stringify(obj);
|
||||||
|
|
||||||
|
describe('parseCommand', () => {
|
||||||
|
describe('navigation', () => {
|
||||||
|
it('should parse navigate command', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'navigate', url: 'https://example.com' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.action).toBe('navigate');
|
||||||
|
expect(result.command.url).toBe('https://example.com');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject navigate without url', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'navigate' }));
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('click', () => {
|
||||||
|
it('should parse click command', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'click', selector: '#btn' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.action).toBe('click');
|
||||||
|
expect(result.command.selector).toBe('#btn');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject click without selector', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'click' }));
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('type', () => {
|
||||||
|
it('should parse type command', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'type', selector: '#input', text: 'hello' })
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.action).toBe('type');
|
||||||
|
expect(result.command.selector).toBe('#input');
|
||||||
|
expect(result.command.text).toBe('hello');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fill', () => {
|
||||||
|
it('should parse fill command', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'fill', selector: '#input', value: 'hello' })
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.action).toBe('fill');
|
||||||
|
expect(result.command.value).toBe('hello');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('wait', () => {
|
||||||
|
it('should parse wait with selector', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'wait', selector: '#loading' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse wait with timeout', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'wait', timeout: 5000 }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse wait with text', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'wait', text: 'Welcome' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('screenshot', () => {
|
||||||
|
it('should parse screenshot command', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'screenshot', path: 'test.png' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse screenshot with fullPage', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'screenshot', fullPage: true }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cookies', () => {
|
||||||
|
it('should parse cookies_get', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'cookies_get' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse cookies_set', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({
|
||||||
|
id: '1',
|
||||||
|
action: 'cookies_set',
|
||||||
|
cookies: [{ name: 'session', value: 'abc123' }],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse cookies_clear', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'cookies_clear' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('storage', () => {
|
||||||
|
it('should parse storage_get', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'storage_get', type: 'local' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse storage_set', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({
|
||||||
|
id: '1',
|
||||||
|
action: 'storage_set',
|
||||||
|
type: 'local',
|
||||||
|
key: 'test',
|
||||||
|
value: 'value',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('semantic locators', () => {
|
||||||
|
it('should parse getbyrole', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({
|
||||||
|
id: '1',
|
||||||
|
action: 'getbyrole',
|
||||||
|
role: 'button',
|
||||||
|
subaction: 'click',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse getbytext', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({
|
||||||
|
id: '1',
|
||||||
|
action: 'getbytext',
|
||||||
|
text: 'Submit',
|
||||||
|
subaction: 'click',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse getbylabel', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({
|
||||||
|
id: '1',
|
||||||
|
action: 'getbylabel',
|
||||||
|
label: 'Email',
|
||||||
|
subaction: 'fill',
|
||||||
|
value: 'test@test.com',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('tabs', () => {
|
||||||
|
it('should parse tab_new', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'tab_new' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse tab_list', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'tab_list' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse tab_switch', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'tab_switch', index: 0 }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse tab_close', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'tab_close' }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('invalid commands', () => {
|
||||||
|
it('should reject unknown action', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'unknown' }));
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject missing id', () => {
|
||||||
|
const result = parseCommand(cmd({ action: 'click', selector: '#btn' }));
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid JSON', () => {
|
||||||
|
const result = parseCommand('not json');
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
include: ['src/**/*.test.ts'],
|
||||||
|
testTimeout: 30000,
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user