import { z } from "zod";
import {
executeDetoxCommand,
listIOSSimulators,
listAndroidEmulators,
} from "../utils/cli-executor.js";
import {
readDetoxConfig,
listConfigurations,
validateConfig,
generateDefaultConfig,
} from "../utils/config-parser.js";
import {
BuildArgsSchema,
TestArgsSchema,
InitArgsSchema,
ReadConfigArgsSchema,
ListConfigurationsArgsSchema,
ValidateConfigArgsSchema,
CreateConfigArgsSchema,
ListDevicesArgsSchema,
GenerateTestArgsSchema,
GenerateMatcherArgsSchema,
GenerateActionArgsSchema,
GenerateExpectationArgsSchema,
zodToJsonSchema,
} from "../utils/validators.js";
import {
generateTestFileTemplate,
generateMatcherCode,
generateActionCode,
generateExpectationCode,
} from "../templates/test-templates.js";
import { writeFile } from "fs/promises";
export interface Tool {
name: string;
description: string;
inputSchema: object;
handler: (args: any) => Promise<any>;
}
// ============ Build Tool ============
export const buildTool: Tool = {
name: "detox_build",
description: "Build the app for Detox testing. Runs the build command from your Detox configuration.",
inputSchema: zodToJsonSchema(BuildArgsSchema),
handler: async (args: z.infer<typeof BuildArgsSchema>) => {
const parsed = BuildArgsSchema.parse(args);
const cliArgs: string[] = ["build"];
if (parsed.configuration) {
cliArgs.push("-c", parsed.configuration);
}
if (parsed.configPath) {
cliArgs.push("-C", parsed.configPath);
}
if (parsed.ifMissing) {
cliArgs.push("--if-missing");
}
if (parsed.silent) {
cliArgs.push("--silent");
}
const result = await executeDetoxCommand(cliArgs, { cwd: parsed.cwd });
return {
success: result.success,
output: result.stdout,
errors: result.stderr,
duration: `${(result.duration / 1000).toFixed(1)}s`,
};
},
};
// ============ Test Tool ============
export const testTool: Tool = {
name: "detox_test",
description: "Run Detox E2E tests with full options for configuration, retries, artifacts, and more.",
inputSchema: zodToJsonSchema(TestArgsSchema),
handler: async (args: z.infer<typeof TestArgsSchema>) => {
const parsed = TestArgsSchema.parse(args);
const cliArgs: string[] = ["test"];
if (parsed.configuration) {
cliArgs.push("-c", parsed.configuration);
}
if (parsed.loglevel) {
cliArgs.push("-l", parsed.loglevel);
}
if (parsed.retries !== undefined) {
cliArgs.push("--retries", String(parsed.retries));
}
if (parsed.reuse) {
cliArgs.push("--reuse");
}
if (parsed.headless) {
cliArgs.push("--headless");
}
if (parsed.recordLogs) {
cliArgs.push("--record-logs", parsed.recordLogs);
}
if (parsed.takeScreenshots) {
cliArgs.push("--take-screenshots", parsed.takeScreenshots);
}
if (parsed.recordVideos) {
cliArgs.push("--record-videos", parsed.recordVideos);
}
if (parsed.artifactsLocation) {
cliArgs.push("--artifacts-location", parsed.artifactsLocation);
}
if (parsed.cleanup) {
cliArgs.push("--cleanup");
}
if (parsed.deviceName) {
cliArgs.push("--device-name", parsed.deviceName);
}
if (parsed.testNamePattern) {
cliArgs.push("--testNamePattern", parsed.testNamePattern);
}
// Add test file paths at the end
if (parsed.testFilePaths && parsed.testFilePaths.length > 0) {
cliArgs.push("--", ...parsed.testFilePaths);
}
const result = await executeDetoxCommand(cliArgs, {
cwd: parsed.cwd,
timeout: 600000, // 10 minutes for tests
});
// Parse test results from output
const passMatch = result.stdout.match(/(\d+) passed/);
const failMatch = result.stdout.match(/(\d+) failed/);
const skipMatch = result.stdout.match(/(\d+) skipped/);
return {
success: result.success,
summary: {
passed: passMatch ? parseInt(passMatch[1]) : 0,
failed: failMatch ? parseInt(failMatch[1]) : 0,
skipped: skipMatch ? parseInt(skipMatch[1]) : 0,
},
output: result.stdout,
errors: result.stderr,
duration: `${(result.duration / 1000).toFixed(1)}s`,
artifactsLocation: parsed.artifactsLocation || "./artifacts",
};
},
};
// ============ Init Tool ============
export const initTool: Tool = {
name: "detox_init",
description: "Initialize Detox in a React Native project. Creates e2e folder structure and configuration.",
inputSchema: zodToJsonSchema(InitArgsSchema),
handler: async (args: z.infer<typeof InitArgsSchema>) => {
const parsed = InitArgsSchema.parse(args);
const cliArgs: string[] = ["init"];
if (parsed.testRunner) {
cliArgs.push("-r", parsed.testRunner);
}
const result = await executeDetoxCommand(cliArgs, { cwd: parsed.projectPath });
return {
success: result.success,
output: result.stdout,
errors: result.stderr,
message: result.success
? "Detox initialized successfully. Check the e2e folder for test files."
: "Failed to initialize Detox.",
};
},
};
// ============ Read Config Tool ============
export const readConfigTool: Tool = {
name: "detox_read_config",
description: "Read and parse the current Detox configuration file (.detoxrc.js or similar).",
inputSchema: zodToJsonSchema(ReadConfigArgsSchema),
handler: async (args: z.infer<typeof ReadConfigArgsSchema>) => {
const parsed = ReadConfigArgsSchema.parse(args);
const projectPath = parsed.projectPath || process.cwd();
const result = await readDetoxConfig(projectPath);
if (!result) {
return {
success: false,
error: "No Detox configuration found. Run 'detox init' to create one.",
};
}
return {
success: true,
configPath: result.configPath,
config: result.config,
};
},
};
// ============ List Configurations Tool ============
export const listConfigurationsTool: Tool = {
name: "detox_list_configurations",
description: "List all available Detox configurations (e.g., ios.sim.debug, android.emu.debug).",
inputSchema: zodToJsonSchema(ListConfigurationsArgsSchema),
handler: async (args: z.infer<typeof ListConfigurationsArgsSchema>) => {
const parsed = ListConfigurationsArgsSchema.parse(args);
const projectPath = parsed.projectPath || process.cwd();
const result = await readDetoxConfig(projectPath);
if (!result) {
return {
success: false,
error: "No Detox configuration found.",
configurations: [],
};
}
const configurations = listConfigurations(result.config);
return {
success: true,
configurations,
};
},
};
// ============ Validate Config Tool ============
export const validateConfigTool: Tool = {
name: "detox_validate_config",
description: "Validate Detox configuration for errors and warnings.",
inputSchema: zodToJsonSchema(ValidateConfigArgsSchema),
handler: async (args: z.infer<typeof ValidateConfigArgsSchema>) => {
const parsed = ValidateConfigArgsSchema.parse(args);
const projectPath = parsed.projectPath || process.cwd();
const result = await readDetoxConfig(projectPath);
if (!result) {
return {
success: false,
valid: false,
errors: ["No Detox configuration found."],
warnings: [],
};
}
const validation = validateConfig(result.config);
return {
success: true,
...validation,
};
},
};
// ============ Create Config Tool ============
export const createConfigTool: Tool = {
name: "detox_create_config",
description: "Generate a new Detox configuration file for your React Native project.",
inputSchema: zodToJsonSchema(CreateConfigArgsSchema),
handler: async (args: z.infer<typeof CreateConfigArgsSchema>) => {
const parsed = CreateConfigArgsSchema.parse(args);
const config = generateDefaultConfig({
platforms: parsed.platforms,
appName: parsed.appName,
bundleId: parsed.bundleId,
packageName: parsed.packageName,
});
const configContent = `/** @type {import('detox').DetoxConfig} */
module.exports = ${JSON.stringify(config, null, 2)};
`;
const configPath = `${parsed.projectPath}/.detoxrc.js`;
await writeFile(configPath, configContent, "utf-8");
return {
success: true,
configPath,
config,
message: `Configuration created at ${configPath}`,
};
},
};
// ============ List Devices Tool ============
export const listDevicesTool: Tool = {
name: "detox_list_devices",
description: "List available iOS simulators and Android emulators.",
inputSchema: zodToJsonSchema(ListDevicesArgsSchema),
handler: async (args: z.infer<typeof ListDevicesArgsSchema>) => {
const parsed = ListDevicesArgsSchema.parse(args);
const devices: any[] = [];
if (parsed.platform === "all" || parsed.platform === "ios") {
const iosDevices = await listIOSSimulators();
devices.push(...iosDevices);
}
if (parsed.platform === "all" || parsed.platform === "android") {
const androidDevices = await listAndroidEmulators();
devices.push(...androidDevices);
}
let filteredDevices = devices;
if (parsed.availableOnly) {
filteredDevices = devices.filter(
(d) => d.state === "Booted" || d.state === "available"
);
}
return {
success: true,
devices: filteredDevices,
total: filteredDevices.length,
};
},
};
// ============ Generate Test Tool ============
export const generateTestTool: Tool = {
name: "detox_generate_test",
description: "Generate a complete Detox test file from a description.",
inputSchema: zodToJsonSchema(GenerateTestArgsSchema),
handler: async (args: z.infer<typeof GenerateTestArgsSchema>) => {
const parsed = GenerateTestArgsSchema.parse(args);
const testCode = generateTestFileTemplate({
testName: parsed.testName,
describeName: parsed.testName,
includeSetup: parsed.includeSetup,
tests: [
{
name: parsed.description,
code: ` // Test: ${parsed.description}\n // TODO: Implement test based on the description above`,
},
],
});
if (parsed.outputPath) {
await writeFile(parsed.outputPath, testCode, "utf-8");
}
return {
success: true,
code: testCode,
outputPath: parsed.outputPath,
};
},
};
// ============ Generate Matcher Tool ============
export const generateMatcherTool: Tool = {
name: "detox_generate_matcher",
description: "Generate Detox matcher code for element selection (by.id, by.text, etc.).",
inputSchema: zodToJsonSchema(GenerateMatcherArgsSchema),
handler: async (args: z.infer<typeof GenerateMatcherArgsSchema>) => {
const parsed = GenerateMatcherArgsSchema.parse(args);
const code = generateMatcherCode({
type: parsed.matcherType || "id",
value: parsed.elementDescription,
withAncestor: parsed.withAncestor,
withDescendant: parsed.withDescendant,
atIndex: parsed.atIndex,
});
return {
success: true,
code,
description: `Matcher for: ${parsed.elementDescription}`,
};
},
};
// ============ Generate Action Tool ============
export const generateActionTool: Tool = {
name: "detox_generate_action",
description: "Generate Detox action code (tap, typeText, scroll, etc.).",
inputSchema: zodToJsonSchema(GenerateActionArgsSchema),
handler: async (args: z.infer<typeof GenerateActionArgsSchema>) => {
const parsed = GenerateActionArgsSchema.parse(args);
const code = generateActionCode({
actionType: parsed.actionType,
elementMatcher: parsed.elementMatcher,
params: parsed.actionParams,
});
return {
success: true,
code,
};
},
};
// ============ Generate Expectation Tool ============
export const generateExpectationTool: Tool = {
name: "detox_generate_expectation",
description: "Generate Detox expectation/assertion code (toBeVisible, toHaveText, etc.).",
inputSchema: zodToJsonSchema(GenerateExpectationArgsSchema),
handler: async (args: z.infer<typeof GenerateExpectationArgsSchema>) => {
const parsed = GenerateExpectationArgsSchema.parse(args);
const code = generateExpectationCode({
expectationType: parsed.expectationType,
elementMatcher: parsed.elementMatcher,
expectedValue: parsed.expectedValue,
negated: parsed.negated,
timeout: parsed.timeout,
visibilityThreshold: parsed.visibilityThreshold,
});
return {
success: true,
code,
};
},
};
// Export all tools
export const allTools: Tool[] = [
buildTool,
testTool,
initTool,
readConfigTool,
listConfigurationsTool,
validateConfigTool,
createConfigTool,
listDevicesTool,
generateTestTool,
generateMatcherTool,
generateActionTool,
generateExpectationTool,
];