Back to Learn Automation
Mobile Automation
Mobile Automation
Production-ready Appium, XCUITest, and Espresso automation for iOS and Android applications
Advanced Appium Gesture Automation
Complex gesture automation including swipe, pinch, zoom, long press, and multi-touch gestures for iOS and Android.
mobile-gestures.ts
// Advanced Appium Gesture Framework
import { remote, RemoteOptions, Element } from 'webdriverio';
interface Point {
x: number;
y: number;
}
class AdvancedMobileGestures {
private driver: WebdriverIO.Browser;
constructor(driver: WebdriverIO.Browser) {
this.driver = driver;
}
// Advanced swipe with custom speed and direction
async swipe(
startPoint: Point,
endPoint: Point,
duration: number = 800
): Promise<void> {
if (this.driver.isIOS) {
await this.driver.execute('mobile: swipe', {
startX: startPoint.x,
startY: startPoint.y,
endX: endPoint.x,
endY: endPoint.y,
duration,
});
} else {
await this.driver.touchPerform([
{ action: 'press', options: startPoint },
{ action: 'wait', options: { ms: duration } },
{ action: 'moveTo', options: endPoint },
{ action: 'release' },
]);
}
}
// Swipe element in specific direction
async swipeElement(
element: Element,
direction: 'up' | 'down' | 'left' | 'right',
distance: number = 0.8
): Promise<void> {
const location = await element.getLocation();
const size = await element.getSize();
const centerX = location.x + size.width / 2;
const centerY = location.y + size.height / 2;
let startPoint: Point, endPoint: Point;
switch (direction) {
case 'up':
startPoint = { x: centerX, y: centerY + size.height * distance / 2 };
endPoint = { x: centerX, y: centerY - size.height * distance / 2 };
break;
case 'down':
startPoint = { x: centerX, y: centerY - size.height * distance / 2 };
endPoint = { x: centerX, y: centerY + size.height * distance / 2 };
break;
case 'left':
startPoint = { x: centerX + size.width * distance / 2, y: centerY };
endPoint = { x: centerX - size.width * distance / 2, y: centerY };
break;
case 'right':
startPoint = { x: centerX - size.width * distance / 2, y: centerY };
endPoint = { x: centerX + size.width * distance / 2, y: centerY };
break;
}
await this.swipe(startPoint, endPoint);
}
// Pinch to zoom
async pinchZoom(
element: Element,
scale: number = 2,
duration: number = 500
): Promise<void> {
const location = await element.getLocation();
const size = await element.getSize();
const centerX = location.x + size.width / 2;
const centerY = location.y + size.height / 2;
if (this.driver.isIOS) {
await this.driver.execute('mobile: pinch', {
x: centerX,
y: centerY,
scale,
velocity: 1.0,
});
} else {
const offset = 100;
await this.driver.multiTouchPerform([
[
{ action: 'press', options: { x: centerX - offset, y: centerY } },
{ action: 'moveTo', options: { x: centerX - offset * scale, y: centerY } },
{ action: 'release' },
],
[
{ action: 'press', options: { x: centerX + offset, y: centerY } },
{ action: 'moveTo', options: { x: centerX + offset * scale, y: centerY } },
{ action: 'release' },
],
]);
}
}
// Drag and drop
async dragAndDrop(
sourceElement: Element,
targetElement: Element,
duration: number = 1000
): Promise<void> {
const sourceLoc = await sourceElement.getLocation();
const sourceSize = await sourceElement.getSize();
const targetLoc = await targetElement.getLocation();
const targetSize = await targetElement.getSize();
const startPoint = {
x: sourceLoc.x + sourceSize.width / 2,
y: sourceLoc.y + sourceSize.height / 2,
};
const endPoint = {
x: targetLoc.x + targetSize.width / 2,
y: targetLoc.y + targetSize.height / 2,
};
await this.swipe(startPoint, endPoint, duration);
}
// Long press with custom duration
async longPress(element: Element, duration: number = 2000): Promise<void> {
if (this.driver.isIOS) {
await this.driver.execute('mobile: touchAndHold', {
elementId: element.elementId,
duration: duration / 1000,
});
} else {
const location = await element.getLocation();
const size = await element.getSize();
await this.driver.touchPerform([
{
action: 'press',
options: {
x: location.x + size.width / 2,
y: location.y + size.height / 2,
},
},
{ action: 'wait', options: { ms: duration } },
{ action: 'release' },
]);
}
}
// Circular swipe (for unlock patterns, etc.)
async circularSwipe(
centerPoint: Point,
radius: number,
duration: number = 1000
): Promise<void> {
const points: Point[] = [];
const steps = 20;
for (let i = 0; i <= steps; i++) {
const angle = (i / steps) * 2 * Math.PI;
points.push({
x: centerPoint.x + radius * Math.cos(angle),
y: centerPoint.y + radius * Math.sin(angle),
});
}
const actions = [
{ action: 'press', options: points[0] },
...points.slice(1).map(point => ({
action: 'moveTo',
options: point,
})),
{ action: 'release' },
];
await this.driver.touchPerform(actions);
}
// Shake device (iOS only)
async shakeDevice(): Promise<void> {
if (this.driver.isIOS) {
await this.driver.execute('mobile: shake');
}
}
// Scroll until element is visible
async scrollToElement(
element: Element,
maxScrolls: number = 10
): Promise<boolean> {
for (let i = 0; i < maxScrolls; i++) {
const isDisplayed = await element.isDisplayed();
if (isDisplayed) return true;
const windowSize = await this.driver.getWindowSize();
await this.swipe(
{ x: windowSize.width / 2, y: windowSize.height * 0.8 },
{ x: windowSize.width / 2, y: windowSize.height * 0.2 },
300
);
await this.driver.pause(500);
}
return false;
}
}
// Usage Example
const options: RemoteOptions = {
capabilities: {
platformName: 'iOS',
'appium:deviceName': 'iPhone 14 Pro',
'appium:platformVersion': '16.0',
'appium:app': '/path/to/app.ipa',
'appium:automationName': 'XCUITest',
},
};
const driver = await remote(options);
const gestures = new AdvancedMobileGestures(driver);
// Swipe to next screen
const element = await driver.$('~nextButton');
await gestures.swipeElement(element, 'left');
// Pinch to zoom on image
const image = await driver.$('~photoImage');
await gestures.pinchZoom(image, 2.5);
// Long press to open context menu
await gestures.longPress(element, 3000);
await driver.deleteSession();Cross-Platform Mobile Test Framework
Unified test framework supporting iOS and Android with page object model and automatic retry logic.
cross-platform-runner.ts
// Cross-Platform Mobile Testing Framework
import { remote, RemoteOptions } from 'webdriverio';
interface TestConfig {
platform: 'iOS' | 'Android';
deviceName: string;
platformVersion: string;
app: string;
}
abstract class BasePage {
protected driver: WebdriverIO.Browser;
constructor(driver: WebdriverIO.Browser) {
this.driver = driver;
}
// Platform-agnostic selectors
protected getSelector(iosSelector: string, androidSelector: string): string {
return this.driver.isIOS ? iosSelector : androidSelector;
}
async waitForElement(selector: string, timeout: number = 10000) {
const element = await this.driver.$(selector);
await element.waitForDisplayed({ timeout });
return element;
}
async tapElement(selector: string, retry: number = 3) {
for (let i = 0; i < retry; i++) {
try {
const element = await this.waitForElement(selector);
await element.click();
return;
} catch (error) {
if (i === retry - 1) throw error;
await this.driver.pause(1000);
}
}
}
async typeText(selector: string, text: string, clearFirst: boolean = true) {
const element = await this.waitForElement(selector);
if (clearFirst) {
await element.clearValue();
}
await element.setValue(text);
// Hide keyboard
if (this.driver.isIOS) {
await this.driver.execute('mobile: hideKeyboard');
} else {
await this.driver.hideKeyboard();
}
}
async getText(selector: string): Promise<string> {
const element = await this.waitForElement(selector);
return await element.getText();
}
}
class LoginPage extends BasePage {
private get usernameInput() {
return this.getSelector('~username-input', 'id:username');
}
private get passwordInput() {
return this.getSelector('~password-input', 'id:password');
}
private get loginButton() {
return this.getSelector('~login-button', 'id:login-btn');
}
private get errorMessage() {
return this.getSelector('~error-message', 'id:error-msg');
}
async login(username: string, password: string) {
await this.typeText(this.usernameInput, username);
await this.typeText(this.passwordInput, password);
await this.tapElement(this.loginButton);
}
async getErrorMessage(): Promise<string> {
return await this.getText(this.errorMessage);
}
}
class TestRunner {
private driver: WebdriverIO.Browser | null = null;
private config: TestConfig;
constructor(config: TestConfig) {
this.config = config;
}
async setup() {
const options: RemoteOptions = {
logLevel: 'info',
capabilities: {
platformName: this.config.platform,
'appium:deviceName': this.config.deviceName,
'appium:platformVersion': this.config.platformVersion,
'appium:app': this.config.app,
'appium:automationName':
this.config.platform === 'iOS' ? 'XCUITest' : 'UiAutomator2',
'appium:noReset': false,
'appium:fullReset': false,
'appium:newCommandTimeout': 300,
},
};
this.driver = await remote(options);
return this.driver;
}
async teardown() {
if (this.driver) {
await this.driver.deleteSession();
}
}
// Screenshot on failure
async takeScreenshot(testName: string) {
if (this.driver) {
const screenshot = await this.driver.takeScreenshot();
const fs = require('fs').promises;
await fs.writeFile(
`./screenshots/${testName}-${Date.now()}.png`,
screenshot,
'base64'
);
}
}
// Get device info
async getDeviceInfo() {
if (!this.driver) return null;
return {
platform: await this.driver.getPlatform(),
orientation: await this.driver.getOrientation(),
battery: this.config.platform === 'Android'
? await this.driver.execute('mobile: batteryInfo', {})
: null,
};
}
}
// Test Suite Example
describe('Login Tests', () => {
let runner: TestRunner;
let driver: WebdriverIO.Browser;
let loginPage: LoginPage;
beforeAll(async () => {
runner = new TestRunner({
platform: 'iOS',
deviceName: 'iPhone 14',
platformVersion: '16.0',
app: '/path/to/app.ipa',
});
driver = await runner.setup();
loginPage = new LoginPage(driver);
});
afterAll(async () => {
await runner.teardown();
});
afterEach(async function() {
if (this.currentTest?.state === 'failed') {
await runner.takeScreenshot(this.currentTest.title);
}
});
it('should login successfully with valid credentials', async () => {
await loginPage.login('testuser@example.com', 'password123');
// Add assertions
});
it('should show error with invalid credentials', async () => {
await loginPage.login('invalid@example.com', 'wrongpass');
const error = await loginPage.getErrorMessage();
expect(error).toContain('Invalid credentials');
});
});Mobile App Performance Testing
Monitor app performance metrics including CPU, memory, battery, network, and FPS during automation.
perf-monitor.ts
// Mobile Performance Monitoring
import { remote } from 'webdriverio';
interface PerformanceMetrics {
cpu: number;
memory: number;
battery?: number;
network: {
sent: number;
received: number;
};
fps?: number;
timestamp: number;
}
class MobilePerformanceMonitor {
private driver: WebdriverIO.Browser;
private metrics: PerformanceMetrics[] = [];
private monitoringInterval: NodeJS.Timeout | null = null;
constructor(driver: WebdriverIO.Browser) {
this.driver = driver;
}
async startMonitoring(intervalMs: number = 1000) {
this.monitoringInterval = setInterval(async () => {
try {
const metric = await this.collectMetrics();
this.metrics.push(metric);
} catch (error) {
console.error('Failed to collect metrics:', error);
}
}, intervalMs);
}
stopMonitoring() {
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
this.monitoringInterval = null;
}
}
private async collectMetrics(): Promise<PerformanceMetrics> {
const isAndroid = this.driver.isAndroid;
if (isAndroid) {
return await this.collectAndroidMetrics();
} else {
return await this.collectiOSMetrics();
}
}
private async collectAndroidMetrics(): Promise<PerformanceMetrics> {
// Get CPU usage
const cpu = await this.driver.execute('mobile: getPerformanceData', {
packageName: 'com.example.app',
dataType: 'cpuinfo',
});
// Get memory usage
const memory = await this.driver.execute('mobile: getPerformanceData', {
packageName: 'com.example.app',
dataType: 'memoryinfo',
});
// Get battery info
const battery = await this.driver.execute('mobile: batteryInfo', {});
// Get network stats
const network = await this.driver.execute('mobile: getPerformanceData', {
packageName: 'com.example.app',
dataType: 'networkinfo',
});
return {
cpu: parseFloat(cpu[0] || 0),
memory: parseFloat(memory[0] || 0),
battery: battery.level,
network: {
sent: parseFloat(network[0] || 0),
received: parseFloat(network[1] || 0),
},
timestamp: Date.now(),
};
}
private async collectiOSMetrics(): Promise<PerformanceMetrics> {
// iOS performance data collection
const perfData = await this.driver.execute('mobile: performanceData', {
timeout: 2000,
pid: 'current',
});
return {
cpu: perfData.cpuUsage || 0,
memory: perfData.memoryUsage || 0,
network: {
sent: perfData.networkSent || 0,
received: perfData.networkReceived || 0,
},
fps: perfData.fps || 0,
timestamp: Date.now(),
};
}
getMetrics(): PerformanceMetrics[] {
return this.metrics;
}
getAverageMetrics(): Partial<PerformanceMetrics> {
if (this.metrics.length === 0) return {};
const sum = this.metrics.reduce(
(acc, metric) => ({
cpu: acc.cpu + metric.cpu,
memory: acc.memory + metric.memory,
networkSent: acc.networkSent + metric.network.sent,
networkReceived: acc.networkReceived + metric.network.received,
}),
{ cpu: 0, memory: 0, networkSent: 0, networkReceived: 0 }
);
const count = this.metrics.length;
return {
cpu: sum.cpu / count,
memory: sum.memory / count,
network: {
sent: sum.networkSent / count,
received: sum.networkReceived / count,
},
};
}
exportToCSV(filename: string) {
const fs = require('fs');
const header = 'timestamp,cpu,memory,battery,network_sent,network_received,fps\n';
const rows = this.metrics.map(m =>
`${m.timestamp},${m.cpu},${m.memory},${m.battery || ''},${m.network.sent},${m.network.received},${m.fps || ''}\n`
);
fs.writeFileSync(filename, header + rows.join(''));
}
clearMetrics() {
this.metrics = [];
}
}
// Usage Example
const driver = await remote({
capabilities: {
platformName: 'Android',
'appium:deviceName': 'Pixel 7',
'appium:app': '/path/to/app.apk',
},
});
const perfMonitor = new MobilePerformanceMonitor(driver);
// Start monitoring
await perfMonitor.startMonitoring(1000);
// Run your test actions
await driver.$('~login-button').click();
await driver.$('~username').setValue('test@example.com');
// ... more actions
// Stop monitoring
perfMonitor.stopMonitoring();
// Get results
const avgMetrics = perfMonitor.getAverageMetrics();
console.log('Average CPU:', avgMetrics.cpu);
console.log('Average Memory:', avgMetrics.memory);
// Export to CSV
perfMonitor.exportToCSV('./performance-report.csv');
await driver.deleteSession();Coming Soon
More Mobile Automation Topics
Deep Link Testing
Push Notification Automation
Biometric Authentication Testing
Camera and Photo Gallery Automation
Location and GPS Mocking
Network Condition Simulation
Device Rotation Testing
Multi-Language Testing
Accessibility Testing
iOS Simulator & Android Emulator Management
Get Started
Need Custom Mobile Automation?
We build comprehensive mobile testing frameworks for iOS and Android applications.