diff --git a/examples/android-gmail.md b/examples/android-gmail.md new file mode 100644 index 0000000..a2082fd --- /dev/null +++ b/examples/android-gmail.md @@ -0,0 +1,7 @@ +Using appium generate test tool create automation test for below step + +Steps: +1. Open Gmail app +2. Compose a email to sudharshrocks@gmail.com with subject "Test email from appium" +3. Add body as "Hello World!" +4. and send \ No newline at end of file diff --git a/examples/android-todo-app.md b/examples/android-todo-app.md new file mode 100644 index 0000000..f04af39 --- /dev/null +++ b/examples/android-todo-app.md @@ -0,0 +1,9 @@ +Using appium generate test tool create automation test for below step + +STEPS: +1. Open TODO app +2. Add a todo list with Title "Complete appium mcp server" +3. Use June 25 2025 as date +4. Add it to personal list + +Use JAVA + Testng for test generation \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6adcc29..1bd2e82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "jest": "^29.7.0", "prettier": "^3.5.3", "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", "typescript": "^5.8.3" } }, @@ -1066,6 +1067,30 @@ "node": ">=0.1.90" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@dabh/diagnostics": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", @@ -2564,6 +2589,43 @@ "lilconfig": ">=2" } }, + "node_modules/@sliphua/lilconfig-ts-loader/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/@sliphua/lilconfig-ts-loader/node_modules/ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "license": "MIT", + "peer": true, + "dependencies": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "typescript": ">=2.7" + } + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -2617,6 +2679,20 @@ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "license": "MIT" }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, "node_modules/@tsconfig/node14": { "version": "14.1.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-14.1.3.tgz", @@ -2624,6 +2700,13 @@ "license": "MIT", "peer": true }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3178,6 +3261,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -12900,8 +12996,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", @@ -13899,8 +13994,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -19902,38 +19996,62 @@ } }, "node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "source-map-support": "^0.5.17", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "bin": { "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js", "ts-script": "dist/bin-script-deprecated.js" }, - "engines": { - "node": ">=10.0.0" - }, "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } } }, + "node_modules/ts-node/node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, "node_modules/ts-node/node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.3.1" } @@ -20042,9 +20160,9 @@ } }, "node_modules/undici": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.9.0.tgz", - "integrity": "sha512-e696y354tf5cFZPXsF26Yg+5M63+5H3oE6Vtkh2oqbvsE2Oe7s2nIbcQh5lmG7Lp/eS29vJtTpw9+p6PX0qNSg==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", + "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==", "license": "MIT", "engines": { "node": ">=20.18.1" @@ -20173,6 +20291,13 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -20677,7 +20802,6 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } diff --git a/package.json b/package.json index 9a00c25..e5f4294 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "jest": "^29.7.0", "prettier": "^3.5.3", "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", "typescript": "^5.8.3" } -} +} \ No newline at end of file diff --git a/src/locators/locator-generation.ts b/src/locators/locator-generation.ts index 06810aa..ea27c15 100644 --- a/src/locators/locator-generation.ts +++ b/src/locators/locator-generation.ts @@ -206,7 +206,7 @@ export function getSuggestedLocators( } } - return sortedLocators; + return [sortedLocators[0]]; } /** diff --git a/src/schema.ts b/src/schema.ts new file mode 100644 index 0000000..38dd78f --- /dev/null +++ b/src/schema.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +export const elementUUIDScheme = z + .string() + .describe('The uuid of the element returned by appium_find_element to click'); diff --git a/src/tools/generate-tests.ts b/src/tools/generate-tests.ts new file mode 100644 index 0000000..7b9a166 --- /dev/null +++ b/src/tools/generate-tests.ts @@ -0,0 +1,50 @@ +import { FastMCP } from 'fastmcp/dist/FastMCP.js'; +import { z } from 'zod'; + +export default function generateTest(server: FastMCP): void { + const generateTestSchema = z.object({ + steps: z.array(z.string()).describe('The steps of the test'), + }); + + const instructions = (params: { steps: string[] }) => + [ + `## Instructions`, + `- You are an Appium test generator.`, + `- You are given a scenario and you need to generate a appium test for it.`, + `- Request user to select the platform first using select_platform tool and create a session`, + `- Use generate_locators tool to fetch all interactable elements from the current screen and use it to generate the tests`, + `- Element can only be clicked only if it is clickable.`, + `- Text can entered in the element only if it is focusable`, + `- If any interaction on element is failed, retry again with a differnt possible locator in the hierrarchy`, + `- Interact with the app using the tools provided and generate the test`, + '- DO NOT generate test code based on the scenario alone. DO run steps one by one using the tools provided instead.', + '- Only after all steps are completed, emit a Appium test based on message history', + '- Save generated test file in the tests directory', + `- Use generate://code-with-locators resource as reference for code generation`, + `- Always call find_element_tool to retrieve the element UUID before interacting with the element`, + `Steps:`, + ...params.steps.map((step, index) => `- ${index + 1}. ${step}`), + ].join('\n'); + + server.addTool({ + name: 'appium_generate_tests', + description: 'Generate tests for a given mobile app', + parameters: generateTestSchema, + annotations: { + readOnlyHint: false, + openWorldHint: false, + }, + execute: async (args: any, context: any): Promise => { + return { + content: [ + { + type: 'text', + text: instructions({ + steps: args.steps, + }), + }, + ], + }; + }, + }); +} diff --git a/src/tools/index.ts b/src/tools/index.ts index 6ddb5b4..f2907c2 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,10 +1,25 @@ +import { FastMCP } from 'fastmcp/dist/FastMCP.js'; import createSession from './create-session.js'; import generateLocators from './locators.js'; import selectPlatform from './select-platform.js'; +import generateTest from './generate-tests.js'; +import findElement from './interactions/find.js'; +import clickElement from './interactions/click.js'; +import setValue from './interactions/setValue.js'; +import getText from './interactions/getText.js'; +import screenshot from './interactions/screenshot.js'; -export default function registerTools(server: any): void { +export default function registerTools(server: FastMCP): void { selectPlatform(server); createSession(server); generateLocators(server); + + findElement(server); + clickElement(server); + setValue(server); + getText(server); + screenshot(server); + + generateTest(server); console.log('All tools registered'); } diff --git a/src/tools/interactions/click.ts b/src/tools/interactions/click.ts new file mode 100644 index 0000000..715680c --- /dev/null +++ b/src/tools/interactions/click.ts @@ -0,0 +1,49 @@ +import { FastMCP } from 'fastmcp/dist/FastMCP.js'; +import { z } from 'zod'; +import { getDriver } from '../sessionStore.js'; +import { checkIsValidElementId } from '../../utils.js'; +import { elementUUIDScheme } from '../../schema.js'; + +export default function generateTest(server: FastMCP): void { + const clickActionSchema = z.object({ + elementUUID: elementUUIDScheme, + }); + + server.addTool({ + name: 'appium_click', + description: 'Click on an element', + parameters: clickActionSchema, + annotations: { + readOnlyHint: false, + openWorldHint: false, + }, + execute: async (args: any, context: any): Promise => { + const driver = getDriver(); + if (!driver) { + throw new Error('No driver found'); + } + + try { + checkIsValidElementId(args.elementUUID); + await driver.click(args.elementUUID); + return { + content: [ + { + type: 'text', + text: `Successfully clicked on element ${args.elementUUID}`, + }, + ], + }; + } catch (err: any) { + return { + content: [ + { + type: 'text', + text: `Failed to click on element ${args.elementUUID}. err: ${err.toString()}`, + }, + ], + }; + } + }, + }); +} diff --git a/src/tools/interactions/find.ts b/src/tools/interactions/find.ts new file mode 100644 index 0000000..44e4bdc --- /dev/null +++ b/src/tools/interactions/find.ts @@ -0,0 +1,59 @@ +import { FastMCP } from 'fastmcp/dist/FastMCP.js'; +import { z } from 'zod'; +import { getDriver } from '../sessionStore.js'; +import { checkIsValidElementId } from '../../utils.js'; + +export default function findElement(server: FastMCP): void { + const findElementSchema = z.object({ + strategy: z.enum([ + 'xpath', + 'id', + 'name', + 'class name', + 'accessibility id', + 'css selector', + '-android uiautomator', + '-ios predicate string', + '-ios class chain', + ]), + selector: z.string().describe('The selector to find the element'), + }); + + server.addTool({ + name: 'appium_find_element', + description: + 'Find an element with the given strategy and selector which will return a uuid that can be used while interaction', + parameters: findElementSchema, + annotations: { + readOnlyHint: false, + openWorldHint: false, + }, + execute: async (args: any, context: any): Promise => { + const driver = getDriver(); + if (!driver) { + throw new Error('No driver found'); + } + + try { + const element = await driver.findElement(args.strategy, args.selector); + return { + content: [ + { + type: 'text', + text: `Successfully found element ${args.selector} with strategy ${args.strategy}. Element id ${element.ELEMENT}`, + }, + ], + }; + } catch (err: any) { + return { + content: [ + { + type: 'text', + text: `Failed to find element ${args.selector} with strategy ${args.strategy}. err: ${err.toString()}`, + }, + ], + }; + } + }, + }); +} diff --git a/src/tools/interactions/getText.ts b/src/tools/interactions/getText.ts new file mode 100644 index 0000000..0f20976 --- /dev/null +++ b/src/tools/interactions/getText.ts @@ -0,0 +1,49 @@ +import { FastMCP } from 'fastmcp/dist/FastMCP.js'; +import { z } from 'zod'; +import { getDriver } from '../sessionStore.js'; +import { elementUUIDScheme } from '../../schema.js'; +import { checkIsValidElementId } from '../../utils.js'; + +export default function getText(server: FastMCP): void { + const getTextSchema = z.object({ + elementUUID: elementUUIDScheme, + }); + + server.addTool({ + name: 'appium_get_text', + description: 'Get text from an element', + parameters: getTextSchema, + annotations: { + readOnlyHint: false, + openWorldHint: false, + }, + execute: async (args: any, context: any): Promise => { + const driver = getDriver(); + if (!driver) { + throw new Error('No driver found'); + } + + try { + checkIsValidElementId(args.elementUUID); + const text = await driver.getText(args.elementUUID); + return { + content: [ + { + type: 'text', + text: `Successfully got text ${text} from element ${args.elementUUID}`, + }, + ], + }; + } catch (err: any) { + return { + content: [ + { + type: 'text', + text: `Failed to get text from element ${args.elementUUID}. err: ${err.toString()}`, + }, + ], + }; + } + }, + }); +} diff --git a/src/tools/interactions/screenshot.ts b/src/tools/interactions/screenshot.ts new file mode 100644 index 0000000..0cadf33 --- /dev/null +++ b/src/tools/interactions/screenshot.ts @@ -0,0 +1,41 @@ +import { FastMCP } from 'fastmcp/dist/FastMCP.js'; +import { z } from 'zod'; +import { getDriver } from '../sessionStore.js'; + +export default function screenshot(server: FastMCP): void { + server.addTool({ + name: 'appium_screenshot', + description: 'Take a screenshot of the current screen in base64 format', + annotations: { + readOnlyHint: false, + openWorldHint: false, + }, + execute: async (args: any, context: any): Promise => { + const driver = getDriver(); + if (!driver) { + throw new Error('No driver found'); + } + + try { + const screenshot = await driver.getScreenshot(); + return { + content: [ + { + type: 'text', + text: screenshot, + }, + ], + }; + } catch (err: any) { + return { + content: [ + { + type: 'text', + text: `Failed to take screenshot. err: ${err.toString()}`, + }, + ], + }; + } + }, + }); +} diff --git a/src/tools/interactions/setValue.ts b/src/tools/interactions/setValue.ts new file mode 100644 index 0000000..ee590cc --- /dev/null +++ b/src/tools/interactions/setValue.ts @@ -0,0 +1,50 @@ +import { FastMCP } from 'fastmcp/dist/FastMCP.js'; +import { z } from 'zod'; +import { getDriver } from '../sessionStore.js'; +import { elementUUIDScheme } from '../../schema.js'; +import { checkIsValidElementId } from '../../utils.js'; + +export default function setValue(server: FastMCP): void { + const setValueSchema = z.object({ + elementUUID: elementUUIDScheme, + text: z.string().describe('The text to enter'), + }); + + server.addTool({ + name: 'appium_set_value', + description: 'Enter text into an element', + parameters: setValueSchema, + annotations: { + readOnlyHint: false, + openWorldHint: false, + }, + execute: async (args: any, context: any): Promise => { + const driver = getDriver(); + if (!driver) { + throw new Error('No driver found'); + } + + try { + checkIsValidElementId(args.elementUUID); + await driver.setValue(args.text, args.elementUUID); + return { + content: [ + { + type: 'text', + text: `Successfully set value ${args.text} into element ${args.elementUUID}`, + }, + ], + }; + } catch (err: any) { + return { + content: [ + { + type: 'text', + text: `Failed to set value ${args.text} into element ${args.elementUUID}. err: ${err.toString()}`, + }, + ], + }; + } + }, + }); +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..2ba5bad --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,12 @@ +export function checkIsValidElementId(str: string) { + // const isValidUUID = + // /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/.test( + // str + // ); + // if (!isValidUUID) { + // throw new Error( + // 'Given id is not a valid element id. Call find_element_tool to fetch the correct uuid of the element' + // ); + // } + return true; +}