/**
* Templates for generating Detox test code
*/
export function generateTestFileTemplate(options: {
testName: string;
describeName?: string;
includeSetup?: boolean;
tests?: Array<{ name: string; code: string }>;
}): string {
const describeName = options.describeName || options.testName;
const tests = options.tests || [
{ name: "should work correctly", code: " // TODO: Add test implementation" },
];
const testCases = tests
.map(
(t) => `
it('${t.name}', async () => {
${t.code}
});`
)
.join("\n");
if (options.includeSetup !== false) {
return `describe('${describeName}', () => {
beforeAll(async () => {
await device.launchApp();
});
beforeEach(async () => {
await device.reloadReactNative();
});
${testCases}
});
`;
}
return `describe('${describeName}', () => {
${testCases}
});
`;
}
export function generateMatcherCode(options: {
type: "id" | "text" | "label" | "type" | "traits";
value: string;
withAncestor?: string;
withDescendant?: string;
atIndex?: number;
}): string {
let matcher: string;
switch (options.type) {
case "id":
matcher = `by.id('${options.value}')`;
break;
case "text":
matcher = `by.text('${options.value}')`;
break;
case "label":
matcher = `by.label('${options.value}')`;
break;
case "type":
matcher = `by.type('${options.value}')`;
break;
case "traits":
matcher = `by.traits(['${options.value}'])`;
break;
default:
matcher = `by.id('${options.value}')`;
}
if (options.withAncestor) {
matcher = `${matcher}.withAncestor(by.id('${options.withAncestor}'))`;
}
if (options.withDescendant) {
matcher = `${matcher}.withDescendant(by.id('${options.withDescendant}'))`;
}
let code = `element(${matcher})`;
if (options.atIndex !== undefined) {
code = `element(${matcher}).atIndex(${options.atIndex})`;
}
return code;
}
export function generateActionCode(options: {
actionType:
| "tap"
| "multiTap"
| "longPress"
| "typeText"
| "replaceText"
| "clearText"
| "scroll"
| "scrollTo"
| "swipe"
| "pinch";
elementMatcher: string;
params?: {
text?: string;
times?: number;
duration?: number;
direction?: "up" | "down" | "left" | "right";
speed?: "fast" | "slow";
offset?: number;
edge?: "top" | "bottom" | "left" | "right";
point?: { x: number; y: number };
};
}): string {
const element = options.elementMatcher;
const p = options.params || {};
switch (options.actionType) {
case "tap":
if (p.point) {
return `await ${element}.tap({ x: ${p.point.x}, y: ${p.point.y} });`;
}
return `await ${element}.tap();`;
case "multiTap":
return `await ${element}.multiTap(${p.times || 2});`;
case "longPress":
if (p.duration) {
return `await ${element}.longPress(${p.duration});`;
}
return `await ${element}.longPress();`;
case "typeText":
return `await ${element}.typeText('${p.text || ""}');`;
case "replaceText":
return `await ${element}.replaceText('${p.text || ""}');`;
case "clearText":
return `await ${element}.clearText();`;
case "scroll":
const scrollOffset = p.offset || 200;
const scrollDir = p.direction || "down";
return `await ${element}.scroll(${scrollOffset}, '${scrollDir}');`;
case "scrollTo":
const edge = p.edge || "bottom";
return `await ${element}.scrollTo('${edge}');`;
case "swipe":
const swipeDir = p.direction || "up";
const speed = p.speed || "fast";
return `await ${element}.swipe('${swipeDir}', '${speed}');`;
case "pinch":
const scale = p.offset || 2;
return `await ${element}.pinch(${scale});`;
default:
return `await ${element}.tap();`;
}
}
export function generateExpectationCode(options: {
expectationType:
| "toBeVisible"
| "toExist"
| "toBeFocused"
| "toHaveText"
| "toHaveLabel"
| "toHaveId"
| "toHaveValue"
| "toHaveSliderPosition"
| "toHaveToggleValue";
elementMatcher: string;
expectedValue?: string | number | boolean;
negated?: boolean;
timeout?: number;
visibilityThreshold?: number;
}): string {
const element = options.elementMatcher;
const not = options.negated ? ".not" : "";
let expectation: string;
switch (options.expectationType) {
case "toBeVisible":
if (options.visibilityThreshold) {
expectation = `toBeVisible(${options.visibilityThreshold})`;
} else {
expectation = "toBeVisible()";
}
break;
case "toExist":
expectation = "toExist()";
break;
case "toBeFocused":
expectation = "toBeFocused()";
break;
case "toHaveText":
expectation = `toHaveText('${options.expectedValue || ""}')`;
break;
case "toHaveLabel":
expectation = `toHaveLabel('${options.expectedValue || ""}')`;
break;
case "toHaveId":
expectation = `toHaveId('${options.expectedValue || ""}')`;
break;
case "toHaveValue":
expectation = `toHaveValue('${options.expectedValue || ""}')`;
break;
case "toHaveSliderPosition":
expectation = `toHaveSliderPosition(${options.expectedValue || 0})`;
break;
case "toHaveToggleValue":
expectation = `toHaveToggleValue(${options.expectedValue ?? true})`;
break;
default:
expectation = "toBeVisible()";
}
let code = `await expect(${element})${not}.${expectation};`;
if (options.timeout) {
code = `await waitFor(${element})${not}.${expectation}.withTimeout(${options.timeout});`;
}
return code;
}
export function generateWaitForCode(options: {
elementMatcher: string;
expectation: "toBeVisible" | "toExist" | "toHaveText" | "toHaveValue";
expectedValue?: string;
timeout: number;
negated?: boolean;
}): string {
const not = options.negated ? ".not" : "";
let expect: string;
switch (options.expectation) {
case "toBeVisible":
expect = "toBeVisible()";
break;
case "toExist":
expect = "toExist()";
break;
case "toHaveText":
expect = `toHaveText('${options.expectedValue || ""}')`;
break;
case "toHaveValue":
expect = `toHaveValue('${options.expectedValue || ""}')`;
break;
}
return `await waitFor(${options.elementMatcher})
${not}.${expect}
.withTimeout(${options.timeout});`;
}
export function generateDeviceActionCode(
action: string,
params?: Record<string, any>
): string {
switch (action) {
case "launchApp":
if (params && Object.keys(params).length > 0) {
return `await device.launchApp(${JSON.stringify(params)});`;
}
return "await device.launchApp();";
case "terminateApp":
return "await device.terminateApp();";
case "reloadReactNative":
return "await device.reloadReactNative();";
case "sendToHome":
return "await device.sendToHome();";
case "shake":
return "await device.shake();";
case "setOrientation":
return `await device.setOrientation('${params?.orientation || "portrait"}');`;
case "setLocation":
return `await device.setLocation(${params?.latitude || 0}, ${params?.longitude || 0});`;
case "openURL":
return `await device.openURL({ url: '${params?.url || ""}' });`;
case "installApp":
return "await device.installApp();";
case "uninstallApp":
return "await device.uninstallApp();";
case "takeScreenshot":
return `await device.takeScreenshot('${params?.name || "screenshot"}');`;
case "pressBack":
return "await device.pressBack();";
case "setBiometricEnrollment":
return `await device.setBiometricEnrollment(${params?.enrolled ?? true});`;
case "matchFace":
return "await device.matchFace();";
case "unmatchFace":
return "await device.unmatchFace();";
case "matchFinger":
return "await device.matchFinger();";
case "unmatchFinger":
return "await device.unmatchFinger();";
default:
return `await device.${action}();`;
}
}
/**
* Detox API reference data for resources
*/
export const API_REFERENCE = {
matchers: {
"by.id": {
description: "Match element by testID",
example: "element(by.id('loginButton'))",
params: [{ name: "id", type: "string | RegExp" }],
},
"by.text": {
description: "Match element by visible text content",
example: "element(by.text('Login'))",
params: [{ name: "text", type: "string | RegExp" }],
},
"by.label": {
description: "Match element by accessibility label",
example: "element(by.label('Submit Button'))",
params: [{ name: "label", type: "string | RegExp" }],
},
"by.type": {
description: "Match element by native view type",
example: "element(by.type('RCTTextInput'))",
params: [{ name: "type", type: "string" }],
},
"by.traits": {
description: "Match element by accessibility traits (iOS only)",
example: "element(by.traits(['button']))",
params: [{ name: "traits", type: "string[]" }],
},
".and": {
description: "Combine matchers with AND",
example: "element(by.id('button').and(by.text('Submit')))",
},
".withAncestor": {
description: "Match element with specific ancestor",
example: "element(by.id('child').withAncestor(by.id('parent')))",
},
".withDescendant": {
description: "Match element with specific descendant",
example: "element(by.id('parent').withDescendant(by.id('child')))",
},
".atIndex": {
description: "Select element at index when multiple match",
example: "element(by.text('Item')).atIndex(2)",
},
},
actions: {
tap: {
description: "Tap on element",
example: "await element(by.id('button')).tap()",
params: [{ name: "point", type: "{ x: number, y: number }", optional: true }],
},
multiTap: {
description: "Tap multiple times",
example: "await element(by.id('button')).multiTap(3)",
params: [{ name: "times", type: "number" }],
},
longPress: {
description: "Long press on element",
example: "await element(by.id('button')).longPress()",
params: [{ name: "duration", type: "number", optional: true }],
},
typeText: {
description: "Type text into input field",
example: "await element(by.id('input')).typeText('hello')",
params: [{ name: "text", type: "string" }],
},
replaceText: {
description: "Replace text in input field",
example: "await element(by.id('input')).replaceText('new text')",
params: [{ name: "text", type: "string" }],
},
clearText: {
description: "Clear text from input field",
example: "await element(by.id('input')).clearText()",
},
scroll: {
description: "Scroll element in direction",
example: "await element(by.id('list')).scroll(200, 'down')",
params: [
{ name: "offset", type: "number" },
{ name: "direction", type: "'up' | 'down' | 'left' | 'right'" },
],
},
scrollTo: {
description: "Scroll to edge",
example: "await element(by.id('list')).scrollTo('bottom')",
params: [{ name: "edge", type: "'top' | 'bottom' | 'left' | 'right'" }],
},
swipe: {
description: "Swipe element in direction",
example: "await element(by.id('card')).swipe('left', 'fast')",
params: [
{ name: "direction", type: "'up' | 'down' | 'left' | 'right'" },
{ name: "speed", type: "'fast' | 'slow'", optional: true },
],
},
pinch: {
description: "Pinch to zoom (iOS only)",
example: "await element(by.id('image')).pinch(2.0)",
params: [{ name: "scale", type: "number" }],
},
},
expectations: {
toBeVisible: {
description: "Assert element is visible",
example: "await expect(element(by.id('title'))).toBeVisible()",
params: [{ name: "threshold", type: "number", optional: true }],
},
toExist: {
description: "Assert element exists in hierarchy",
example: "await expect(element(by.id('button'))).toExist()",
},
toBeFocused: {
description: "Assert element is focused",
example: "await expect(element(by.id('input'))).toBeFocused()",
},
toHaveText: {
description: "Assert element has specific text",
example: "await expect(element(by.id('title'))).toHaveText('Welcome')",
params: [{ name: "text", type: "string" }],
},
toHaveLabel: {
description: "Assert element has accessibility label",
example: "await expect(element(by.id('button'))).toHaveLabel('Submit')",
params: [{ name: "label", type: "string" }],
},
toHaveId: {
description: "Assert element has specific testID",
example: "await expect(element(by.id('button'))).toHaveId('submitBtn')",
params: [{ name: "id", type: "string" }],
},
toHaveValue: {
description: "Assert element has specific value",
example: "await expect(element(by.id('input'))).toHaveValue('hello')",
params: [{ name: "value", type: "string" }],
},
toHaveSliderPosition: {
description: "Assert slider position",
example: "await expect(element(by.id('slider'))).toHaveSliderPosition(0.5)",
params: [
{ name: "position", type: "number" },
{ name: "tolerance", type: "number", optional: true },
],
},
toHaveToggleValue: {
description: "Assert toggle/switch value",
example: "await expect(element(by.id('switch'))).toHaveToggleValue(true)",
params: [{ name: "value", type: "boolean" }],
},
},
device: {
launchApp: {
description: "Launch the app",
example: "await device.launchApp({ newInstance: true })",
params: [{ name: "options", type: "LaunchAppConfig", optional: true }],
},
terminateApp: {
description: "Terminate the app",
example: "await device.terminateApp()",
},
reloadReactNative: {
description: "Reload React Native bundle",
example: "await device.reloadReactNative()",
},
sendToHome: {
description: "Send app to background",
example: "await device.sendToHome()",
},
setLocation: {
description: "Set device GPS location",
example: "await device.setLocation(40.7128, -74.0060)",
params: [
{ name: "latitude", type: "number" },
{ name: "longitude", type: "number" },
],
},
setOrientation: {
description: "Set device orientation",
example: "await device.setOrientation('landscape')",
params: [{ name: "orientation", type: "'portrait' | 'landscape'" }],
},
shake: {
description: "Shake device (iOS only)",
example: "await device.shake()",
},
openURL: {
description: "Open URL with app",
example: "await device.openURL({ url: 'myapp://page' })",
params: [{ name: "params", type: "{ url: string }" }],
},
takeScreenshot: {
description: "Take screenshot",
example: "await device.takeScreenshot('home-screen')",
params: [{ name: "name", type: "string" }],
},
pressBack: {
description: "Press back button (Android)",
example: "await device.pressBack()",
},
matchFace: {
description: "Simulate Face ID match",
example: "await device.matchFace()",
},
unmatchFace: {
description: "Simulate Face ID failure",
example: "await device.unmatchFace()",
},
matchFinger: {
description: "Simulate Touch ID match",
example: "await device.matchFinger()",
},
unmatchFinger: {
description: "Simulate Touch ID failure",
example: "await device.unmatchFinger()",
},
},
};