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(); }); it('should reject invalid executablePath', async () => { const testBrowser = new BrowserManager(); await expect( testBrowser.launch({ headless: true, executablePath: '/nonexistent/path/to/chromium', }) ).rejects.toThrow(); }); }); 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 set cookie with domain', async () => { const page = browser.getPage(); const context = page.context(); await context.addCookies([ { name: 'domainCookie', value: 'domainValue', domain: 'example.com', path: '/' }, ]); const cookies = await context.cookies(); const testCookie = cookies.find((c) => c.name === 'domainCookie'); expect(testCookie?.value).toBe('domainValue'); }); it('should set multiple cookies at once', async () => { const page = browser.getPage(); const context = page.context(); await context.clearCookies(); await context.addCookies([ { name: 'cookie1', value: 'value1', url: 'https://example.com' }, { name: 'cookie2', value: 'value2', url: 'https://example.com' }, ]); const cookies = await context.cookies(); expect(cookies.find((c) => c.name === 'cookie1')?.value).toBe('value1'); expect(cookies.find((c) => c.name === 'cookie2')?.value).toBe('value2'); }); 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('localStorage operations', () => { it('should set and get localStorage item', async () => { const page = browser.getPage(); await page.goto('https://example.com'); await page.evaluate(() => localStorage.setItem('testKey', 'testValue')); const value = await page.evaluate(() => localStorage.getItem('testKey')); expect(value).toBe('testValue'); }); it('should get all localStorage items', async () => { const page = browser.getPage(); await page.evaluate(() => { localStorage.clear(); localStorage.setItem('key1', 'value1'); localStorage.setItem('key2', 'value2'); }); const storage = await page.evaluate(() => { const items: Record = {}; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key) items[key] = localStorage.getItem(key) || ''; } return items; }); expect(storage.key1).toBe('value1'); expect(storage.key2).toBe('value2'); }); 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(); }); it('should return null for non-existent key', async () => { const page = browser.getPage(); await page.evaluate(() => localStorage.clear()); const value = await page.evaluate(() => localStorage.getItem('nonexistent')); expect(value).toBeNull(); }); }); describe('sessionStorage operations', () => { it('should set and get sessionStorage item', async () => { const page = browser.getPage(); await page.goto('https://example.com'); await page.evaluate(() => sessionStorage.setItem('sessionKey', 'sessionValue')); const value = await page.evaluate(() => sessionStorage.getItem('sessionKey')); expect(value).toBe('sessionValue'); }); it('should get all sessionStorage items', async () => { const page = browser.getPage(); await page.evaluate(() => { sessionStorage.clear(); sessionStorage.setItem('skey1', 'svalue1'); sessionStorage.setItem('skey2', 'svalue2'); }); const storage = await page.evaluate(() => { const items: Record = {}; for (let i = 0; i < sessionStorage.length; i++) { const key = sessionStorage.key(i); if (key) items[key] = sessionStorage.getItem(key) || ''; } return items; }); expect(storage.skey1).toBe('svalue1'); expect(storage.skey2).toBe('svalue2'); }); it('should clear sessionStorage', async () => { const page = browser.getPage(); await page.evaluate(() => sessionStorage.clear()); const value = await page.evaluate(() => sessionStorage.getItem('sessionKey')); 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); }); }); describe('snapshot', () => { it('should get snapshot with refs', async () => { const page = browser.getPage(); await page.goto('https://example.com'); const { tree, refs } = await browser.getSnapshot(); expect(tree).toContain('heading'); expect(tree).toContain('Example Domain'); expect(typeof refs).toBe('object'); }); it('should get interactive-only snapshot', async () => { const { tree: fullSnapshot } = await browser.getSnapshot(); const { tree: interactiveSnapshot } = await browser.getSnapshot({ interactive: true }); // Interactive snapshot should be shorter (fewer elements) expect(interactiveSnapshot.length).toBeLessThanOrEqual(fullSnapshot.length); }); it('should get snapshot with depth limit', async () => { const { tree: fullSnapshot } = await browser.getSnapshot(); const { tree: limitedSnapshot } = await browser.getSnapshot({ maxDepth: 2 }); // Limited depth should have fewer nested elements const fullLines = fullSnapshot.split('\n').length; const limitedLines = limitedSnapshot.split('\n').length; expect(limitedLines).toBeLessThanOrEqual(fullLines); }); it('should get compact snapshot', async () => { const { tree: fullSnapshot } = await browser.getSnapshot(); const { tree: compactSnapshot } = await browser.getSnapshot({ compact: true }); // Compact should be equal or shorter expect(compactSnapshot.length).toBeLessThanOrEqual(fullSnapshot.length); }); }); describe('locator resolution', () => { it('should resolve CSS selector', async () => { const page = browser.getPage(); await page.goto('https://example.com'); const locator = browser.getLocator('h1'); const text = await locator.textContent(); expect(text).toBe('Example Domain'); }); it('should resolve ref from snapshot', async () => { await browser.getSnapshot(); // Populates refs // After snapshot, refs like @e1 should be available // This tests the ref resolution mechanism const page = browser.getPage(); const h1 = await page.locator('h1').textContent(); expect(h1).toBe('Example Domain'); }); }); describe('scoped headers', () => { it('should register route for scoped headers', async () => { // Test that setScopedHeaders doesn't throw and completes successfully await browser.clearScopedHeaders(); await expect( browser.setScopedHeaders('https://example.com', { 'X-Test': 'value' }) ).resolves.not.toThrow(); await browser.clearScopedHeaders(); }); it('should handle full URL origin', async () => { await browser.clearScopedHeaders(); await expect( browser.setScopedHeaders('https://api.example.com/path', { Authorization: 'Bearer token' }) ).resolves.not.toThrow(); await browser.clearScopedHeaders(); }); it('should handle hostname-only origin', async () => { await browser.clearScopedHeaders(); await expect( browser.setScopedHeaders('example.com', { 'X-Custom': 'value' }) ).resolves.not.toThrow(); await browser.clearScopedHeaders(); }); it('should clear scoped headers for specific origin', async () => { await browser.clearScopedHeaders(); await browser.setScopedHeaders('https://example.com', { 'X-Test': 'value' }); await expect(browser.clearScopedHeaders('https://example.com')).resolves.not.toThrow(); }); it('should clear all scoped headers', async () => { await browser.setScopedHeaders('https://example.com', { 'X-Test-1': 'value1' }); await browser.setScopedHeaders('https://example.org', { 'X-Test-2': 'value2' }); await expect(browser.clearScopedHeaders()).resolves.not.toThrow(); }); it('should replace headers when called twice for same origin', async () => { await browser.clearScopedHeaders(); await browser.setScopedHeaders('https://example.com', { 'X-First': 'first' }); // Second call should replace, not add await expect( browser.setScopedHeaders('https://example.com', { 'X-Second': 'second' }) ).resolves.not.toThrow(); await browser.clearScopedHeaders(); }); it('should handle clearing non-existent origin gracefully', async () => { await browser.clearScopedHeaders(); // Should not throw when clearing headers that were never set await expect(browser.clearScopedHeaders('https://never-set.com')).resolves.not.toThrow(); }); }); });