Skip to main content
Glama
xml-parser.test.ts6.95 kB
import { describe, it, expect } from 'vitest'; import * as fs from 'fs/promises'; import * as path from 'path'; // Tests will import actual parser once implemented // import { parseAndroidHierarchy, parseIOSHierarchy } from '../../../src/utils/xml-parser.js'; describe('XML Hierarchy Parser', () => { describe('parseAndroidHierarchy', () => { it('should parse Android UI hierarchy XML', async () => { const xml = `<?xml version="1.0" encoding="UTF-8"?> <hierarchy rotation="0"> <node index="0" text="Welcome" resource-id="com.example.app:id/title" class="android.widget.TextView" package="com.example.app" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[100,200][980,300]" /> <node index="1" text="Login" resource-id="com.example.app:id/btn_login" class="android.widget.Button" package="com.example.app" content-desc="Login button" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true" bounds="[200,400][880,500]" /> </hierarchy>`; // When implemented: // const elements = await parseAndroidHierarchy(xml); // expect(elements).toHaveLength(2); // expect(elements[0].text).toBe('Welcome'); // expect(elements[0].type).toBe('text'); // expect(elements[1].text).toBe('Login'); // expect(elements[1].type).toBe('button'); // expect(elements[1].clickable).toBe(true); // Placeholder test structure const expectedOutput = { elements: [ { text: 'Welcome', type: 'text' }, { text: 'Login', type: 'button' }, ], }; expect(expectedOutput.elements).toHaveLength(2); }); it('should handle nested elements', async () => { const xml = `<?xml version="1.0" encoding="UTF-8"?> <hierarchy rotation="0"> <node index="0" class="android.widget.FrameLayout" bounds="[0,0][1080,2340]"> <node index="0" class="android.widget.LinearLayout" bounds="[0,0][1080,2340]"> <node index="0" text="Nested" class="android.widget.TextView" bounds="[100,100][200,150]" visible-to-user="true" enabled="true" /> </node> </node> </hierarchy>`; // When implemented: // const elements = await parseAndroidHierarchy(xml, { flatten: true }); // expect(elements.some(e => e.text === 'Nested')).toBe(true); expect(true).toBe(true); }); it('should filter invisible elements', async () => { const xml = `<?xml version="1.0" encoding="UTF-8"?> <hierarchy rotation="0"> <node index="0" text="Visible" class="android.widget.Button" bounds="[100,100][200,150]" visible-to-user="true" enabled="true" clickable="true" /> <node index="1" text="Hidden" class="android.widget.Button" bounds="[0,0][0,0]" visible-to-user="false" enabled="true" clickable="true" /> </hierarchy>`; // When implemented: // const elements = await parseAndroidHierarchy(xml, { includeInvisible: false }); // expect(elements.filter(e => e.visible)).toHaveLength(1); expect(true).toBe(true); }); it('should map element types correctly', async () => { const xml = `<?xml version="1.0" encoding="UTF-8"?> <hierarchy rotation="0"> <node class="android.widget.Button" bounds="[0,0][100,50]" visible-to-user="true" enabled="true" /> <node class="android.widget.EditText" bounds="[0,50][100,100]" visible-to-user="true" enabled="true" /> <node class="android.widget.ImageView" bounds="[0,100][100,150]" visible-to-user="true" enabled="true" /> <node class="androidx.recyclerview.widget.RecyclerView" bounds="[0,150][100,500]" visible-to-user="true" enabled="true" /> </hierarchy>`; // When implemented: // const elements = await parseAndroidHierarchy(xml); // expect(elements[0].type).toBe('button'); // expect(elements[1].type).toBe('input'); // expect(elements[2].type).toBe('image'); // expect(elements[3].type).toBe('list'); const expectedTypes = ['button', 'input', 'image', 'list']; expect(expectedTypes).toHaveLength(4); }); it('should use mock response file', async () => { // Read actual mock file const mockPath = path.join( process.cwd(), 'tests/mocks/adb-responses/ui-hierarchy.xml' ); try { const mockXml = await fs.readFile(mockPath, 'utf-8'); // When implemented: // const elements = await parseAndroidHierarchy(mockXml); // expect(elements.length).toBeGreaterThan(0); expect(mockXml).toContain('<hierarchy'); } catch { // Mock file may not exist yet expect(true).toBe(true); } }); }); describe('parseIOSHierarchy', () => { it('should parse iOS accessibility hierarchy', async () => { // iOS hierarchy is typically in a different format // This tests the expected structure const iosHierarchy = { elements: [ { type: 'XCUIElementTypeButton', label: 'Login', identifier: 'loginButton', frame: { x: 100, y: 200, width: 200, height: 50 }, }, ], }; // When implemented: // const elements = parseIOSHierarchy(JSON.stringify(iosHierarchy)); // expect(elements[0].type).toBe('button'); expect(iosHierarchy.elements).toHaveLength(1); }); }); }); describe('Element Type Mapping', () => { const androidToUnifiedMap: Record<string, string> = { 'android.widget.Button': 'button', 'android.widget.TextView': 'text', 'android.widget.EditText': 'input', 'android.widget.ImageView': 'image', 'androidx.recyclerview.widget.RecyclerView': 'list', 'android.widget.ListView': 'list', 'android.widget.ScrollView': 'scroll', 'android.widget.Switch': 'switch', 'android.widget.CheckBox': 'checkbox', 'android.widget.LinearLayout': 'container', }; it('should have mappings for common Android views', () => { expect(Object.keys(androidToUnifiedMap).length).toBeGreaterThan(5); expect(androidToUnifiedMap['android.widget.Button']).toBe('button'); }); const iosToUnifiedMap: Record<string, string> = { XCUIElementTypeButton: 'button', XCUIElementTypeStaticText: 'text', XCUIElementTypeTextField: 'input', XCUIElementTypeImage: 'image', XCUIElementTypeTable: 'list', XCUIElementTypeScrollView: 'scroll', XCUIElementTypeSwitch: 'switch', }; it('should have mappings for common iOS elements', () => { expect(Object.keys(iosToUnifiedMap).length).toBeGreaterThan(5); expect(iosToUnifiedMap['XCUIElementTypeButton']).toBe('button'); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/abd3lraouf/specter-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server