/*jshint node: true, jasmine: true, browser: true */ /*global ContactFindOptions, ContactName, Q*/ /* * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ // these tests are meant to be executed by Cordova Medic Appium runner // you can find it here: https://github.com/apache/cordova-medic/ // it is not necessary to do a full CI setup to run these tests, just run: // node cordova-medic/medic/medic.js appium --platform android --plugins cordova-plugin-contacts 'use strict'; var wdHelper = global.WD_HELPER; var screenshotHelper = global.SCREENSHOT_HELPER; var contactsHelper = require('../helpers/contactsHelper'); var MINUTE = 60 * 1000; var PLATFORM = global.PLATFORM; var UNORM = global.UNORM; describe('Contacts UI Automation Tests', function () { var driver; var webviewContext; var promiseCount = 0; // going to set this to false if session is created successfully var failedToStart = true; function getNextPromiseId() { return 'appium_promise_' + promiseCount++; } function saveScreenshotAndFail(error) { fail(error); return screenshotHelper .saveScreenshot(driver) .quit() .then(function () { return getDriver(); }); } function getDriver() { driver = wdHelper.getDriver(PLATFORM); return wdHelper.getWebviewContext(driver, 2) .then(function (context) { webviewContext = context; return driver.context(webviewContext); }) .then(function () { return wdHelper.waitForDeviceReady(driver); }) .then(function () { return wdHelper.injectLibraries(driver); }); } function addContact(firstName, lastName, bday) { var bdayString = bday ? bday.toDateString() : undefined; var contactName = contactsHelper.getContactName(firstName, lastName); return driver .context(webviewContext) .setAsyncScriptTimeout(MINUTE) .executeAsync(function (contactname, bday, callback) { navigator.contacts.create({ 'displayName': contactname.formatted, 'name': contactname, 'note': 'DeleteMe', 'birthday': new Date(bday) }).save(function (successResult) { callback(successResult); }, function (failureResult) { callback(failureResult); }); }, [contactName, bdayString]) .then(function (result) { if (result && result.hasOwnProperty('code')) { throw result; } return result; }); } function pickContact(name) { var promiseId = getNextPromiseId(); return driver .context(webviewContext) .execute(function (pID) { navigator._appiumPromises[pID] = Q.defer(); navigator.contacts.pickContact(function (contact) { navigator._appiumPromises[pID].resolve(contact); }, function (err) { navigator._appiumPromises[pID].reject(err); }); }, [promiseId]) .context('NATIVE_APP') .then(function () { switch (PLATFORM) { case 'ios': return driver .waitForElementByAccessibilityId(name, 20000) .elementByAccessibilityId(name); case 'android': return driver .waitForElementByXPath('//android.widget.TextView[@text="' + name + '"]', MINUTE); } }) .click() .context(webviewContext) .executeAsync(function (pID, cb) { navigator._appiumPromises[pID].promise .then(function (contact) { // for some reason Appium cannot get Date object // let's make birthday a string then contact.birthday = contact.birthday.toDateString(); cb(contact); }, function (err) { cb('ERROR: ' + err); }); }, [promiseId]) .then(function (result) { if (typeof result === 'string' && result.indexOf('ERROR:') === 0) { throw result; } return result; }); } function renameContact(oldName, newGivenName, newFamilyName) { return driver .context(webviewContext) .setAsyncScriptTimeout(7 * MINUTE) .executeAsync(function (oldname, newgivenname, newfamilyname, callback) { var obj = new ContactFindOptions(); obj.filter = oldname; obj.multiple = false; navigator.contacts.find(['displayName', 'name'], function (contacts) { if (contacts.length === 0) { callback({ 'code': -35142 }); return; } var contact = contacts[0]; contact.displayName = newgivenname + ' ' + newfamilyname; var name = new ContactName(); name.givenName = newgivenname; name.familyName = newfamilyname; contact.name = name; contact.save(callback, callback); }, function (result) { callback(result); }, obj); }, [oldName, newGivenName, newFamilyName]) .then(function (result) { if (result && result.hasOwnProperty('code')) { if (result.code === -35142) { throw 'Couldn\'t find the contact "' + oldName + '"'; } throw result; } return result; }); } function removeTestContacts() { return driver .context(webviewContext) .setAsyncScriptTimeout(MINUTE) .executeAsync(function (callback) { var obj = new ContactFindOptions(); obj.filter = 'DeleteMe'; obj.multiple = true; navigator.contacts.find(['note'], function (contacts) { var removes = []; contacts.forEach(function (contact) { removes.push(contact); }); if (removes.length === 0) { return; } var nextToRemove; if (removes.length > 0) { nextToRemove = removes.shift(); } function removeNext(item) { if (typeof item === 'undefined') { callback(); return; } if (removes.length > 0) { nextToRemove = removes.shift(); } else { nextToRemove = undefined; } item.remove(function removeSucceeded() { removeNext(nextToRemove); }, function removeFailed() { removeNext(nextToRemove); }); } removeNext(nextToRemove); }, function (failureResult) { callback(failureResult); }, obj); }, []) .then(function (result) { if (typeof result !== 'undefined') { throw result; } }); } function checkSession(done) { if (failedToStart) { fail('Failed to start a session'); done(); } } afterAll(function (done) { checkSession(done); driver .quit() .done(done); }, MINUTE); it('should connect to an appium endpoint properly', function (done) { getDriver() .then(function () { failedToStart = false; }, fail) .then(function () { // on iOS and Android >= 6, first interaction with contacts API will trigger the permission dialog. // We will attempt to bust it manually here, by triggering the contacts API // and waiting for the native dialog to show up, then dismissing the alert. // This only needs to be done once. // NOTE: in earlier versions of iOS (9.3 and below), using the older UI testing library // (UIAutomation), Appium's autoAcceptAlerts capability handles this for us. This logic // is here as a transition between UIAutomation and XCUITest and is compatible with both. // More details in the comment below. var promiseId = getNextPromiseId(); var contactName = contactsHelper.getContactName('Permission', 'Buster'); return driver .context(webviewContext) .execute(function (pID, contactname) { navigator._appiumPromises[pID] = Q.defer(); navigator.contacts.create({ 'displayName': contactname.formatted, 'name': contactname, 'note': 'DeleteMe' }).save(function (contact) { navigator._appiumPromises[pID].resolve(contact); }, function (err) { navigator._appiumPromises[pID].reject(err); }); }, [promiseId, contactName]) .context('NATIVE_APP') .then(function () { // iOS if (PLATFORM === 'ios') { return driver.acceptAlert() .then(function alertDismissed() { // TODO: once we move to only XCUITest-based (which is force on you in either iOS 10+ or Xcode 8+) // UI tests, we will have to: // a) remove use of autoAcceptAlerts appium capability since it no longer functions in XCUITest // b) can remove this entire then() clause, as we do not need to explicitly handle the acceptAlert // failure callback, since we will be guaranteed to hit the permission dialog on startup. }, function noAlert() { // in case the contacts permission alert never showed up: no problem, don't freak out. // This can happen if: // a) The applications-under-test already had contacts permissions granted to it // b) Appium's autoAcceptAlerts capability is provided (and functioning) }); } // Android return driver .elementByXPath('//android.widget.Button[translate(@text, "alow", "ALOW")="ALLOW"]') .click() .fail(function noAlert() { }); }) .context(webviewContext) .executeAsync(function (pID, cb) { navigator._appiumPromises[pID].promise .then(function (result) { cb(result); }, function (err) { cb('ERROR: ' + err); }); }, [promiseId]) .then(function (result) { if (typeof result === 'string' && result.indexOf('ERROR:') === 0) { throw result; } return result; }); }) .done(done); }, 10 * MINUTE); describe('Picking contacts', function () { afterEach(function (done) { checkSession(done); removeTestContacts() .finally(done); }, MINUTE); it('contacts.ui.spec.1 Pick a contact', function (done) { checkSession(done); var bday = new Date(1991, 1, 1); driver .then(function () { return addContact('Test', 'Contact', bday); }) .then(function () { return pickContact('Test Contact'); }) .then(function (contact) { expect(contact.name.givenName).toBe('Test'); expect(contact.name.familyName).toBe('Contact'); expect(contact.birthday).toBe(bday.toDateString()); }) .fail(saveScreenshotAndFail) .done(done); }, 5 * MINUTE); it('contacts.ui.spec.2 Update an existing contact', function (done) { checkSession(done); driver .then(function () { return addContact('Dooney', 'Evans'); }) .then(function () { return renameContact('Dooney Evans', 'Urist', 'McContact'); }) .then(function () { return pickContact('Urist McContact'); }) .then(function (contact) { expect(contact.name.givenName).toBe('Urist'); expect(contact.name.familyName).toBe('McContact'); }) .fail(saveScreenshotAndFail) .done(done); }, 10 * MINUTE); it('contacts.ui.spec.3 Create a contact with no name', function (done) { checkSession(done); driver .then(function () { return addContact(); }) .then(function () { switch (PLATFORM) { case 'android': return pickContact('(No name)'); case 'ios': return pickContact('No Name'); } }) .then(function (contact) { if (contact.name) { expect(contact.name.givenName).toBeFalsy(); expect(contact.name.middleName).toBeFalsy(); expect(contact.name.familyName).toBeFalsy(); expect(contact.name.formatted).toBeFalsy(); } else { expect(contact.name).toBeFalsy(); } }) .fail(saveScreenshotAndFail) .done(done); }, 5 * MINUTE); it('contacts.ui.spec.4 Create a contact with Unicode characters in name', function (done) { checkSession(done); driver .then(function () { return addContact('Н€йромонах', 'ФеофаЊ'); }) .then(function () { return pickContact('Н€йромонах ФеофаЊ'); }) .then(function (contact) { expect(contact.name.givenName).toBe('Н€йромонах'); expect(contact.name.familyName).toBe('ФеофаЊ'); }) .fail(saveScreenshotAndFail) .done(done); }, 5 * MINUTE); }); });