import { describe, it, vi, expect, beforeEach } from 'vitest' import { getShadowRootManager, ShadowRootTree } from '../src/session/shadowRoot.js' const defaultBrowser = { sessionId: '123', sessionSubscribe: vi.fn().mockResolvedValue({}), on: vi.fn(), scriptAddPreloadScript: vi.fn(), capabilities: {} } describe('ShadowRootManager', () => { beforeEach(() => { vi.clearAllMocks() }) it('should get shadow root manager per browser session', () => { const browserA = { ...defaultBrowser } as any const browserB = { ...defaultBrowser } as any const managerA = getShadowRootManager(browserA) const managerB = getShadowRootManager(browserB) const managerC = getShadowRootManager(browserA) expect(managerA).not.toBe(managerB) expect(managerA).toBe(managerC) }) it('registers correct event listeners', async () => { const wid = process.env.WDIO_UNIT_TESTS delete process.env.WDIO_UNIT_TESTS const browser = { ...defaultBrowser, isBidi: true, options: { capabilities: { webSocketUrl: './' } } } as any browser.sessionId = '234' const manager = getShadowRootManager(browser) process.env.WDIO_UNIT_TESTS = wid expect(await manager.initialize()).toBe(true) expect(browser.sessionSubscribe).toBeCalledTimes(1) expect(browser.on).toBeCalledTimes(4) expect(browser.scriptAddPreloadScript).toBeCalledTimes(1) }) it('should not register event listeners if not in bidi mode', async () => { const browser = { ...defaultBrowser } as any browser.sessionId = '345' const manager = getShadowRootManager(browser) expect(await manager.initialize()).toBe(true) expect(browser.sessionSubscribe).toBeCalledTimes(0) expect(browser.on).toBeCalledTimes(1) expect(browser.scriptAddPreloadScript).toBeCalledTimes(0) }) it('should not register event listeners if not using webdriver as automation protocol', async () => { const browser = { ...defaultBrowser, isBidi: true, automationProtocol: './protocol-stub.js' } as any browser.sessionId = '456' const manager = getShadowRootManager(browser) expect(await manager.initialize()).toBe(true) expect(browser.sessionSubscribe).toBeCalledTimes(0) expect(browser.on).toBeCalledTimes(1) expect(browser.scriptAddPreloadScript).toBeCalledTimes(0) }) it('should capture shadow root elements', () => { const browser = { ...defaultBrowser } as any const manager = getShadowRootManager(browser) manager.handleLogEntry({ level: 'debug', args: [ { type: 'string', value: '[WDIO]' }, { type: 'string', value: 'newShadowRoot' }, { type: 'node', sharedId: 'foobar', value: { shadowRoot: { sharedId: 'shadowFoobar', value: { nodeType: 11, mode: 'closed' } } } }, { type: 'node', sharedId: 'barfoo' } ], source: { context: 'foobarContext' } } as any) expect(manager.getShadowElementsByContextId('foobarContext')).toEqual(['barfoo', 'shadowFoobar']) expect(manager.getShadowRootModeById('foobarContext', 'foobar')).toBe('closed') expect(manager.getShadowElementPairsByContextId('foobarContext')).toEqual([ ['barfoo', undefined], ['foobar', 'shadowFoobar'] ]) }) it('should ignore log entries that are not of interest', () => { const browser = { ...defaultBrowser } as any const manager = getShadowRootManager(browser) manager.handleLogEntry({ level: 'debug', args: [ { type: 'string', value: 'foobar' }, { type: 'string', value: 'newShadowRoot' }, { type: 'node', sharedId: 'foobar' }, { type: 'node', sharedId: 'barfoo' } ], source: { context: 'foobarContext' } } as any) expect(manager.getShadowElementsByContextId('foobarContext')).toEqual([]) }) it('should throw if log entry is invalid', () => { const browser = { ...defaultBrowser } as any const manager = getShadowRootManager(browser) expect(() => manager.handleLogEntry({ level: 'debug', args: [ { type: 'string', value: '[WDIO]' }, { type: 'string', value: 'foobar' }, { type: 'node', sharedId: 'foobar' }, { type: 'node', sharedId: 'barfoo' } ], source: { context: 'foobar' } } as any)).toThrow() }) }) describe('ShadowRootTree', () => { const root = new ShadowRootTree('1') root.addShadowElement(new ShadowRootTree('2', '3')) root.addShadowElement(new ShadowRootTree('4', '5')) root.addShadowElement(new ShadowRootTree('6', '7')) root.addShadowElement('4', new ShadowRootTree('8', '9')) root.addShadowElement('8', new ShadowRootTree('10', '11')) root.addShadowElement('8', new ShadowRootTree('12', '13')) root.addShadowElement('12', new ShadowRootTree('14', '15')) root.addShadowElement(new ShadowRootTree('16', '17')) root.addShadowElement('16', new ShadowRootTree('18', '19')) root.addShadowElement('16', new ShadowRootTree('18', '19')) root.addShadowElement('18', new ShadowRootTree('20', '21')) it('can find the root of a tree', () => { const tree = root.find('8') expect(tree?.shadowRoot).toBe('9') expect(tree?.children.size).toBe(2) expect(root.find('1234')).toBe(undefined) }) it('can findByShadowId', () => { expect(root.findByShadowId('9')?.element).toBe('8') expect(root.findByShadowId('15')?.element).toBe('14') expect(root.findByShadowId('1234')).toBe(undefined) }) it('can get all trees', () => { expect(root.getAllLookupScopes()).toMatchInlineSnapshot(` [ "1", "3", "5", "9", "11", "13", "15", "7", "17", "19", "21", "19", ] `) expect(root.find('8')?.getAllLookupScopes()).toMatchInlineSnapshot(` [ "9", "11", "13", "15", ] `) }) it('can delete children and sub trees', () => { const child = root.find('6') expect(child?.remove('8')).toBe(false) expect(root.remove('8')).toBe(true) expect(root.getAllLookupScopes()).toMatchInlineSnapshot(` [ "1", "3", "5", "7", "17", "19", "21", "19", ] `) }) it('can delete children in the right order', () => { expect(root.remove('18')).toBe(true) const child = root.find('16') expect(child?.getAllLookupScopes()).toMatchInlineSnapshot(` [ "17", "19", "21", ] `) }) })