diff options
| -rw-r--r-- | config.xml | 6 | ||||
| -rw-r--r-- | package.json | 4 | ||||
| -rw-r--r-- | tests/.gitignore | 1 | ||||
| -rw-r--r-- | tests/README.TXT | 9 | ||||
| -rw-r--r-- | tests/chromedriver/.gitignore | 1 | ||||
| -rw-r--r-- | tests/chromedriver/README.TXT | 2 | ||||
| -rwxr-xr-x | tests/startappium.sh | 2 | ||||
| -rw-r--r-- | tests/testcases/.gitignore | 3 | ||||
| -rw-r--r-- | tests/testcases/README.TXT | 1 | ||||
| -rw-r--r-- | tests/testcases/__init__.py | 0 | ||||
| -rw-r--r-- | tests/testcases/app.py | 25 | ||||
| -rw-r--r-- | tests/testcases/common.py | 113 | ||||
| -rw-r--r-- | tests/testcases/montage.py | 20 | ||||
| -rw-r--r-- | tests/testcases/test.py | 74 | ||||
| -rw-r--r-- | tests/testcases/wizard.py | 56 | ||||
| -rw-r--r-- | www/templates/first-use.html | 4 | ||||
| -rw-r--r-- | www/templates/login.html | 2 | ||||
| -rw-r--r-- | www/templates/menu.html | 6 | ||||
| -rw-r--r-- | www/templates/wizard.html | 26 |
19 files changed, 331 insertions, 24 deletions
@@ -173,10 +173,10 @@ </plugin> <plugin name="cordova-custom-config" spec="5.0.2" /> <plugin name="cordova-plugin-advanced-http" spec="https://github.com/silkimen/cordova-plugin-advanced-http.git" /> - <plugin name="cordova-plugin-ionic-webview" spec="https://github.com/pliablepixels/cordova-plugin-ionic-webview.git"> - <variable name="ANDROID_SUPPORT_ANNOTATIONS_VERSION" value="27.+" /> - </plugin> <plugin name="cordova-plugin-media-pp-fork" spec="^1.0.2-dev" /> <engine name="android" spec="7.1.4" /> <engine name="ios" spec="5.0.0" /> + <plugin name="cordova-plugin-ionic-webview" spec="https://github.com/pliablepixels/cordova-plugin-ionic-webview.git"> + <variable name="ANDROID_SUPPORT_ANNOTATIONS_VERSION" value="27.+" /> + </plugin> </widget> diff --git a/package.json b/package.json index 42eaa48e..6a97ea10 100644 --- a/package.json +++ b/package.json @@ -57,10 +57,10 @@ "FCM_VERSION": "11.6.2" }, "cordova-custom-config": {}, + "cordova-plugin-media-pp-fork": {}, "cordova-plugin-ionic-webview": { "ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+" - }, - "cordova-plugin-media-pp-fork": {} + } } }, "dependencies": { diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000..fbfa7d1b --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +**/*.pyc diff --git a/tests/README.TXT b/tests/README.TXT new file mode 100644 index 00000000..70cfeeba --- /dev/null +++ b/tests/README.TXT @@ -0,0 +1,9 @@ +Appium install: http://appium.io/docs/en/about-appium/getting-started/ +Python Appium client install: https://github.com/appium/python-client + +I currently test on an Android 7.1.1 emulator. Change test.py config as needed + +./startappium.sh + + + diff --git a/tests/chromedriver/.gitignore b/tests/chromedriver/.gitignore new file mode 100644 index 00000000..2004ae02 --- /dev/null +++ b/tests/chromedriver/.gitignore @@ -0,0 +1 @@ +chromedriver diff --git a/tests/chromedriver/README.TXT b/tests/chromedriver/README.TXT new file mode 100644 index 00000000..1c78b3b2 --- /dev/null +++ b/tests/chromedriver/README.TXT @@ -0,0 +1,2 @@ +Download the chrome driver version corresponding to the chrome browser version you are testing with +from http://chromedriver.chromium.org/downloads and save it here as 'chromedriver' diff --git a/tests/startappium.sh b/tests/startappium.sh new file mode 100755 index 00000000..257dbfff --- /dev/null +++ b/tests/startappium.sh @@ -0,0 +1,2 @@ +#!/bin/bash +appium --chromedriver-executable ./chromedriver/chromedriver diff --git a/tests/testcases/.gitignore b/tests/testcases/.gitignore new file mode 100644 index 00000000..5f62c8e9 --- /dev/null +++ b/tests/testcases/.gitignore @@ -0,0 +1,3 @@ +screenshots +*.png +*.apk diff --git a/tests/testcases/README.TXT b/tests/testcases/README.TXT new file mode 100644 index 00000000..7cae6770 --- /dev/null +++ b/tests/testcases/README.TXT @@ -0,0 +1 @@ +python ./test.py diff --git a/tests/testcases/__init__.py b/tests/testcases/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/testcases/__init__.py diff --git a/tests/testcases/app.py b/tests/testcases/app.py new file mode 100644 index 00000000..b76f1edd --- /dev/null +++ b/tests/testcases/app.py @@ -0,0 +1,25 @@ +''' +App level functions for view interaction +''' + + + +import common as c + + +# tap global menu directly by invoking its controller JS +# seems reliable +def tap_menu_js(): + c.log ('Tapping menu') + c.driver.execute_script("angular.element(document.getElementById('testaut-menu-controller')).scope().openMenu();") + +# bah, in some cases this won't work, when the menu is open, +# there are two left items +# so I'm probably not going to use this +def tap_menu(): + c.log ('Tapping menu') + element = c.driver.find_element_by_id('testaut_app_menu') + element = element.find_element_by_class_name('left-buttons') + element = element.find_element_by_tag_name('button') + element.click() + diff --git a/tests/testcases/common.py b/tests/testcases/common.py new file mode 100644 index 00000000..6b3b2571 --- /dev/null +++ b/tests/testcases/common.py @@ -0,0 +1,113 @@ +''' +Common functions used by test cases + +''' + + +from time import sleep,strftime +import os +from selenium.webdriver.common.touch_actions import TouchActions +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC + + + +# global pointer to chrome driver +driver = None + +# keeps incrementing by 1 for screenshots saved +image_counter = 1 + +# global ZM portal data +testConfig = { + 'portal':None, + 'user':None, + 'password':None, + 'use_auth':False, + 'use_zm_auth':False, + 'use_basic_auth':False, + 'basic_user':None, + 'basic_password':None, + 'screenshot_dir':'./screenshots' + +} + +def log(s): + print (strftime("%H:%M:%S") + ": " + s) + + +# central function to save a screenshot +def take_screenshot(id,fname): + global image_counter + log ('Taking screenshot') + if fname == None: + fname = id+'-image.png' + fname = '{:03d}-'.format(image_counter)+fname + driver.get_screenshot_as_file(testConfig['screenshot_dir']+'/'+fname) + log ('Screenshot stored in '+fname) + image_counter = image_counter + 1 + + +# makes sure we can see the element to avoid out of view issues +def _goto_element(e): + driver.execute_script("arguments[0].scrollIntoView();", e) + +# waits for an element to load +# allows you to also specify if you want a screenshot after it comes in +def _wait_for_id(id=id,dur=30, save_screenshot=False, save_screenshot_file=None): + log ('Waiting for '+id+'...') + WebDriverWait(driver, dur).until(EC.presence_of_element_located((By.ID, id))) + if save_screenshot: + take_screenshot(id,save_screenshot_file) + + +def get_element_attributes(id=id, wait_dur=30, save_screenshot=False, save_screenshot_file=None): + _wait_for_id(id=id, save_screenshot=save_screenshot, save_screenshot_file = save_screenshot_file) + element = driver.find_element_by_id(id) + return element + +# handle ion-alerts. Only single button for now. May extend later if I need +def click_popup(save_screenshot=False, save_screenshot_file=None): + log ('Waiting for popup...') + WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, 'popup-buttons'))) + if save_screenshot: + take_screenshot(None,'wizard-save-results.png') + element = driver.find_element_by_class_name('popup-buttons') + element = element.find_element_by_tag_name('button') + element.click() + + +# handle ion-toggle +def tap_toggle(id, save_screenshot=False, save_screenshot_file=None): + _wait_for_id(id=id, save_screenshot=save_screenshot, save_screenshot_file = save_screenshot_file) + element = driver.find_element_by_id(id) + _goto_element(element) + element = element.find_element_by_tag_name('label') + element.click() + +# generates click event for any web element id +def click_item(id=id, save_screenshot=False, save_screenshot_file=None): + _wait_for_id(id=id, save_screenshot=save_screenshot, save_screenshot_file = save_screenshot_file) + element = driver.find_element_by_id(id) + _goto_element(element) + element.click() + #sleep(wait) + +# generated double click event for any web element id +def dbl_click_item(id=id, save_screenshot=False, save_screenshot_file=None): + _wait_for_id(id=id, save_screenshot=save_screenshot, save_screenshot_file = save_screenshot_file) + element = driver.find_element_by_id(id) + actions = TouchActions(driver) + actions.double_tap(element) + actions.perform() + +# handles text input +def input_item(id=id,txt="you forgot to specify text", save_screenshot=False, save_screenshot_file=None): + _wait_for_id(id=id, save_screenshot=save_screenshot, save_screenshot_file = save_screenshot_file) + element = driver.find_element_by_id(id) + _goto_element(element) + element.send_keys(txt) + driver.hide_keyboard() + #sleep(wait) diff --git a/tests/testcases/montage.py b/tests/testcases/montage.py new file mode 100644 index 00000000..2b3f2c4d --- /dev/null +++ b/tests/testcases/montage.py @@ -0,0 +1,20 @@ +''' +Validates Montage view +''' + +import common as c +from time import sleep +import app + +def run_tests(self): + app.tap_menu_js() + c.log ('Validating montage') + c.click_item('testaut_menu_montage') + c.click_item('img-1') + sleep(4) + c.take_screenshot(None,'montage-singleview.png') + c.dbl_click_item('singlemonitor') + + + + diff --git a/tests/testcases/test.py b/tests/testcases/test.py new file mode 100644 index 00000000..fff7c607 --- /dev/null +++ b/tests/testcases/test.py @@ -0,0 +1,74 @@ +''' +Main zmNinja test driver +Invokes other test cases +''' + +import unittest +from time import sleep +from appium import webdriver +import os +import glob + +import common as c +import wizard +import app +import montage + +class ZmninjaAndroidTests(unittest.TestCase): + 'Class to run tests against zmNinja' + + def setUp(self): + c.log ('Setting up....') + + desired_caps = { + 'platformName': 'Android', + 'automationName': 'UiAutomator2', + 'platformVersion': '7.1.1', + 'deviceName': 'Pixel', + 'nativeWebTap': True, + 'nativeWebScreenshot': True, # important, for screenshots + 'autoAcceptAlerts': True, + 'autoGrantPermissions': True, + 'appPackage': 'com.pliablepixels.zmninja_pro', + 'appActivity': 'com.pliablepixels.zmninja_pro.MainActivity' + } + # Returns abs path relative to this file and not cwd + desired_caps['app'] = os.path.abspath(os.path.join(os.path.dirname(__file__),'./zmNinja.apk')) + c.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) + c.driver.switch_to.context('WEBVIEW_com.pliablepixels.zmninja_pro') + + + def tearDown(self): + c.log ('Test complete') + c.driver.quit() + + + def wait_for_app_start(self): + c.log ('Waiting for app to start') + #sleep (5) + + def test_app(self): + c.testConfig['portal'] = 'https://demo.zoneminder.com/zm' + c.testConfig['user'] = 'zmuser' + c.testConfig['password'] = 'zmpass' + c.testConfig['use_auth'] = True + c.testConfig['use_zm_auth'] = True + c.testConfig['use_basic_auth'] = False + c.testConfig['screenshot_dir'] = './screenshots' + + if not os.path.exists(c.testConfig['screenshot_dir']): + os.makedirs(c.testConfig['screenshot_dir']) + files = glob.glob(c.testConfig['screenshot_dir']+'/*') + for f in files: + os.remove(f) + + self.wait_for_app_start() + wizard.run_tests(self) + montage.run_tests(self) + + + +#---START OF SCRIPT +if __name__ == '__main__': + suite = unittest.TestLoader().loadTestsFromTestCase(ZmninjaAndroidTests) + unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/tests/testcases/wizard.py b/tests/testcases/wizard.py new file mode 100644 index 00000000..cf73deab --- /dev/null +++ b/tests/testcases/wizard.py @@ -0,0 +1,56 @@ +''' +Validates Wizard view +''' + +import common as c +import app +from time import sleep + +def run_tests(self): + + success_color = 'rgba(22, 160, 133, 1)' + fail_color = 'rgba(231, 76, 60, 1)' + + c.log ('Validating wizard test case') + c.click_item('testaut_wizard_button') + + c.log ('Entering portal text') + c.input_item('testaut_portal_input', c.testConfig['portal']) + + c.click_item('testaut_wiz1_next_button') + + c.log ('Setting up auth parameters') + # fill in auth settings based on how you configured the server + if c.testConfig['use_auth']: + c.tap_toggle('testaut_useauth_toggle') + if c.testConfig['use_zm_auth']: + c.tap_toggle('testaut_usezmauth_toggle') + c.input_item('testaut_zmauthusername_input', c.testConfig['user']) + c.input_item('testaut_zmauthpassword_input', c.testConfig['password']) + if c.testConfig['use_basic_auth']: + c.tap_toggle('testaut_usebasicauth_toggle') + c.input_item('testaut_basicauthusername_input', c.testConfig['user']) + c.input_item('testaut_basicauthpassword_input', c.testConfig['password']) + c.click_item('testaut_wiz2_next_button') + + # Now check wizard results + portal_ok = c.get_element_attributes('testaut_wizard_portal_results') + portal_color = portal_ok.value_of_css_property('color') + self.assertEqual(portal_color,success_color) + # don't do this before assert. If portal fails, api won't show + api_ok = c.get_element_attributes('testaut_wizard_api_results') + api_color = api_ok.value_of_css_property('color') + self.assertEqual(api_color,success_color) + + # Wait for bit for cgi-bin. Don't really care, but hey if we catch it, cool + sleep(3) + c.take_screenshot(None,'wizard-detection-results.png') + + c.click_item('testaut_wizard_goto_login') + c.click_item('testaut_settings_save') + + + # discard the popup and get to sane state + c.click_popup(save_screenshot=True, save_screenshot_file='./screenshots/wizard-save-report.png') + sleep(3) + app.tap_menu_js() diff --git a/www/templates/first-use.html b/www/templates/first-use.html index 5f98bb6b..751fc786 100644 --- a/www/templates/first-use.html +++ b/www/templates/first-use.html @@ -20,10 +20,10 @@ </span> <br /> <div id="firstuse"> - <button class="button icon icon-left ion-wand button-stable animated bounceInUp" ng-click="goToWizard()"> + <button id = "testaut_wizard_button" class="button icon icon-left ion-wand button-stable animated bounceInUp" ng-click="goToWizard()"> {{'kWizard' | translate}} </button> - <button class="button icon icon-left ion-university button-stable animated bounceInUp" ng-click="goToLogin()"> + <button class="button icon icon-left ion-university button-stable animated bounceInUp" ng-click="goToLogin()"> {{'kExpert' | translate }} </button> <br /> diff --git a/www/templates/login.html b/www/templates/login.html index 2d406223..5e823a56 100644 --- a/www/templates/login.html +++ b/www/templates/login.html @@ -6,7 +6,7 @@ ng-click="handleAlarms();" ng-if="$root.isAlarm"></button> </ion-nav-buttons> <ion-nav-buttons side="right"> - <button class="button button-clear" ng-click="saveItems()">{{'kSave' | translate}}</button> + <button id = 'testaut_settings_save' class="button button-clear" ng-click="saveItems()">{{'kSave' | translate}}</button> </ion-nav-buttons> <ion-content delegate-handle="none" overflow-scroll="false" mouse-wheel-scroll> <div class="item item-text-wrap item-input-inset"> diff --git a/www/templates/menu.html b/www/templates/menu.html index ba8b78fa..26201765 100644 --- a/www/templates/menu.html +++ b/www/templates/menu.html @@ -1,5 +1,5 @@ -<div ng-controller="MenuController"> - <ion-side-menus delegate-handle="sideMenu" ng-cloak> +<div id="testaut-menu-controller" ng-controller="MenuController"> + <ion-side-menus id="testaut_app_menu" delegate-handle="sideMenu" ng-cloak> <ion-side-menu-content> <ion-nav-bar class="bar-stable nav-title-slide-ios7"> <ion-nav-back-button class="button-icon"> @@ -25,7 +25,7 @@ </span>{{'kBookmark'| translate}} </ion-item> - <ion-item id="menu-move-1" href="#/app/montage" ng-click="go('/app/montage')"> + <ion-item id="testaut_menu_montage" href="#/app/montage" ng-click="go('/app/montage')"> <!--<span ng-if="$root.runMode=='lowbw'" style="float:right;margin-top:-18px;background-color:#f1c40f;color:#000;font-size:11px;opacity:0.7;width:20px;border-radius: 0px 0px 5px 5px;display:inline-block;text-align:center;"> <i class="icon ion-arrow-graph-down-left"></i> </span>--> <span class=" item-icon-left"> diff --git a/www/templates/wizard.html b/www/templates/wizard.html index 25e59737..de1e6e67 100644 --- a/www/templates/wizard.html +++ b/www/templates/wizard.html @@ -13,7 +13,7 @@ <p>{{'kWizConfigPain' | translate}}</p> <h4>{{'kWizPortalUrl' | translate}}</h4> <label class="item item-input"> - <input autocorrect="off" autocapitalize="none" autocomplete="off" type="text" placeholder="{{'kPortalUrlExample' | translate}}" + <input id="testaut_portal_input" autocorrect="off" autocapitalize="none" autocomplete="off" type="text" placeholder="{{'kPortalUrlExample' | translate}}" ng-model="wizard.portalurl"> </label> <p ng-if="$root.platformOS=='android'" style="font-size:0.8em; color:gray">{{'kDisableSamsung' | translate}}</p> @@ -24,43 +24,43 @@ <img src="img/portalurl.png" width="30%"> </div> <br /> - <button class="button button-small icon icon-right ion-chevron-right" wz-next>{{'kNext' | translate}}</button> + <button id = "testaut_wiz1_next_button" class="button button-small icon icon-right ion-chevron-right" wz-next>{{'kNext' | translate}}</button> </wz-step> <!-- auth mode --> <wz-step wz-title="2"> <h4>{{'kWizPortalAuth' | translate}}</h4> <!--<img src="img/wizard.svg" width="100px" style="float:left"/>--> <p>{{'kWizPortalText' | translate}}</p> - <ion-toggle ng-change="toggleAuth()" ng-model="wizard.useauth" toggle-class="toggle-calm">{{'kWizUseAuth' | + <ion-toggle id="testaut_useauth_toggle" ng-change="toggleAuth()" ng-model="wizard.useauth" toggle-class="toggle-calm">{{'kWizUseAuth' | translate}}</ion-toggle> - <ion-toggle ng-show="wizard.useauth" ng-model="wizard.usezmauth" toggle-class="toggle-calm">{{'kWizZMAuth' | + <ion-toggle id="testaut_usezmauth_toggle" ng-show="wizard.useauth" ng-model="wizard.usezmauth" toggle-class="toggle-calm">{{'kWizZMAuth' | translate}}</ion-toggle> <div ng-if="wizard.usezmauth"> <label class="item item-input item-floating-label"> <span class="input-label">{{'kUserName' | translate}}</span> - <input autocorrect="off" autocapitalize="none" autocomplete="off" type="text" ng-model="wizard.zmuser" + <input id="testaut_zmauthusername_input" autocorrect="off" autocapitalize="none" autocomplete="off" type="text" ng-model="wizard.zmuser" placeholder="{{'kPlaceHolderZMAuthUser'|translate}}"> </label> <label class="item item-text-wrap item-input item-floating-label"> <span class="input-label">{{'kPassword' | translate}}</span> - <input type="password" ng-model="wizard.zmpassword" placeholder="{{'kPlaceHolderZMAuthPass'|translate}}"> + <input id="testaut_zmauthpassword_input" type="password" ng-model="wizard.zmpassword" placeholder="{{'kPlaceHolderZMAuthPass'|translate}}"> <!--<p >{{'kWizPasswdNote' | translate}}</p>--> </label> </div> - <ion-toggle ng-show="wizard.useauth" ng-model="wizard.usebasicauth" toggle-class="toggle-calm">{{'kWizBasicAuth' + <ion-toggle id="testaut_usebasicauth_toggle" ng-show="wizard.useauth" ng-model="wizard.usebasicauth" toggle-class="toggle-calm">{{'kWizBasicAuth' | translate}} <p ng-show="wizard.usebasicauth">{{'kWarningBasicAuth'|translate}}</p> </ion-toggle> <label class="item item-input item-floating-label" ng-show="wizard.usebasicauth"> <span class="input-label">{{'kUserName' | translate}}</span> - <input autocorrect="off" autocapitalize="none" autocomplete="off" type="text" ng-model="wizard.basicuser" + <input id="testaut_basicauthusername_input" autocorrect="off" autocapitalize="none" autocomplete="off" type="text" ng-model="wizard.basicuser" placeholder="{{'kPlaceHolderBasicAuthUser'|translate}}"> </label> <label class="item item-input item-text-wrap item-floating-label" ng-show="wizard.usebasicauth"> <span class="input-label">{{'kPassword' | translate}}</span> - <input type="password" ng-model="wizard.basicpassword" placeholder="{{'kPlaceHolderBasicAuthPass'|translate}}"> + <input id="testaut_basicauth_password_input" type="password" ng-model="wizard.basicpassword" placeholder="{{'kPlaceHolderBasicAuthPass'|translate}}"> <p>{{'kWizPasswdNote' | translate}}</p> </label> <a class="button icon-left ion-information-circled button-clear button-dark" ng-click="toggleTip()">{{wizard.tiptext}}</a> @@ -70,18 +70,18 @@ </div> <br /> <button class="button button-small icon icon-left ion-chevron-left" wz-previous>{{'kPrev' | translate}}</button> - <button class="button button-small icon icon-right ion-chevron-right" ng-click="exitAuth()">{{'kNext' | + <button id="testaut_wiz2_next_button" class="button button-small icon icon-right ion-chevron-right" ng-click="exitAuth()">{{'kNext' | translate}}</button> </wz-step> <wz-step wz-title="3"> <br /> <br /> <h4>{{'kWizResults' | translate}}</h4> - <span ng-if="wizard.portalValidText" ng-style="{'color':wizard.portalColor}"> + <span id="testaut_wizard_portal_results" ng-if="wizard.portalValidText" ng-style="{'color':wizard.portalColor}"> <i ng-class="wizard.portalColor=='#16a085' ? 'ion-checkmark-circled':'ion-close-circled'"></i> {{wizard.portalValidText}} <br /> </span> - <span ng-if="wizard.apiValidText" ng-style="{'color':wizard.apiColor}"> + <span id="testaut_wizard_api_results" ng-if="wizard.apiValidText" ng-style="{'color':wizard.apiColor}"> <i ng-class=" wizard.apiColor=='#16a085' ? 'ion-checkmark-circled':'ion-close-circled'"></i> {{wizard.apiValidText}} <br /> </span> @@ -100,7 +100,7 @@ <br /> <br /> <button class="button button-small icon icon-left ion-chevron-left" wz-previous>{{'kPrev' | translate}}</button> - <button class="button button-small icon icon-right ion-chevron-right" ng-click="gotoLoginState()">{{'kWizGotoLogin' + <button id = "testaut_wizard_goto_login" class="button button-small icon icon-right ion-chevron-right" ng-click="gotoLoginState()">{{'kWizGotoLogin' | translate}}</button> </wz-step> </wizard> |
