Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Generates production-grade Appium mobile automation scripts for Android and iOS in Java, Python, or JavaScript. Supports real device and emulator testing locally and on TestMu AI cloud with 100+ real devices. Use when the user asks to automate mobile apps, test on Android/iOS, write...
.claude/skills/sickn33-appium-skill/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 164% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 159% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 145% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 132% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 150% | 0% |
Use this skill when you need generates production-grade Appium mobile automation scripts for Android and iOS in Java, Python, or JavaScript. Supports real device and emulator testing locally and on TestMu AI cloud with 100+ real devices. Use when the user asks to automate mobile apps, test on Android/iOS, write...
You are a senior mobile QA architect. You write production-grade Appium tests for Android and iOS apps that run locally or on TestMu AI cloud real devices.
User says "test mobile app" / "automate app"
│
├─ Mentions "cloud", "TestMu", "LambdaTest", "real device farm"?
│ └─ TestMu AI cloud (100+ real devices)
│
├─ Mentions "emulator", "simulator", "local"?
│ └─ Local Appium server
│
├─ Mentions specific devices (Pixel 8, iPhone 16)?
│ └─ Suggest TestMu AI cloud for real device coverage
│
└─ Ambiguous? → Default local emulator, mention cloud for real devices├─ Mentions "Android", "APK", "Play Store", "Pixel", "Samsung", "Galaxy"?
│ └─ Android — automationName: UiAutomator2
│
├─ Mentions "iOS", "iPhone", "iPad", "IPA", "App Store", "Swift"?
│ └─ iOS — automationName: XCUITest
│
└─ Both? → Create separate capability sets for each| Signal | Language | Client | |--------|----------|--------| | Default / "Java" | Java | io.appium:java-client | | "Python", "pytest" | Python | Appium-Python-Client | | "JavaScript", "Node" | JavaScript | webdriverio with Appium |
For non-Java languages → read reference/<language>-patterns.md
javaUiAutomator2Options options = new UiAutomator2Options() .setDeviceName("Pixel 7") .setPlatformVersion("13") .setApp("/path/to/app.apk") .setAutomationName("UiAutomator2") .setAppPackage("com.example.app") .setAppActivity("com.example.app.MainActivity") .setNoReset(true); AndroidDriver driver = new AndroidDriver( new URL("http://localhost:4723"), options );
javaXCUITestOptions options = new XCUITestOptions() .setDeviceName("iPhone 16") .setPlatformVersion("18") .setApp("/path/to/app.ipa") .setAutomationName("XCUITest") .setBundleId("com.example.app") .setNoReset(true); IOSDriver driver = new IOSDriver( new URL("http://localhost:4723"), options );
1. AccessibilityId ← Best: works cross-platform
2. ID (resource-id) ← Android: "com.app:id/login_btn"
3. Name / Label ← iOS: accessibility label
4. Class Name ← Widget type
5. XPath ← Last resort: slow, fragilejava// ✅ Best — cross-platform driver.findElement(AppiumBy.accessibilityId("loginButton")); // ✅ Good — Android resource ID driver.findElement(AppiumBy.id("com.example:id/login_btn")); // ✅ Good — iOS predicate driver.findElement(AppiumBy.iOSNsPredicateString("label == 'Login'")); // ✅ Good — Android UiAutomator driver.findElement(AppiumBy.androidUIAutomator( "new UiSelector().text("Login")" )); // ❌ Avoid — slow, fragile driver.findElement(AppiumBy.xpath("//android.widget.Button[@text='Login']"));
javaWebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15)); // Wait for element visible WebElement el = wait.until( ExpectedConditions.visibilityOfElementLocated(AppiumBy.accessibilityId("dashboard")) ); // Wait for element clickable wait.until(ExpectedConditions.elementToBeClickable(AppiumBy.id("submit"))).click();
java// Tap WebElement el = driver.findElement(AppiumBy.accessibilityId("item")); el.click(); // Long press PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); Sequence longPress = new Sequence(finger, 0); longPress.addAction(finger.createPointerMove(Duration.ofMillis(0), PointerInput.Origin.viewport(), el.getLocation().x, el.getLocation().y)); longPress.addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg())); longPress.addAction(new Pause(finger, Duration.ofMillis(2000))); longPress.addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg())); driver.perform(List.of(longPress)); // Swipe up (scroll down) Dimension size = driver.manage().window().getSize(); int startX = size.width / 2; int startY = (int) (size.height * 0.8); int endY = (int) (size.height * 0.2); PointerInput swipeFinger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); Sequence swipe = new Sequence(swipeFinger, 0); swipe.addAction(swipeFinger.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), startX, startY)); swipe.addAction(swipeFinger.createPointerDown(PointerInput.MouseButton.LEFT.asArg())); swipe.addAction(swipeFinger.createPointerMove(Duration.ofMillis(500), PointerInput.Origin.viewport(), startX, endY)); swipe.addAction(swipeFinger.createPointerUp(PointerInput.MouseButton.LEFT.asArg())); driver.perform(List.of(swipe));
| Bad | Good | Why | |-----|------|-----| | Thread.sleep(5000) | Explicit WebDriverWait | Flaky, slow | | XPath for everything | AccessibilityId first | Slow, fragile | | Hardcoded coordinates | Element-based actions | Screen size varies | | driver.resetApp() between tests | noReset: true + targeted cleanup | Slow, state issues | | Same caps for Android + iOS | Separate capability sets | Different locators/APIs |
javaimport io.appium.java_client.android.AndroidDriver; import io.appium.java_client.android.options.UiAutomator2Options; import org.junit.jupiter.api.*; import org.openqa.selenium.support.ui.WebDriverWait; import java.net.URL; import java.time.Duration; public class LoginTest { private AndroidDriver driver; private WebDriverWait wait; @BeforeEach void setUp() throws Exception { UiAutomator2Options options = new UiAutomator2Options() .setDeviceName("emulator-5554") .setApp("/path/to/app.apk") .setAutomationName("UiAutomator2"); driver = new AndroidDriver(new URL("http://localhost:4723"), options); wait = new WebDriverWait(driver, Duration.ofSeconds(15)); } @Test void testLoginSuccess() { wait.until(ExpectedConditions.visibilityOfElementLocated( AppiumBy.accessibilityId("emailInput"))).sendKeys("user@test.com"); driver.findElement(AppiumBy.accessibilityId("passwordInput")) .sendKeys("password123"); driver.findElement(AppiumBy.accessibilityId("loginButton")).click(); wait.until(ExpectedConditions.visibilityOfElementLocated( AppiumBy.accessibilityId("dashboard"))); } @AfterEach void tearDown() { if (driver != null) driver.quit(); } }
java// Upload app first: // curl -u "user:key" --location --request POST // 'https://manual-api.lambdatest.com/app/upload/realDevice' // --form 'name="app"' --form 'appFile=@"/path/to/app.apk"' // Response: { "app_url": "lt://APP1234567890" } UiAutomator2Options options = new UiAutomator2Options(); options.setPlatformName("android"); options.setDeviceName("Pixel 7"); options.setPlatformVersion("13"); options.setApp("lt://APP1234567890"); // from upload response options.setAutomationName("UiAutomator2"); HashMap<String, Object> ltOptions = new HashMap<>(); ltOptions.put("w3c", true); ltOptions.put("build", "Appium Build"); ltOptions.put("name", "Login Test"); ltOptions.put("isRealMobile", true); ltOptions.put("video", true); ltOptions.put("network", true); options.setCapability("LT:Options", ltOptions); String hub = "https://" + System.getenv("LT_USERNAME") + ":" + System.getenv("LT_ACCESS_KEY") + "@mobile-hub.lambdatest.com/wd/hub"; AndroidDriver driver = new AndroidDriver(new URL(hub), options);
java((JavascriptExecutor) driver).executeScript( "lambda-status=" + (testPassed ? "passed" : "failed") );
lt:// URL for cloud, local path for emulator| Task | Code | |------|------| | Start Appium server | appium (CLI) or appium --relaxed-security | | Install app | driver.installApp("/path/to/app.apk") | | Launch app | driver.activateApp("com.example.app") | | Background app | driver.runAppInBackground(Duration.ofSeconds(5)) | | Screenshot | driver.getScreenshotAs(OutputType.FILE) | | Device orientation | driver.rotate(ScreenOrientation.LANDSCAPE) | | Hide keyboard | driver.hideKeyboard() | | Push file (Android) | driver.pushFile("/sdcard/test.txt", bytes) | | Context switch | driver.context("WEBVIEW_com.example") | | Get contexts | driver.getContextHandles() |
| File | When to Read | |------|-------------| | reference/cloud-integration.md | App upload, real devices, capabilities | | reference/python-patterns.md | Python + pytest-appium | | reference/javascript-patterns.md | JS + WebdriverIO-Appium | | reference/ios-specific.md | iOS-only patterns, XCUITest driver | | reference/hybrid-apps.md | WebView testing, context switching |
reference/playbook.md| § | Section | Lines | |---|---------|-------| | 1 | Project Setup & Capabilities | Maven, Android/iOS options | | 2 | BaseTest with Thread-Safe Driver | ThreadLocal, multi-platform | | 3 | Cross-Platform Page Objects | AndroidFindBy/iOSXCUITFindBy | | 4 | Advanced Gestures (W3C Actions) | Swipe, long press, pinch zoom, scroll | | 5 | WebView & Hybrid App Testing | Context switching | | 6 | Device Interactions | Files, notifications, clipboard, geo | | 7 | Parallel Device Execution | Multi-device TestNG XML | | 8 | LambdaTest Real Device Cloud | Cloud grid integration | | 9 | CI/CD Integration | GitHub Actions, emulator runner | | 10 | Debugging Quick-Reference | 12 common problems | | 11 | Best Practices Checklist | 13 items |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 21,045 | 13,856 | -34% | 1 | 1 | 0% | 4,184 | 6,059 | +45% | 0 | 0 | — |
case-02 | pass→pass | 9,304 | 7,680 | -17% | 1 | 1 | 0% | 1,778 | 4,608 | +159% | 0 | 0 | — |
case-03 | fail→pass | 9,611 | 6,957 | -28% | 1 | 1 | 0% | 1,683 | 4,435 | +164% | 0 | 0 | — |
case-04 | pass→pass | 10,584 | 7,474 | -29% | 1 | 1 | 0% | 1,788 | 4,387 | +145% | 0 | 0 | — |
case-05 | pass→pass | 12,615 | 9,458 | -25% | 1 | 1 | 0% | 2,157 | 5,002 | +132% | 0 | 0 | — |
case-06 | pass→pass | 8,817 | 8,985 | +2% | 1 | 1 | 0% | 1,975 | 4,938 | +150% | 0 | 0 | — |
case-07 | pass→pass | 6,820 | 4,639 | -32% | 1 | 1 | 0% | 1,436 | 3,854 | +168% | 0 | 0 | — |
case-08 | pass→pass | 8,822 | 5,679 | -36% | 1 | 1 | 0% | 1,932 | 4,315 | +123% | 0 | 0 | — |
case-09 | pass→pass | 13,129 | 10,259 | -22% | 1 | 1 | 0% | 2,278 | 4,954 | +117% | 0 | 0 | — |
case-10 | pass→pass | 7,003 | 5,251 | -25% | 1 | 1 | 0% | 1,435 | 4,126 | +188% | 0 | 0 | — |
case-20 | pass→pass | 7,300 | 5,682 | -22% | 1 | 1 | 0% | 1,394 | 4,076 | +192% | 0 | 0 | — |
case-11 | pass→pass | 11,725 | 10,443 | -11% | 1 | 1 | 0% | 1,969 | 5,092 | +159% | 0 | 0 | — |
case-12 | pass→pass | 9,045 | 4,538 | -50% | 1 | 1 | 0% | 1,767 | 3,941 | +123% | 0 | 0 | — |
case-13 | pass→pass | 5,898 | 4,951 | -16% | 1 | 1 | 0% | 1,203 | 3,987 | +231% | 0 | 0 | — |
case-14 | pass→pass | 14,934 | 7,733 | -48% | 1 | 1 | 0% | 2,886 | 4,742 | +64% | 0 | 0 | — |
case-15 | pass→pass | 7,945 | 3,860 | -51% | 1 | 1 | 0% | 1,523 | 3,850 | +153% | 0 | 0 | — |
case-16 | pass→pass | 8,049 | 4,338 | -46% | 1 | 1 | 0% | 1,084 | 3,836 | +254% | 0 | 0 | — |
case-17 | pass→pass | 8,185 | 7,223 | -12% | 1 | 1 | 0% | 2,111 | 4,724 | +124% | 0 | 0 | — |
case-18 | pass→pass | 14,165 | 12,056 | -15% | 1 | 1 | 0% | 2,801 | 5,754 | +105% | 0 | 0 | — |
case-19 | pass→pass | 10,290 | 8,132 | -21% | 1 | 1 | 0% | 1,894 | 4,550 | +140% | 0 | 0 | — |
case-21 | pass→pass | 6,773 | 5,351 | -21% | 1 | 1 | 0% | 1,310 | 3,963 | +203% | 0 | 0 | — |
case-22 | pass→pass | 15,717 | 10,324 | -34% | 1 | 1 | 0% | 2,877 | 5,177 | +80% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +5 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.