I'm Srinivasan
Lead Consultant
@srinivasanskr
srinivasanTarget
Problem Statement
Android Architecture
W3C
Server Request
Server
Server Request
Appium Android Driver
Appium ADB
Appium Espresso Driver
Appium Base Driver
Appium UIA2 Driver
Appium Support
Initialisation
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
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);
async startEspressoSession () {
logger.info(`EspressoDriver version: ${version}`);
await helpers.getDeviceInfoFromCaps(this.opts);
....
await androidHelpers.createADB(this.opts);
....
this.initEspressoServer();
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
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
IOS Session Creation
Problem Statement
W3C
Server Request
WDA
IOS Architecture
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'];
Merges the default values to the user given capability
../appium-xcuitest-driver/lib/driver.js
// 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);
async function getExistingSim (opts) {
const devices = await getDevices(opts.platformVersion);
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}; }
const device = await getSimulator(this.opts.udid);
const device = await getRealDeviceObj(this.opts.udid);
const devices = await getConnectedDevices();
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);
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);
wdaLocalPort
async start (buildOnly = false) {
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;
}
});
}
[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