Spread the love

















Ionic is a popular framework in the world of hybrid app development. Sadly, it is well known that app testing isn’t really a major thought given by the framework designers. I believe it makes it a really bad choice for anyone who wants to write a production app using Ionic. Any good framework should encourage and facilitate testing.

While building production apps, we at LeanAgri insist at having tests to maintain high standards in the app. We have currently taken a call of not writing Unit tests and focusing only on E2E tests for our apps. Why?

It just seems like the fastest way to move forward without breaking things. We are rapidly changing our app and we want to confirm that our app flow doesn’t break even if it is restructured.

I think Unit tests which “Mock the world” are really not helpful for an application. (This won’t hold true for Public APIs)

There are a couple of blog posts which explain how to run Ionic E2E tests using ionic serve . Unfortunately, they don’t qualify as E2E tests for us since they are run on a browser, and an app with any added native functionality wouldn’t work on a browser.

Here’s the missing guide to writing a real end to end test with Ionic using Appium.

First and foremost, the acknowledgements: I would thank Microsoft guys for taking the time to write some very good documentation on WD.js which doesn’t exist on the web.

Testing mechanism

Deploy the latest version of your Android app using ionic

Start Appium server to connect with the Android device

Run the tests using Protractor

The stack

Appium with any Android device / Emulator – Read the web on how to install

Working chromedriver for the app you are testing – You can find compatibility information here. This is really important, if you launch your tests with an incompatible version of chromedriver, tests would fail with usually arbitrary reasons.

Testing tools: I ran the tests using Protractor and Jasmine, read below if you want to use WD.js or WebdriverIO for running the tests.

Setup Protractor config:

