├── .gitignore ├── Gemfile ├── LICENSE ├── README.md ├── android └── appium.txt ├── config.yml ├── examples ├── preview_login_and_logout.png ├── results_folder │ └── 2016-08-29_17:59:35 │ │ ├── appium_booster.log │ │ ├── appium_server.log │ │ ├── client_server.log │ │ ├── screenshots │ │ ├── 18_01_11_btnAgreeShareLocation.click.png │ │ ├── 18_01_19_btnMenuMyAccount.click.png │ │ ├── 18_01_25_tablecellMyAccountLogin.click.png │ │ ├── 18_01_32_txtfieldEmailAddress.type_leo.lee@dji.com.png │ │ ├── 18_01_39_sectxtfieldPassword.type_123321.png │ │ ├── 18_01_45_btnLogin.click.png │ │ ├── 18_01_52_btnClose.click.png │ │ ├── 18_01_57_btnMenuMyAccount.click.png │ │ ├── 18_02_04_tablecellMyAccountSystemSettings.click.png │ │ └── 18_02_11_btnLogout.click.png │ │ └── xmls │ │ ├── 18_01_11_btnAgreeShareLocation.click.dom │ │ ├── 18_01_19_btnMenuMyAccount.click.dom │ │ ├── 18_01_25_tablecellMyAccountLogin.click.dom │ │ ├── 18_01_32_txtfieldEmailAddress.type_leo.lee@dji.com.dom │ │ ├── 18_01_39_sectxtfieldPassword.type_123321.dom │ │ ├── 18_01_45_btnLogin.click.dom │ │ ├── 18_01_52_btnClose.click.dom │ │ ├── 18_01_57_btnMenuMyAccount.click.dom │ │ ├── 18_02_04_tablecellMyAccountSystemSettings.click.dom │ │ └── 18_02_11_btnLogout.click.dom └── testcase_login_and_logout.jpg ├── ios ├── appium.txt ├── features │ ├── Account.yml │ ├── Forum.yml │ ├── Message.yml │ ├── Nearby.yml │ ├── Settings.yml │ └── Store.yml ├── steps │ ├── Account.yml │ ├── Forum.yml │ ├── Message.yml │ ├── Nearby.yml │ ├── Settings.yml │ └── Store.yml └── testcases │ ├── login_and_logout.csv │ ├── login_and_logout.yml │ ├── send_group_messages.yml │ ├── send_messages.yml │ ├── smoke_test.csv │ └── smoke_test.yml ├── lib ├── helpers │ ├── colorize.rb │ ├── config_helper.rb │ ├── converter_helper.rb │ ├── csv_helper.rb │ ├── driver_helper.rb │ ├── env_helper.rb │ ├── exceptions.rb │ ├── logger_helper.rb │ ├── simulator_helper.rb │ ├── testcase_helper.rb │ ├── utils.rb │ └── yaml_helper.rb ├── pages │ ├── actions.rb │ ├── control.rb │ └── inner_screen.rb ├── requires.rb └── runner.rb └── start.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | results/ 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # filename: Gemfile 2 | source 'https://gems.ruby-china.org' 3 | 4 | gem 'appium_lib' 5 | gem 'appium_console' 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Leo Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AppiumBooster 2 | 3 | AppiumBooster helps you to write automation testcases in yaml format or csv tables, without writing a snippet of code. 4 | 5 | ## write testcases in yaml (recommended) 6 | 7 | Take DebugTalk+ Discover's login and logout function as an example. 8 | 9 | ![](examples/preview_login_and_logout.png) 10 | 11 | In order to test these functions above, you can write testcases in yaml format like this. 12 | 13 | ```yaml 14 | # ios/testcases/login_and_logout.yml 15 | --- 16 | Login and Logout: 17 | - SettingsFeatures | initialize first startup 18 | - AccountFeatures | login with valid test account 19 | - AccountFeatures | logout 20 | ``` 21 | 22 | In the testcases, each step is combined with two parts, joined by a separator `|`. The former part indicates essential features defined in `ios/features/` directory, and the latter part indicates feature name, which is defined in feature yaml files like below. 23 | 24 | ```yaml 25 | # ios/features/Settings.yml 26 | --- 27 | SettingsFeatures: 28 | initialize first startup: 29 | - SettingsSteps | agree share location(optional) 30 | 31 | # ios/features/Account.yml 32 | --- 33 | AccountFeatures: 34 | login with valid test account: 35 | - AccountSteps | enter My Account page 36 | - AccountSteps | enter Login page 37 | - AccountSteps | input test EmailAddress 38 | - AccountSteps | input test Password 39 | - AccountSteps | login 40 | - AccountSteps | close coupon popup window(optional) 41 | 42 | logout: 43 | - AccountSteps | enter My Account page 44 | - SettingsSteps | enter Settings page 45 | - AccountSteps | logout 46 | ``` 47 | 48 | Likewise, each step of essential features is combined with two parts, joined by a separator `|`. The former part indicates step file located in `ios/steps/` directory, and the latter part indicates feature step name, which is defined in steps yaml files like below. 49 | 50 | ```yaml 51 | # ios/steps/AccountSteps.yml 52 | --- 53 | AccountSteps: 54 | enter My Account page: 55 | control_id: btnMenuMyAccount 56 | control_action: click 57 | expectation: tablecellMyAccountSystemSettings 58 | 59 | enter Login page: 60 | control_id: tablecellMyAccountLogin 61 | control_action: click 62 | expectation: btnForgetPassword 63 | 64 | input test EmailAddress: 65 | control_id: txtfieldEmailAddress 66 | control_action: type 67 | data: ${config.TestEnvAccount.UserName} 68 | expectation: sectxtfieldPassword 69 | 70 | input test Password: 71 | control_id: sectxtfieldPassword 72 | control_action: type 73 | data: ${config.TestEnvAccount.Password} 74 | expectation: btnLogin 75 | 76 | login: 77 | control_id: btnLogin 78 | control_action: click 79 | expectation: tablecellMyMessage 80 | ``` 81 | 82 | ## write testcases in tables 83 | 84 | You can also write testcases in any table tools, including MS Excel and iWork Numbers, and even in plain CSV format. 85 | 86 | In order to test the same functions above, you can write testcases in tables like this. 87 | 88 | ![](examples/testcase_login_and_logout.jpg) 89 | 90 | After the testcases are finished, export to CSV format, and put the csv files under `ios/testcases/` directory. 91 | 92 | ## start 93 | 94 | Once the testcases are done, you are ready to run automation test on your app. 95 | 96 | Run the automation testcases is very easy. You can execute `ruby start.rb -h` in the project root directory to see the usage. 97 | 98 | ``` 99 | $ ruby start.rb -h 100 | Usage: start.rb [options] 101 | -p, --app_path Specify app path 102 | -t, --app_type Specify app type, ios or android 103 | -f, --testcase_file Specify testcase file(s) 104 | -d, --output_folder Specify output folder 105 | -c, --convert_type Specify testcase converter, yaml2csv or csv2yaml 106 | --disable_output_color Disable output color 107 | ``` 108 | 109 | And here are some examples. 110 | 111 | ```bash 112 | $ cd ${AppiumBooster} 113 | # execute specified testcase with absolute testcase file path 114 | $ ruby run.rb -p "ios/app/test.zip" -f "/Users/Leo/MyProjects/AppiumBooster/ios/testcases/login.yml" 115 | 116 | # execute specified testcase with relative testcase file path 117 | $ ruby run.rb -p "ios/app/test.zip" -f "ios/testcases/login.yml" 118 | 119 | # execute all yaml format testcases 120 | $ ruby run.rb -p "ios/app/test.zip" -f "ios/testcases/*.yml" 121 | 122 | # execute all csv format testcases located in ios folder 123 | $ ruby run.rb -p "ios/app/test.zip" -t "ios" -f "*.csv" 124 | 125 | # convert yaml format testcase to csv format testcase 126 | $ ruby start.rb -c "yaml2csv" -f ios/testcases/login_and_logout.yml 127 | ``` 128 | 129 | ## Read more ... 130 | 131 | [《打造心目中理想的自动化测试框架(AppiumBooster)》](http://debugtalk.com/post/build-ideal-app-automation-test-framework/) 132 | -------------------------------------------------------------------------------- /android/appium.txt: -------------------------------------------------------------------------------- 1 | [caps] 2 | deviceName = "Android" 3 | platformName = "Android" 4 | platformVersion = "5.1" 5 | app = "android/app/api.apk" 6 | avd = "training" 7 | 8 | [appium_lib] 9 | require = ["../lib/requires.rb"] 10 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | TestEnvAccount: 3 | UserName: test@debugtalk.com 4 | Password: 123456 5 | 6 | ProductionEnvAccount: 7 | UserName: production@debugtalk.com 8 | Password: 12345678 9 | -------------------------------------------------------------------------------- /examples/preview_login_and_logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/examples/preview_login_and_logout.png -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/appium_booster.log: -------------------------------------------------------------------------------- 1 | [2016-08-29 17:59:35] INFO: Logs directory: /Users/Leo/MyProjects/AppiumBooster/results/2016-08-29_17:59:35 2 | [2016-08-29 17:59:35] INFO: Results directory: /Users/Leo/MyProjects/AppiumBooster/results/2016-08-29_17:59:35 3 | [2016-08-29 17:59:35] INFO: Screenshots directory: /Users/Leo/MyProjects/AppiumBooster/results/2016-08-29_17:59:35/screenshots 4 | [2016-08-29 17:59:35] INFO: Errors directory: /Users/Leo/MyProjects/AppiumBooster/results/2016-08-29_17:59:35/errors 5 | [2016-08-29 17:59:35] INFO: initialize AppiumDriver ... 6 | [2016-08-29 17:59:35] INFO: initialize appium server. 7 | [2016-08-29 17:59:35] INFO: kill appium server process. 8 | [2016-08-29 17:59:35] INFO: start appium server process. 9 | [2016-08-29 17:59:45] INFO: Remove iOS simulator: iPhone 6s (9.3) (97968412-FAD8-4797-B2D9-47197C990A6B) 10 | [2016-08-29 17:59:45] INFO: Create iOS simulator: iPhone 6s (9.3) (F23B3F85-7B65-4999-9F1C-80111783F5A5) 11 | [2016-08-29 17:59:45] INFO: initialize appium client instance with capability: {:caps=>{:platformName=>"ios", :deviceName=>"iPhone 6s", :platformVersion=>"9.3", :autoAcceptAlerts=>"true", :app=>"/Users/Leo/MyProjects/AppiumBooster/ios/app/Store.app.zip"}, :appium_lib=>{:require=>["/Users/Leo/MyProjects/AppiumBooster/lib/requires.rb"]}} 12 | [2016-08-29 17:59:45] INFO: load testcase yaml file: /Users/Leo/MyProjects/AppiumBooster/ios/testcases/login_and_logout.yml 13 | [2016-08-29 17:59:45] INFO: load steps yaml file: /Users/Leo/MyProjects/AppiumBooster/ios/steps/Account.yml 14 | [2016-08-29 17:59:45] INFO: load steps yaml file: /Users/Leo/MyProjects/AppiumBooster/ios/steps/Forum.yml 15 | [2016-08-29 17:59:45] INFO: load steps yaml file: /Users/Leo/MyProjects/AppiumBooster/ios/steps/Nearby.yml 16 | [2016-08-29 17:59:45] INFO: load steps yaml file: /Users/Leo/MyProjects/AppiumBooster/ios/steps/Settings.yml 17 | [2016-08-29 17:59:45] INFO: load steps yaml file: /Users/Leo/MyProjects/AppiumBooster/ios/steps/Store.yml 18 | [2016-08-29 17:59:45] INFO: load features yaml file: /Users/Leo/MyProjects/AppiumBooster/ios/features/Account.yml 19 | [2016-08-29 17:59:45] INFO: load features yaml file: /Users/Leo/MyProjects/AppiumBooster/ios/features/Forum.yml 20 | [2016-08-29 17:59:46] INFO: load features yaml file: /Users/Leo/MyProjects/AppiumBooster/ios/features/Nearby.yml 21 | [2016-08-29 17:59:46] INFO: load features yaml file: /Users/Leo/MyProjects/AppiumBooster/ios/features/Settings.yml 22 | [2016-08-29 17:59:46] INFO: load features yaml file: /Users/Leo/MyProjects/AppiumBooster/ios/features/Store.yml 23 | [2016-08-29 17:59:46] INFO: testcase_hash: {"testcase_name"=>"Login and Logout", "features_suite"=>[{"feature_name"=>"initialize first startup", "feature_steps"=>[{"control_id"=>"btnAgreeShareLocation", "control_action"=>"click", "optional"=>true, "step_desc"=>"agree share location(optional)"}]}, {"feature_name"=>"login with valid production account", "feature_steps"=>[{"control_id"=>"btnMenuMyAccount", "control_action"=>"click", "expectation"=>"tablecellMyAccountSystemSettings", "step_desc"=>"enter My Account page"}, {"control_id"=>"tablecellMyAccountLogin", "control_action"=>"click", "expectation"=>"btnForgetPassword", "step_desc"=>"enter Login page"}, {"control_id"=>"txtfieldEmailAddress", "control_action"=>"type", "data"=>"leo.lee@debugtalk.com", "expectation"=>"sectxtfieldPassword", "step_desc"=>"input production EmailAddress"}, {"control_id"=>"sectxtfieldPassword", "control_action"=>"type", "data"=>123321, "expectation"=>"btnLogin", "step_desc"=>"input production Password"}, {"control_id"=>"btnLogin", "control_action"=>"click", "expectation"=>"tablecellMyMessage", "step_desc"=>"login"}, {"control_id"=>"btnClose", "control_action"=>"click", "expectation"=>nil, "optional"=>true, "step_desc"=>"close coupon popup window(optional)"}]}, {"feature_name"=>"logout", "feature_steps"=>[{"control_id"=>"btnMenuMyAccount", "control_action"=>"click", "expectation"=>"tablecellMyAccountSystemSettings", "step_desc"=>"enter My Account page"}, {"control_id"=>"tablecellMyAccountSystemSettings", "control_action"=>"click", "expectation"=>"txtCountryDistrict", "step_desc"=>"enter Settings page"}, {"control_id"=>"btnLogout", "control_action"=>"click", "expectation"=>"uiviewMyAccount", "step_desc"=>"logout"}]}]} 24 | [2016-08-29 17:59:46] INFO: start appium client driver ... 25 | [2016-08-29 18:01:09] INFO: ======= start to run test testcase: Login and Logout ======= 26 | [2016-08-29 18:01:09] INFO: B------ Start to run feature: initialize first startup 27 | [2016-08-29 18:01:09] INFO: feature: {"feature_name"=>"initialize first startup", "feature_steps"=>[{"control_id"=>"btnAgreeShareLocation", "control_action"=>"click", "optional"=>true, "step_desc"=>"agree share location(optional)"}]} 28 | [2016-08-29 18:01:09] INFO: step_1: agree share location(optional) 29 | [2016-08-29 18:01:16] INFO: btnAgreeShareLocation.click ... ✓ 30 | [2016-08-29 18:01:16] INFO: E------ initialize first startup 31 |  32 | [2016-08-29 18:01:16] INFO: B------ Start to run feature: login with valid production account 33 | [2016-08-29 18:01:16] INFO: feature: {"feature_name"=>"login with valid production account", "feature_steps"=>[{"control_id"=>"btnMenuMyAccount", "control_action"=>"click", "expectation"=>"tablecellMyAccountSystemSettings", "step_desc"=>"enter My Account page"}, {"control_id"=>"tablecellMyAccountLogin", "control_action"=>"click", "expectation"=>"btnForgetPassword", "step_desc"=>"enter Login page"}, {"control_id"=>"txtfieldEmailAddress", "control_action"=>"type", "data"=>"leo.lee@debugtalk.com", "expectation"=>"sectxtfieldPassword", "step_desc"=>"input production EmailAddress"}, {"control_id"=>"sectxtfieldPassword", "control_action"=>"type", "data"=>123321, "expectation"=>"btnLogin", "step_desc"=>"input production Password"}, {"control_id"=>"btnLogin", "control_action"=>"click", "expectation"=>"tablecellMyMessage", "step_desc"=>"login"}, {"control_id"=>"btnClose", "control_action"=>"click", "expectation"=>nil, "optional"=>true, "step_desc"=>"close coupon popup window(optional)"}]} 34 | [2016-08-29 18:01:16] INFO: step_1: enter My Account page 35 | [2016-08-29 18:01:23] INFO: btnMenuMyAccount.click ... ✓ 36 | [2016-08-29 18:01:23] INFO: step_2: enter Login page 37 | [2016-08-29 18:01:29] INFO: tablecellMyAccountLogin.click ... ✓ 38 | [2016-08-29 18:01:29] INFO: step_3: input production EmailAddress 39 | [2016-08-29 18:01:36] INFO: txtfieldEmailAddress.type leo.lee@debugtalk.com ... ✓ 40 | [2016-08-29 18:01:36] INFO: step_4: input production Password 41 | [2016-08-29 18:01:43] INFO: sectxtfieldPassword.type 123321 ... ✓ 42 | [2016-08-29 18:01:43] INFO: step_5: login 43 | [2016-08-29 18:01:50] INFO: btnLogin.click ... ✓ 44 | [2016-08-29 18:01:50] INFO: step_6: close coupon popup window(optional) 45 | [2016-08-29 18:01:55] INFO: btnClose.click ... ✓ 46 | [2016-08-29 18:01:55] INFO: E------ login with valid production account 47 |  48 | [2016-08-29 18:01:55] INFO: B------ Start to run feature: logout 49 | [2016-08-29 18:01:55] INFO: feature: {"feature_name"=>"logout", "feature_steps"=>[{"control_id"=>"btnMenuMyAccount", "control_action"=>"click", "expectation"=>"tablecellMyAccountSystemSettings", "step_desc"=>"enter My Account page"}, {"control_id"=>"tablecellMyAccountSystemSettings", "control_action"=>"click", "expectation"=>"txtCountryDistrict", "step_desc"=>"enter Settings page"}, {"control_id"=>"btnLogout", "control_action"=>"click", "expectation"=>"uiviewMyAccount", "step_desc"=>"logout"}]} 50 | [2016-08-29 18:01:55] INFO: step_1: enter My Account page 51 | [2016-08-29 18:02:02] INFO: btnMenuMyAccount.click ... ✓ 52 | [2016-08-29 18:02:02] INFO: step_2: enter Settings page 53 | [2016-08-29 18:02:08] INFO: tablecellMyAccountSystemSettings.click ... ✓ 54 | [2016-08-29 18:02:08] INFO: step_3: logout 55 | [2016-08-29 18:02:16] INFO: btnLogout.click ... ✓ 56 | [2016-08-29 18:02:16] INFO: E------ logout 57 |  58 | [2016-08-29 18:02:16] INFO: ============ all features have been executed. ============ 59 | [2016-08-29 18:02:16] INFO: quit appium client driver. 60 | [2016-08-29 18:02:22] INFO:  61 | >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 62 |  63 | -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_11_btnAgreeShareLocation.click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_11_btnAgreeShareLocation.click.png -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_19_btnMenuMyAccount.click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_19_btnMenuMyAccount.click.png -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_25_tablecellMyAccountLogin.click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_25_tablecellMyAccountLogin.click.png -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_32_txtfieldEmailAddress.type_leo.lee@dji.com.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_32_txtfieldEmailAddress.type_leo.lee@dji.com.png -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_39_sectxtfieldPassword.type_123321.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_39_sectxtfieldPassword.type_123321.png -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_45_btnLogin.click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_45_btnLogin.click.png -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_52_btnClose.click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_52_btnClose.click.png -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_57_btnMenuMyAccount.click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/examples/results_folder/2016-08-29_17:59:35/screenshots/18_01_57_btnMenuMyAccount.click.png -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/screenshots/18_02_04_tablecellMyAccountSystemSettings.click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/examples/results_folder/2016-08-29_17:59:35/screenshots/18_02_04_tablecellMyAccountSystemSettings.click.png -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/screenshots/18_02_11_btnLogout.click.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/examples/results_folder/2016-08-29_17:59:35/screenshots/18_02_11_btnLogout.click.png -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/xmls/18_01_11_btnAgreeShareLocation.click.dom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/xmls/18_01_19_btnMenuMyAccount.click.dom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/xmls/18_01_25_tablecellMyAccountLogin.click.dom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/xmls/18_01_32_txtfieldEmailAddress.type_leo.lee@dji.com.dom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/xmls/18_01_39_sectxtfieldPassword.type_123321.dom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/xmls/18_01_45_btnLogin.click.dom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/xmls/18_01_52_btnClose.click.dom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/xmls/18_01_57_btnMenuMyAccount.click.dom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/xmls/18_02_04_tablecellMyAccountSystemSettings.click.dom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /examples/results_folder/2016-08-29_17:59:35/xmls/18_02_11_btnLogout.click.dom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /examples/testcase_login_and_logout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/examples/testcase_login_and_logout.jpg -------------------------------------------------------------------------------- /ios/appium.txt: -------------------------------------------------------------------------------- 1 | [caps] 2 | platformName = "ios" 3 | deviceName = "iPhone 6s" 4 | platformVersion = "9.3" 5 | autoAcceptAlerts = 'true' 6 | 7 | [appium_lib] 8 | require = ["../lib/requires.rb"] 9 | 10 | [simulators] 11 | ios_devices = [ 12 | ['iPhone 4s', '8.4'], 13 | ['iPhone 6s', '9.3'], 14 | ] 15 | -------------------------------------------------------------------------------- /ios/features/Account.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AccountFeatures: 3 | login with valid test account: 4 | - AccountSteps | enter My Account page 5 | - AccountSteps | enter Login page 6 | - AccountSteps | input test EmailAddress 7 | - AccountSteps | input test Password 8 | - AccountSteps | login 9 | - AccountSteps | close coupon popup window(optional) 10 | 11 | login with valid production account: 12 | - AccountSteps | enter My Account page 13 | - AccountSteps | enter Login page 14 | - AccountSteps | input production EmailAddress 15 | - AccountSteps | input production Password 16 | - AccountSteps | login 17 | - AccountSteps | close coupon popup window(optional) 18 | 19 | logout: 20 | - AccountSteps | enter My Account page 21 | - SettingsSteps | enter Settings page 22 | - AccountSteps | logout 23 | 24 | Change Shipping Address to China: 25 | - AccountSteps | enter My Account page 26 | - SettingsSteps | enter ShoppingInfo page 27 | - SettingsSteps | enter ShippingAddress page 28 | - SettingsSteps | switch Shipping Address to China 29 | - SettingsSteps | confirm Shipping Address selection 30 | - SettingsSteps | back to last page 31 | -------------------------------------------------------------------------------- /ios/features/Forum.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ForumFeatures: 3 | check Forum module: 4 | - ForumSteps | enter Forum page 5 | -------------------------------------------------------------------------------- /ios/features/Message.yml: -------------------------------------------------------------------------------- 1 | --- 2 | MessageFeatures: 3 | enter follower zeng1 message page: 4 | - AccountSteps | enter My Account page 5 | - AccountSteps | enter Follower page 6 | - AccountSteps | enter zeng1 page from Follower page 7 | - AccountSteps | enter Message page from Pilot information page 8 | 9 | popup emoji image board: 10 | - MessageSteps | popup emoji image board 11 | 12 | input emoji image 😎: 13 | - MessageSteps | select 😎 14 | 15 | input emoji image 😲: 16 | - MessageSteps | select 😲 17 | 18 | input emoji image 😍: 19 | - MessageSteps | select 😍 20 | 21 | send text message: 22 | - MessageSteps | input text message 23 | - MessageSteps | send message 24 | 25 | enter group message page: 26 | - AccountSteps | enter My Account page 27 | - MessageSteps | enter My Favorite page 28 | - MessageSteps | enter Testhahahahah detail page 29 | - MessageSteps | enter group chat 30 | -------------------------------------------------------------------------------- /ios/features/Nearby.yml: -------------------------------------------------------------------------------- 1 | --- 2 | NearbyFeatures: 3 | check Nearby module: 4 | - NearbySteps | enter Nearby page 5 | -------------------------------------------------------------------------------- /ios/features/Settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | SettingsFeatures: 3 | initialize first startup: 4 | - SettingsSteps | agree share location(optional) 5 | 6 | Switch to test environment: 7 | - AccountSteps | enter My Account page 8 | - SettingsSteps | switch to test environment 9 | 10 | Change Country to China: 11 | - AccountSteps | enter My Account page 12 | - SettingsSteps | enter Settings page 13 | - SettingsSteps | enter Select Country page 14 | - SettingsSteps | select China 15 | - SettingsSteps | back to last page 16 | 17 | Change Country to HongKong: 18 | - AccountSteps | enter My Account page 19 | - SettingsSteps | enter Settings page 20 | - SettingsSteps | enter Select Country page 21 | - SettingsSteps | select HongKong 22 | - SettingsSteps | back to last page 23 | -------------------------------------------------------------------------------- /ios/features/Store.yml: -------------------------------------------------------------------------------- 1 | --- 2 | StoreFeatures: 3 | add phantom 4 to cart: 4 | - StoreSteps | enter Store page 5 | - StoreSteps | enter Phantom 4 page 6 | - StoreSteps | add to cart 7 | - StoreSteps | view My Cart page 8 | 9 | finish order: 10 | - StoreSteps | checkout cart when logged in 11 | - StoreSteps | choose Shenzhen city(optional) 12 | - StoreSteps | confirm My Address(optional) 13 | - StoreSteps | choose cash on delivery 14 | - StoreSteps | confirm order 15 | - StoreSteps | back to Store page from order page 16 | -------------------------------------------------------------------------------- /ios/steps/Account.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AccountSteps: 3 | enter My Account page: 4 | control_id: btnMenuMyAccount 5 | control_action: click 6 | expectation: tablecellMyAccountSystemSettings 7 | 8 | enter Login page: 9 | control_id: tablecellMyAccountLogin 10 | control_action: click 11 | expectation: btnForgetPassword 12 | 13 | input test EmailAddress: 14 | control_id: txtfieldEmailAddress 15 | control_action: type 16 | data: ${config.TestEnvAccount.UserName} 17 | expectation: sectxtfieldPassword 18 | 19 | input test Password: 20 | control_id: sectxtfieldPassword 21 | control_action: type 22 | data: ${config.TestEnvAccount.Password} 23 | expectation: btnLogin 24 | 25 | input production EmailAddress: 26 | control_id: txtfieldEmailAddress 27 | control_action: type 28 | data: ${config.ProductionEnvAccount.UserName} 29 | expectation: sectxtfieldPassword 30 | 31 | input production Password: 32 | control_id: sectxtfieldPassword 33 | control_action: type 34 | data: ${config.ProductionEnvAccount.Password} 35 | expectation: btnLogin 36 | 37 | login: 38 | control_id: btnLogin 39 | control_action: click 40 | expectation: tablecellMyMessage 41 | 42 | enter Settings page: 43 | control_id: tablecellMyAccountSystemSettings 44 | control_action: click 45 | expectation: txtCountryDistrict 46 | 47 | logout: 48 | control_id: btnLogout 49 | control_action: click 50 | expectation: uiviewMyAccount 51 | 52 | enter Register page: 53 | control_id: btnRegister 54 | control_action: click 55 | expectation: btnSignUp 56 | 57 | confirm Register Terms: 58 | control_id: btnAgreeAndGetStarted 59 | control_action: click 60 | expectation: btnTermsOfUse 61 | 62 | register successfully: 63 | control_id: btnSignUp 64 | control_action: click 65 | expectation: tablecellMyMessage 66 | 67 | register failed: 68 | control_id: btnSignUp 69 | control_action: click 70 | expectation: Account already exists. Please login or retrieve password.||账号已存在,请登录或找回密码 71 | 72 | check if coupon popup window exists(optional): 73 | control_id: inner_screen 74 | control_action: has_control 75 | data: btnViewMyCoupons 76 | expectation: btnClose 77 | optional: true 78 | 79 | close coupon popup window(optional): 80 | control_id: btnClose 81 | control_action: click 82 | expectation: !btnViewMyCoupons 83 | optional: true 84 | 85 | enter Follower page: 86 | control_id: txtFollow 87 | control_action: click 88 | expectation: Follower 89 | 90 | enter zeng1 page from Follower page: 91 | control_id: zeng1 92 | control_action: click 93 | expectation: btnMessage 94 | 95 | enter Message page from Pilot information page: 96 | control_id: btnMessage 97 | control_action: click 98 | expectation: btnEmoji 99 | -------------------------------------------------------------------------------- /ios/steps/Forum.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ForumSteps: 3 | enter Forum page: 4 | control_id: btnMenuForum 5 | control_action: click 6 | expectation: 社区 7 | -------------------------------------------------------------------------------- /ios/steps/Message.yml: -------------------------------------------------------------------------------- 1 | --- 2 | MessageSteps: 3 | popup emoji image board: 4 | control_id: btnEmoji 5 | control_action: click 6 | expectation: btnEmojiSend 7 | 8 | input text message: 9 | control_id: txtfieldMessageInput 10 | control_action: type 11 | data: This is a message sent by robot ${Time.now.to_i} 12 | 13 | select 😎: 14 | control_id: 😎 15 | control_action: click 16 | 17 | select 😲: 18 | control_id: 😲 19 | control_action: click 20 | 21 | select 😍: 22 | control_id: 😍 23 | control_action: click 24 | 25 | send emoji message: 26 | control_id: btnEmojiSend 27 | control_action: click 28 | 29 | send message: 30 | control_id: txtfieldMessageInput 31 | control_action: send_keys 32 | data: '\n' 33 | 34 | enter My Favorite page: 35 | control_id: tablecellMyFavorite 36 | control_action: click 37 | expectation: Testhahahahah 38 | 39 | enter Testhahahahah detail page: 40 | control_id: Testhahahahah 41 | control_action: click 42 | expectation: Chat 43 | 44 | enter group chat: 45 | control_id: Chat 46 | control_action: click 47 | expectation: btnEmoji 48 | -------------------------------------------------------------------------------- /ios/steps/Nearby.yml: -------------------------------------------------------------------------------- 1 | --- 2 | NearbySteps: 3 | enter Nearby page: 4 | control_id: btnMenuNearby 5 | control_action: click 6 | expectation: TaskHomeView 7 | -------------------------------------------------------------------------------- /ios/steps/Settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | SettingsSteps: 3 | enter Settings page: 4 | control_id: tablecellMyAccountSystemSettings 5 | control_action: click 6 | expectation: txtCountryDistrict 7 | 8 | enter Select Country page: 9 | control_id: txtCountryDistrict 10 | control_action: click 11 | expectation: uiviewSelectCountry 12 | 13 | select China: 14 | control_id: tablecellSelectCN 15 | control_action: click 16 | expectation: txtCountryDistrict&&设置 17 | 18 | select HongKong: 19 | control_id: tablecellSelectHK 20 | control_action: click 21 | expectation: txtCountryDistrict&&Settings 22 | 23 | enter ShoppingInfo page: 24 | control_id: tablecellShoppingInfo 25 | control_action: click 26 | expectation: uiviewShoppingInfo 27 | 28 | enter ShippingAddress page: 29 | control_id: tablecellShippingAddress 30 | control_action: click 31 | expectation: uiviewMyAddresses 32 | 33 | switch Shipping Address to China: 34 | control_id: Test_CN_Address 35 | control_action: click 36 | 37 | confirm Shipping Address selection: 38 | control_id: btnConfirm 39 | control_action: click 40 | expectation: uiviewShoppingInfo 41 | 42 | back to last page: 43 | control_id: btnArrowLeft 44 | control_action: click 45 | 46 | agree share location(optional): 47 | control_id: btnAgreeShareLocation 48 | control_action: click 49 | optional: true 50 | 51 | disagree share location(optional): 52 | control_id: btnDisagreeShareLocation 53 | control_action: click 54 | optional: true 55 | 56 | switch to test environment: 57 | control_id: switchMyAccountServerEnv 58 | control_action: click 59 | expectation: 切换后台 (当前测试环境) 60 | -------------------------------------------------------------------------------- /ios/steps/Store.yml: -------------------------------------------------------------------------------- 1 | --- 2 | StoreSteps: 3 | enter Store page: 4 | control_id: btnMenuStore 5 | control_action: click 6 | expectation: uiviewStore 7 | 8 | enter Phantom 4 page: 9 | control_id: txtProduct-phantom-4 10 | control_action: click 11 | expectation: Phantom 4 12 | 13 | add to cart: 14 | control_id: btnAddToCart 15 | control_action: click 16 | expectation: btnBuyNow 17 | 18 | back to Store page from product page: 19 | control_id: btnArrowLeft 20 | control_action: click 21 | expectation: uiviewStore 22 | 23 | enter My Cart page: 24 | control_id: btnProductCart 25 | control_action: click 26 | expectation: uiviewMyCart 27 | 28 | view My Cart page: 29 | control_id: gotoShopCart 30 | control_action: click 31 | expectation: uiviewMyCart 32 | 33 | checkout cart when unlogged in: 34 | control_id: btnCheckOut 35 | control_action: click 36 | expectation: uiviewLogIn 37 | 38 | checkout cart when logged in: 39 | control_id: btnCheckOut 40 | control_action: click 41 | expectation: uiviewMyAddresses 42 | 43 | choose Shenzhen city(optional): 44 | control_id: Test_CN_ShenZhen 45 | control_action: click 46 | optional: true 47 | 48 | choose cash on delivery: 49 | control_id: tablecellcash_on_delivery 50 | control_action: click 51 | expectation: 结算&&tabelcellUseCoupon 52 | 53 | confirm order: 54 | control_id: 提交订单 55 | control_action: click 56 | expectation: 订单已提交 57 | 58 | back to Store page from order page: 59 | control_id: tabbar home gray 60 | control_action: click 61 | expectation: uiviewStore 62 | -------------------------------------------------------------------------------- /ios/testcases/login_and_logout.csv: -------------------------------------------------------------------------------- 1 | feature_name,step_desc,control_id,control_action,data,expectation,optional 2 | initialize first startup,agree share location(optional),btnAgreeShareLocation,click,,,true 3 | ,,,,,, 4 | login with valid production account,enter My Account page,btnMenuMyAccount,click,,tablecellMyAccountSystemSettings, 5 | ,enter Login page,tablecellMyAccountLogin,click,,btnForgetPassword, 6 | ,input production EmailAddress,txtfieldEmailAddress,type,${config.ProductionEnvAccount.UserName},sectxtfieldPassword, 7 | ,input production Password,sectxtfieldPassword,type,${config.ProductionEnvAccount.Password},btnLogin, 8 | ,login,btnLogin,click,,tablecellMyMessage, 9 | ,close coupon popup window(optional),btnClose,click,,,true 10 | ,,,,,, 11 | logout,enter My Account page,btnMenuMyAccount,click,,tablecellMyAccountSystemSettings, 12 | ,enter Settings page,tablecellMyAccountSystemSettings,click,,txtCountryDistrict, 13 | ,logout,btnLogout,click,,uiviewMyAccount, 14 | ,,,,,, 15 | -------------------------------------------------------------------------------- /ios/testcases/login_and_logout.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Login and Logout: 3 | - SettingsFeatures | initialize first startup 4 | - AccountFeatures | login with valid production account 5 | - AccountFeatures | logout 6 | -------------------------------------------------------------------------------- /ios/testcases/send_group_messages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Send text messages in group: 3 | - SettingsFeatures | initialize first startup 4 | - AccountFeatures | login with valid test account 5 | - MessageFeatures | enter group message page 6 | - MessageFeatures | send text message | 100 7 | -------------------------------------------------------------------------------- /ios/testcases/send_messages.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Send text messages: 3 | - SettingsFeatures | initialize first startup 4 | - AccountFeatures | login with valid test account 5 | - MessageFeatures | enter follower zeng1 message page 6 | - MessageFeatures | send text message | 10 7 | -------------------------------------------------------------------------------- /ios/testcases/smoke_test.csv: -------------------------------------------------------------------------------- 1 | feature_name,step_desc,control_id,control_action,data,expectation,optional 2 | initialize first startup,agree share location(optional),btnAgreeShareLocation,click,,,true 3 | ,,,,,, 4 | Change Country to China,enter My Account page,btnMenuMyAccount,click,,tablecellMyAccountSystemSettings, 5 | ,enter Settings page,tablecellMyAccountSystemSettings,click,,txtCountryDistrict, 6 | ,enter Select Country page,txtCountryDistrict,click,,uiviewSelectCountry, 7 | ,select China,tablecellSelectCN,click,,txtCountryDistrict&&设置, 8 | ,back to last page,btnArrowLeft,click,,, 9 | ,,,,,, 10 | ,,,,,, 11 | Change Shipping Address to China,enter My Account page,btnMenuMyAccount,click,,tablecellMyAccountSystemSettings, 12 | ,enter ShoppingInfo page,tablecellShoppingInfo,click,,uiviewShoppingInfo, 13 | ,enter ShippingAddress page,tablecellShippingAddress,click,,uiviewMyAddresses, 14 | ,switch Shipping Address to China,Test_CN_Address,click,,, 15 | ,confirm Shipping Address selection,btnConfirm,click,,uiviewShoppingInfo, 16 | ,back to last page,btnArrowLeft,click,,, 17 | ,,,,,, 18 | add phantom 4 to cart,enter Store page,btnMenuStore,click,,uiviewStore, 19 | ,enter Phantom 4 page,txtProduct-phantom-4,click,,Phantom 4, 20 | ,add to cart,btnAddToCart,click,,btnBuyNow, 21 | ,view My Cart page,gotoShopCart,click,,uiviewMyCart, 22 | ,,,,,, 23 | finish order,checkout cart when logged in,btnCheckOut,click,,uiviewMyAddresses, 24 | ,choose Shenzhen city(optional),Test_CN_ShenZhen,click,,,true 25 | ,confirm My Address(optional),,,,, 26 | ,choose cash on delivery,tablecellcash_on_delivery,click,,结算&&tabelcellUseCoupon, 27 | ,confirm order,提交订单,click,,订单已提交, 28 | ,back to Store page from order page,tabbar home gray,click,,uiviewStore, 29 | ,,,,,, 30 | check Nearby module,enter Nearby page,btnMenuNearby,click,,TaskHomeView, 31 | ,,,,,, 32 | check Forum module,enter Forum page,btnMenuForum,click,,社区, 33 | ,,,,,, 34 | logout,enter My Account page,btnMenuMyAccount,click,,tablecellMyAccountSystemSettings, 35 | ,enter Settings page,tablecellMyAccountSystemSettings,click,,txtCountryDistrict, 36 | ,logout,btnLogout,click,,uiviewMyAccount, 37 | ,,,,,, 38 | -------------------------------------------------------------------------------- /ios/testcases/smoke_test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | SmokeTest: 3 | - SettingsFeatures | initialize first startup 4 | - SettingsFeatures | Change Country to China 5 | - AccountFeatures | login with valid account 6 | - AccountFeatures | Change Shipping Address to China 7 | - StoreFeatures | add phantom 4 to cart 8 | - StoreFeatures | finish order 9 | - NearbyFeatures | check Nearby module 10 | - ForumFeatures | check Forum module 11 | - AccountFeatures | logout 12 | -------------------------------------------------------------------------------- /lib/helpers/colorize.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/helpers/colorize.rb 2 | 3 | class String 4 | # colorization 5 | def colorize(color_code) 6 | if (defined? OUTPUT_WITH_COLOR) && (not OUTPUT_WITH_COLOR) 7 | self 8 | else 9 | "\e[#{color_code}m#{self}\e[0m" 10 | end 11 | end 12 | 13 | def black 14 | colorize(30) 15 | end 16 | 17 | def red 18 | colorize(31) 19 | end 20 | 21 | def green 22 | colorize(32) 23 | end 24 | 25 | def yellow 26 | colorize(33) 27 | end 28 | 29 | def blue 30 | colorize(34) 31 | end 32 | 33 | def magenta 34 | colorize(35) 35 | end 36 | 37 | def cyan 38 | colorize(36) 39 | end 40 | 41 | def white 42 | colorize(37) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/helpers/config_helper.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/helpers/config_helper.rb 2 | require 'yaml' 3 | 4 | class Hash 5 | def method_missing(method, *opts) 6 | self[method.to_s] || super 7 | end 8 | end 9 | 10 | module Kernel 11 | def config 12 | YAML.load_file File.join(Dir.pwd, 'config.yml') 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/helpers/converter_helper.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/helpers/converter_helper.rb 2 | 3 | require_relative 'yaml_helper' 4 | 5 | def convert_yaml_to_csv(testcase_yaml_file_path) 6 | $LOG.info "Convert #{testcase_yaml_file_path} to CSV format test testcases.".green 7 | testcase_hash = load_testcase_yaml_file(testcase_yaml_file_path) 8 | features_suite = testcase_hash['features_suite'] 9 | testcase_csv_file_name = File.basename(testcase_yaml_file_path, ".*") + ".csv" 10 | testcase_csv_file_path = File.expand_path( 11 | File.join(File.dirname(testcase_yaml_file_path), testcase_csv_file_name) 12 | ) 13 | titles = ['feature_name','step_desc','control_id','control_action','data','expectation','optional'] 14 | open(testcase_csv_file_path, 'w') do |f| 15 | f.puts titles.join(',') 16 | features_suite.each do |feature| 17 | feature_name = feature['feature_name'] 18 | feature['feature_steps'].each do |step| 19 | line_content_list = Array.new 20 | line_content_list << feature_name 21 | titles[1..-1].each do |title| 22 | line_content_list << "#{step[title]}" 23 | end 24 | f.puts line_content_list.join(',') 25 | feature_name = nil if feature_name 26 | end 27 | f.puts ",,,,,," 28 | end 29 | end 30 | $LOG.info "CSV format test testcases generated: #{testcase_csv_file_path}".green 31 | end 32 | -------------------------------------------------------------------------------- /lib/helpers/csv_helper.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/helpers/csv_helper.rb 2 | 3 | require 'csv' 4 | 5 | def load_testcase_csv_file(testcase_csv_file_path) 6 | """ load csv format testcase file. 7 | output testcase_hash format example: { 8 | 'testcase_name': 'Login and Logout', 9 | 'features_suite': [ 10 | { 11 | 'feature_name': 'login with valid account', 12 | 'feature_steps': [ 13 | {'control_id': 'btnMenuMyAccount', 'control_action': 'click', 'expectation': 'tablecellMyAccountSystemSettings', 'step_desc': 'enter My Account page'}, 14 | {'control_id': 'tablecellMyAccountLogin', 'control_action': 'click', 'expectation': 'btnForgetPassword', 'step_desc': 'enter Login page'}, 15 | {'control_id': 'txtfieldEmailAddress', 'control_action': 'type', 'data': 'leo.lee@debugtalk.com', 'expectation': 'sectxtfieldPassword', 'step_desc': 'input EmailAddress'}, 16 | {'control_id': 'sectxtfieldPassword', 'control_action': 'type', 'data': 12345678, 'expectation': 'btnLogin', 'step_desc': 'input Password'}, 17 | {'control_id': 'btnLogin', 'control_action': 'click', 'expectation': 'tablecellMyMessage', 'step_desc': 'login'}, 18 | {'control_id': 'btnClose', 'control_action': 'click', 'expectation': nil, 'optional': true, 'step_desc': 'close coupon popup window(optional)'} 19 | ] 20 | }, 21 | { 22 | 'feature_name': 'logout', 23 | 'feature_steps': [ 24 | {'control_id': 'btnMenuMyAccount', 'control_action': 'click', 'expectation': 'tablecellMyAccountSystemSettings', 'step_desc': 'enter My Account page'}, 25 | {'control_id': 'tablecellMyAccountSystemSettings', 'control_action': 'click', 'expectation': 'txtCountryDistrict', 'step_desc': 'enter Settings page'}, 26 | {'control_id': 'btnLogout', 'control_action': 'click', 'expectation': 'uiviewMyAccount', 'step_desc': 'logout'} 27 | ] 28 | } 29 | ] 30 | } 31 | """ 32 | $LOG.info "load testcase csv file: #{testcase_csv_file_path}".magenta 33 | # the first line is titles by default 34 | row_num = 1 35 | testcase_hash = Hash.new 36 | testcase_hash['testcase_name'] = File.basename(testcase_csv_file_path, ".csv") 37 | testcase_hash['features_suite'] = Array.new 38 | titles = Array.new 39 | feature = nil 40 | 41 | CSV.foreach(testcase_csv_file_path) do |row_content| 42 | if row_num == 1 43 | titles = row_content 44 | # check whether titles are valid 45 | raise if titles[0] != "feature_name" 46 | row_num += 1 47 | next 48 | end 49 | 50 | feature_name = row_content[0] 51 | step_desc = row_content[1] 52 | next unless (feature_name || step_desc) 53 | if feature_name 54 | feature = Hash.new 55 | feature["feature_name"] = feature_name 56 | feature["feature_steps"] = Array.new 57 | testcase_hash['features_suite'] << feature 58 | end 59 | 60 | step = Hash.new 61 | row_content[1..-1].each_with_index do |cell, index| 62 | title = titles[index+1] 63 | next if title.nil? 64 | step[title] = cell.strip unless cell.nil? 65 | end 66 | feature["feature_steps"] << step 67 | 68 | end 69 | testcase_hash 70 | end 71 | -------------------------------------------------------------------------------- /lib/helpers/driver_helper.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/helpers/driver_helper.rb 2 | 3 | require 'appium_lib' 4 | require 'fileutils' 5 | 6 | class AppiumDriver 7 | 8 | def initialize(options) 9 | $LOG.info "initialize AppiumDriver ...".green 10 | @app_type = options[:app_type] 11 | @results_dir = options[:results_dir] 12 | @screenshots_dir = options[:screenshots_dir] 13 | @xmls_dir = options[:xmls_dir] 14 | @errors_dir = options[:errors_dir] 15 | init_appium_server 16 | end 17 | 18 | def init_appium_server 19 | $LOG.info "initialize appium server.".green 20 | cmd = "ps | grep 'node .*/appium'" 21 | IO.popen(cmd).each_line do |text| 22 | if text.include? "bin/appium" 23 | $LOG.info "kill appium server process." 24 | appium_pid = text.split[0].to_i 25 | Process.kill(:SIGINT, appium_pid) 26 | end 27 | end 28 | $LOG.info "start appium server process." 29 | system("appium | tee #{@results_dir}/appium_server.log &") 30 | sleep(10) 31 | end 32 | 33 | def get_capability 34 | appium_txt = File.join(Dir.pwd, @app_type, 'appium.txt') 35 | Appium.load_appium_txt file: appium_txt 36 | end 37 | 38 | def init_client_instance(capability) 39 | $LOG.info "initialize appium client instance with capability: #{capability}".green 40 | setup_driver(capability) 41 | promote_methods 42 | end 43 | 44 | def setup_driver(capability) 45 | @driver = Appium::Driver.new capability 46 | end 47 | 48 | def promote_methods 49 | Appium.promote_singleton_appium_methods Pages 50 | end 51 | 52 | def start_driver 53 | $LOG.info "start appium client driver ...".green 54 | @driver.start_driver 55 | end 56 | 57 | def driver_quit 58 | $LOG.info "quit appium client driver.".green 59 | @driver.driver_quit 60 | end 61 | 62 | def alert_accept 63 | @driver.alert_accept 64 | $LOG.info "alert accepted!".green 65 | rescue 66 | $LOG.warn "no alert found, continue." 67 | end 68 | 69 | def screenshot(png_file_name, error=false) 70 | png_file_name = png_file_name.strip.gsub(/\s/, '_') 71 | time = Time.now.strftime "%H_%M_%S" 72 | file_name = "#{time}_#{png_file_name}" 73 | screenshot_save_file = File.join(@screenshots_dir, "#{file_name}.png") 74 | @driver.screenshot screenshot_save_file 75 | FileUtils.cp(screenshot_save_file, @errors_dir) if error 76 | xml_save_file = File.join(@xmls_dir, "#{file_name}.dom") 77 | dump_xml(xml_save_file, error) 78 | end 79 | 80 | def dump_xml(xml_save_file, error=false) 81 | open(xml_save_file, 'w') do |f| 82 | f.puts @driver.get_source 83 | end 84 | FileUtils.cp(xml_save_file, @errors_dir) if error 85 | end 86 | 87 | end 88 | -------------------------------------------------------------------------------- /lib/helpers/env_helper.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/helpers/env_helper.rb 2 | 3 | def initialize_project_environment(options) 4 | output_folder = options[:output_folder] 5 | 6 | unless File.exists?(output_folder) 7 | Dir.mkdir(output_folder) 8 | end 9 | 10 | time = Time.now.strftime "%Y-%m-%d_%H:%M:%S" 11 | results_dir = File.expand_path(File.join(output_folder, "#{time}")) 12 | unless File.exists?(results_dir) 13 | Dir.mkdir(results_dir) 14 | screenshots_dir = File.join(results_dir, "screenshots") 15 | Dir.mkdir(screenshots_dir) 16 | xmls_dir = File.join(results_dir, "xmls") 17 | Dir.mkdir(xmls_dir) 18 | errors_dir = File.join(results_dir, "errors") 19 | Dir.mkdir(errors_dir) 20 | end 21 | 22 | initialize_logger results_dir 23 | 24 | options[:results_dir] = results_dir 25 | options[:screenshots_dir] = screenshots_dir 26 | options[:xmls_dir] = xmls_dir 27 | options[:errors_dir] = errors_dir 28 | 29 | $LOG.info "Results directory: #{results_dir}".green 30 | $LOG.info "Screenshots directory: #{screenshots_dir}".green 31 | $LOG.info "Errors directory: #{errors_dir}".green 32 | 33 | end 34 | 35 | def initialize_ios_simulators(ios_devices) 36 | devices_list = Array.new 37 | ios_devices.each do |ios_device| 38 | device = Hash.new 39 | device["deviceName"] = ios_device[0] 40 | device["platformVersion"] = ios_device[1] 41 | recreate_ios_simulator ios_device[0], ios_device[1] 42 | devices_list << device 43 | end 44 | devices_list 45 | end 46 | -------------------------------------------------------------------------------- /lib/helpers/exceptions.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/debugtalk/AppiumBooster/808b04ea0ec046b6cf59f8b0b07e195764eb58c0/lib/helpers/exceptions.rb -------------------------------------------------------------------------------- /lib/helpers/logger_helper.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/helpers/logger_helper.rb 2 | 3 | require 'logger' 4 | 5 | def initialize_logger(log_save_dir) 6 | log_save_path = File.join(log_save_dir, "appium_booster.log") 7 | $LOG = Logger.new("| tee #{log_save_path}") 8 | $LOG.level = Logger::INFO 9 | 10 | $LOG.formatter = proc do |severity, datetime, progname, msg| 11 | datetime_format = datetime.strftime("%Y-%m-%d %H:%M:%S") 12 | "[#{datetime_format}] #{severity}: #{msg}\n" 13 | end 14 | $LOG.info "Logs directory: #{log_save_dir}".green 15 | end 16 | 17 | if /\/AppiumBooster\/(ios|android)/ =~ Dir.pwd 18 | # make logger compatible with arc tool 19 | initialize_logger Dir.pwd 20 | end 21 | -------------------------------------------------------------------------------- /lib/helpers/simulator_helper.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/helpers/simulator_helper.rb 2 | 3 | def get_device_type_id device_type 4 | # device_type format: e.g. iPhone 5s 5 | device_types_output = `xcrun simctl list devicetypes` 6 | device_type_ids = device_types_output.scan /#{device_type} \((.*)\)/ 7 | device_type_ids[0][0] 8 | end 9 | 10 | def get_runtime_id runtime 11 | # runtime format: X.X, e.g. 9.3 12 | runtime = "iOS #{runtime}" 13 | runtimes_output = `xcrun simctl list runtimes` 14 | runtime_ids = runtimes_output.scan /#{runtime} \(.*\) \((com.apple[^)]+)\)$/ 15 | runtime_ids[0][0] 16 | end 17 | 18 | def delete_existed_simulators(device_type, runtime) 19 | devices_output = `xcrun simctl list devices` 20 | devices = devices_output.scan /\s#{device_type}\s\(([^)]+)\).*/ 21 | devices.each do |device| 22 | $LOG.info "Remove iOS simulator: #{device_type} (#{runtime}) (#{device[0]})".green 23 | `xcrun simctl delete #{device[0]}` 24 | end 25 | end 26 | 27 | def recreate_ios_simulator(device_type, runtime) 28 | device_type_id = get_device_type_id(device_type) 29 | runtime_id = get_runtime_id(runtime) 30 | delete_existed_simulators(device_type, runtime) 31 | simulator = `xcrun simctl create '#{device_type}' #{device_type_id} #{runtime_id}` 32 | $LOG.info "Create iOS simulator: #{device_type} (#{runtime}) (#{simulator.strip})".green 33 | end 34 | -------------------------------------------------------------------------------- /lib/helpers/testcase_helper.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/helpers/testcase_helper.rb 2 | 3 | require_relative 'utils' 4 | require_relative 'yaml_helper' 5 | require_relative 'csv_helper' 6 | 7 | def exec_feature_step(control_id, control_action, data, step_optional=nil) 8 | """ execute feature step. 9 | """ 10 | begin 11 | if control_id.nil? or control_id.to_s == "N/A" 12 | # this type of action do not need control_id, such as 'alert_accept' 13 | step_action = "#{control_action}" 14 | elsif control_id == 'inner_screen' 15 | # check if the current page has specified control 16 | step_action = "inner_screen.#{control_action}" 17 | step_action += " '#{data}'" 18 | else 19 | # execute action on control, such as btnLogin.click, or txtfieldUser.type 'debugtalk' 20 | control_element = control.specify control_id 21 | step_action = "control_element.#{control_action}" 22 | step_action += " '#{data}'" unless data.nil? 23 | end 24 | 25 | eval step_action 26 | rescue 27 | # do not fail the testcase if the failed step is an optional step 28 | unless step_optional 29 | raise 30 | end 31 | end 32 | end 33 | 34 | def verify_step_expectation(expectation) 35 | """ verify if step executed as expectation. 36 | """ 37 | inner_screen.check_elements expectation 38 | end 39 | 40 | def run_testcase(testcase_hash) 41 | testcase_name = testcase_hash['testcase_name'] 42 | $LOG.info "======= start to run test testcase: #{testcase_name} =======".yellow 43 | features_suite = testcase_hash['features_suite'] 44 | features_suite.each do |feature| 45 | $LOG.info "B------ Start to run feature: #{feature['feature_name']}".blue 46 | $LOG.info "feature: #{feature}" 47 | step_action_desc = "" 48 | begin 49 | feature['feature_steps'].each_with_index do |step, index| 50 | $LOG.info "step_#{index+1}: #{step['step_desc']}".cyan 51 | control_id = step['control_id'] 52 | control_action = step['control_action'] 53 | data = eval_expression(step['data']) 54 | expectation = step['expectation'] 55 | step_optional = step['optional'] 56 | 57 | step_action_desc = "#{control_id}.#{control_action} #{data}" 58 | exec_feature_step(control_id, control_action, data, step_optional) 59 | $appium_driver.screenshot(step_action_desc) 60 | 61 | # check if feature step executed successfully 62 | if expectation 63 | raise unless verify_step_expectation(expectation) 64 | end 65 | step_action_desc += " ... ✓" 66 | $LOG.info step_action_desc.green 67 | 68 | step_action_desc = "" 69 | end 70 | rescue => ex 71 | $appium_driver.screenshot(step_action_desc, error=true) 72 | step_action_desc += " ... ✖" 73 | $LOG.error step_action_desc.red 74 | $LOG.error "#{ex}".red 75 | end 76 | $LOG.info "E------ #{feature['feature_name']}\n".blue 77 | end # features_suite 78 | $LOG.info "============ all features have been executed. ============".yellow 79 | end 80 | 81 | def parse_testcase_file(testcase_file) 82 | if testcase_file.end_with? ".csv" 83 | testcase_hash = load_testcase_csv_file(testcase_file) 84 | elsif testcase_file.end_with? ".yml" 85 | testcase_hash = load_testcase_yaml_file(testcase_file) 86 | else 87 | raise "Only support yaml and csv format!" 88 | end 89 | testcase_hash 90 | end 91 | 92 | def run_all_testcases(testcase_files) 93 | Dir.glob(testcase_files) do |testcase_file| 94 | testcase_file = File.expand_path(testcase_file) 95 | testcase_hash = parse_testcase_file(testcase_file) 96 | $LOG.info "testcase_hash: #{testcase_hash}" 97 | next if testcase_hash.empty? || testcase_hash['features_suite'].empty? 98 | $appium_driver.start_driver 99 | # $appium_driver.alert_accept 100 | run_testcase(testcase_hash) 101 | $appium_driver.driver_quit 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/helpers/utils.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/helpers/util.rb 2 | 3 | def eval_expression(str) 4 | """ evaluate ruby expression 5 | e.g. 6 | ${Time.now.to_i}@debugtalk.com => 1471318368@debugtalk.com 7 | """ 8 | if str && str.class == String 9 | str_copy = str.dup 10 | str_copy.gsub!(/\$\{(.*?)\}/) do 11 | eval($1) 12 | end 13 | return str_copy 14 | end 15 | str 16 | end 17 | -------------------------------------------------------------------------------- /lib/helpers/yaml_helper.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/helpers/yaml_helper.rb 2 | 3 | require 'yaml' 4 | 5 | def load_steps_lib 6 | """ load yaml format steps library. 7 | output steps_lib_hash format: { 8 | 'AccountSteps': { 9 | 'enter My Account page': { 10 | 'control_id': 'btnMenuMyAccount', 11 | 'control_action': 'click', 12 | 'expectation': 'tablecellMyAccountSystemSettings' 13 | }, 14 | 'input EmailAddress': { 15 | 'control_id': 'txtfieldEmailAddress', 16 | 'control_action': 'type', 17 | 'data': 'leo.lee@debugtalk.com', 18 | 'expectation': 'sectxtfieldPassword' 19 | } 20 | }, 21 | 'SettingsSteps': { 22 | 'enter Settings page': { 23 | 'control_id': 'tablecellMyAccountSystemSettings', 24 | 'control_action': 'click', 25 | 'expectation': 'txtCountryDistrict' 26 | } 27 | } 28 | } 29 | """ 30 | steps_lib_hash = Hash.new 31 | steps_yaml_files = File.expand_path(File.join(Dir.pwd, 'ios', 'steps', "*.yml")) 32 | Dir.glob(steps_yaml_files).each do |steps_yaml_file_path| 33 | $LOG.info "load steps yaml file: #{steps_yaml_file_path}".cyan 34 | steps = YAML.load_file(steps_yaml_file_path) 35 | $LOG.debug "steps: #{steps}" 36 | steps_lib_hash.merge!(steps) 37 | end 38 | $LOG.debug "steps_lib_hash: #{steps_lib_hash}" 39 | steps_lib_hash 40 | end 41 | 42 | def load_features_lib 43 | """ load yaml format features library. 44 | output features_lib_hash format: { 45 | 'AccountFeatures': { 46 | 'login with valid account': [ 47 | steps_lib_hash['AccountSteps']['enter My Account page'], 48 | steps_lib_hash['AccountSteps']['enter Login page'], 49 | steps_lib_hash['AccountSteps']['input EmailAddress'], 50 | steps_lib_hash['AccountSteps']['input Password'], 51 | steps_lib_hash['AccountSteps']['login'], 52 | ], 53 | 'logout': [ 54 | steps_lib_hash['AccountSteps']['enter My Account page'], 55 | steps_lib_hash['SettingsSteps']['enter Settings page'], 56 | steps_lib_hash['AccountSteps']['logout'], 57 | ] 58 | }, 59 | 'SettingsFeatures': { 60 | 'Change Country to China': [ 61 | steps_lib_hash['SettingsSteps']['enter My Account page'], 62 | steps_lib_hash['SettingsSteps']['enter Settings page'], 63 | steps_lib_hash['SettingsSteps']['enter Select Country page'], 64 | steps_lib_hash['SettingsSteps']['select China'], 65 | steps_lib_hash['SettingsSteps']['back to last page'], 66 | ] 67 | } 68 | } 69 | """ 70 | steps_lib_hash = load_steps_lib() 71 | features_lib_hash = Hash.new 72 | features_yaml_files = File.expand_path(File.join(Dir.pwd, 'ios', 'features', "*.yml")) 73 | Dir.glob(features_yaml_files).each do |features_yaml_file_path| 74 | $LOG.info "load features yaml file: #{features_yaml_file_path}".cyan 75 | features = YAML.load_file(features_yaml_file_path) 76 | features.each do |features_suite_name, features_suite_hash| 77 | features_lib_hash[features_suite_name] = Hash.new 78 | features_suite_hash.each do |feature_name, feature_steps_list| 79 | features_lib_hash[features_suite_name][feature_name] = Array.new 80 | feature_steps_list.each do |step| 81 | steps_suite_name, step_name = step.split('|') 82 | steps_suite_name.strip! 83 | step_name.strip! 84 | step_hash = steps_lib_hash[steps_suite_name][step_name] || Hash.new 85 | step_hash['step_desc'] = step_name 86 | features_lib_hash[features_suite_name][feature_name] << step_hash 87 | end 88 | end 89 | $LOG.debug "#{features_suite_name}: #{features_lib_hash[features_suite_name]}" 90 | end 91 | end 92 | $LOG.debug "features_lib_hash: #{features_lib_hash}" 93 | features_lib_hash 94 | end 95 | 96 | def load_testcase_yaml_file(testcase_yaml_file_path) 97 | """ load yaml format testcase file. 98 | output testcase_hash format: { 99 | 'testcase_name': 'Login and Logout', 100 | 'features_suite': [ 101 | features_lib_hash['AccountFeatures']['login with valid account'], 102 | features_lib_hash['AccountFeatures']['logout'], 103 | ] 104 | } 105 | """ 106 | $LOG.info "load testcase yaml file: #{testcase_yaml_file_path}".magenta 107 | features_lib_hash = load_features_lib() 108 | 109 | testcase_hash = Hash.new 110 | YAML.load_file(testcase_yaml_file_path).each do |testcase_name, features_suite| 111 | testcase_hash['testcase_name'] = testcase_name 112 | testcase_hash['features_suite'] = Array.new 113 | features_suite.each do |feature| 114 | features_suite_name, feature_name, run_times = feature.strip.split('|') 115 | features_suite_name.strip! 116 | feature_name.strip! 117 | run_times ||= "1" 118 | run_times = run_times.strip.to_i 119 | run_times.times.each do |t| 120 | feature_hash = Hash.new 121 | feature_hash['feature_name'] = feature_name 122 | feature_hash['feature_steps'] = features_lib_hash[features_suite_name][feature_name] || Array.new 123 | testcase_hash['features_suite'] << feature_hash 124 | end 125 | end 126 | $LOG.debug "#{testcase_name}: #{testcase_hash}" 127 | end 128 | testcase_hash 129 | end 130 | -------------------------------------------------------------------------------- /lib/pages/actions.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/pages/actions.rb 2 | 3 | module Pages 4 | module Actions 5 | 6 | def send_keys(text) 7 | wait { @found_element.send_keys text } 8 | end 9 | 10 | def _type(text) 11 | wait { @found_element.type text } 12 | end 13 | 14 | def type(text) 15 | begin 16 | _type(text) 17 | rescue Selenium::WebDriver::Error::UnknownError => ex 18 | $LOG.error "Selenium::WebDriver::Error::UnknownError: #{ex}".magenta 19 | scrollToDisplay 20 | _type(text) 21 | end 22 | end 23 | 24 | def _click 25 | wait { @found_element.click } 26 | end 27 | 28 | def click 29 | begin 30 | _click 31 | rescue Selenium::WebDriver::Error::UnknownError => ex 32 | $LOG.error "Selenium::WebDriver::Error::UnknownError: #{ex}".magenta 33 | scrollToDisplay 34 | _click 35 | end 36 | end 37 | 38 | def tapByCoordinate 39 | scrollToDisplay 40 | x_loc = @found_element.location.x 41 | y_loc = @found_element.location.y 42 | Appium::TouchAction.new.tap(x:x_loc, y:y_loc).perform 43 | end 44 | 45 | def scrollToDisplay 46 | while true 47 | if @found_element.displayed? 48 | return 49 | end 50 | 51 | x = eval @found_element.location_rel.x 52 | y = eval @found_element.location_rel.y 53 | 54 | if y < 0 55 | scrollUp 56 | elsif y > 1 57 | scrollDown 58 | elsif x < 0 59 | scrollRightAtHeight 60 | elsif x > 1 61 | scrollLeftAtHeight 62 | else 63 | # 0 < x < 1 && 0 < y < 1 64 | # the element is in current screen 65 | break 66 | end 67 | 68 | next 69 | end # while 70 | end 71 | 72 | def scroll(direction) 73 | begin 74 | $LOG.info "scroll #{direction}" 75 | execute_script 'mobile: scroll', direction: direction 76 | rescue Selenium::WebDriver::Error::JavascriptError 77 | end 78 | end 79 | 80 | def scrollUp 81 | scroll 'up' 82 | end 83 | 84 | def scrollDown 85 | scroll 'down' 86 | end 87 | 88 | def scrollLeft 89 | scroll 'left' 90 | end 91 | 92 | def scrollRight 93 | scroll 'right' 94 | end 95 | 96 | def scrollHorizontally(direction) 97 | y_rel = @found_element.location_rel.y 98 | $LOG.info "scroll #{direction} at height #{y_rel}" 99 | 100 | width = window_size.width 101 | y_loc = @found_element.location.y 102 | if direction.downcase == 'left' 103 | x_start = width 104 | x_move = -1 * width 105 | elsif direction.downcase == 'right' 106 | x_start = 0 107 | x_move = width 108 | else 109 | raise "unexpected direction!" 110 | end 111 | 112 | Appium::TouchAction.new. 113 | press(:x => x_start, :y => y_loc). 114 | move_to(:x => x_move, :y => 0). 115 | release. 116 | perform 117 | end 118 | 119 | def scrollLeftAtHeight 120 | scrollHorizontally 'left' 121 | end 122 | 123 | def scrollRightAtHeight 124 | scrollHorizontally 'right' 125 | end 126 | 127 | end # module Actions 128 | end # module Pages 129 | -------------------------------------------------------------------------------- /lib/pages/control.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/pages/control.rb 2 | 3 | require_relative 'actions' 4 | 5 | module Pages 6 | module Control 7 | class << self 8 | 9 | include Pages::Actions 10 | 11 | def specify(control_id) 12 | @found_element = wait { id control_id } 13 | self 14 | end 15 | 16 | end 17 | end # module Control 18 | end # module Pages 19 | 20 | module Kernel 21 | def control 22 | Pages::Control 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pages/inner_screen.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/pages/inner_screen.rb 2 | 3 | module Pages 4 | module InnerScreen 5 | class << self 6 | 7 | def should_have_text(text) 8 | wait { text_exact text } 9 | end 10 | 11 | def should_have_button(name) 12 | wait { button_exact name } 13 | end 14 | 15 | def should_have_class(name) 16 | wait { tag name } 17 | end 18 | 19 | def should_not_exist(control_id) 20 | wait { id control_id } 21 | raise "#{control_id} still exists!" 22 | rescue Selenium::WebDriver::Error::TimeOutError => ex 23 | end 24 | 25 | def check_element(control_element) 26 | if control_element.start_with?('!') 27 | control_element = control_element[1..-1] 28 | should_not_exist control_element 29 | else 30 | wait { id control_element } 31 | end 32 | end 33 | 34 | def check_elements(control_ids) 35 | """check if control elements exist as expectation. 36 | - if control_ids is like !A, A should not be existed. 37 | - if control_ids is like A||B, either A or B exists will be OK. 38 | - if control_ids is like A&&B, only A and B exists will be OK. 39 | - if control_ids is like A&&!B, A should be existed and B should not be existed. 40 | """ 41 | check_result = true 42 | if control_ids.include? '&&' 43 | control_ids.split('&&').each do |control_element| 44 | check_element(control_element.strip) 45 | end 46 | else 47 | check_result = false 48 | control_ids.split('||').each do |control_element| 49 | begin 50 | check_element(control_element.strip) 51 | check_result = true 52 | break 53 | rescue Selenium::WebDriver::Error::TimeOutError => ex 54 | next 55 | end 56 | end 57 | raise "#{control_ids} not exist" if not check_result 58 | end 59 | check_result 60 | end 61 | 62 | end # end of self 63 | end # end of InnerScreen 64 | end # end of Pages 65 | 66 | module Kernel 67 | def inner_screen 68 | Pages::InnerScreen 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/requires.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/requires.rb 2 | 3 | # require config helper 4 | require_relative 'helpers/config_helper.rb' 5 | 6 | # require the ios page checker 7 | require_relative 'pages/inner_screen' 8 | 9 | # require control helper 10 | require_relative 'pages/control' 11 | 12 | # require environment initializer 13 | require_relative 'helpers/env_helper' 14 | 15 | # setup driver 16 | require_relative 'helpers/driver_helper' 17 | 18 | # require output colorization 19 | require_relative 'helpers/colorize' 20 | 21 | # require testcase helper 22 | require_relative 'helpers/testcase_helper' 23 | 24 | # require test testcases converter helper 25 | require_relative 'helpers/converter_helper' 26 | 27 | # require logger helper 28 | require_relative 'helpers/logger_helper' 29 | 30 | # require ios simulator helper 31 | require_relative 'helpers/simulator_helper' 32 | 33 | # require testcase runner 34 | require_relative 'runner' 35 | -------------------------------------------------------------------------------- /lib/runner.rb: -------------------------------------------------------------------------------- 1 | # filename: lib/runner.rb 2 | require "pathname" 3 | 4 | def run_test(options) 5 | app_path = options[:app_path] 6 | app_type = options[:app_type] 7 | testcase_file = options[:testcase_file] 8 | 9 | $appium_driver = AppiumDriver.new options 10 | capability = $appium_driver.get_capability 11 | capability[:caps][:app] = app_path if app_path 12 | 13 | if app_type == "ios" 14 | ios_devices = capability.delete(:simulators).delete(:ios_devices) 15 | devices_list = initialize_ios_simulators(ios_devices) 16 | else 17 | raise "android test is not implemented currently." 18 | end 19 | 20 | devices_list.each do |device| 21 | capability[:caps][:deviceName] = device["deviceName"] 22 | capability[:caps][:platformVersion] = device["platformVersion"] 23 | 24 | begin 25 | $appium_driver.init_client_instance(capability) 26 | 27 | if File.exists? testcase_file 28 | # e.g. /Users/Leo/MyProjects/AppiumBooster/ios/testcases/login.yml 29 | testcase_files = testcase_file 30 | elsif Pathname.new(testcase_file).absolute? 31 | # e.g. /Users/Leo/MyProjects/AppiumBooster/ios/testcases/*.yml 32 | testcase_files = testcase_file 33 | elsif testcase_file.include? File::SEPARATOR 34 | # e.g. ios/testcases/*.yml 35 | testcase_files = File.join(Dir.pwd, "#{testcase_file}") 36 | else 37 | # e.g. *.yml 38 | testcases_dir = File.join(Dir.pwd, app_type, 'testcases') 39 | testcase_files = File.join(testcases_dir, "#{testcase_file}") 40 | end 41 | run_all_testcases(testcase_files) 42 | rescue => ex 43 | $LOG.error "#{ex}".red 44 | ensure 45 | $LOG.info "\n#{'>'*60}===#{'<'*60}\n".yellow 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /start.rb: -------------------------------------------------------------------------------- 1 | # filename: start.rb 2 | 3 | require 'optparse' 4 | require_relative 'lib/requires' 5 | 6 | options = {} 7 | 8 | OptionParser.new do |opts| 9 | opts.banner = "Usage: start.rb [options]" 10 | 11 | opts.on("-p ", "--app_path", "Specify app path") do |v| 12 | options[:app_path] = v 13 | end 14 | 15 | options[:app_type] = "ios" 16 | opts.on("-t ", "--app_type", "Specify app type, ios or android") do |v| 17 | app_type = v.downcase 18 | unless ["ios", "android"].include? app_type 19 | raise ArgumentError, "app_type should only be ios or android!" 20 | end 21 | options[:app_type] = app_type 22 | end 23 | 24 | options[:testcase_file] = "*.yml" 25 | opts.on("-f ", "--testcase_file", "Specify testcase file(s)") do |file| 26 | file.downcase! 27 | file = "*.yml" if file == "*.yaml" 28 | options[:testcase_file] = file 29 | end 30 | 31 | options[:output_folder] = File.join(Dir.pwd, "results") 32 | opts.on("-d ", "--output_folder", "Specify output folder") do |v| 33 | options[:output_folder] = v 34 | end 35 | 36 | options[:convert_type] = "yaml2csv" 37 | opts.on("-c ", "--convert_type", "Specify testcase converter, yaml2csv or csv2yaml") do |v| 38 | options[:convert_type] = v 39 | end 40 | 41 | options[:output_color] = true 42 | opts.on("--disable_output_color", "Disable output color") do 43 | options[:output_color] = false 44 | end 45 | 46 | end.parse! 47 | 48 | initialize_project_environment options 49 | OUTPUT_WITH_COLOR = options[:output_color] 50 | 51 | if options[:app_path] && options[:app_type] 52 | run_test(options) 53 | elsif options[:convert_type] && File.file?(options[:testcase_file]) 54 | convert_yaml_to_csv(options[:testcase_file]) if options[:convert_type] == "yaml2csv" 55 | else 56 | raise ArgumentError 57 | end 58 | --------------------------------------------------------------------------------