appium-skill — Claude Code skill: Appium tests for Android and iOS
Writes Appium mobile automation scripts for Android and iOS, running on a local emulator or on real devices in the TestMu AI cloud.
npx skills add LambdaTest/agent-skills --skill appium-skill -a claude-codeWhat appium-skill does
The skill turns your agent into a senior mobile QA architect. You describe the app and the task, and it assembles a working test: capabilities, locators, waits and gestures. Java is the default language with the io.appium:java-client client, but there are pattern files for Python with Appium-Python-Client, JavaScript with webdriverio, plus Ruby and C#.
It works in steps. First the execution target — a local Appium server or the TestMu AI cloud. Then the platform: Android gets automationName: UiAutomator2, iOS gets XCUITest. Then the language. Locators follow a priority order: accessibilityId first, then resource-id, iOS predicates, UiAutomator, and XPath only as a last resort. Explicit WebDriverWait replaces Thread.sleep.
One thing to plan for: cloud runs need a TestMu AI account and its capabilities, and a local setup will not fill those in. If your project already wraps the driver in its own helpers, expect to adapt the generated code by hand.
What it helps with
- generate an Android login test with UiAutomator2
- replace fragile XPath locators with accessibilityId
- swap Thread.sleep for explicit WebDriverWait calls
- build separate capability sets for Android and iOS
- set up a run on real devices in the cloud
Example prompts for the agent
- write an Appium test in Java for login in an Android app
- convert this test to iOS with XCUITest
- generate a Python Appium test with pytest for swiping a list
How to install appium-skill
1Skills CLI (Vercel)
npx skills add LambdaTest/agent-skills --skill appium-skill -a claude-code2Manually: put the files in place
~/.claude/skills/appium-skill/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/appium-skill/
1Skills CLI (Vercel)
npx skills add LambdaTest/agent-skills --skill appium-skill -a codex2Manually: put the files in place
.agents/skills/appium-skill/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add LambdaTest/agent-skills --skill appium-skill -a cursor2Manually: put the files in place
.cursor/skills/appium-skill/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install LambdaTest/agent-skills appium-skill2Manually: put the files in place
.github/skills/appium-skill/SKILL.md1Skills CLI (Vercel)
npx skills add LambdaTest/agent-skills --skill appium-skill -a gemini-cli2Platform CLI
gemini skills install https://github.com/LambdaTest/agent-skills --path appium-skillManually: put the files in place
.devin/skills/appium-skill/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add LambdaTest/agent-skills --skill appium-skill -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about appium-skill
How do I install appium-skill in Claude?
Run npx skills add LambdaTest/agent-skills --skill appium-skill -a claude-code. The skill lands in ~/.claude/skills/appium-skill/SKILL.md.
What does appium-skill do?
It generates Appium mobile automation scripts for Android and iOS in Java, Python or JavaScript. Runs work locally or on real devices in the TestMu AI cloud.
Which languages does the skill support?
Java is the default, and separate pattern files for Python, JavaScript, Ruby and C# live in reference/<language>-patterns.md.
Source text
SKILL.md · MIT
---
name: appium-skill
description: >
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 Appium tests, or mentions "Appium",
"mobile testing", "real device", "app automation". Triggers on: "Appium",
"mobile test", "Android test", "iOS test", "real device", "app automation",
"UiAutomator", "XCUITest driver", "TestMu", "LambdaTest".
languages:
- Java
- Python
- JavaScript
- Ruby
- C#
category: mobile-testing
license: MIT
metadata:
author: TestMu AI
version: "1.0"
---
# Appium Automation Skill
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.
## Step 1 — Execution Target
```
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
```
## Step 2 — Platform Detection
```
├─ 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
```
## Step 3 — Language Detection
| 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`
## Core Patterns — Java (Default)
### Desired Capabilities — Android
```java
UiAutomator2Options 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
);
```
### Desired Capabilities — iOS
```java
XCUITestOptions 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
);
```
### Locator Strategy Priority
```
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, fragile
```
```java
// ✅ 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']"));
```
### Wait Strategy
```java
WebDriverWait 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();
```
### Gestures
```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));
```
### Anti-Patterns
| 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 |
### Test Structure (JUnit 5)
```java
import 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("[email protected]");
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();
}
}
```
### TestMu AI Cloud — Quick Setup
```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);
```
### Test Status Reporting
```java
((JavascriptExecutor) driver).executeScript(
"lambda-status=" + (testPassed ? "passed" : "failed")
);
```
## Validation Workflow
1. **Platform caps**: Correct automationName (UiAutomator2 / XCUITest)
2. **Locators**: AccessibilityId first, no absolute XPath
3. **Waits**: Explicit WebDriverWait, zero Thread.sleep()
4. **Gestures**: Use W3C Actions API, not deprecated TouchAction
5. **App upload**: Use `lt://` URL for cloud, local path for emulator
6. **Timeout**: 30s+ for real devices (slower than emulators)
## Quick Reference
| 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()` |
## Reference Files
| 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 |
## Deep Patterns → `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 |
© TestMu AI · MIT · LambdaTest/agent-skills