import { z } from 'zod';
import { findElements, getElementAttribute, getElementId } from '../executor/wda.js';
import { getOrCreateSession } from '../utils/wda-session.js';
import type { WdaFindStrategy } from '../types/wda.js';
export const wdaFindSchema = z.object({
strategy: z
.enum([
'accessibility id',
'class name',
'name',
'predicate string',
'class chain',
])
.describe(
'Locator strategy: "accessibility id" (recommended), "class name" (e.g., XCUIElementTypeButton), "predicate string" (NSPredicate), "class chain" (XCUITest class chain)'
),
value: z
.string()
.describe(
'Value for the locator strategy (e.g., accessibility ID, class name, predicate)'
),
bundle_id: z
.string()
.optional()
.describe('Bundle ID of app to activate for this session'),
port: z.number().optional().describe('WDA server port (default: 8100)'),
});
export type WdaFindInput = z.infer<typeof wdaFindSchema>;
export const wdaFindTool = {
name: 'wda_find',
description:
'Find UI elements using various locator strategies. Returns element IDs that can be used with wda_tap. Common strategies: "accessibility id" for accessibility identifiers, "class name" for element types like XCUIElementTypeButton.',
inputSchema: wdaFindSchema,
handler: async (input: WdaFindInput) => {
const options = { port: input.port };
const sessionId = await getOrCreateSession(input.bundle_id, options);
const elements = await findElements(
sessionId,
input.strategy as WdaFindStrategy,
input.value,
options
);
if (elements.length === 0) {
return {
content: [
{
type: 'text' as const,
text: `No elements found with ${input.strategy}: "${input.value}"`,
},
],
};
}
// Get additional info about each element (limit to first 10)
const elementDetails = await Promise.all(
elements.slice(0, 10).map(async (element, index) => {
const elementId = getElementId(element);
if (!elementId) return `${index + 1}. (invalid element)`;
try {
const [label, value, type, enabled] = await Promise.all([
getElementAttribute(sessionId, elementId, 'label', options),
getElementAttribute(sessionId, elementId, 'value', options),
getElementAttribute(sessionId, elementId, 'type', options),
getElementAttribute(sessionId, elementId, 'enabled', options),
]);
const parts = [`${index + 1}. ${type ?? 'Element'}`];
if (label) parts.push(`label="${label}"`);
if (value) parts.push(`value="${value}"`);
parts.push(`enabled=${enabled}`);
parts.push(`element_id="${elementId}"`);
return parts.join(' | ');
} catch {
return `${index + 1}. Element (element_id="${elementId}")`;
}
})
);
const lines = [
`Found ${elements.length} element(s) with ${input.strategy}: "${input.value}"`,
'',
...elementDetails,
];
if (elements.length > 10) {
lines.push('', `... and ${elements.length - 10} more elements`);
}
lines.push('', 'Use element_id with wda_tap to interact with an element.');
return {
content: [
{
type: 'text' as const,
text: lines.join('\n'),
},
],
};
},
};