Improve your Mobile Automation Framework with Reporting, Parallel Execution and more— Appium 2.x, WebDriverIO and Typescript
In this guide, I’ll be focusing on improvements for the Mobile Automation framework built in my previous guide. If you haven’t read, please do :)
Here’s how to build your own automation framework for Mobile test automation : link to guide
Now, let’s look at the additional improvements you can make on your project..
1. Install dotenv to separate environment variables:
- Run command to install
npm install dotenv
- Add Environment configurations to
.env
file to the root directory:
# Emulator
ANDROID_DEVICE_NAME=emulator-5554
ANDROID_PLATFORM_VERSION=14
# Simulator
IOS_DEVICE_NAME=iPhone 15
IOS_PLATFORM_VERSION=17.2
Some limitations / common issues on Windows vs Mac:
Resolutions for above:
- Call
dotenv.config()
at the Start
#Make sure to load dotenv at the beginning of your wdio.conf.ts or entry script:
import dotenv from 'dotenv';
dotenv.config();
- Use
cross-env
for Cross-Platform Compatibility
#Instead of setting environment variables differently for Windows and macOS/Linux, use cross-env:
npm install --save-dev cross-env
2. Separate capabilities according to Platform & OS:
Modify your WebDriverIO configuration file (wdio.conf.ts
) to include capabilities:
— Android Native App and Chrome Browser
— iOS Native App and iOS Safari Browser
- Create new
capabilities.ts
in root directory (or you can maintain a separate folder for Configs) and add the following:
export const androidAppiumConfig = {
platformName: "Android",
"appium:deviceName": process.env.ANDROID_DEVICE_NAME || "emulator-5554",
"appium:platformVersion": process.env.ANDROID_PLATFORM_VERSION || "14",
"appium:automationName": "UiAutomator2",
"appium:app": "src/app/app.apk",
"appium:autoGrantPermissions": true,
"appium:chromedriverAutodownload": true,
"appium:ignoreHiddenApiPolicyError": true
};
export const androidChromeConfig = {
platformName: "Android",
"appium:deviceName": process.env.ANDROID_DEVICE_NAME || "emulator-5554",
"appium:platformVersion": process.env.ANDROID_PLATFORM_VERSION || "14",
"appium:automationName": "UiAutomator2",
browserName: "Chrome",
"appium:chromedriverAutodownload": true,
"appium:chromedriverExecutable": "chromedriver-mobile/chromedriver"
};
export const iosAppiumConfig = {
platformName: "iOS",
"appium:deviceName": process.env.IOS_DEVICE_NAME || "iPhone 15",
"appium:platformVersion": process.env.IOS_PLATFORM_VERSION || "17.2",
"appium:automationName": "XCUITest",
"appium:app": "src/app/app.ipa",
"appium:autoGrantPermissions": true,
"appium:showXcodeLog": true,
"appium:wdaLaunchTimeout": 60000,
"appium:wdaStartupRetries": 2,
};
export const iosSafariConfig = {
platformName: "iOS",
"appium:deviceName": process.env.IOS_DEVICE_NAME || "iPhone 15",
"appium:platformVersion": process.env.IOS_PLATFORM_VERSION || "17.2",
"appium:automationName": "XCUITest",
browserName: "Safari",
"appium:showXcodeLog": true,
"appium:wdaLaunchTimeout": 60000,
"appium:wdaStartupRetries": 2,
};
- Modify your WebDriverIO configuration file (
wdio.conf.ts
) to use the above configurations:
import { androidAppiumConfig, androidChromeConfig, iosAppiumConfig, iosSafariConfig } from "./capabilities";
const selectedCapability =
process.env.TEST_ENV === "app" && process.env.PLATFORM === "android" ? androidAppiumConfig :
process.env.TEST_ENV === "browser" && process.env.PLATFORM === "android" ? androidChromeConfig :
process.env.TEST_ENV === "app" && process.env.PLATFORM === "ios" ? iosAppiumConfig :
process.env.TEST_ENV === "browser" && process.env.PLATFORM === "ios" ? iosSafariConfig :
androidChromeConfig; // Default to Android Chrome if no matching condition
export const config: WebdriverIO.Config = {
capabilities: [selectedCapability],
}
- Update
LoginPage.ts
so it can handle both Chrome and Native App flows:
class LoginPage {
async open() {
if (process.env.TEST_ENV === "app") {
// If testing the app, it will be launched via Appium
console.log("Running tests in native app mode");
} else {
// If testing in Chrome, open URL
await browser.url('https://example.com/login');
}
}
export default new LoginPage();
- Modify your test file
test.e2e.ts
to support both Chrome and the native app.
import { expect } from '@wdio/globals';
import LoginPage from '../pageobjects/login.page';
import SecurePage from '../pageobjects/secure.page';
describe('My Login application', () => {
it('should login with valid credentials', async () => {
await LoginPage.open();
await LoginPage.login('tomsmith', 'SuperSecretPassword!');
// Verify login success
await expect(SecurePage.flashAlert).toBeExisting();
await expect(SecurePage.flashAlert).toHaveTextContaining('You logged into a secure area!');
});
});
- Modify test run command aligning to above config changes:
# Example : Android Native App test run
npx cross-env TEST_ENV=app PLATFORM=android npx wdio run wdio.conf.ts
# Example : iOS Safari test run
npx cross-env TEST_ENV=browser PLATFORM=ios npx wdio run wdio.conf.ts
3. Externalize Testdata
- Create a Fixture File
testData.json
inside your test project (e.g.,./fixtures/testData.json
):
{
"login": {
"username": "TestUser1",
"password": "Testuser@123"
}
}
- And import the JSON data to your test layer:
// Import test data from the fixture JSON
import * as testData from '../../fixtures/testData.json';
describe('User login to application', async () => {
it('User should be able to login to app successfully', async () => {
await LoginPage.open();
// Use data from the JSON file
await LoginPage.login(testData.login.username, testData.login.password);
});
});
Bonus: Using Multiple Data Sets (for Data-Driven Testing)
- If you have multiple test scenarios, modify
testData.json
like this:
{
"users": [
{
"username": "TestUser1",
"password": "Password@123"
},
{
"username": "TestUser2",
"password": "Password@456"
}
]
}
- Modify your test to run for multiple users:
testData.users.forEach((user) => {
it(`User ${user.username} should be able to place a single bet`, async () => {
await LoginPage.open();
await LoginPage.login(user.username, user.password);
});
});
4. Integrate Allure reporting
- Install Required Dependencies
#Run the following command in your project root:
npm install --save-dev allure-commandline @wdio/allure-reporter
#This will install
# - allure-commandline: CLI tool to generate reports.
# - @wdio/allure-reporter: WebDriverIO plugin for Allure.
- Modify your
wdio.conf.ts
file to include the Allure reporter:
#Add this on top to imports
import allureReporter from "@wdio/allure-reporter";
#Find the reporters section and update it like this:
reporters: [
'spec',
['allure', {
outputDir: 'allure-results', // Folder where raw allure results will be stored
disableWebdriverStepsReporting: false, // Set to true if you don't want to log WebDriver steps
disableWebdriverScreenshotsReporting: false, // Set to false to include screenshots
useCucumberStepReporter: false, // Set to true if using Cucumber
}]
],
- Add Allure hook — Modify the
onComplete
hook inwdio.conf.ts
onComplete: function () {
const reportError = new Error('Could not generate Allure report');
const { exec } = require('child_process');
exec('npx allure generate allure-results --clean -o allure-report', (error: any) => {
if (error) {
console.error(reportError);
} else {
console.log('Allure report successfully generated');
}
});
}
- Capture screenshots on failure — Modify
afterTest
hook inwdio.conf.ts
to take screenshots:
afterTest: async function (test, context, { error }) {
if (error) {
await browser.takeScreenshot();
allureReporter.addAttachment('Screenshot', await browser.takeScreenshot(), 'image/png');
}
}
- Adding Allure Annotations to your functions / tests
public async login (username: string, password: string) {
allureReporter.addStep("Login to application")
await this.inputUsername.setValue(username);
await this.inputPassword.setValue(password);
await this.btnSubmit.click();
}
You can use according to your desire..
- Run Tests and Generate Allure Report
#Execute tests
npx wdio wdio.conf.ts
#Build report
npx allure generate allure-results --clean -o allure-report
#Open report
npx allure open allure-report
Optional: Install Allure for Global Use
- To avoid running
npx
every time;
npm install -g allure-commandline
#Now you can directly user
allure generate allure-results --clean -o allure-report
allure open allure-report
- You can also combine both commands together, and run:
allure generate allure-results --clean -o allure-report && allure open allure-report
This will generate the report and open it in one go!
5. Set Up Multiple Devices
- First, connect multiple devices/emulators and ensure they are listed with:
adb devices
# Example output
List of devices attached
emulator-5554 device
emulator-5556 device
RZ8M92LZN5P device
#Each device has a unique UDID
- Run Multiple Appium Servers (One Per Device) — since Appium does not handle multiple devices on the same server instance well, run separate Appium servers on different ports
appium -p 4723 --relaxed-security &
appium -p 4725 --relaxed-security &
# For Appium 2.0
appium --port 4723 --allow-insecure adb_shell &
appium --port 4725 --allow-insecure adb_shell &
# If you have more than two devices
appium -p 4727 &
- Modify your WebdriverIO config (
wdio.conf.ts
) to run multiple sessions (for 2 devices) — here both devices are hard coded
export const config: WebdriverIO.Config = {
runner: 'local',
port: 4723, // Default Appium port
specs: ['./test/specs/**/*.ts'], // Path to test specs
maxInstances: 2, // Run two tests in parallel
capabilities: [
{
platformName: 'Android',
'appium:deviceName': 'emulator-5554',
'appium:udid': 'emulator-5554', // First device
'appium:platformVersion': '13.0',
'appium:automationName': 'UiAutomator2',
'appium:app': '/path/to/app.apk',
'appium:noReset': true,
'appium:newCommandTimeout': 240,
},
{
platformName: 'Android',
'appium:deviceName': 'emulator-5556',
'appium:udid': 'emulator-5556', // Second device
'appium:platformVersion': '12.0',
'appium:automationName': 'UiAutomator2',
'appium:app': '/path/to/app.apk',
'appium:noReset': true,
'appium:newCommandTimeout': 240,
}
],
services: ['appium'], // Enable Appium service
framework: 'mocha',
reporters: ['spec'],
};
Since capabilities are managed separately, to run tests on multiple devices in parallel, you need to modify selectedCapability
to allow an array of capabilities instead of a single one.
Right now, selectedCapability
only picks one capability. Instead, update it to handle multiple devices.
- Update your
wdio.config.ts
like this:
const selectedCapabilities = process.env.TEST_ENV === "app" && process.env.PLATFORM === "android"
? [androidAppiumConfig1, androidAppiumConfig2] // Add multiple devices
: process.env.TEST_ENV === "browser" && process.env.PLATFORM === "android"
? [androidChromeConfig1, androidChromeConfig2]
: process.env.TEST_ENV === "app" && process.env.PLATFORM === "ios"
? [iosAppiumConfig1, iosAppiumConfig2]
: process.env.TEST_ENV === "browser" && process.env.PLATFORM === "ios"
? [iosSafariConfig1, iosSafariConfig2]
: [androidChromeConfig1, androidChromeConfig2]; // Default to multiple Android Chrome instances
export const config: WebdriverIO.Config = {
capabilities: selectedCapabilities, // Use an array of capabilities
maxInstances: selectedCapabilities.length, // Set max instances dynamically
};
- Modify Your
capabilities.ts
to include Multiple Devices
export const androidAppiumConfig1 = {
platformName: 'Android',
'appium:deviceName': 'emulator-5554',
'appium:udid': 'emulator-5554',
'appium:platformVersion': '13.0',
'appium:automationName': 'UiAutomator2',
'appium:app': '/path/to/app.apk',
'appium:noReset': true,
};
export const androidAppiumConfig2 = {
platformName: 'Android',
'appium:deviceName': 'emulator-5556',
'appium:udid': 'emulator-5556',
'appium:platformVersion': '12.0',
'appium:automationName': 'UiAutomator2',
'appium:app': '/path/to/app.apk',
'appium:noReset': true,
};
- Start Multiple Appium Servers — Since you’re testing multiple devices, each needs its own Appium server
appium -p 4723 - relaxed-security - allow-insecure adb_shell &
appium -p 4725 - relaxed-security - allow-insecure adb_shell &
#Run the Tests in Parallel
TEST_ENV=app PLATFORM=android npx wdio run src/config/wdio.conf.ts
To support multiple Android devices with dynamic environment variables, update your capability setup as follows:
- Updated
androidAppiumConfig.ts
for Multiple Devices — Instead of a single capability object, define an array that supports multiple devices.
export const androidAppiumConfigs = [
{
platformName: "Android",
"appium:deviceName": process.env.ANDROID_DEVICE_NAME_1 || "emulator-5554",
"appium:udid": process.env.ANDROID_UDID_1 || "emulator-5554",
"appium:platformVersion": process.env.ANDROID_PLATFORM_VERSION_1 || "14",
"appium:automationName": "UiAutomator2",
"appium:app": "src/app/app.apk",
"appium:autoGrantPermissions": true,
"appium:chromedriverAutodownload": true,
"appium:ignoreHiddenApiPolicyError": true
},
{
platformName: "Android",
"appium:deviceName": process.env.ANDROID_DEVICE_NAME_2 || "emulator-5556",
"appium:udid": process.env.ANDROID_UDID_2 || "emulator-5556",
"appium:platformVersion": process.env.ANDROID_PLATFORM_VERSION_2 || "13",
"appium:automationName": "UiAutomator2",
"appium:app": "src/app/app.apk",
"appium:autoGrantPermissions": true,
"appium:chromedriverAutodownload": true,
"appium:ignoreHiddenApiPolicyError": true
}
];
- Update
wdio.config.ts
to Support Parallel Execution — Now update your WDIO config to use multiple devices:
import { androidAppiumConfigs } from "./android.appium.config";
export const config: WebdriverIO.Config = {
capabilities: androidAppiumConfigs, // Pass multiple devices
maxInstances: androidAppiumConfigs.length, // Run as many instances as devices available
// Optional: Increase timeouts for multiple device execution
connectionRetryTimeout: 120000,
connectionRetryCount: 3,
// Optional: Parallel execution settings
services: ["appium"],
port: 4723, // Default Appium port
};
- Start Appium Server for Multiple Devices
#Open multiple terminals and run:
appium -p 4723 --relaxed-security --allow-insecure adb_shell &
appium -p 4725 --relaxed-security --allow-insecure adb_shell &
- Run WDIO Tests
AANDROID_DEVICE_NAME_1=emulator-5554 \
AANDROID_DEVICE_NAME_2=emulator-5556 \
npx wdio run src/config/wdio.conf.ts
6. Add Scripts to your Package.json
- For ease of maintenance, you can add your commands to the package.json files and run directly
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"run:wdio": "npx wdio run src/config/wdio.conf.ts",
"start:appium": "appium --relaxed-security",
"run:AndroidApp": "npx cross-env TEST_ENV=app PLATFORM=android npx wdio run src/config/wdio.conf.ts",
"run:AndroidChrome": "npx cross-env TEST_ENV=browser PLATFORM=android npx wdio run src/config/wdio.conf.ts",
"allure:report": "allure generate allure-results --clean -o allure-report && allure open allure-report"
},
- Run as you wish
#Example : to run generate Allure report and open
npm run allure:report
This is it for now, as I improve my project I’ll continue posting to help whoever is interested :)
Until then,
Thank you for reading and Happy Automating!! 🚀