var SpecReporter = require('jasmine-spec-reporter').SpecReporter; const util = require('util'); const exec = util.promisify(require('child_process').exec); const spawn = require('child_process').spawn; PromiseTool = require('promise-tool'); var appiumProcess; var config = { seleniumAddress: 'http://localhost:4723/wd/hub', specs: ['./e2e/*.e2e-spec.ts'], // Spec files are stored here multiCapabilities: [{ browserName: '', // Leave as blank to run on Appium appPackage: 'io.ionic.starter', // Name of the package appActivity: '.MainActivity', // MainActivity is the activity to launch platformName: 'Android', deviceName: '11cad4be', autoAcceptAlerts: 'true', autoWebview: true // Sets to Webview to allow testing }], framework: 'jasmine', SELENIUM_PROMISE_MANAGER: false, useAllAngular2AppRoots: true, random: false, jasmineNodeOpts: { random: false, }, onPrepare: function () { jasmine.getEnv().addReporter(new SpecReporter()); }, beforeLaunch: async () => { require('ts-node').register({ project: 'e2e' }); } };

An example test login.e2e-spec.ts . Test files should be added in e2e/ folder according to our config above.

import { browser, element, by, ElementFinder, protractor } from 'protractor'; describe('Login', () => { beforeEach(() => { var until = protractor.ExpectedConditions; browser.wait(until.presenceOf(element(by.css('#username input'))), 30000, 'Element didn\'t load till 30 seconds'); }); it('Click login button with valid credentials', async () => { const usernameInput = await element(by.css('#username input')); await usernameInput.clear(); await usernameInput.sendKeys('username'); const passwordInput = await element(by.css('#password input')); await passwordInput.clear(); await passwordInput.sendKeys('password'); await expect(element(by.css('.input .label')) .getAttribute('innerHTML')) .toContain('Username'); await element(by.css('#loginButton')).click(); await expect(element(by.css('.input .label')) .getAttribute('innerHTML')) .toContain('Logged In'); }); });

The above config and example should be self-explanatory. (Leave a comment if it isn’t and I will do my best to help you).

Running the tests

Start appium server with the appropriate version of chromedriver according to your test device. Repeating, this is necessary

I install both of them as dependencies, so I can just run using: ./node_modules/.bin/appium --chromedriver-executable ./node_modules/.bin/chromedriver

I install both of them as dependencies, so I can just run using: Run the test using protractor

Automating everything

I did a couple of further hacks to allow my team to run the tests using a single command.

Added pretest to package.json which deploys the app on phone using ionic cordova run android

to package.json which deploys the app on phone using Protractor starts and stops the appium server using magic of beforeLaunch and afterLaunch in my protractor config. Here is my final config var SpecReporter = require('jasmine-spec-reporter').SpecReporter; const util = require('util'); const exec = util.promisify(require('child_process').exec); const spawn = require('child_process').spawn; PromiseTool = require('promise-tool'); var appiumProcess; var config = { seleniumAddress: 'http://localhost:4723/wd/hub', specs: ['./e2e/*.e2e-spec.ts'], multiCapabilities: [{ browserName: '', appPackage: 'io.ionic.starter', appActivity: '.MainActivity', platformName: 'Android', deviceName: '11cad4be', autoAcceptAlerts: 'true', autoWebview: true }], framework: 'jasmine', SELENIUM_PROMISE_MANAGER: false, useAllAngular2AppRoots: true, random: false, jasmineNodeOpts: { random: false, }, onPrepare: function () { jasmine.getEnv().addReporter(new SpecReporter()); }, beforeLaunch: async () => { require('ts-node').register({ project: 'e2e' }); await launch_appium(); }, afterLaunch: async (code) => { // Stop appium server appiumProcess.kill('SIGINT'); } }; exports.config = config; function launch_appium() { const appium = spawn(process.env.PWD + '/node_modules/.bin/appium', ['--chromedriver-executable', process.env.PWD + '/node_modules/.bin/chromedriver']); appium.stdout.on('data', data => { // console.log(`stdout: ${data}`); }) appium.stderr.on('data', data => { console.log(`ERROR: ${data}`); }) appiumProcess = appium; // Timeout to wait for appium to startup return PromiseTool.setTimeout(10000); }

Brownie points

Other ways of testing if you don’t want to use protractor based setup.

Using WebdriverIO for testing hybrid apps with Appium /// Using WebdriverIO var webdriverio = require('webdriverio'); var client = webdriverio.remote({ port: 4723, logLevel: 'verbose', desiredCapabilities: { browserName: '', app: '/home/kunal/leanagri-data-collection/platforms/android/app/build/outputs/apk/debug/app-debug.apk', platformName: 'Android', deviceName: '11cad4be', autoAcceptAlerts: 'true', automationName: 'Appium', autoWebview: true }, services: ['appium'] }); client.init() .pause(2000) .waitForExist('#username') .clearElement('#username input') .setValue('#username input', 'kunal') .click('#loginButton') .pause(2000) .end()

Using WD.js for testing hybrid apps with Appium var wd = require("wd"); var appDriver = wd.promiseChainRemote({ hostname: 'localhost', port: 4723, }); config = { browserName: '', app: '/home/kunal/leanagri-data-collection/platforms/android/app/build/outputs/apk/debug/app-debug.apk', platformName: 'Android', deviceName: '11cad4be', autoAcceptAlerts: 'true', automationName: 'Appium', autoWebview: true }; /// Method 1 WD appDriver.init(config) .sleep(3000) .waitForElementByCssSelector('#username input') .elementByCssSelector('#username input') .sendKeys('qeruty') .elementById('loginButton') .click() .sleep(4000) .fin(function() { return appDriver.quit(); }) .done(); /// Method 2 WD (Allows debugging) appDriver.init(config, function () { console.log("initialized"); appDriver.sleep(3000, function () { console.log("Sleeping completed"); appDriver.elementByCssSelector('#username input', function (err, element) { console.log("Found element"); element.sendKeys('', function (err) { console.log("Sent keys"); console.log(err); appDriver.elementByCssSelector('#loginButton', function(err, element) { element.click(function() { appDriver.sleep(3000, function () { appDriver.quit(); }); }); }) }); }); });

Share this: Twitter

Facebook

LinkedIn

Reddit

Pocket

WhatsApp



Like this: Like Loading...