of an
Appium
LIFE CYCLE
Command




Agenda
Session Creation
new AndroidDriver() and new IOSDriver()
FindElementBy.X


Problem Statement


Timed out waiting for Espresso Server to start due to Socket exception.
Android Architecture




W3C
Server Request

Server
Appium Server
Appium Espresso Driver


Client



Appium Android Driver
Appium ADB
Appium Espresso Driver
Appium Base Driver
Appium UIA2 Driver
Appium Support


Intialisation
Creation
Instrumentation
Execution
Android Life Cycle
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("deviceName", "Android Emulator");
capabilities.setCapability("app", app.getAbsolutePath());
capabilities.setCapability("appPackage", "io.appium.android.apis");
capabilities.setCapability("appActivity", ".ApiDemos");
capabilities.setCapability("automationName", "Espresso"); driver = new AndroidDriver<WebElement>(getServiceUrl(), capabilities);
Intialization
Driver Creation
App Launch State
automationNameCap = automationNameCap.toLowerCase();
try {
const {driverPackage, driverClassName} = DRIVER_MAP[automationNameCap];
const driver = require('appium-espresso-driver')[driverClassName];
return {
driver,
version: this.getDriverVersion(driver.name, driverPackage),
};
} catch () {
Appium Base Driver
Protocol Proxy
UIA2 or
Espresso Driver
Espresso Driver
- EspressoDriver extends BaseDriver
-
Merges the default values with desired capability
async createSession (...args) {
try {
let [sessionId, caps] = await super.createSession(...args);
let serverDetails = {
platform: 'LINUX',
webStorageEnabled: false,
takesScreenshot: true,
javascriptEnabled: true,
databaseEnabled: false,
networkConnectionEnabled: true,
locationContextEnabled: false,
warnings: {},
desired: Object.assign({}, this.caps)
};
this.caps = Object.assign(serverDetails, this.caps);
Espresso Driver
async startEspressoSession () {
logger.info(`EspressoDriver version: ${version}`);
await helpers.getDeviceInfoFromCaps(this.opts);
....
await androidHelpers.createADB(this.opts);
....
this.initEspressoServer();
appium-android-driver
appium-adb
initEspressoServer () {
this.espresso = new EspressoRunner({
host: this.opts.remoteAdbHost || this.opts.host || 'localhost',
systemPort: this.opts.systemPort,
devicePort: DEVICE_PORT,
adb: this.adb,
apk: this.opts.app,
tmpDir: this.opts.tmpDir,
appPackage: this.opts.appPackage,
appActivity: this.opts.appActivity,
forceEspressoRebuild: !!this.opts.forceEspressoRebuild,
serverLaunchTimeout: this.opts.espressoServerLaunchTimeout,
androidInstallTimeout: this.opts.androidInstallTimeout,
});
this.proxyReqRes = this.espresso.proxyReqRes.bind(this.espresso);
}
espresso-driver/lib/driver.js
Espresso Session Creation
adb forward tcp:8300 tcp:6791
AUT
await this.adb.forwardPort(this.opts.systemPort, DEVICE_PORT);
await this.adb.install(this.modServerPath, { replace: false,timeout: this.androidInstallTimeout });
Instrumentation



async startSession (caps) {
await this.cleanupSessionLeftovers();
const cmd = [
'shell',
'am', 'instrument',
'-w',
'-e', 'debug', process.env.ESPRESSO_JAVA_DEBUG === 'true' ? 'true' : 'false',
`${TEST_APK_PKG}/androidx.test.runner.AndroidJUnitRunner`,
];
logger.info(`Starting Espresso Server v${version} with cmd: adb ${cmd.join(' ')}`);
Execution
try {
await retryInterval(20, 1000, async () => {
await this.jwproxy.command('/status', 'GET');
});
} catch (e) {
if (hasSocketError) {
logger.errorAndThrow('....');
} else {
logger.errorAndThrow(`Timed out waiting for Espresso Server to start. Original error: ${e.message}`);
}
}
await this.jwproxy.command('/session', 'POST', {desiredCapabilities: caps});
Execution


{
"desiredCapabilities": {
"desired": {
"platformName": "android",
"app": "/Users/saikrisv/git/VodQaAdvancedAppium/VodQA.apk",
"automationName": "Espresso",
"deviceName": "Android Emulator",
"launchTimeout": 900000
},
"platformName": "android",
"app": "/Users/saikrisv/git/VodQaAdvancedAppium/VodQA.apk",
"automationName": "Espresso",
"deviceName": "emulator-5554",
"launchTimeout": 900000,
"deviceUDID": "emulator-5554",
"appPackage": "com.vodqareactnative",
"appWaitPackage": "com.vodqareactnative",
"appActivity": "com.vodqareactnative.MainActivity",
"appWaitActivity": "com.vodqareactnative.MainActivity"
}
Execution
POST /wd/hub/session

Demo
IOS Session Creation
Problem Statement


Unable to launch WebDriverAgent because of xcodebuild failure: "xcodebuild failed with code 65".




W3C
Server Request

WDA
Appium XCUI driver


IOS Architecture
Appium Server
Client

appium-ios-simulator
node-simctl
appium-xcode
appium-remote-debugger
Appium XCUI Driver
Appium IOS Driver
Appium Base Driver


Intialization
APP & WDA
Execution
iOS Life Cycle
Creation
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "iOS");
caps.setCapability("platformVersion", "12.2");
caps.setCapability("deviceName", "iPhone Xs");
caps.setCapability(IOSMobileCapabilityType.BUNDLE_ID, BUNDLE_ID);
caps.setCapability("udid", "666B367A-62E7-45F1-B430-36C2A1971BB8");
driver = new IOSDriver<MobileElement>(new URL("http://localhost:4723/wd/hub"), caps);
Intialization
Driver Creation
App Launch State
Protocol Proxy
XCUI Driver
Appium Base Driver
let automationNameCap = caps.automationName;
automationNameCap = automationNameCap.toLowerCase();
try {
const {driverPackage, driverClassName} = DRIVER_MAP[automationNameCap];
return {
driver,
version: this.getDriverVersion(driver.name, driverPackage),
};

const driver = require['appium-xcuitest-driver'];
- XCUIDriver extends BaseDriver
-
Merges the default values to the user given capability
../appium-xcuitest-driver/lib/driver.js
XCUI Driver
// merge server capabilities + desired capabilities
caps = Object.assign({}, defaultServerCaps, caps);

async createSession (...args) {
this.lifecycleData = {};
try {
let [sessionId, caps] = await super.createSession(...args);
this.opts.sessionId = sessionId;
await this.start();
../appium-xcuitest-driver/lib/simulator-management.js
const {device, udid, realDevice} = await this.determineDevice();
return await getSimulator(appiumTestDevice.udid);
Device Name Platform Version

node-simctl

appium-ios-simulator
async function getExistingSim (opts) {
const devices = await getDevices(opts.platformVersion);
Simulators
Simulators/Real Device
log.debug(`Available devices: ${devices.join(', ')}`); if (!devices.includes(this.opts.udid)) { if (await simExists(this.opts.udid)) { return {device, realDevice: false, udid: this.opts.udid}; } throw new Error(`Unknown device or simulator UDID: '${this.opts.udid}'`); } } if (_.isEmpty(this.opts.platformVersion)) { log.info('Trying to determine platformVersion from ideviceinfo output'); try { const {stdout} = await exec('ideviceinfo', [ '-u', this.opts.udid, '-s', '-k', 'ProductVersion', ]); this.opts.platformVersion = util.coerceVersion(stdout.trim(), false); } catch (e) { log.warn(`Cannot determine real device platform version. Original error: ${e.message}`); } } return {device, realDevice: true, udid: this.opts.udid}; }


node-simctl
idevice_id
const device = await getSimulator(this.opts.udid);
const device = await getRealDeviceObj(this.opts.udid);
const devices = await getConnectedDevices();
App Installation
async function installToSimulator (device, app, bundleId, noReset = true) {
if (bundleId) {
if (await device.isAppInstalled(bundleId)) {
if (noReset) {
log.debug(`App '${bundleId}' is already installed. No need to reinstall.`);
return;
}
log.debug(`Reset requested. Removing app with id '${bundleId}' from the device`);
await device.removeApp(bundleId);
}
}
......
try {
} catch (e) {
}
log.info('Retrying application install');
....
}
log.debug('The app has been installed successfully.');
} finally {
if (tmpRoot && await fs.exists(tmpRoot)) {
await fs.rimraf(tmpRoot);
}
}
}
node-simctl

await device.installApp(app);
App Installation
ios-deploy
async function installToRealDevice (device, app, bundleId, noReset = true) { if (!device.udid || !app) { log.debug('No device id or app, not installing to real device.'); return; } if (await device.isAppInstalled(bundleId)) { if (noReset) { log.debug(`App '${bundleId}' is already installed. No need to reinstall.`); return; } log.debug(`Reset requested. Removing app with id '${bundleId}' from the device`); await device.remove(bundleId); } }

await device.install(app);
WDA Start
wdaLocalPort

async start (buildOnly = false) {
Execution
this.xcodebuild.processExited = true;
if (this.xcodebuild._wda_error_occurred || (!signal && code !== 0)) {
return reject(new Error(`xcodebuild failed with code ${code}${EOL}` +
`xcodebuild error message:${EOL}${this.xcodebuild._wda_error_message}`));
}
this.xcodebuild = await this.createSubProcess(buildOnly);
this.xcodebuild._wda_error_message = '';

try {
await retryInterval(15, 1000, async () => {
try {
this.proxyCommand('/status', 'GET');
await this.startWdaSession(this.opts.bundleId, this.opts.processArguments);
} catch (err) {
originalStacktrace = err.stack;
log.debug(`Failed to create WDA session (${err.message}). Retrying...`);
throw err;
}
});
}

Execution
FindElement
driver.findElement(By.accessibility)
[HTTP] --> POST /wd/hub/session/ca5d6df3-c557-40ae-973a-6620d4a9fc42/elements
[HTTP] {"using":"accessibility id","value":"login"}

@saikrisv
saikrisv@thoughtworks.com
github.com/saikrishna321



@srinivasanskr
sekars@thoughtworks.com
github.com/srinivasantarget







Appium LifeCycle
By Sai Krishna
Appium LifeCycle
- 2,930