├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── checkstyles ├── checkstyle-suppressions.xml └── checkstyle.xml ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── automate │ │ │ ├── constants │ │ │ ├── FrameworkConstants.java │ │ │ └── StringConstants.java │ │ │ ├── customannotations │ │ │ └── FrameworkAnnotation.java │ │ │ ├── customexceptions │ │ │ ├── DriverInitializationException.java │ │ │ ├── FrameworkException.java │ │ │ ├── InvalidPathException.java │ │ │ ├── JsonFileUsageException.java │ │ │ └── PropertyFileUsageException.java │ │ │ ├── driver │ │ │ ├── Drivers.java │ │ │ ├── factory │ │ │ │ └── DriverFactory.java │ │ │ └── manager │ │ │ │ ├── DeviceManager.java │ │ │ │ ├── DriverManager.java │ │ │ │ └── PlatformManager.java │ │ │ ├── entity │ │ │ ├── LoginData.java │ │ │ ├── SearchData.java │ │ │ └── TestData.java │ │ │ ├── enums │ │ │ ├── CategoryType.java │ │ │ ├── ConfigJson.java │ │ │ ├── ConfigProperties.java │ │ │ ├── MobileBrowserName.java │ │ │ ├── MobileFindBy.java │ │ │ ├── MobilePlatformName.java │ │ │ └── WaitStrategy.java │ │ │ ├── factories │ │ │ └── WaitFactory.java │ │ │ ├── listeners │ │ │ ├── AnnotationTransformer.java │ │ │ ├── Listeners.java │ │ │ └── Retry.java │ │ │ ├── pages │ │ │ ├── GoogleSearchPage.java │ │ │ ├── GoogleSearchResultPage.java │ │ │ ├── LoginPage.java │ │ │ ├── MenuPage.java │ │ │ ├── ProductPage.java │ │ │ ├── SettingsPage.java │ │ │ └── screen │ │ │ │ └── ScreenActions.java │ │ │ ├── reports │ │ │ ├── ExtentReportLogger.java │ │ │ └── ExtentReportManager.java │ │ │ └── utils │ │ │ ├── AppiumServerManager.java │ │ │ ├── DecodeUtils.java │ │ │ ├── TestUtils.java │ │ │ ├── configloader │ │ │ ├── JsonUtils.java │ │ │ └── PropertyUtils.java │ │ │ ├── dataprovider │ │ │ ├── DataProviderUtils.java │ │ │ └── ExcelUtils.java │ │ │ ├── screenrecording │ │ │ ├── ScreenRecordingService.java │ │ │ └── ScreenRecordingUtils.java │ │ │ └── screenshot │ │ │ ├── ScreenshotService.java │ │ │ └── ScreenshotUtils.java │ └── resources │ │ └── log4j2.properties └── test │ ├── java │ ├── base │ │ └── BaseTest.java │ └── com │ │ └── tests │ │ ├── GoogleTest.java │ │ └── LoginTest.java │ └── resources │ ├── app │ └── Android.SauceLabs.Mobile.Sample.app.2.7.1.apk │ ├── config │ ├── config.json │ └── config.properties │ ├── data │ └── testdata.xlsx │ └── executables │ └── chromedriver.exe ├── testng_android_Emulator.xml ├── testng_android_ParallelExecution.xml └── testng_android_RealDevice.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 120 10 | tab_width = 4 11 | ij_continuation_indent_size = 8 12 | ij_formatter_off_tag = @formatter:off 13 | ij_formatter_on_tag = @formatter:on 14 | ij_formatter_tags_enabled = false 15 | ij_smart_tabs = false 16 | ij_visual_guides = none 17 | ij_wrap_on_typing = false 18 | 19 | [*.java] 20 | indent_size = 2 21 | max_line_length = 136 22 | tab_width = 2 23 | ij_continuation_indent_size = 2 24 | ij_java_align_consecutive_assignments = false 25 | ij_java_align_consecutive_variable_declarations = false 26 | ij_java_align_group_field_declarations = false 27 | ij_java_align_multiline_annotation_parameters = false 28 | ij_java_align_multiline_array_initializer_expression = false 29 | ij_java_align_multiline_assignment = false 30 | ij_java_align_multiline_binary_operation = false 31 | ij_java_align_multiline_chained_methods = false 32 | ij_java_align_multiline_extends_list = false 33 | ij_java_align_multiline_for = true 34 | ij_java_align_multiline_method_parentheses = false 35 | ij_java_align_multiline_parameters = true 36 | ij_java_align_multiline_parameters_in_calls = true 37 | ij_java_align_multiline_parenthesized_expression = false 38 | ij_java_align_multiline_records = true 39 | ij_java_align_multiline_resources = true 40 | ij_java_align_multiline_ternary_operation = false 41 | ij_java_align_multiline_text_blocks = false 42 | ij_java_align_multiline_throws_list = false 43 | ij_java_align_subsequent_simple_methods = false 44 | ij_java_align_throws_keyword = false 45 | ij_java_annotation_parameter_wrap = off 46 | ij_java_array_initializer_new_line_after_left_brace = false 47 | ij_java_array_initializer_right_brace_on_new_line = false 48 | ij_java_array_initializer_wrap = normal 49 | ij_java_assert_statement_colon_on_next_line = false 50 | ij_java_assert_statement_wrap = normal 51 | ij_java_assignment_wrap = normal 52 | ij_java_binary_operation_sign_on_next_line = false 53 | ij_java_binary_operation_wrap = normal 54 | ij_java_blank_lines_after_anonymous_class_header = 0 55 | ij_java_blank_lines_after_class_header = 0 56 | ij_java_blank_lines_after_imports = 1 57 | ij_java_blank_lines_after_package = 1 58 | ij_java_blank_lines_around_class = 1 59 | ij_java_blank_lines_around_field = 0 60 | ij_java_blank_lines_around_field_in_interface = 0 61 | ij_java_blank_lines_around_initializer = 1 62 | ij_java_blank_lines_around_method = 1 63 | ij_java_blank_lines_around_method_in_interface = 1 64 | ij_java_blank_lines_before_class_end = 0 65 | ij_java_blank_lines_before_imports = 1 66 | ij_java_blank_lines_before_method_body = 0 67 | ij_java_blank_lines_before_package = 0 68 | ij_java_block_brace_style = end_of_line 69 | ij_java_block_comment_add_space = false 70 | ij_java_block_comment_at_first_column = true 71 | ij_java_builder_methods = none 72 | ij_java_call_parameters_new_line_after_left_paren = false 73 | ij_java_call_parameters_right_paren_on_new_line = false 74 | ij_java_call_parameters_wrap = normal 75 | ij_java_case_statement_on_separate_line = true 76 | ij_java_catch_on_new_line = false 77 | ij_java_class_annotation_wrap = split_into_lines 78 | ij_java_class_brace_style = end_of_line 79 | ij_java_class_count_to_use_import_on_demand = 999 80 | ij_java_class_names_in_javadoc = 1 81 | ij_java_do_not_indent_top_level_class_members = false 82 | ij_java_do_not_wrap_after_single_annotation = false 83 | ij_java_do_while_brace_force = never 84 | ij_java_doc_add_blank_line_after_description = true 85 | ij_java_doc_add_blank_line_after_param_comments = false 86 | ij_java_doc_add_blank_line_after_return = false 87 | ij_java_doc_add_p_tag_on_empty_lines = true 88 | ij_java_doc_align_exception_comments = true 89 | ij_java_doc_align_param_comments = true 90 | ij_java_doc_do_not_wrap_if_one_line = false 91 | ij_java_doc_enable_formatting = true 92 | ij_java_doc_enable_leading_asterisks = true 93 | ij_java_doc_indent_on_continuation = false 94 | ij_java_doc_keep_empty_lines = true 95 | ij_java_doc_keep_empty_parameter_tag = true 96 | ij_java_doc_keep_empty_return_tag = true 97 | ij_java_doc_keep_empty_throws_tag = true 98 | ij_java_doc_keep_invalid_tags = true 99 | ij_java_doc_param_description_on_new_line = false 100 | ij_java_doc_preserve_line_breaks = false 101 | ij_java_doc_use_throws_not_exception_tag = true 102 | ij_java_else_on_new_line = false 103 | ij_java_enum_constants_wrap = normal 104 | ij_java_extends_keyword_wrap = normal 105 | ij_java_extends_list_wrap = normal 106 | ij_java_field_annotation_wrap = split_into_lines 107 | ij_java_finally_on_new_line = false 108 | ij_java_for_brace_force = never 109 | ij_java_for_statement_new_line_after_left_paren = false 110 | ij_java_for_statement_right_paren_on_new_line = false 111 | ij_java_for_statement_wrap = normal 112 | ij_java_generate_final_locals = false 113 | ij_java_generate_final_parameters = false 114 | ij_java_if_brace_force = never 115 | ij_java_imports_layout = *, |, javax.**, java.**, |, $* 116 | ij_java_indent_case_from_switch = true 117 | ij_java_insert_inner_class_imports = false 118 | ij_java_insert_override_annotation = true 119 | ij_java_keep_blank_lines_before_right_brace = 2 120 | ij_java_keep_blank_lines_between_package_declaration_and_header = 2 121 | ij_java_keep_blank_lines_in_code = 2 122 | ij_java_keep_blank_lines_in_declarations = 2 123 | ij_java_keep_builder_methods_indents = false 124 | ij_java_keep_control_statement_in_one_line = true 125 | ij_java_keep_first_column_comment = true 126 | ij_java_keep_indents_on_empty_lines = false 127 | ij_java_keep_line_breaks = true 128 | ij_java_keep_multiple_expressions_in_one_line = false 129 | ij_java_keep_simple_blocks_in_one_line = false 130 | ij_java_keep_simple_classes_in_one_line = false 131 | ij_java_keep_simple_lambdas_in_one_line = false 132 | ij_java_keep_simple_methods_in_one_line = false 133 | ij_java_label_indent_absolute = false 134 | ij_java_label_indent_size = 0 135 | ij_java_lambda_brace_style = end_of_line 136 | ij_java_layout_static_imports_separately = true 137 | ij_java_line_comment_add_space = false 138 | ij_java_line_comment_at_first_column = true 139 | ij_java_method_annotation_wrap = split_into_lines 140 | ij_java_method_brace_style = end_of_line 141 | ij_java_method_call_chain_wrap = normal 142 | ij_java_method_parameters_new_line_after_left_paren = false 143 | ij_java_method_parameters_right_paren_on_new_line = false 144 | ij_java_method_parameters_wrap = normal 145 | ij_java_modifier_list_wrap = false 146 | ij_java_names_count_to_use_import_on_demand = 999 147 | ij_java_new_line_after_lparen_in_record_header = false 148 | ij_java_parameter_annotation_wrap = normal 149 | ij_java_parentheses_expression_new_line_after_left_paren = false 150 | ij_java_parentheses_expression_right_paren_on_new_line = false 151 | ij_java_place_assignment_sign_on_next_line = false 152 | ij_java_prefer_longer_names = true 153 | ij_java_prefer_parameters_wrap = false 154 | ij_java_record_components_wrap = normal 155 | ij_java_repeat_synchronized = true 156 | ij_java_replace_instanceof_and_cast = false 157 | ij_java_replace_null_check = true 158 | ij_java_replace_sum_lambda_with_method_ref = true 159 | ij_java_resource_list_new_line_after_left_paren = false 160 | ij_java_resource_list_right_paren_on_new_line = false 161 | ij_java_resource_list_wrap = normal 162 | ij_java_rparen_on_new_line_in_record_header = false 163 | ij_java_space_after_closing_angle_bracket_in_type_argument = false 164 | ij_java_space_after_colon = true 165 | ij_java_space_after_comma = true 166 | ij_java_space_after_comma_in_type_arguments = true 167 | ij_java_space_after_for_semicolon = true 168 | ij_java_space_after_quest = true 169 | ij_java_space_after_type_cast = true 170 | ij_java_space_before_annotation_array_initializer_left_brace = false 171 | ij_java_space_before_annotation_parameter_list = false 172 | ij_java_space_before_array_initializer_left_brace = true 173 | ij_java_space_before_catch_keyword = true 174 | ij_java_space_before_catch_left_brace = true 175 | ij_java_space_before_catch_parentheses = true 176 | ij_java_space_before_class_left_brace = true 177 | ij_java_space_before_colon = true 178 | ij_java_space_before_colon_in_foreach = true 179 | ij_java_space_before_comma = false 180 | ij_java_space_before_do_left_brace = true 181 | ij_java_space_before_else_keyword = true 182 | ij_java_space_before_else_left_brace = true 183 | ij_java_space_before_finally_keyword = true 184 | ij_java_space_before_finally_left_brace = true 185 | ij_java_space_before_for_left_brace = true 186 | ij_java_space_before_for_parentheses = true 187 | ij_java_space_before_for_semicolon = false 188 | ij_java_space_before_if_left_brace = true 189 | ij_java_space_before_if_parentheses = true 190 | ij_java_space_before_method_call_parentheses = false 191 | ij_java_space_before_method_left_brace = true 192 | ij_java_space_before_method_parentheses = false 193 | ij_java_space_before_opening_angle_bracket_in_type_parameter = false 194 | ij_java_space_before_quest = true 195 | ij_java_space_before_switch_left_brace = true 196 | ij_java_space_before_switch_parentheses = true 197 | ij_java_space_before_synchronized_left_brace = true 198 | ij_java_space_before_synchronized_parentheses = true 199 | ij_java_space_before_try_left_brace = true 200 | ij_java_space_before_try_parentheses = true 201 | ij_java_space_before_type_parameter_list = false 202 | ij_java_space_before_while_keyword = true 203 | ij_java_space_before_while_left_brace = true 204 | ij_java_space_before_while_parentheses = true 205 | ij_java_space_inside_one_line_enum_braces = false 206 | ij_java_space_within_empty_array_initializer_braces = false 207 | ij_java_space_within_empty_method_call_parentheses = false 208 | ij_java_space_within_empty_method_parentheses = false 209 | ij_java_spaces_around_additive_operators = true 210 | ij_java_spaces_around_assignment_operators = true 211 | ij_java_spaces_around_bitwise_operators = true 212 | ij_java_spaces_around_equality_operators = true 213 | ij_java_spaces_around_lambda_arrow = true 214 | ij_java_spaces_around_logical_operators = true 215 | ij_java_spaces_around_method_ref_dbl_colon = false 216 | ij_java_spaces_around_multiplicative_operators = true 217 | ij_java_spaces_around_relational_operators = true 218 | ij_java_spaces_around_shift_operators = true 219 | ij_java_spaces_around_type_bounds_in_type_parameters = true 220 | ij_java_spaces_around_unary_operator = false 221 | ij_java_spaces_within_angle_brackets = false 222 | ij_java_spaces_within_annotation_parentheses = false 223 | ij_java_spaces_within_array_initializer_braces = false 224 | ij_java_spaces_within_braces = false 225 | ij_java_spaces_within_brackets = false 226 | ij_java_spaces_within_cast_parentheses = false 227 | ij_java_spaces_within_catch_parentheses = false 228 | ij_java_spaces_within_for_parentheses = false 229 | ij_java_spaces_within_if_parentheses = false 230 | ij_java_spaces_within_method_call_parentheses = false 231 | ij_java_spaces_within_method_parentheses = false 232 | ij_java_spaces_within_parentheses = false 233 | ij_java_spaces_within_record_header = false 234 | ij_java_spaces_within_switch_parentheses = false 235 | ij_java_spaces_within_synchronized_parentheses = false 236 | ij_java_spaces_within_try_parentheses = false 237 | ij_java_spaces_within_while_parentheses = false 238 | ij_java_special_else_if_treatment = true 239 | ij_java_subclass_name_suffix = Impl 240 | ij_java_ternary_operation_signs_on_next_line = false 241 | ij_java_ternary_operation_wrap = normal 242 | ij_java_test_name_suffix = Test 243 | ij_java_throws_keyword_wrap = normal 244 | ij_java_throws_list_wrap = normal 245 | ij_java_use_external_annotations = false 246 | ij_java_use_fq_class_names = false 247 | ij_java_use_relative_indents = false 248 | ij_java_use_single_class_imports = true 249 | ij_java_variable_annotation_wrap = normal 250 | ij_java_visibility = public 251 | ij_java_while_brace_force = never 252 | ij_java_while_on_new_line = false 253 | ij_java_wrap_comments = false 254 | ij_java_wrap_first_method_in_call_chain = false 255 | ij_java_wrap_long_lines = false 256 | 257 | [*.properties] 258 | ij_properties_align_group_field_declarations = false 259 | ij_properties_keep_blank_lines = false 260 | ij_properties_key_value_delimiter = equals 261 | ij_properties_spaces_around_key_value_delimiter = false 262 | 263 | [.editorconfig] 264 | ij_editorconfig_align_group_field_declarations = false 265 | ij_editorconfig_space_after_colon = false 266 | ij_editorconfig_space_after_comma = true 267 | ij_editorconfig_space_before_colon = false 268 | ij_editorconfig_space_before_comma = false 269 | ij_editorconfig_spaces_around_assignment_operators = true 270 | 271 | [{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.jspx,*.pom,*.rng,*.tagx,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] 272 | ij_xml_align_attributes = true 273 | ij_xml_align_text = false 274 | ij_xml_attribute_wrap = normal 275 | ij_xml_block_comment_add_space = false 276 | ij_xml_block_comment_at_first_column = true 277 | ij_xml_keep_blank_lines = 2 278 | ij_xml_keep_indents_on_empty_lines = false 279 | ij_xml_keep_line_breaks = true 280 | ij_xml_keep_line_breaks_in_text = true 281 | ij_xml_keep_whitespaces = false 282 | ij_xml_keep_whitespaces_around_cdata = preserve 283 | ij_xml_keep_whitespaces_inside_cdata = false 284 | ij_xml_line_comment_at_first_column = true 285 | ij_xml_space_after_tag_name = false 286 | ij_xml_space_around_equals_in_attribute = false 287 | ij_xml_space_inside_empty_tag = false 288 | ij_xml_text_wrap = normal 289 | ij_xml_use_custom_settings = false 290 | 291 | [{*.bash,*.sh,*.zsh}] 292 | indent_size = 2 293 | tab_width = 2 294 | ij_shell_binary_ops_start_line = false 295 | ij_shell_keep_column_alignment_padding = false 296 | ij_shell_minify_program = false 297 | ij_shell_redirect_followed_by_space = false 298 | ij_shell_switch_cases_indented = false 299 | ij_shell_use_unix_line_separator = true 300 | 301 | [{*.har,*.json}] 302 | indent_size = 2 303 | ij_json_keep_blank_lines_in_code = 0 304 | ij_json_keep_indents_on_empty_lines = false 305 | ij_json_keep_line_breaks = true 306 | ij_json_space_after_colon = true 307 | ij_json_space_after_comma = true 308 | ij_json_space_before_colon = true 309 | ij_json_space_before_comma = false 310 | ij_json_spaces_within_braces = false 311 | ij_json_spaces_within_brackets = false 312 | ij_json_wrap_long_lines = false 313 | 314 | [{*.markdown,*.md}] 315 | ij_markdown_force_one_space_after_blockquote_symbol = true 316 | ij_markdown_force_one_space_after_header_symbol = true 317 | ij_markdown_force_one_space_after_list_bullet = true 318 | ij_markdown_force_one_space_between_words = true 319 | ij_markdown_keep_indents_on_empty_lines = false 320 | ij_markdown_max_lines_around_block_elements = 1 321 | ij_markdown_max_lines_around_header = 1 322 | ij_markdown_max_lines_between_paragraphs = 1 323 | ij_markdown_min_lines_around_block_elements = 1 324 | ij_markdown_min_lines_around_header = 1 325 | ij_markdown_min_lines_between_paragraphs = 1 326 | 327 | [{*.yaml,*.yml}] 328 | indent_size = 2 329 | ij_yaml_align_values_properties = do_not_align 330 | ij_yaml_autoinsert_sequence_marker = true 331 | ij_yaml_block_mapping_on_new_line = false 332 | ij_yaml_indent_sequence_value = true 333 | ij_yaml_keep_indents_on_empty_lines = false 334 | ij_yaml_keep_line_breaks = true 335 | ij_yaml_sequence_on_new_line = false 336 | ij_yaml_space_before_colon = false 337 | ij_yaml_spaces_within_braces = true 338 | ij_yaml_spaces_within_brackets = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /extent-test-report/ 2 | /logs/ 3 | /Screenshots/ 4 | /screen-recordings/ 5 | /server-logs/ 6 | /target/ 7 | /.idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Thangaraj 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 | # Appium Mobile Automation Framework 2 | 3 | Framework for Mobile test automation (Native app and Browser) on Android and IOS devices :iphone: 4 | 5 | 6 | 7 | ## :rocket: Quick Start - Appium set up on Windows (Android): 8 | 9 | 1) Install [Java JDK8](https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html) 10 | and [IntelliJ IDEA](https://www.jetbrains.com/idea/download/) 11 | 2) Install [NodeJS](https://nodejs.org/en/download/) 12 | 3) Install [Android studio](https://developer.android.com/studio) 13 | 4) Install Appium Server using npm (CLI) command `npm install -g appium`. Appium server version 1.22.1 14 | 15 | ``` 16 | Command to check the installed appium version: `appium --version` 17 | ``` 18 | 19 | 5) Add below Android SDK path in the environment variable 20 | 21 | ``` 22 | - ANDROID_HOME = 23 | - %ANDROID_HOME%\tools 24 | - %ANDROID_HOME%\tools\bin 25 | - %ANDROID_HOME%\platform-tools 26 | ``` 27 | 28 | 6) Install [Appium desktop](https://github.com/appium/appium-desktop/releases/) 29 | 7) Install [Appium Inspector](https://github.com/appium/appium-inspector/releases) 30 | 31 | ## :rocket: Quick Start - Appium set up on MAC (Android): 32 | 33 | 1) Install Homebrew 34 | 2) Install [NodeJS](https://nodejs.org/en/download/) 35 | 3) Install [Java JDK8](https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html) 36 | and [IntelliJ IDEA](https://www.jetbrains.com/idea/download/) 37 | 4) Install Appium server using npm (CLI) or Appium desktop client 38 | 5) Install [Android studio](https://developer.android.com/studio) 39 | 6) Install [Appium Inspector](https://github.com/appium/appium-inspector/releases) 40 | 7) Set JAVA_HOME and ANDROID_HOME environment variables 41 | 42 | ## :pushpin: Appium Doctor to verify the installations 43 | 44 | 1) Install appium-doctor using command `npm install -g appium-doctor` 45 | 2) To view the list of available options `appium-doctor --help` 46 | 47 | ``` 48 | To check Android set up `appium-doctor --android` 49 | To check ios set up `appium-doctor --ios` 50 | ``` 51 | 52 | ## :pushpin: Creating Android Virtual Device (Emulator) from Android Studio: 53 | 54 | 1) Open Android Studio. 55 | 2) Click on Tools -> AVD Manager -> Create Virtual Device -> Select the device and OS version -> Finish. 56 | 3) Once Virtual device is created, click on Launch this AVD in the emulator. 57 | 4) Command to view the list of devices attached `adb devices` 58 | 59 | ## :pushpin: Android Real Device Set up: 60 | 61 | 1) Connect Android real device to the machine(Desktop/Laptop) 62 | 2) Turn on the developer options in android mobile 63 | 3) Enable USB debugging 64 | 4) Run command `adb devices` in cmd prompt to check whether the device is recognised 65 | 66 | ## :pushpin: Mirror android/ios device to your desktop 67 | 68 | 1) Download [Vysor](https://www.vysor.io/) 69 | 70 | ## :pushpin: Start Android Emulator from Command line 71 | 72 | 1) Open command prompt, go to `` 73 | 74 | ``` 75 | Command to stard AVD: `emulator -avd ` 76 | Command to stop/kill AVD: `adb -e emu kill` 77 | ``` 78 | 79 | ## :pushpin: Pushing the App (.apk file) to Android Emulator: 80 | 81 | 1) Copy the .apk file and paste it in the path - `` 82 | 2) Open the cmd terminal from the directory where APK file is placed and enter command `adb install ` 83 | 84 | ## :pushpin: Android - Finding appPackage and appActivity: 85 | 86 | If the app is already installed on your device then we can make use of appPackage and appActivity to launch the app 87 | 88 | Option 1 : 89 | 1) Open the app on the device, for which appPackage and appActivity is required. 90 | 2) Open powershell and enter command `adb shell dumpsys window | grep -E 'mCurrentFocus|mFocusedApp'` 91 | NOTE: This command may not work for newer Android OS (10 or 11). In that case, use command: 92 | `adb shell "dumpsys activity activities | grep mResumedActivity"` 93 | 94 | Option 2 : 95 | Install APK info app to retrieve appPackage and appActivity for the app installed in your device 96 | 97 | ## :pushpin: Inspecting Elements 98 | 99 | ### uiautomatorviewer 100 | 101 | 1) Go to the path - `\tools\bin\` 102 | 2) click on `uiautomatorviewer` 103 | 3) On the UI Automator Viewer, click on Device Screenshot (uiautomator dump). Ui automator will capture the screenshot 104 | of current open screen in the device. 105 | 106 | UiAutomatorViewer 107 | 108 | ### Appium Inspector 109 | 110 | 1) Start the Appium Server and connect with Real device/Emulator. 111 | 2) Open Appium Inspector app and provide the appium server details and Desired Capabilities. 112 | 113 | Appium Inspector 114 | 115 | 3) Click on Start session which will start the appium inspector with layout shown below. 116 | 117 | Appium 118 | 119 | ## :pushpin: Inspecting Element for mobile web browser 120 | 121 | ``` 122 | Type url `chrome://inspect/#devices` in the desktop chrome browser and start inspecting element 123 | ``` 124 | 125 | Capture 126 | 127 | ## :pushpin: Launching Android Emulator Automatically 128 | 129 | Add below lines in the Desired capabilities 130 | 131 | ``` 132 | capability.setCapability(AndroidMobileCapabilityType.AVD, "Pixel_3a"); 133 | capability.setCapability(AndroidMobileCapabilityType.AVD_LAUNCH_TIMEOUT, "180000"); 134 | ``` 135 | 136 | ## :pushpin: Auto Discovery of compatible ChromeDriver 137 | 138 | Start appium server using command `appium --allow-insecure chromedriver_autodownload` 139 | 140 | ## :pushpin: Auto download of compatible ChromeDriver programmatically 141 | 142 | Add below line in the `AppiumServiceBuilder` 143 | 144 | ``` 145 | AppiumServiceBuilder builder = new AppiumServiceBuilder(); 146 | builder.withArgument(GeneralServerFlag.ALLOW_INSECURE, "chromedriver_autodownload"); 147 | ``` 148 | 149 | ## :pushpin: Start Appium server programmatically 150 | 151 | Use `AppiumServiceBuilder` and `AppiumDriverLocalService` to start the server programmatically Set environment 152 | variable `APPIUM_HOME = \node_modules\appium\build\lib` where `main.js` file is present 153 | 154 | ## :pushpin: Key Features 155 | 156 | :point_right: Supports Android and iOS Real Devices and Emulators. 157 | 158 | :point_right: Ability to start and stop the appium server on run-time. Configurable through `config.properties` 159 | 160 | :point_right: Supports capturing appium server logs on run-time. 161 | 162 | :point_right: Page object model design. 163 | 164 | :point_right: Supports parallel and sequential execution of tests. 165 | 166 | :point_right: Ability to capture screen(video) recording of tests on Android and iOS. Configurable 167 | through `config.properties` 168 | 169 | :point_right: Supports capturing screenshots for passed/failed/skipped steps which is configurable 170 | through `config.properties` 171 | 172 | :point_right: Ability to retry failed tests which is configurable through `config.properties` 173 | 174 | :point_right: Customised exception handling to provide the exceptions in a meaningful way. 175 | 176 | :point_right: Custom framework annotation to provide author name and category for each test. 177 | 178 | :point_right: Supports utilities to read test data from excel workbook and provides data to each test based on the test 179 | name. 180 | 181 | ## :pushpin: Running tests through Maven 182 | 183 | :point_right: Run test using command `mvn test -Dsurefire.suiteXmlFiles=` 184 | 185 | ## :pushpin: Running tests through testng xml 186 | 187 | :point_right: Create or Select the required testng xml -> Right click and select Run 188 | 189 | ## :pushpin: Custom Configurations in config.properties 190 | 191 | Capture 192 | 193 | ## :pushpin: Report (Extent reports) 194 | 195 | ![Web capture_24-1-2022_224531_](https://user-images.githubusercontent.com/48508827/150834585-bf17de21-9e56-494c-b0f6-9ba8451638e6.jpeg) 196 | 197 | ![Web capture_24-1-2022_224634_](https://user-images.githubusercontent.com/48508827/150834616-3c15ee3a-67cd-4e90-90c7-1f664848fd82.jpeg) 198 | 199 | -------------------------------------------------------------------------------- /checkstyles/checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /checkstyles/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 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 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | MobileAutomation 6 | AppiumMobileAutomationFramework 7 | 0.0.1-SNAPSHOT 8 | 9 | 10 | UTF-8 11 | 11 12 | 11 13 | 7.6.1 14 | 7.6.0 15 | 5.2.2 16 | 2.7.0 17 | 2.19.0 18 | 5.0.9 19 | 2.14.0 20 | 1.18.24 21 | 20220924 22 | 2.11.0 23 | 3.2.0 24 | 1.1.0 25 | 10.5.0 26 | checkstyles/checkstyle.xml 27 | checkstyles/checkstyle-suppressions.xml 28 | 29 | 30 | 31 | 32 | org.testng 33 | testng 34 | ${testng.version} 35 | 36 | 37 | 38 | 39 | io.appium 40 | java-client 41 | ${appium-java-client.version} 42 | 43 | 44 | 45 | 46 | 47 | org.apache.poi 48 | poi 49 | ${apache-poi.version} 50 | 51 | 52 | 53 | 54 | org.apache.poi 55 | poi-ooxml 56 | ${apache-poi.version} 57 | 58 | 59 | 60 | 61 | commons-io 62 | commons-io 63 | ${commons-io.version} 64 | 65 | 66 | 67 | 68 | com.aventstack 69 | extentreports 70 | ${extent-reports.version} 71 | 72 | 73 | 74 | 75 | com.jayway.jsonpath 76 | json-path 77 | ${json-path.version} 78 | 79 | 80 | 81 | 82 | org.json 83 | json 84 | ${json.version} 85 | 86 | 87 | 88 | 89 | org.apache.logging.log4j 90 | log4j-core 91 | ${log4j.version} 92 | 93 | 94 | 95 | 96 | org.apache.logging.log4j 97 | log4j-api 98 | ${log4j.version} 99 | 100 | 101 | 102 | 103 | com.fasterxml.jackson.core 104 | jackson-databind 105 | ${jackson.version} 106 | 107 | 108 | 109 | 110 | com.fasterxml.jackson.core 111 | jackson-core 112 | ${jackson.version} 113 | 114 | 115 | 116 | 117 | org.projectlombok 118 | lombok 119 | ${lombok.version} 120 | 121 | 122 | 123 | org.duraspace 124 | codestyle 125 | ${codestyle.version} 126 | 127 | 128 | 129 | com.puppycrawl.tools 130 | checkstyle 131 | ${checkstyle.version} 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | org.apache.maven.plugins 140 | maven-compiler-plugin 141 | 3.10.1 142 | 143 | 144 | 145 | org.apache.maven.plugins 146 | maven-surefire-plugin 147 | 3.0.0-M5 148 | 149 | 0 150 | 151 | 152 | ${suiteXmlFile} 153 | 154 | 155 | 156 | 157 | 158 | org.apache.maven.plugins 159 | maven-checkstyle-plugin 160 | ${maven-checkstyle-plugin.version} 161 | 162 | 163 | verify-style 164 | verify 165 | 166 | check 167 | 168 | 169 | 170 | 171 | ${checkstyle.config} 172 | ${checkstyle.suppress} 173 | UTF-8 174 | UTF-8 175 | true 176 | true 177 | true 178 | true 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /src/main/java/com/automate/constants/FrameworkConstants.java: -------------------------------------------------------------------------------- 1 | package com.automate.constants; 2 | 3 | import com.automate.enums.ConfigProperties; 4 | import com.automate.utils.configloader.PropertyUtils; 5 | import lombok.AccessLevel; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.File; 9 | import java.time.LocalDateTime; 10 | import java.time.format.DateTimeFormatter; 11 | 12 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 13 | public final class FrameworkConstants { 14 | 15 | public static final String PROJECT_PATH = System.getProperty("user.dir"); 16 | public static final String TEST_RESOURCES_DIR = PROJECT_PATH + File.separator + "src/test/resources"; 17 | public static final String ANDROID_APK_PATH = 18 | TEST_RESOURCES_DIR + File.separator + "app" + File.separator + 19 | "Android.SauceLabs.Mobile.Sample.app.2.7.1.apk"; 20 | public static final String CHROME_DRIVER_EXE_PATH = 21 | TEST_RESOURCES_DIR + File.separator + "executables" + File.separator + "chromedriver.exe"; 22 | public static final String CONFIG_PROPERTIES_PATH = 23 | TEST_RESOURCES_DIR + File.separator + "config" + File.separator + "config.properties"; 24 | public static final String CONFIG_JSON_PATH = 25 | TEST_RESOURCES_DIR + File.separator + "config" + File.separator + "config.json"; 26 | public static final String TEST_DATA_FILEPATH = 27 | TEST_RESOURCES_DIR + File.separator + "data" + File.separator + "testdata.xlsx"; 28 | public static final String APPIUM_SERVER_HOST = "127.0.0.1"; 29 | public static final int APPIUM_SERVER_PORT = 4723; 30 | public static final String APPIUM_JS_PATH = System.getenv("APPIUM_HOME") + File.separator + "main.js"; 31 | public static final String CREDENTIALS_JSON = "data/credentials.json"; 32 | public static final long EXPLICIT_WAIT = 15; 33 | public static final String TEST_DATA_SHEET = "TEST_DATA"; 34 | public static final String IOS_APP_PATH = ""; 35 | public static final String SCREENSHOT_PATH = PROJECT_PATH + File.separator + "screenshots"; 36 | public static final String NODEJS_PATH = System.getenv("NODE_HOME") + File.separator + "node.exe"; 37 | 38 | private static final String EXTENT_REPORT_PATH = PROJECT_PATH + File.separator + "extent-test-report"; 39 | private static final String APPIUM_SERVER_LOGS_PATH = PROJECT_PATH + File.separator + "server-logs"; 40 | private static final String SCREEN_RECORDING_PATH = PROJECT_PATH + File.separator + "screen-recordings"; 41 | 42 | public static String getExtentReportPath() { 43 | if (PropertyUtils.getPropertyValue(ConfigProperties.OVERRIDE_REPORTS).equalsIgnoreCase("yes")) { 44 | return EXTENT_REPORT_PATH + File.separator + "index.html"; 45 | } else { 46 | return EXTENT_REPORT_PATH + File.separator + getCurrentDateTime() + File.separator + "index.html"; 47 | } 48 | } 49 | 50 | public static String getAppiumServerLogsPath() { 51 | if (PropertyUtils.getPropertyValue(ConfigProperties.OVERRIDE_SERVER_LOG).equalsIgnoreCase("yes")) { 52 | return APPIUM_SERVER_LOGS_PATH + File.separator + "server.log"; 53 | } else { 54 | return APPIUM_SERVER_LOGS_PATH + File.separator + getCurrentDateTime() + File.separator + "server.log"; 55 | } 56 | } 57 | 58 | public static String getScreenRecordingsPath() { 59 | File screenRecordingsDir = new File(SCREEN_RECORDING_PATH); 60 | if (!screenRecordingsDir.exists()) { 61 | screenRecordingsDir.mkdir(); 62 | } 63 | return SCREEN_RECORDING_PATH; 64 | } 65 | 66 | private static String getCurrentDateTime() { 67 | DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss"); 68 | LocalDateTime localDateTime = LocalDateTime.now(); 69 | return dateTimeFormatter.format(localDateTime); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/automate/constants/StringConstants.java: -------------------------------------------------------------------------------- 1 | package com.automate.constants; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 7 | public final class StringConstants { 8 | 9 | public static final String INVALID_LOGIN_ERROR_MESSAGE = "Username and password do not match any user in this service."; 10 | public static final String PRODUCT_PAGE_TITLE = "PRODUCTS"; 11 | public static final String SEARCH_RESULTS_PAGE_TITLE = "Appium - Google Search"; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/automate/customannotations/FrameworkAnnotation.java: -------------------------------------------------------------------------------- 1 | package com.automate.customannotations; 2 | 3 | import com.automate.enums.CategoryType; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.METHOD) 12 | public @interface FrameworkAnnotation { 13 | String[] author(); 14 | 15 | CategoryType[] category(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/automate/customexceptions/DriverInitializationException.java: -------------------------------------------------------------------------------- 1 | package com.automate.customexceptions; 2 | 3 | public class DriverInitializationException extends FrameworkException { 4 | public DriverInitializationException(String message) { 5 | super(message); 6 | } 7 | 8 | public DriverInitializationException(String message, Throwable t) { 9 | super(message, t); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/automate/customexceptions/FrameworkException.java: -------------------------------------------------------------------------------- 1 | package com.automate.customexceptions; 2 | 3 | public class FrameworkException extends RuntimeException { 4 | 5 | public FrameworkException(String message) { 6 | super(message); 7 | } 8 | 9 | public FrameworkException(String message, Throwable t) { 10 | super(message, t); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/automate/customexceptions/InvalidPathException.java: -------------------------------------------------------------------------------- 1 | package com.automate.customexceptions; 2 | 3 | public class InvalidPathException extends FrameworkException { 4 | 5 | public InvalidPathException(String message) { 6 | super(message); 7 | } 8 | 9 | public InvalidPathException(String message, Throwable t) { 10 | super(message, t); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/automate/customexceptions/JsonFileUsageException.java: -------------------------------------------------------------------------------- 1 | package com.automate.customexceptions; 2 | 3 | public class JsonFileUsageException extends FrameworkException { 4 | 5 | public JsonFileUsageException(String message) { 6 | super(message); 7 | } 8 | 9 | public JsonFileUsageException(String message, Throwable t) { 10 | super(message, t); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/automate/customexceptions/PropertyFileUsageException.java: -------------------------------------------------------------------------------- 1 | package com.automate.customexceptions; 2 | 3 | public class PropertyFileUsageException extends FrameworkException { 4 | 5 | public PropertyFileUsageException(String message) { 6 | super(message); 7 | } 8 | 9 | public PropertyFileUsageException(String message, Throwable t) { 10 | super(message, t); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/automate/driver/Drivers.java: -------------------------------------------------------------------------------- 1 | package com.automate.driver; 2 | 3 | import com.automate.constants.FrameworkConstants; 4 | import com.automate.customexceptions.DriverInitializationException; 5 | import com.automate.enums.ConfigJson; 6 | import com.automate.enums.MobileBrowserName; 7 | import io.appium.java_client.AppiumDriver; 8 | import io.appium.java_client.MobileElement; 9 | import io.appium.java_client.android.AndroidDriver; 10 | import io.appium.java_client.ios.IOSDriver; 11 | import io.appium.java_client.remote.AndroidMobileCapabilityType; 12 | import io.appium.java_client.remote.AutomationName; 13 | import io.appium.java_client.remote.IOSMobileCapabilityType; 14 | import io.appium.java_client.remote.MobileCapabilityType; 15 | import lombok.AccessLevel; 16 | import lombok.NoArgsConstructor; 17 | import org.openqa.selenium.Platform; 18 | import org.openqa.selenium.remote.CapabilityType; 19 | import org.openqa.selenium.remote.DesiredCapabilities; 20 | 21 | import java.net.URL; 22 | 23 | import static com.automate.utils.configloader.JsonUtils.getConfig; 24 | 25 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 26 | public final class Drivers { 27 | 28 | public static AppiumDriver createAndroidDriverForNativeApp(String deviceName, String udid, int port, String emulator) { 29 | try { 30 | var capability = new DesiredCapabilities(); 31 | capability.setCapability(CapabilityType.PLATFORM_NAME, Platform.ANDROID); 32 | capability.setCapability(MobileCapabilityType.DEVICE_NAME, deviceName); 33 | capability.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2); // Specific to Android 34 | capability.setCapability(MobileCapabilityType.UDID, udid); // To uniquely identify device 35 | capability.setCapability(MobileCapabilityType.APP, FrameworkConstants.ANDROID_APK_PATH); 36 | capability.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, getConfig(ConfigJson.APP_PACKAGE)); 37 | capability.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, getConfig(ConfigJson.APP_ACTIVITY)); 38 | capability.setCapability(AndroidMobileCapabilityType.SYSTEM_PORT, 39 | port); // To set different port for each thread - This port is used to communicate with UiAutomator2 40 | if (emulator.equalsIgnoreCase("yes")) { 41 | capability.setCapability(AndroidMobileCapabilityType.AVD, deviceName); 42 | capability.setCapability(AndroidMobileCapabilityType.AVD_LAUNCH_TIMEOUT, 43 | Integer.parseInt(getConfig(ConfigJson.AVD_LAUNCH_TIMEOUT))); 44 | } 45 | return new AndroidDriver<>(new URL(getConfig(ConfigJson.APPIUM_URL)), capability); 46 | } catch (Exception e) { 47 | throw new DriverInitializationException("Failed to initialize driver. Please check the desired capabilities", e); 48 | } 49 | } 50 | 51 | public static AppiumDriver createAndroidDriverForWeb(String deviceName, String udid, int port, String emulator) { 52 | try { 53 | var capability = new DesiredCapabilities(); 54 | capability.setCapability(CapabilityType.PLATFORM_NAME, Platform.ANDROID); 55 | capability.setCapability(MobileCapabilityType.DEVICE_NAME, deviceName); 56 | capability.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2); 57 | capability.setCapability(MobileCapabilityType.UDID, udid); 58 | capability.setCapability(CapabilityType.BROWSER_NAME, MobileBrowserName.CHROME); 59 | capability.setCapability(AndroidMobileCapabilityType.CHROMEDRIVER_PORT, 60 | port); // For Web view/Chrome browser to launch the browser on different port 61 | if (emulator.equalsIgnoreCase("yes")) { 62 | capability.setCapability(AndroidMobileCapabilityType.AVD, deviceName); 63 | capability.setCapability(AndroidMobileCapabilityType.AVD_LAUNCH_TIMEOUT, 64 | Integer.parseInt(getConfig(ConfigJson.AVD_LAUNCH_TIMEOUT))); 65 | } 66 | 67 | return new AndroidDriver<>(new URL(getConfig(ConfigJson.APPIUM_URL)), capability); 68 | } catch (Exception e) { 69 | throw new DriverInitializationException("Failed to initialize driver. Please check the desired capabilities", e); 70 | } 71 | } 72 | 73 | public static AppiumDriver createIOSDriverForNativeApp(String deviceName, String udid, int port) { 74 | try { 75 | var capability = new DesiredCapabilities(); 76 | capability.setCapability(CapabilityType.PLATFORM_NAME, Platform.IOS); 77 | capability.setCapability(MobileCapabilityType.DEVICE_NAME, deviceName); 78 | capability.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.IOS_XCUI_TEST); 79 | capability.setCapability(MobileCapabilityType.UDID, udid); 80 | capability.setCapability(MobileCapabilityType.APP, FrameworkConstants.IOS_APP_PATH); 81 | capability.setCapability(IOSMobileCapabilityType.BUNDLE_ID, getConfig(ConfigJson.BUNDLE_ID)); 82 | capability.setCapability(IOSMobileCapabilityType.WDA_LOCAL_PORT, 83 | port); // To set different port for each thread - This port is used to communicate with WebDriverAgent driver 84 | 85 | return new IOSDriver<>(new URL(getConfig(ConfigJson.APPIUM_URL)), capability); 86 | } catch (Exception e) { 87 | throw new DriverInitializationException("Failed to initialize driver. Please check the desired capabilities", e); 88 | } 89 | } 90 | 91 | public static AppiumDriver createIOSDriverForWeb(String deviceName, String udid, int port) { 92 | try { 93 | var capability = new DesiredCapabilities(); 94 | capability.setCapability(CapabilityType.PLATFORM_NAME, Platform.IOS); 95 | capability.setCapability(MobileCapabilityType.DEVICE_NAME, deviceName); 96 | capability.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.IOS_XCUI_TEST); 97 | capability.setCapability(MobileCapabilityType.UDID, udid); 98 | capability.setCapability(IOSMobileCapabilityType.BUNDLE_ID, getConfig(ConfigJson.BUNDLE_ID)); 99 | capability.setCapability(CapabilityType.BROWSER_NAME, MobileBrowserName.SAFARI); 100 | capability.setCapability("webkitDebugProxyPort", port); // For web view/Safari browser testing on real device 101 | 102 | return new IOSDriver<>(new URL(getConfig(ConfigJson.APPIUM_URL)), capability); 103 | } catch (Exception e) { 104 | throw new DriverInitializationException("Failed to initialize driver. Please check the desired capabilities", e); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/automate/driver/factory/DriverFactory.java: -------------------------------------------------------------------------------- 1 | package com.automate.driver.factory; 2 | 3 | import com.automate.customexceptions.DriverInitializationException; 4 | import com.automate.driver.Drivers; 5 | import com.automate.driver.manager.DriverManager; 6 | import com.automate.enums.MobilePlatformName; 7 | import io.appium.java_client.AppiumDriver; 8 | import io.appium.java_client.MobileElement; 9 | import lombok.AccessLevel; 10 | import lombok.NoArgsConstructor; 11 | 12 | import java.util.Objects; 13 | 14 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 15 | public final class DriverFactory { 16 | 17 | public static void initializeDriver(MobilePlatformName mobilePlatformName, String deviceName, String udid, int port, 18 | String emulator) { 19 | AppiumDriver driver; 20 | switch (mobilePlatformName) { 21 | case ANDROID: 22 | driver = Drivers.createAndroidDriverForNativeApp(deviceName, udid, port, emulator); 23 | break; 24 | case ANDROID_WEB: 25 | driver = Drivers.createAndroidDriverForWeb(deviceName, udid, port, emulator); 26 | break; 27 | case IOS: 28 | driver = Drivers.createIOSDriverForNativeApp(deviceName, udid, port); 29 | break; 30 | case IOS_WEB: 31 | driver = Drivers.createIOSDriverForWeb(deviceName, udid, port); 32 | break; 33 | default: 34 | throw new DriverInitializationException( 35 | "Platform name " + mobilePlatformName + " is not found. Please check the platform name"); 36 | } 37 | DriverManager.setAppiumDriver(driver); 38 | } 39 | 40 | public static void quitDriver() { 41 | if (Objects.nonNull(DriverManager.getDriver())) { 42 | DriverManager.getDriver().quit(); 43 | DriverManager.unload(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/automate/driver/manager/DeviceManager.java: -------------------------------------------------------------------------------- 1 | package com.automate.driver.manager; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 7 | public final class DeviceManager { 8 | 9 | private static final ThreadLocal deviceName = new ThreadLocal<>(); 10 | 11 | public static String getDeviceName() { 12 | return deviceName.get(); 13 | } 14 | 15 | public static void setDeviceName(String device) { 16 | deviceName.set(device); 17 | } 18 | 19 | public static void unloadDeviceName() { 20 | deviceName.remove(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/automate/driver/manager/DriverManager.java: -------------------------------------------------------------------------------- 1 | package com.automate.driver.manager; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.appium.java_client.MobileElement; 5 | import lombok.AccessLevel; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.Objects; 9 | 10 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 11 | public final class DriverManager { 12 | 13 | private static final ThreadLocal> threadLocalDriver = new ThreadLocal<>(); 14 | 15 | public static AppiumDriver getDriver() { 16 | return threadLocalDriver.get(); 17 | } 18 | 19 | public static void setAppiumDriver(AppiumDriver driver) { 20 | if (Objects.nonNull(driver)) 21 | threadLocalDriver.set(driver); 22 | } 23 | 24 | public static void unload() { 25 | threadLocalDriver.remove(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/automate/driver/manager/PlatformManager.java: -------------------------------------------------------------------------------- 1 | package com.automate.driver.manager; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 7 | public class PlatformManager { 8 | 9 | private static final ThreadLocal platformName = new ThreadLocal<>(); 10 | 11 | public static String getPlatformName() { 12 | return platformName.get(); 13 | } 14 | 15 | public static void setPlatformName(String platform) { 16 | platformName.set(platform); 17 | } 18 | 19 | public static void unloadPlatformName() { 20 | platformName.remove(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/automate/entity/LoginData.java: -------------------------------------------------------------------------------- 1 | package com.automate.entity; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @Builder(setterPrefix = "set") 8 | public class LoginData { 9 | 10 | private String loginUsername; 11 | private String loginPassword; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/automate/entity/SearchData.java: -------------------------------------------------------------------------------- 1 | package com.automate.entity; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @Builder(setterPrefix = "set") 8 | public class SearchData { 9 | 10 | private String searchText; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/automate/entity/TestData.java: -------------------------------------------------------------------------------- 1 | package com.automate.entity; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @Builder(setterPrefix = "set") 8 | public class TestData { 9 | 10 | private LoginData loginData; 11 | private SearchData searchData; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/automate/enums/CategoryType.java: -------------------------------------------------------------------------------- 1 | package com.automate.enums; 2 | 3 | public enum CategoryType { 4 | 5 | REGRESSION, SANITY, SMOKE 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/automate/enums/ConfigJson.java: -------------------------------------------------------------------------------- 1 | package com.automate.enums; 2 | 3 | public enum ConfigJson { 4 | APP_ACTIVITY, APP_PACKAGE, APPIUM_URL, AVD_LAUNCH_TIMEOUT, 5 | BUNDLE_ID, 6 | URL 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/automate/enums/ConfigProperties.java: -------------------------------------------------------------------------------- 1 | package com.automate.enums; 2 | 3 | public enum ConfigProperties { 4 | RECORD_SCREEN, START_APPIUM_SERVER, 5 | OVERRIDE_REPORTS, PASSED_STEP_SCREENSHOTS, FAILED_STEP_SCREENSHOTS, SKIPPED_STEP_SCREENSHOTS, 6 | RETRY_FAILED_TESTS, RETRY_COUNT, 7 | OVERRIDE_SERVER_LOG 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/automate/enums/MobileBrowserName.java: -------------------------------------------------------------------------------- 1 | package com.automate.enums; 2 | 3 | public enum MobileBrowserName { 4 | CHROME, 5 | SAFARI 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/automate/enums/MobileFindBy.java: -------------------------------------------------------------------------------- 1 | package com.automate.enums; 2 | 3 | public enum MobileFindBy { 4 | XPATH, CSS, ID, NAME, CLASS, ACCESSIBILITY_ID 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/automate/enums/MobilePlatformName.java: -------------------------------------------------------------------------------- 1 | package com.automate.enums; 2 | 3 | public enum MobilePlatformName { 4 | 5 | ANDROID, 6 | ANDROID_WEB, 7 | IOS, 8 | IOS_WEB 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/automate/enums/WaitStrategy.java: -------------------------------------------------------------------------------- 1 | package com.automate.enums; 2 | 3 | public enum WaitStrategy { 4 | CLICKABLE, PRESENCE, VISIBLE, NONE 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/automate/factories/WaitFactory.java: -------------------------------------------------------------------------------- 1 | package com.automate.factories; 2 | 3 | import com.automate.constants.FrameworkConstants; 4 | import com.automate.driver.manager.DriverManager; 5 | import com.automate.enums.WaitStrategy; 6 | import io.appium.java_client.MobileElement; 7 | import lombok.AccessLevel; 8 | import lombok.NoArgsConstructor; 9 | import org.openqa.selenium.By; 10 | import org.openqa.selenium.WebElement; 11 | import org.openqa.selenium.support.ui.ExpectedConditions; 12 | import org.openqa.selenium.support.ui.WebDriverWait; 13 | 14 | import java.util.EnumMap; 15 | import java.util.Map; 16 | import java.util.function.Function; 17 | 18 | import static com.automate.enums.WaitStrategy.CLICKABLE; 19 | import static com.automate.enums.WaitStrategy.NONE; 20 | import static com.automate.enums.WaitStrategy.PRESENCE; 21 | import static com.automate.enums.WaitStrategy.VISIBLE; 22 | 23 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 24 | public final class WaitFactory { 25 | 26 | private static final Map> WAIT_FOR_ELEMENT_FUNCTION_MAP = 27 | new EnumMap<>(WaitStrategy.class); 28 | 29 | private static final Function CLICKABLE_ELEMENT = mobileElement -> 30 | new WebDriverWait(DriverManager.getDriver(), FrameworkConstants.EXPLICIT_WAIT) 31 | .until(ExpectedConditions.elementToBeClickable(mobileElement)); 32 | private static final Function VISIBILITY_OF_ELEMENT = mobileElement -> 33 | new WebDriverWait(DriverManager.getDriver(), FrameworkConstants.EXPLICIT_WAIT) 34 | .until(ExpectedConditions.visibilityOf(mobileElement)); 35 | private static final Function NO_MATCH = mobileElement -> mobileElement; 36 | 37 | private static final Map> WAIT_FOR_ELEMENT_LOCATED_BY_FUNCTION_MAP = 38 | new EnumMap<>(WaitStrategy.class); 39 | 40 | private static final Function CLICKABLE_ELEMENT_BY = by -> 41 | new WebDriverWait(DriverManager.getDriver(), FrameworkConstants.EXPLICIT_WAIT) 42 | .until(ExpectedConditions.elementToBeClickable(by)); 43 | private static final Function PRESENCE_OF_ELEMENT_BY = by -> 44 | new WebDriverWait(DriverManager.getDriver(), FrameworkConstants.EXPLICIT_WAIT) 45 | .until(ExpectedConditions.presenceOfElementLocated(by)); 46 | private static final Function VISIBILITY_OF_ELEMENT_BY = by -> 47 | new WebDriverWait(DriverManager.getDriver(), FrameworkConstants.EXPLICIT_WAIT) 48 | .until(ExpectedConditions.visibilityOfElementLocated(by)); 49 | private static final Function NO_MATCH_BY = by -> DriverManager.getDriver().findElement(by); 50 | 51 | static { 52 | WAIT_FOR_ELEMENT_FUNCTION_MAP.put(CLICKABLE, CLICKABLE_ELEMENT); 53 | WAIT_FOR_ELEMENT_FUNCTION_MAP.put(VISIBLE, VISIBILITY_OF_ELEMENT); 54 | WAIT_FOR_ELEMENT_FUNCTION_MAP.put(NONE, NO_MATCH); 55 | WAIT_FOR_ELEMENT_LOCATED_BY_FUNCTION_MAP.put(CLICKABLE, CLICKABLE_ELEMENT_BY); 56 | WAIT_FOR_ELEMENT_LOCATED_BY_FUNCTION_MAP.put(PRESENCE, PRESENCE_OF_ELEMENT_BY); 57 | WAIT_FOR_ELEMENT_LOCATED_BY_FUNCTION_MAP.put(VISIBLE, VISIBILITY_OF_ELEMENT_BY); 58 | WAIT_FOR_ELEMENT_LOCATED_BY_FUNCTION_MAP.put(NONE, NO_MATCH_BY); 59 | } 60 | 61 | public static WebElement explicitlyWaitForElementLocatedBy(WaitStrategy waitStrategy, By by) { 62 | return WAIT_FOR_ELEMENT_LOCATED_BY_FUNCTION_MAP.get(waitStrategy).apply(by); 63 | } 64 | 65 | public static WebElement explicitlyWaitForElement(WaitStrategy waitStrategy, MobileElement mobileElement) { 66 | return WAIT_FOR_ELEMENT_FUNCTION_MAP.get(waitStrategy).apply(mobileElement); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/automate/listeners/AnnotationTransformer.java: -------------------------------------------------------------------------------- 1 | package com.automate.listeners; 2 | 3 | import com.automate.utils.dataprovider.DataProviderUtils; 4 | import org.testng.IAnnotationTransformer; 5 | import org.testng.annotations.ITestAnnotation; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.Method; 9 | 10 | public class AnnotationTransformer implements IAnnotationTransformer { 11 | 12 | @Override 13 | public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) { 14 | annotation.setDataProvider("getData"); 15 | annotation.setDataProviderClass(DataProviderUtils.class); 16 | annotation.setRetryAnalyzer(Retry.class); 17 | } 18 | } 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/com/automate/listeners/Listeners.java: -------------------------------------------------------------------------------- 1 | package com.automate.listeners; 2 | 3 | import com.automate.customannotations.FrameworkAnnotation; 4 | import com.automate.reports.ExtentReportLogger; 5 | import com.automate.reports.ExtentReportManager; 6 | import org.testng.ISuite; 7 | import org.testng.ISuiteListener; 8 | import org.testng.ITestContext; 9 | import org.testng.ITestListener; 10 | import org.testng.ITestResult; 11 | 12 | public class Listeners implements ITestListener, ISuiteListener { 13 | 14 | @Override 15 | public void onStart(ISuite suite) { 16 | ExtentReportManager.initExtentReport(); 17 | } 18 | 19 | @Override 20 | public void onTestStart(ITestResult result) { 21 | ExtentReportManager.createTest(result.getMethod().getMethodName()); 22 | ExtentReportManager.addAuthors( 23 | result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(FrameworkAnnotation.class).author()); 24 | ExtentReportManager.addCategories( 25 | result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(FrameworkAnnotation.class).category()); 26 | ExtentReportManager.addDevices(); 27 | ExtentReportLogger.logInfo("Test - " + result.getMethod().getMethodName() + " is started"); 28 | } 29 | 30 | @Override 31 | public void onTestSuccess(ITestResult result) { 32 | ExtentReportLogger.logPass("Test - " + result.getMethod().getMethodName() + " is passed"); 33 | } 34 | 35 | @Override 36 | public void onTestFailure(ITestResult result) { 37 | ExtentReportLogger.logFail("Test - " + result.getMethod().getMethodName() + " is failed", result.getThrowable()); 38 | } 39 | 40 | @Override 41 | public void onTestSkipped(ITestResult result) { 42 | ExtentReportLogger.logSkip("Test - " + result.getMethod().getMethodName() + " is skipped"); 43 | } 44 | 45 | @Override 46 | public void onFinish(ISuite suite) { 47 | ExtentReportManager.flushExtentReport(); 48 | } 49 | 50 | @Override 51 | public void onTestFailedButWithinSuccessPercentage(ITestResult result) { 52 | // No implementation 53 | } 54 | 55 | @Override 56 | public void onStart(ITestContext iTestContext) { 57 | // No implementation 58 | } 59 | 60 | @Override 61 | public void onFinish(ITestContext iTestContext) { 62 | // No implementation 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/automate/listeners/Retry.java: -------------------------------------------------------------------------------- 1 | package com.automate.listeners; 2 | 3 | import com.automate.enums.ConfigProperties; 4 | import com.automate.utils.configloader.PropertyUtils; 5 | import org.testng.IRetryAnalyzer; 6 | import org.testng.ITestResult; 7 | 8 | public class Retry implements IRetryAnalyzer { 9 | 10 | private final int maxRetry = Integer.parseInt(PropertyUtils.getPropertyValue(ConfigProperties.RETRY_COUNT)); 11 | private int count = 0; 12 | 13 | @Override 14 | public boolean retry(ITestResult result) { 15 | boolean value = false; 16 | if (PropertyUtils.getPropertyValue(ConfigProperties.RETRY_FAILED_TESTS).equalsIgnoreCase("yes")) { 17 | value = count < maxRetry; 18 | count++; 19 | } 20 | return value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/automate/pages/GoogleSearchPage.java: -------------------------------------------------------------------------------- 1 | package com.automate.pages; 2 | 3 | import com.automate.pages.screen.ScreenActions; 4 | import io.appium.java_client.MobileElement; 5 | import org.openqa.selenium.support.FindBy; 6 | 7 | public final class GoogleSearchPage extends ScreenActions { 8 | 9 | @FindBy(xpath = "//input[@aria-label='Search']") 10 | private static MobileElement txtFieldSearch; 11 | 12 | public GoogleSearchResultPage performSearch(String searchText) { 13 | enterValueAndPressEnter(txtFieldSearch, searchText, "Search text box"); 14 | return new GoogleSearchResultPage(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/automate/pages/GoogleSearchResultPage.java: -------------------------------------------------------------------------------- 1 | package com.automate.pages; 2 | 3 | import com.automate.driver.manager.DriverManager; 4 | import com.automate.pages.screen.ScreenActions; 5 | 6 | public final class GoogleSearchResultPage extends ScreenActions { 7 | 8 | public String getSearchResultsPageTitle() { 9 | return DriverManager.getDriver().getTitle(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/automate/pages/LoginPage.java: -------------------------------------------------------------------------------- 1 | package com.automate.pages; 2 | 3 | import com.automate.entity.LoginData; 4 | import com.automate.enums.WaitStrategy; 5 | import com.automate.pages.screen.ScreenActions; 6 | import io.appium.java_client.MobileElement; 7 | import io.appium.java_client.pagefactory.AndroidFindBy; 8 | import io.appium.java_client.pagefactory.iOSXCUITFindBy; 9 | 10 | public final class LoginPage extends ScreenActions { 11 | 12 | @AndroidFindBy(accessibility = "test-Username") 13 | @iOSXCUITFindBy(accessibility = "test-Username") 14 | private static MobileElement txtFieldUsername; 15 | 16 | @AndroidFindBy(accessibility = "test-Password") 17 | @iOSXCUITFindBy(accessibility = "test-Password") 18 | private static MobileElement txtFieldPassword; 19 | 20 | @AndroidFindBy(accessibility = "test-LOGIN") 21 | @iOSXCUITFindBy(accessibility = "test-LOGIN") 22 | private static MobileElement btnLogin; 23 | 24 | @AndroidFindBy(xpath = "//android.view.ViewGroup[@content-desc='test-Error message']/android.widget.TextView") 25 | private static MobileElement errorMessage; 26 | 27 | public boolean isLoginPageDisplayed() { 28 | return isElementDisplayed(txtFieldUsername); 29 | } 30 | 31 | public LoginPage setUsername(String username) { 32 | enter(txtFieldUsername, username, "Username"); 33 | return this; 34 | } 35 | 36 | public LoginPage setPassword(String password) { 37 | enter(txtFieldPassword, password, "Password"); 38 | return this; 39 | } 40 | 41 | public ProductPage tapOnLogin() { 42 | click(btnLogin, "Login"); 43 | return new ProductPage(); 44 | } 45 | 46 | public ProductPage login(LoginData loginData) { 47 | setUsername(loginData.getLoginUsername()) 48 | .setPassword(loginData.getLoginPassword()) 49 | .tapOnLogin(); 50 | 51 | return new ProductPage(); 52 | } 53 | 54 | public String getErrorText() { 55 | return getText(errorMessage, WaitStrategy.VISIBLE); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/automate/pages/MenuPage.java: -------------------------------------------------------------------------------- 1 | package com.automate.pages; 2 | 3 | import com.automate.pages.screen.ScreenActions; 4 | import io.appium.java_client.MobileElement; 5 | import io.appium.java_client.pagefactory.AndroidFindBy; 6 | 7 | public final class MenuPage extends ScreenActions { 8 | 9 | @AndroidFindBy(xpath = "//android.view.ViewGroup[@content-desc=\"test-Menu\"]/android.view.ViewGroup/android.widget.ImageView") 10 | private static MobileElement menuIcon; 11 | 12 | public SettingsPage pressMenuIcon() { 13 | click(menuIcon, "Menu icon"); 14 | return new SettingsPage(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/automate/pages/ProductPage.java: -------------------------------------------------------------------------------- 1 | package com.automate.pages; 2 | 3 | import com.automate.enums.WaitStrategy; 4 | import com.automate.pages.screen.ScreenActions; 5 | import io.appium.java_client.MobileElement; 6 | import io.appium.java_client.pagefactory.AndroidFindBy; 7 | 8 | public final class ProductPage extends ScreenActions { 9 | 10 | @AndroidFindBy(xpath = "//android.view.ViewGroup[@content-desc=\"test-Cart drop zone\"]/android.view.ViewGroup/android.widget.TextView") 11 | private static MobileElement productPageTitle; 12 | 13 | public String getProductPageTitle() { 14 | return getText(productPageTitle, WaitStrategy.VISIBLE); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/automate/pages/SettingsPage.java: -------------------------------------------------------------------------------- 1 | package com.automate.pages; 2 | 3 | import com.automate.pages.screen.ScreenActions; 4 | import io.appium.java_client.MobileElement; 5 | import io.appium.java_client.pagefactory.AndroidFindBy; 6 | 7 | public final class SettingsPage extends ScreenActions { 8 | 9 | @AndroidFindBy(accessibility = "test-LOGOUT") 10 | private static MobileElement logOutButton; 11 | 12 | public LoginPage pressLogOutButton() { 13 | click(logOutButton, "Logout"); 14 | return new LoginPage(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/automate/pages/screen/ScreenActions.java: -------------------------------------------------------------------------------- 1 | package com.automate.pages.screen; 2 | 3 | import com.automate.driver.manager.DriverManager; 4 | import com.automate.enums.MobileFindBy; 5 | import com.automate.enums.WaitStrategy; 6 | import com.automate.reports.ExtentReportLogger; 7 | import com.google.common.collect.ImmutableMap; 8 | import com.google.common.collect.Ordering; 9 | import io.appium.java_client.MobileElement; 10 | import io.appium.java_client.MultiTouchAction; 11 | import io.appium.java_client.TouchAction; 12 | import io.appium.java_client.android.AndroidDriver; 13 | import io.appium.java_client.android.PowerACState; 14 | import io.appium.java_client.pagefactory.AppiumFieldDecorator; 15 | import io.appium.java_client.touch.TapOptions; 16 | import io.appium.java_client.touch.WaitOptions; 17 | import io.appium.java_client.touch.offset.ElementOption; 18 | import io.appium.java_client.touch.offset.PointOption; 19 | import org.openqa.selenium.By; 20 | import org.openqa.selenium.Dimension; 21 | import org.openqa.selenium.Keys; 22 | import org.openqa.selenium.ScreenOrientation; 23 | import org.openqa.selenium.WebElement; 24 | import org.openqa.selenium.interactions.Actions; 25 | import org.openqa.selenium.interactions.touch.TouchActions; 26 | import org.openqa.selenium.support.PageFactory; 27 | 28 | import java.time.Duration; 29 | import java.util.EnumMap; 30 | import java.util.HashMap; 31 | import java.util.List; 32 | import java.util.Map; 33 | import java.util.concurrent.TimeUnit; 34 | import java.util.function.Function; 35 | 36 | import static com.automate.enums.MobileFindBy.ACCESSIBILITY_ID; 37 | import static com.automate.enums.MobileFindBy.CLASS; 38 | import static com.automate.enums.MobileFindBy.CSS; 39 | import static com.automate.enums.MobileFindBy.ID; 40 | import static com.automate.enums.MobileFindBy.NAME; 41 | import static com.automate.enums.MobileFindBy.XPATH; 42 | import static com.automate.factories.WaitFactory.explicitlyWaitForElement; 43 | 44 | public class ScreenActions { 45 | 46 | private final Map> mobileFindByFunctionMap = new EnumMap<>(MobileFindBy.class); 47 | private final Function findByXpath = 48 | mobileElement -> DriverManager.getDriver().findElementByXPath(mobileElement); 49 | private final Function findByCss = 50 | mobileElement -> DriverManager.getDriver().findElementByCssSelector(mobileElement); 51 | private final Function findById = mobileElement -> DriverManager.getDriver().findElementById(mobileElement); 52 | private final Function findByName = 53 | mobileElement -> DriverManager.getDriver().findElementByName(mobileElement); 54 | private final Function findByAccessibilityId = 55 | mobileElement -> DriverManager.getDriver().findElementByAccessibilityId(mobileElement); 56 | private final Function findByClassName = 57 | mobileElement -> DriverManager.getDriver().findElementByClassName(mobileElement); 58 | protected ScreenActions() { 59 | PageFactory.initElements(new AppiumFieldDecorator(DriverManager.getDriver()), this); 60 | } 61 | 62 | private MobileElement getMobileElement(String mobileElement, MobileFindBy mobileFindBy) { 63 | if (mobileFindByFunctionMap.isEmpty()) { 64 | mobileFindByFunctionMap.put(XPATH, findByXpath); 65 | mobileFindByFunctionMap.put(CSS, findByCss); 66 | mobileFindByFunctionMap.put(ID, findById); 67 | mobileFindByFunctionMap.put(NAME, findByName); 68 | mobileFindByFunctionMap.put(ACCESSIBILITY_ID, findByAccessibilityId); 69 | mobileFindByFunctionMap.put(CLASS, findByClassName); 70 | } 71 | return mobileFindByFunctionMap.get(mobileFindBy).apply(mobileElement); 72 | } 73 | 74 | protected MobileElement getDynamicMobileElement(String mobileElement, MobileFindBy mobileFindBy) { 75 | if (mobileFindBy == XPATH) { 76 | return DriverManager.getDriver().findElement(By.xpath(mobileElement)); 77 | } else if (mobileFindBy == MobileFindBy.CSS) { 78 | return DriverManager.getDriver().findElement(By.cssSelector(mobileElement)); 79 | } 80 | return null; 81 | } 82 | 83 | protected void waitForPageLoad(int waitTime) { 84 | DriverManager.getDriver().manage().timeouts().pageLoadTimeout(waitTime, TimeUnit.SECONDS); 85 | } 86 | 87 | protected String getTextFromAttribute(WaitStrategy waitStrategy, MobileElement element) { 88 | return explicitlyWaitForElement(waitStrategy, element).getAttribute("text"); 89 | } 90 | 91 | protected String getText(MobileElement element, WaitStrategy waitStrategy) { 92 | return explicitlyWaitForElement(waitStrategy, element).getText(); 93 | } 94 | 95 | protected boolean isElementDisplayed(MobileElement element) { 96 | return element.isDisplayed(); 97 | } 98 | 99 | protected void doClear(MobileElement element) { 100 | element.clear(); 101 | } 102 | 103 | protected void getServerStatus() { 104 | DriverManager.getDriver().getStatus(); 105 | } 106 | 107 | protected void setOrientation(ScreenOrientation screenOrientationType) { 108 | switch (screenOrientationType) { 109 | case LANDSCAPE: 110 | DriverManager.getDriver().rotate(ScreenOrientation.LANDSCAPE); 111 | ExtentReportLogger.logInfo("Device Orientation is set to Landscape"); 112 | break; 113 | case PORTRAIT: 114 | DriverManager.getDriver().rotate(ScreenOrientation.PORTRAIT); 115 | ExtentReportLogger.logInfo("Device Orientation is set to Portrait"); 116 | break; 117 | default: 118 | throw new IllegalStateException("Unexpected value in Screen Orientation: " + screenOrientationType); 119 | } 120 | } 121 | 122 | protected void backgroundApp() { 123 | DriverManager.getDriver().runAppInBackground(Duration.ofSeconds(10)); 124 | } 125 | 126 | protected String getElementAttribute(MobileElement element, String attributeName) { 127 | return element.getAttribute(attributeName); 128 | } 129 | 130 | protected boolean isElementSelected(MobileElement element) { 131 | return element.isSelected(); 132 | } 133 | 134 | protected boolean isElementEnabled(MobileElement element) { 135 | return element.isEnabled(); 136 | } 137 | 138 | protected WebElement getActiveElement() { 139 | return DriverManager.getDriver().switchTo().activeElement(); 140 | } 141 | 142 | protected void moveMouseToElement(WebElement element, int xoffset, int yoffset) { 143 | new Actions(DriverManager.getDriver()) 144 | .moveToElement(element, xoffset, yoffset) 145 | .perform(); 146 | ExtentReportLogger.logInfo("Move to target element :" + element); 147 | } 148 | 149 | protected void doubleClickOnElement(WebElement element) { 150 | new Actions(DriverManager.getDriver()) 151 | .moveToElement(element) 152 | .doubleClick() 153 | .perform(); 154 | ExtentReportLogger.logInfo("Double click on element : " + element); 155 | } 156 | 157 | protected void performSingleTap(WebElement element) { 158 | new TouchActions(DriverManager.getDriver()) 159 | .singleTap(element) 160 | .perform(); 161 | ExtentReportLogger.logInfo("Single tap on element : " + element); 162 | } 163 | 164 | protected void performDoubleTap(WebElement element) { 165 | new TouchActions(DriverManager.getDriver()) 166 | .doubleTap(element) 167 | .perform(); 168 | ExtentReportLogger.logInfo("Double tap on element : " + element); 169 | } 170 | 171 | protected void performLongTap(WebElement element) { 172 | new TouchActions(DriverManager.getDriver()) 173 | .longPress(element) 174 | .perform(); 175 | ExtentReportLogger.logInfo("Long press on element : " + element); 176 | } 177 | 178 | protected void touchScreenScroll(WebElement element, int x, int y) { 179 | new TouchActions(DriverManager.getDriver()) 180 | .scroll(element, x, y) 181 | .perform(); 182 | } 183 | 184 | protected void hideKeyboard() { 185 | DriverManager.getDriver().hideKeyboard(); 186 | } 187 | 188 | protected void scrollClickAndroid(String scrollableListId, String selectionText) { 189 | ((AndroidDriver) DriverManager.getDriver()).findElementByAndroidUIAutomator( 190 | "new UiScrollable(new UiSelector().scrollable(true)." 191 | + "resourceId(\"" + scrollableListId + "\"))" 192 | + ".setAsHorizontalList().scrollIntoView(new UiSelector().text(\"" + selectionText + "\"))").click(); 193 | } 194 | 195 | protected void click(MobileElement element, String elementName) { 196 | try { 197 | element.click(); 198 | ExtentReportLogger.logInfo("Clicked on " + elementName); 199 | } catch (Exception e) { 200 | ExtentReportLogger.logFail("Exception occurred when clicking on - " + elementName, e); 201 | } 202 | } 203 | 204 | public void click(String element, MobileFindBy elementType, String elementName) { 205 | click(getMobileElement(element, elementType), elementName); 206 | } 207 | 208 | protected void enter(MobileElement element, String value, String elementName) { 209 | try { 210 | explicitlyWaitForElement(WaitStrategy.VISIBLE, element); 211 | doClear(element); 212 | element.sendKeys(value); 213 | ExtentReportLogger.logInfo("Entered value - " + value + " in the field " + elementName); 214 | } catch (Exception e) { 215 | ExtentReportLogger.logFail("Exception occurred while entering value in the field - " + elementName, e); 216 | } 217 | } 218 | 219 | protected void enterValueAndPressEnter(MobileElement element, String value, String elementName) { 220 | try { 221 | doClear(element); 222 | element.sendKeys(value, Keys.ENTER); 223 | ExtentReportLogger.logInfo("Entered value - " + value + " in the field " + elementName + " and pressed enter"); 224 | } catch (Exception e) { 225 | ExtentReportLogger.logFail("Exception caught while entering value", e); 226 | } 227 | } 228 | 229 | protected void enter(String element, MobileFindBy elementType, String value, String elementName) { 230 | enter(getMobileElement(element, elementType), value, elementName); 231 | } 232 | 233 | public boolean isTextPresent(String containsText) { 234 | return DriverManager.getDriver().getPageSource().contains(containsText); 235 | } 236 | 237 | public void powerStateAndroid(String powerState) { 238 | switch (powerState) { 239 | case "ON": 240 | ((AndroidDriver) DriverManager.getDriver()).setPowerAC(PowerACState.ON); 241 | break; 242 | case "OFF": 243 | ((AndroidDriver) DriverManager.getDriver()).setPowerAC(PowerACState.OFF); 244 | break; 245 | default: 246 | ExtentReportLogger.warning("Voice state not available"); 247 | break; 248 | } 249 | } 250 | 251 | /** 252 | * Swipe Down 253 | */ 254 | public void swipeDown() { 255 | DriverManager.getDriver().executeScript("mobile:scroll", 256 | ImmutableMap.of("direction", "down")); 257 | ExtentReportLogger.logInfo("Swipe Down"); 258 | } 259 | 260 | /** 261 | * Swipe Up 262 | */ 263 | public void swipeUP() { 264 | DriverManager.getDriver().executeScript("mobile:scroll", ImmutableMap.of("direction", "up")); 265 | ExtentReportLogger.logInfo("Swipe Up"); 266 | } 267 | 268 | /** 269 | * Accept Alert 270 | */ 271 | public void acceptAlert() { 272 | DriverManager.getDriver().executeScript("mobile:acceptAlert"); 273 | ExtentReportLogger.logInfo("Accept Alert"); 274 | } 275 | 276 | /** 277 | * Dismiss Alert 278 | */ 279 | public void dismissAlert() { 280 | DriverManager.getDriver().executeScript("mobile:dismissAlert"); 281 | ExtentReportLogger.logInfo("Dismiss Alert"); 282 | } 283 | 284 | /** 285 | * Long press key 286 | * 287 | * @param element element 288 | */ 289 | public void longPress(MobileElement element) { 290 | try { 291 | new TouchAction<>(DriverManager.getDriver()) 292 | .longPress(ElementOption.element(element)) 293 | .perform(); 294 | } catch (Exception e) { 295 | ExtentReportLogger.logFail("Exception caught while performing long press on the Mobile Element", e); 296 | } 297 | } 298 | 299 | /** 300 | * Scroll to specific location 301 | */ 302 | public void scrollToLocation() { 303 | try { 304 | HashMap scrollElement = new HashMap<>(); 305 | scrollElement.put("startX", 0.50); 306 | scrollElement.put("startY", 0.95); 307 | scrollElement.put("endX", 0.50); 308 | scrollElement.put("endY", 0.01); 309 | scrollElement.put("duration", 3.0); 310 | DriverManager.getDriver().executeScript("mobile: swipe", scrollElement); 311 | } catch (Exception e) { 312 | ExtentReportLogger.logFail("Exception caught when scrolling to specific location", e); 313 | } 314 | } 315 | 316 | public boolean checkListIsSorted(List listToSort) { 317 | if (!listToSort.isEmpty()) { 318 | try { 319 | if (Ordering.natural().isOrdered(listToSort)) { 320 | ExtentReportLogger.logPass("List is sorted"); 321 | return true; 322 | } else { 323 | ExtentReportLogger.logInfo("List is not sorted"); 324 | return false; 325 | } 326 | } catch (Exception e) { 327 | ExtentReportLogger.logFail("Exception caught when checking if list is sorted", e); 328 | } 329 | } else { 330 | ExtentReportLogger.warning("List is empty"); 331 | } 332 | return false; 333 | } 334 | 335 | /** 336 | * Touch Actions 337 | * 338 | * @param a1 axis 1 339 | * @param b1 axis 2 340 | * @param a2 axis 3 341 | * @param b2 axis 4 342 | * @param time time 343 | */ 344 | @SuppressWarnings("rawtypes") 345 | private void touchActions(int a1, int b1, int a2, int b2, int time) { 346 | TouchAction touchAction = new TouchAction(DriverManager.getDriver()); 347 | touchAction.press(PointOption.point(a1, b1)). 348 | waitAction(WaitOptions.waitOptions(Duration.ofMillis(time))). 349 | moveTo(PointOption.point(a2, b2)).release(); 350 | touchAction.perform(); 351 | } 352 | 353 | /** 354 | * Swipe with axix 355 | * 356 | * @param x x axis 357 | * @param y y axis 358 | * @param x1 x1 axis 359 | * @param y1 y1 axis 360 | * @param time timeInMilli 361 | */ 362 | protected void swipeAxis(int x, int y, int x1, int y1, int count, int time) { 363 | for (int i = 0; i < count; i++) { 364 | touchActions(x, y, x1, y1, time); 365 | } 366 | } 367 | 368 | /** 369 | * tap to element for 250sec 370 | * 371 | * @param androidElement element 372 | */ 373 | @SuppressWarnings("rawtypes") 374 | public void tapByElement(MobileElement androidElement) { 375 | new TouchAction(DriverManager.getDriver()) 376 | .tap(TapOptions.tapOptions().withElement(ElementOption.element(androidElement))) 377 | .waitAction(WaitOptions.waitOptions(Duration.ofMillis(250))).perform(); 378 | } 379 | 380 | /** 381 | * Tap by coordinates 382 | * 383 | * @param x x 384 | * @param y y 385 | */ 386 | @SuppressWarnings("rawtypes") 387 | public void tapByCoordinates(int x, int y) { 388 | new TouchAction(DriverManager.getDriver()) 389 | .tap(PointOption.point(x, y)) 390 | .waitAction(WaitOptions.waitOptions(Duration.ofMillis(250))).perform(); 391 | } 392 | 393 | /** 394 | * Press by element 395 | * 396 | * @param element element 397 | * @param seconds time 398 | */ 399 | @SuppressWarnings("rawtypes") 400 | public void pressByElement(MobileElement element, long seconds) { 401 | new TouchAction(DriverManager.getDriver()) 402 | .press(ElementOption.element(element)) 403 | .waitAction(WaitOptions.waitOptions(Duration.ofSeconds(seconds))) 404 | .release() 405 | .perform(); 406 | } 407 | 408 | /** 409 | * LongPress by element 410 | * 411 | * @param element element 412 | * @param seconds time 413 | */ 414 | @SuppressWarnings("rawtypes") 415 | public void longPressByElement(MobileElement element, long seconds) { 416 | new TouchAction(DriverManager.getDriver()) 417 | .longPress(ElementOption.element(element)) 418 | .waitAction(WaitOptions.waitOptions(Duration.ofSeconds(seconds))) 419 | .release() 420 | .perform(); 421 | } 422 | 423 | /** 424 | * Press by co-ordinates 425 | * 426 | * @param x x 427 | * @param y y 428 | * @param seconds time 429 | */ 430 | @SuppressWarnings("rawtypes") 431 | public void pressByCoordinates(int x, int y, long seconds) { 432 | new TouchAction(DriverManager.getDriver()) 433 | .press(PointOption.point(x, y)) 434 | .waitAction(WaitOptions.waitOptions(Duration.ofSeconds(seconds))) 435 | .release() 436 | .perform(); 437 | } 438 | 439 | /** 440 | * Horizontal swipe by percentage 441 | * 442 | * @param startPercentage start 443 | * @param endPercentage end 444 | * @param anchorPercentage anchor 445 | */ 446 | @SuppressWarnings("rawtypes") 447 | public void horizontalSwipeByPercentage(double startPercentage, double endPercentage, double anchorPercentage) { 448 | Dimension size = DriverManager.getDriver().manage().window().getSize(); 449 | int anchor = (int) (size.height * anchorPercentage); 450 | int startPoint = (int) (size.width * startPercentage); 451 | int endPoint = (int) (size.width * endPercentage); 452 | new TouchAction(DriverManager.getDriver()) 453 | .press(PointOption.point(startPoint, anchor)) 454 | .waitAction(WaitOptions.waitOptions(Duration.ofMillis(1000))) 455 | .moveTo(PointOption.point(endPoint, anchor)) 456 | .release().perform(); 457 | } 458 | 459 | /** 460 | * Vertical swipe by percentage 461 | * 462 | * @param startPercentage start 463 | * @param endPercentage end 464 | * @param anchorPercentage anchor 465 | */ 466 | @SuppressWarnings("rawtypes") 467 | public void verticalSwipeByPercentages(double startPercentage, double endPercentage, double anchorPercentage) { 468 | Dimension size = DriverManager.getDriver().manage().window().getSize(); 469 | int anchor = (int) (size.width * anchorPercentage); 470 | int startPoint = (int) (size.height * startPercentage); 471 | int endPoint = (int) (size.height * endPercentage); 472 | 473 | new TouchAction(DriverManager.getDriver()) 474 | .press(PointOption.point(anchor, startPoint)) 475 | .waitAction(WaitOptions.waitOptions(Duration.ofMillis(1000))) 476 | .moveTo(PointOption.point(anchor, endPoint)) 477 | .release().perform(); 478 | } 479 | 480 | /** 481 | * Swipe by elements 482 | * 483 | * @param startElement start 484 | * @param endElement end 485 | */ 486 | @SuppressWarnings("rawtypes") 487 | public void swipeByElements(MobileElement startElement, MobileElement endElement) { 488 | int startX = startElement.getLocation().getX() + (startElement.getSize().getWidth() / 2); 489 | int startY = startElement.getLocation().getY() + (startElement.getSize().getHeight() / 2); 490 | 491 | int endX = endElement.getLocation().getX() + (endElement.getSize().getWidth() / 2); 492 | int endY = endElement.getLocation().getY() + (endElement.getSize().getHeight() / 2); 493 | 494 | new TouchAction(DriverManager.getDriver()) 495 | .press(PointOption.point(startX, startY)) 496 | .waitAction(WaitOptions.waitOptions(Duration.ofMillis(1000))) 497 | .moveTo(PointOption.point(endX, endY)) 498 | .release().perform(); 499 | } 500 | 501 | /** 502 | * Multi touch by element 503 | * 504 | * @param androidElement element 505 | */ 506 | @SuppressWarnings("rawtypes") 507 | public void multiTouchByElement(MobileElement androidElement) { 508 | TouchAction press = new TouchAction(DriverManager.getDriver()) 509 | .press(ElementOption.element(androidElement)) 510 | .waitAction(WaitOptions.waitOptions(Duration.ofSeconds(1))) 511 | .release(); 512 | 513 | new MultiTouchAction(DriverManager.getDriver()) 514 | .add(press) 515 | .perform(); 516 | } 517 | 518 | /** 519 | * Swipe touch (UP,DOWN,LEFT,RIGHT) 520 | * 521 | * @param direction direction 522 | * @param count count 523 | */ 524 | protected void swipe(String direction, int count, int time) { 525 | Dimension size = DriverManager.getDriver().manage().window().getSize(); 526 | try { 527 | switch (direction) { 528 | case "left": 529 | case "LEFT": 530 | for (int i = 0; i < count; i++) { 531 | int startx = (int) (size.width * 0.8); 532 | int endx = (int) (size.width * 0.20); 533 | int starty = size.height / 2; 534 | touchActions(startx, starty, endx, starty, time); 535 | } 536 | break; 537 | case "right": 538 | case "RIGHT": 539 | for (int j = 0; j < count; j++) { 540 | int endx = (int) (size.width * 0.8); 541 | int startx = (int) (size.width * 0.20); 542 | int starty = size.height / 2; 543 | touchActions(startx, starty, endx, starty, time); 544 | } 545 | break; 546 | case "up": 547 | case "UP": 548 | for (int j = 0; j < count; j++) { 549 | int starty = (int) (size.height * 0.80); 550 | int endy = (int) (size.height * 0.20); 551 | int startx = size.width / 2; 552 | touchActions(startx, starty, startx, endy, time); 553 | } 554 | break; 555 | case "down": 556 | case "DOWN": 557 | for (int j = 0; j < count; j++) { 558 | int starty = (int) (size.height * 0.80); 559 | int endy = (int) (size.height * 0.20); 560 | int startx = size.width / 2; 561 | touchActions(startx, endy, startx, starty, time); 562 | } 563 | break; 564 | default: 565 | ExtentReportLogger.logInfo("Direction not found"); 566 | break; 567 | } 568 | } catch (Exception e) { 569 | ExtentReportLogger.logFail("Exception caught while performing Swipe", e); 570 | } 571 | } 572 | 573 | protected void closeApp() { 574 | DriverManager.getDriver().closeApp(); 575 | } 576 | 577 | protected void launchApp() { 578 | DriverManager.getDriver().launchApp(); 579 | } 580 | } 581 | -------------------------------------------------------------------------------- /src/main/java/com/automate/reports/ExtentReportLogger.java: -------------------------------------------------------------------------------- 1 | package com.automate.reports; 2 | 3 | import com.automate.enums.ConfigProperties; 4 | import com.automate.utils.configloader.PropertyUtils; 5 | import com.automate.utils.screenshot.ScreenshotService; 6 | import com.aventstack.extentreports.MediaEntityBuilder; 7 | import com.aventstack.extentreports.Status; 8 | import com.aventstack.extentreports.markuputils.ExtentColor; 9 | import com.aventstack.extentreports.markuputils.MarkupHelper; 10 | import lombok.AccessLevel; 11 | import lombok.NoArgsConstructor; 12 | 13 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 14 | public final class ExtentReportLogger { 15 | 16 | public static void logPass(String message) { 17 | if (PropertyUtils.getPropertyValue(ConfigProperties.PASSED_STEP_SCREENSHOTS).equalsIgnoreCase("yes")) { 18 | ExtentReportManager.getExtentTest().pass(message, 19 | MediaEntityBuilder.createScreenCaptureFromBase64String( 20 | ScreenshotService.getScreenshotAsBase64()).build()); 21 | } else { 22 | ExtentReportManager.getExtentTest().pass(MarkupHelper.createLabel(message, ExtentColor.GREEN)); 23 | } 24 | } 25 | 26 | public static void logFail(String message, Throwable t) { 27 | if (PropertyUtils.getPropertyValue(ConfigProperties.FAILED_STEP_SCREENSHOTS).equalsIgnoreCase("yes")) { 28 | ExtentReportManager.getExtentTest().fail(MarkupHelper.createLabel(message, ExtentColor.RED)) 29 | .fail(MediaEntityBuilder.createScreenCaptureFromBase64String(ScreenshotService.getScreenshotAsBase64()).build()) 30 | .fail(t); 31 | } else { 32 | ExtentReportManager.getExtentTest().fail(message).fail(t); 33 | } 34 | } 35 | 36 | public static void logSkip(String message) { 37 | if (PropertyUtils.getPropertyValue(ConfigProperties.SKIPPED_STEP_SCREENSHOTS).equalsIgnoreCase("yes")) { 38 | ExtentReportManager.getExtentTest().skip(message, 39 | MediaEntityBuilder.createScreenCaptureFromBase64String( 40 | ScreenshotService.getScreenshotAsBase64()).build()); 41 | } else { 42 | ExtentReportManager.getExtentTest().log(Status.SKIP, message); 43 | } 44 | } 45 | 46 | public static void logInfo(String message) { 47 | ExtentReportManager.getExtentTest().log(Status.INFO, message); 48 | } 49 | 50 | public static void warning(String message) { 51 | ExtentReportManager.getExtentTest().log(Status.WARNING, message); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/automate/reports/ExtentReportManager.java: -------------------------------------------------------------------------------- 1 | package com.automate.reports; 2 | 3 | import com.automate.constants.FrameworkConstants; 4 | import com.automate.driver.manager.DeviceManager; 5 | import com.automate.driver.manager.PlatformManager; 6 | import com.automate.enums.CategoryType; 7 | import com.aventstack.extentreports.ExtentReports; 8 | import com.aventstack.extentreports.ExtentTest; 9 | import com.aventstack.extentreports.reporter.ExtentSparkReporter; 10 | import com.aventstack.extentreports.reporter.configuration.Theme; 11 | import lombok.AccessLevel; 12 | import lombok.NoArgsConstructor; 13 | 14 | import java.awt.Desktop; 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.net.InetAddress; 18 | import java.util.Objects; 19 | 20 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 21 | public final class ExtentReportManager { 22 | 23 | private static final ExtentSparkReporter extentSparkReporter = new ExtentSparkReporter(FrameworkConstants.getExtentReportPath()); 24 | private static final ThreadLocal threadLocalExtentTest = new ThreadLocal<>(); 25 | private static ExtentReports extentReports; 26 | private static InetAddress ip; 27 | private static String hostname; 28 | 29 | /** 30 | * This method is to initialize the Extent Report 31 | */ 32 | public static void initExtentReport() { 33 | try { 34 | if (Objects.isNull(extentReports)) { 35 | extentReports = new ExtentReports(); 36 | extentReports.attachReporter(extentSparkReporter); 37 | ip = InetAddress.getLocalHost(); 38 | hostname = ip.getHostName(); 39 | extentReports.setSystemInfo("Host Name", hostname); 40 | extentReports.setSystemInfo("Environment", "Mobile Automation - Appium"); 41 | extentReports.setSystemInfo("User Name", System.getProperty("user.name")); 42 | extentSparkReporter.config().setDocumentTitle("HTML Report"); 43 | extentSparkReporter.config().setReportName("Mobile Automation Test"); 44 | extentSparkReporter.config().setTheme(Theme.DARK); 45 | } 46 | } catch (Exception e) { 47 | e.printStackTrace(); 48 | } 49 | } 50 | 51 | public static void createTest(String testCaseName) { 52 | setExtentTest(extentReports.createTest(testCaseName)); 53 | } 54 | 55 | public static void flushExtentReport() { 56 | if (Objects.nonNull(extentReports)) { 57 | extentReports.flush(); 58 | } 59 | unload(); 60 | try { 61 | Desktop.getDesktop().browse(new File(FrameworkConstants.getExtentReportPath()).toURI()); 62 | } catch (IOException e) { 63 | e.printStackTrace(); 64 | } 65 | } 66 | 67 | public static ExtentTest getExtentTest() { 68 | return threadLocalExtentTest.get(); 69 | } 70 | 71 | static void setExtentTest(ExtentTest test) { 72 | threadLocalExtentTest.set(test); 73 | } 74 | 75 | static void unload() { 76 | threadLocalExtentTest.remove(); 77 | } 78 | 79 | public static void addAuthors(String[] authors) { 80 | for (String author : authors) { 81 | getExtentTest().assignAuthor(author); 82 | } 83 | } 84 | 85 | public static void addCategories(CategoryType[] categories) { 86 | for (CategoryType category : categories) { 87 | getExtentTest().assignCategory(category.toString()); 88 | } 89 | } 90 | 91 | public static void addDevices() { 92 | getExtentTest().assignDevice(PlatformManager.getPlatformName() + "-" + DeviceManager.getDeviceName()); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/automate/utils/AppiumServerManager.java: -------------------------------------------------------------------------------- 1 | package com.automate.utils; 2 | 3 | import com.automate.constants.FrameworkConstants; 4 | import com.automate.enums.ConfigProperties; 5 | import com.automate.utils.configloader.PropertyUtils; 6 | import io.appium.java_client.service.local.AppiumDriverLocalService; 7 | import io.appium.java_client.service.local.AppiumServiceBuilder; 8 | import io.appium.java_client.service.local.flags.GeneralServerFlag; 9 | import lombok.AccessLevel; 10 | import lombok.NoArgsConstructor; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.net.ServerSocket; 15 | 16 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 17 | public final class AppiumServerManager { 18 | 19 | private static AppiumDriverLocalService service; 20 | 21 | static boolean checkIfServerIsRunning(int port) { 22 | boolean isServerRunning = false; 23 | try { 24 | ServerSocket serverSocket = new ServerSocket(port); 25 | serverSocket.close(); 26 | } catch (IOException e) { 27 | isServerRunning = true; 28 | } 29 | return isServerRunning; 30 | } 31 | 32 | public static void startAppiumServer() { 33 | if (PropertyUtils.getPropertyValue(ConfigProperties.START_APPIUM_SERVER).equalsIgnoreCase("yes")) { 34 | if (!AppiumServerManager.checkIfServerIsRunning(FrameworkConstants.APPIUM_SERVER_PORT)) { 35 | //Build the Appium service 36 | AppiumServiceBuilder builder = new AppiumServiceBuilder(); 37 | builder.usingDriverExecutable(new File(FrameworkConstants.NODEJS_PATH)) 38 | .withAppiumJS(new File(FrameworkConstants.APPIUM_JS_PATH)) 39 | .withIPAddress(FrameworkConstants.APPIUM_SERVER_HOST) 40 | .usingPort(FrameworkConstants.APPIUM_SERVER_PORT) 41 | .withArgument(GeneralServerFlag.SESSION_OVERRIDE) 42 | .withArgument(GeneralServerFlag.ALLOW_INSECURE, "chromedriver_autodownload") 43 | .withLogFile(new File(FrameworkConstants.getAppiumServerLogsPath())); 44 | //Start the server with the builder 45 | service = AppiumDriverLocalService.buildService(builder); 46 | // service = AppiumDriverLocalService.buildDefaultService(); 47 | service.start(); 48 | service.clearOutPutStreams(); 49 | } 50 | } 51 | } 52 | 53 | public static void stopAppiumServer() { 54 | if (PropertyUtils.getPropertyValue(ConfigProperties.START_APPIUM_SERVER).equalsIgnoreCase("yes")) { 55 | service.stop(); 56 | Runtime runtime = Runtime.getRuntime(); 57 | try { 58 | runtime.exec("taskkill /F /IM node.exe"); 59 | runtime.exec("taskkill /F /IM cmd.exe"); 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/automate/utils/DecodeUtils.java: -------------------------------------------------------------------------------- 1 | package com.automate.utils; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | import java.util.Base64; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 9 | public final class DecodeUtils { 10 | 11 | public static String getDecodedString(String encodedString) { 12 | return new String(Base64.getDecoder().decode(encodedString.getBytes())); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/automate/utils/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.automate.utils; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | import org.w3c.dom.Document; 8 | import org.w3c.dom.Element; 9 | import org.w3c.dom.Node; 10 | import org.w3c.dom.NodeList; 11 | import org.xml.sax.SAXException; 12 | 13 | import javax.xml.parsers.DocumentBuilder; 14 | import javax.xml.parsers.DocumentBuilderFactory; 15 | import javax.xml.parsers.ParserConfigurationException; 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.util.Arrays; 20 | import java.util.HashMap; 21 | import java.util.Objects; 22 | 23 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 24 | public final class TestUtils { 25 | 26 | public static void deleteFolder(File file) { 27 | File[] files = file.listFiles(); 28 | if (Objects.nonNull(files)) 29 | Arrays.asList(files).forEach(content -> file.delete()); 30 | } 31 | 32 | public static HashMap parseStringXML(InputStream in) throws IOException, SAXException, ParserConfigurationException { 33 | HashMap map = new HashMap<>(); 34 | 35 | DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); 36 | DocumentBuilder builder = docFactory.newDocumentBuilder(); 37 | 38 | Document document = builder.parse(in); 39 | document.getDocumentElement().normalize(); 40 | 41 | Element root = document.getDocumentElement(); 42 | NodeList nList = document.getElementsByTagName("string"); 43 | 44 | for (int temp = 0; temp < nList.getLength(); temp++) { 45 | Node node = nList.item(temp); 46 | if (node.getNodeType() == Node.ELEMENT_NODE) { 47 | Element element = (Element) node; 48 | map.put(element.getAttribute("name"), element.getTextContent()); 49 | } 50 | } 51 | return map; 52 | } 53 | 54 | public static Logger log() { 55 | return LogManager.getLogger(Thread.currentThread().getStackTrace()[2].getClassName()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/automate/utils/configloader/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package com.automate.utils.configloader; 2 | 3 | import com.automate.constants.FrameworkConstants; 4 | import com.automate.customexceptions.InvalidPathException; 5 | import com.automate.customexceptions.JsonFileUsageException; 6 | import com.automate.enums.ConfigJson; 7 | import com.fasterxml.jackson.core.type.TypeReference; 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | import com.jayway.jsonpath.JsonPath; 10 | import lombok.AccessLevel; 11 | import lombok.NoArgsConstructor; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | import java.util.Objects; 18 | 19 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 20 | public final class JsonUtils { 21 | 22 | private static Map map; 23 | 24 | public static String getValue(String key) { 25 | try { 26 | return JsonPath.read(new File(FrameworkConstants.CONFIG_JSON_PATH), key); 27 | } catch (IOException e) { 28 | throw new InvalidPathException("Check the config.json"); 29 | } 30 | } 31 | 32 | static void readJson(String jsonPath) { 33 | try { 34 | map = new ObjectMapper().readValue(new File(jsonPath), 35 | new TypeReference>() { 36 | }); 37 | } catch (IOException e) { 38 | throw new JsonFileUsageException("IOException occurred while reading Json file in the specified path"); 39 | } 40 | } 41 | 42 | public static String getConfig(ConfigJson key) { 43 | readJson(FrameworkConstants.CONFIG_JSON_PATH); 44 | if (Objects.isNull(map.get(key.name().toLowerCase()))) { 45 | throw new JsonFileUsageException("Property name - " + key + " is not found. Please check the config.json"); 46 | } 47 | return map.get(key.name().toLowerCase()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/automate/utils/configloader/PropertyUtils.java: -------------------------------------------------------------------------------- 1 | package com.automate.utils.configloader; 2 | 3 | import com.automate.constants.FrameworkConstants; 4 | import com.automate.customexceptions.PropertyFileUsageException; 5 | import com.automate.enums.ConfigProperties; 6 | import lombok.AccessLevel; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.FileInputStream; 10 | import java.io.IOException; 11 | import java.util.Objects; 12 | import java.util.Properties; 13 | 14 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 15 | public final class PropertyUtils { 16 | 17 | private static final Properties property = new Properties(); 18 | 19 | static void loadProperties(String propertyFilePath) { 20 | try (FileInputStream input = new FileInputStream(propertyFilePath)) { 21 | property.load(input); 22 | } catch (IOException e) { 23 | throw new PropertyFileUsageException("IOException occurred while loading Property file in the specified path"); 24 | } 25 | } 26 | 27 | public static String getPropertyValue(ConfigProperties key) { 28 | loadProperties(FrameworkConstants.CONFIG_PROPERTIES_PATH); 29 | if (Objects.isNull(property.getProperty(key.name().toLowerCase())) || Objects.isNull(key.name().toLowerCase())) { 30 | throw new PropertyFileUsageException("Property name - " + key + " is not found. Please check the config.properties"); 31 | } 32 | return property.getProperty(key.name().toLowerCase()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/automate/utils/dataprovider/DataProviderUtils.java: -------------------------------------------------------------------------------- 1 | package com.automate.utils.dataprovider; 2 | 3 | import com.automate.constants.FrameworkConstants; 4 | import com.automate.entity.LoginData; 5 | import com.automate.entity.SearchData; 6 | import com.automate.entity.TestData; 7 | import lombok.AccessLevel; 8 | import lombok.NoArgsConstructor; 9 | import org.testng.annotations.DataProvider; 10 | 11 | import java.lang.reflect.Method; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.function.Predicate; 16 | 17 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 18 | public final class DataProviderUtils { 19 | 20 | private static List> list = new ArrayList<>(); 21 | 22 | @DataProvider 23 | public static Object[][] getData(Method method) { 24 | LoginData loginData; 25 | SearchData searchData; 26 | TestData testData = null; 27 | String testName = method.getName(); 28 | 29 | if (list.isEmpty()) 30 | list = ExcelUtils.getTestDetails(FrameworkConstants.TEST_DATA_SHEET); 31 | 32 | List> smallList = new ArrayList<>(list); 33 | 34 | Predicate> isTestNameNotMatching = map -> !map.get("TestCaseName").equalsIgnoreCase(testName); 35 | 36 | smallList.removeIf(isTestNameNotMatching); 37 | 38 | for (Map mapData : smallList) { 39 | 40 | loginData = LoginData.builder() 41 | .setLoginUsername(mapData.get("username")) 42 | .setLoginPassword(mapData.get("password")) 43 | .build(); 44 | 45 | searchData = SearchData.builder() 46 | .setSearchText(mapData.get("searchTerm")) 47 | .build(); 48 | 49 | testData = TestData.builder() 50 | .setLoginData(loginData) 51 | .setSearchData(searchData) 52 | .build(); 53 | } 54 | return new Object[][] { 55 | {testData} 56 | }; 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/main/java/com/automate/utils/dataprovider/ExcelUtils.java: -------------------------------------------------------------------------------- 1 | package com.automate.utils.dataprovider; 2 | 3 | import com.automate.constants.FrameworkConstants; 4 | import com.automate.customexceptions.FrameworkException; 5 | import com.automate.customexceptions.InvalidPathException; 6 | import lombok.AccessLevel; 7 | import lombok.NoArgsConstructor; 8 | import org.apache.poi.xssf.usermodel.XSSFSheet; 9 | import org.apache.poi.xssf.usermodel.XSSFWorkbook; 10 | 11 | import java.io.FileInputStream; 12 | import java.io.FileNotFoundException; 13 | import java.io.IOException; 14 | import java.util.ArrayList; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 20 | public final class ExcelUtils { 21 | 22 | public static List> getTestDetails(String sheetName) { 23 | List> list; 24 | 25 | try (FileInputStream fs = new FileInputStream(FrameworkConstants.TEST_DATA_FILEPATH)) { 26 | XSSFWorkbook workbook = new XSSFWorkbook(fs); 27 | XSSFSheet sheet = workbook.getSheet(sheetName); 28 | 29 | int lastRowNum = sheet.getLastRowNum(); 30 | int lastColNum = sheet.getRow(0).getLastCellNum(); 31 | 32 | Map map; 33 | list = new ArrayList<>(); 34 | 35 | for (int i = 1; i <= lastRowNum; i++) { 36 | map = new HashMap<>(); 37 | for (int j = 0; j < lastColNum; j++) { 38 | String key = sheet.getRow(0).getCell(j).getStringCellValue(); 39 | String value = sheet.getRow(i).getCell(j).getStringCellValue(); 40 | map.put(key, value); 41 | } 42 | list.add(map); 43 | } 44 | } catch (FileNotFoundException e) { 45 | throw new InvalidPathException("Excel File you trying to read is not found"); 46 | } catch (IOException e) { 47 | throw new FrameworkException("IOException happened while reading excel file"); 48 | } 49 | return list; 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/automate/utils/screenrecording/ScreenRecordingService.java: -------------------------------------------------------------------------------- 1 | package com.automate.utils.screenrecording; 2 | 3 | import com.automate.enums.ConfigProperties; 4 | import com.automate.utils.configloader.PropertyUtils; 5 | import lombok.AccessLevel; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 9 | public final class ScreenRecordingService { 10 | 11 | public static void startRecording() { 12 | if (PropertyUtils.getPropertyValue(ConfigProperties.RECORD_SCREEN).equalsIgnoreCase("yes")) { 13 | ScreenRecordingUtils.startScreenRecording(); 14 | } 15 | } 16 | 17 | public static void stopRecording(String methodName) { 18 | if (PropertyUtils.getPropertyValue(ConfigProperties.RECORD_SCREEN).equalsIgnoreCase("yes")) { 19 | ScreenRecordingUtils.stopScreenRecording(methodName); 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/automate/utils/screenrecording/ScreenRecordingUtils.java: -------------------------------------------------------------------------------- 1 | package com.automate.utils.screenrecording; 2 | 3 | import com.automate.constants.FrameworkConstants; 4 | import com.automate.driver.manager.DriverManager; 5 | import io.appium.java_client.screenrecording.CanRecordScreen; 6 | import lombok.AccessLevel; 7 | import lombok.NoArgsConstructor; 8 | import org.apache.commons.codec.binary.Base64; 9 | 10 | import java.io.File; 11 | import java.io.FileOutputStream; 12 | import java.io.IOException; 13 | 14 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 15 | public final class ScreenRecordingUtils { 16 | 17 | public static void startScreenRecording() { 18 | ((CanRecordScreen) DriverManager.getDriver()).startRecordingScreen(); 19 | } 20 | 21 | public static void stopScreenRecording(String methodName) { 22 | var recordedVideoFile = ((CanRecordScreen) DriverManager.getDriver()).stopRecordingScreen(); 23 | var pathToWriteVideoFile = FrameworkConstants.getScreenRecordingsPath() + File.separator + methodName + ".mp4"; 24 | writeToOutputStream(pathToWriteVideoFile, recordedVideoFile); 25 | } 26 | 27 | static void writeToOutputStream(String filePathToWrite, String recordedVideoFile) { 28 | try (FileOutputStream outputStream = new FileOutputStream(filePathToWrite)) { 29 | outputStream.write(Base64.decodeBase64(recordedVideoFile)); 30 | } catch (IOException e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/automate/utils/screenshot/ScreenshotService.java: -------------------------------------------------------------------------------- 1 | package com.automate.utils.screenshot; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 7 | public final class ScreenshotService { 8 | 9 | // Abstract layer to handle the change in business requirement 10 | public static String getScreenshotAsBase64() { 11 | return ScreenshotUtils.captureScreenshotAsBase64(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/automate/utils/screenshot/ScreenshotUtils.java: -------------------------------------------------------------------------------- 1 | package com.automate.utils.screenshot; 2 | 3 | import com.automate.constants.FrameworkConstants; 4 | import com.automate.driver.manager.DriverManager; 5 | import lombok.AccessLevel; 6 | import lombok.NoArgsConstructor; 7 | import lombok.SneakyThrows; 8 | import org.apache.commons.io.FileUtils; 9 | import org.openqa.selenium.OutputType; 10 | import org.openqa.selenium.TakesScreenshot; 11 | 12 | import java.io.File; 13 | 14 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 15 | public final class ScreenshotUtils { 16 | 17 | // This class is to handle the change in third party library 18 | @SneakyThrows 19 | public static void captureScreenshotAsFile(String testName) { 20 | File source = ((TakesScreenshot) DriverManager.getDriver()).getScreenshotAs(OutputType.FILE); 21 | File destination = new File(FrameworkConstants.SCREENSHOT_PATH + File.separator + testName + ".png"); 22 | FileUtils.copyFile(source, destination); 23 | } 24 | 25 | public static String captureScreenshotAsBase64() { 26 | return ((TakesScreenshot) DriverManager.getDriver()).getScreenshotAs(OutputType.BASE64); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.properties: -------------------------------------------------------------------------------- 1 | status=warn 2 | appender.console.type=Console 3 | appender.console.name=LogToConsole 4 | appender.console.layout.type=PatternLayout 5 | appender.console.layout.pattern=[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n 6 | # Rotate log file 7 | appender.rolling.type=RollingFile 8 | appender.rolling.name=LogToRollingFile 9 | appender.rolling.fileName=logs/app.log 10 | appender.rolling.filePattern=logs/$${date:yyyy-MM-dd}/app-%d{yyyy-MM-dd}-%i.log.gz 11 | appender.rolling.layout.type=PatternLayout 12 | appender.rolling.layout.pattern=%d %p %C{1.} [%t] %m%n 13 | appender.rolling.policies.type=Policies 14 | appender.rolling.policies.time.type=TimeBasedTriggeringPolicy 15 | appender.rolling.policies.size.type=SizeBasedTriggeringPolicy 16 | appender.rolling.policies.size.size=10MB 17 | appender.rolling.strategy.type=DefaultRolloverStrategy 18 | appender.rolling.strategy.max=10 19 | # Log to console and rolling file 20 | logger.app.name=com.automate 21 | logger.app.level=debug 22 | logger.app.additivity=false 23 | logger.app.appenderRef.rolling.ref=LogToRollingFile 24 | logger.app.appenderRef.console.ref=LogToConsole 25 | rootLogger.level=info 26 | rootLogger.appenderRef.stdout.ref=LogToConsole 27 | -------------------------------------------------------------------------------- /src/test/java/base/BaseTest.java: -------------------------------------------------------------------------------- 1 | package base; 2 | 3 | import com.automate.driver.factory.DriverFactory; 4 | import com.automate.driver.manager.DeviceManager; 5 | import com.automate.driver.manager.DriverManager; 6 | import com.automate.driver.manager.PlatformManager; 7 | import com.automate.enums.MobilePlatformName; 8 | import com.automate.utils.AppiumServerManager; 9 | import com.automate.utils.screenrecording.ScreenRecordingService; 10 | import org.testng.ITestResult; 11 | import org.testng.annotations.AfterMethod; 12 | import org.testng.annotations.AfterSuite; 13 | import org.testng.annotations.BeforeMethod; 14 | import org.testng.annotations.BeforeSuite; 15 | import org.testng.annotations.Optional; 16 | import org.testng.annotations.Parameters; 17 | 18 | import java.util.Objects; 19 | 20 | public class BaseTest { 21 | 22 | protected BaseTest() { 23 | } 24 | 25 | @BeforeSuite(alwaysRun = true) 26 | protected void beforeSuite() { 27 | AppiumServerManager.startAppiumServer(); 28 | } 29 | 30 | @Parameters({"platformName", "udid", "deviceName", "systemPort", "chromeDriverPort", "emulator", "wdaLocalPort", 31 | "webkitDebugProxyPort"}) 32 | @BeforeMethod 33 | protected void setUp(String platformName, String udid, String deviceName, @Optional("androidOnly") String systemPort, 34 | @Optional("androidOnly") String chromeDriverPort, @Optional("androidOnly") String emulator, 35 | @Optional("iOSOnly") String wdaLocalPort, @Optional("iOSOnly") String webkitDebugProxyPort) { 36 | PlatformManager.setPlatformName(platformName); 37 | DeviceManager.setDeviceName(deviceName); 38 | if (Objects.isNull(DriverManager.getDriver())) { 39 | DriverFactory.initializeDriver(MobilePlatformName.valueOf(platformName.toUpperCase()), deviceName, udid, 40 | Integer.parseInt(systemPort), emulator); 41 | } 42 | ScreenRecordingService.startRecording(); 43 | } 44 | 45 | @AfterMethod 46 | protected void tearDown(ITestResult result) { 47 | ScreenRecordingService.stopRecording(result.getName()); 48 | DriverFactory.quitDriver(); 49 | } 50 | 51 | @AfterSuite(alwaysRun = true) 52 | protected void afterSuite() { 53 | AppiumServerManager.stopAppiumServer(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/tests/GoogleTest.java: -------------------------------------------------------------------------------- 1 | package com.tests; 2 | 3 | import base.BaseTest; 4 | import com.automate.constants.StringConstants; 5 | import com.automate.customannotations.FrameworkAnnotation; 6 | import com.automate.driver.manager.DriverManager; 7 | import com.automate.entity.TestData; 8 | import com.automate.enums.CategoryType; 9 | import com.automate.enums.ConfigJson; 10 | import com.automate.pages.GoogleSearchPage; 11 | import lombok.AccessLevel; 12 | import lombok.NoArgsConstructor; 13 | import org.testng.Assert; 14 | import org.testng.annotations.Test; 15 | 16 | import static com.automate.utils.configloader.JsonUtils.getConfig; 17 | 18 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 19 | public final class GoogleTest extends BaseTest { 20 | 21 | @FrameworkAnnotation(author = "User-1", category = {CategoryType.REGRESSION, CategoryType.SANITY, CategoryType.SMOKE}) 22 | @Test(description = "Google search") 23 | public void googleSearch(TestData data) { 24 | DriverManager.getDriver().get(getConfig(ConfigJson.URL)); 25 | String searchResultsPageTitle = new GoogleSearchPage() 26 | .performSearch(data.getSearchData().getSearchText()) 27 | .getSearchResultsPageTitle(); 28 | 29 | Assert.assertEquals(searchResultsPageTitle, StringConstants.SEARCH_RESULTS_PAGE_TITLE); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/tests/LoginTest.java: -------------------------------------------------------------------------------- 1 | package com.tests; 2 | 3 | import base.BaseTest; 4 | import com.automate.constants.StringConstants; 5 | import com.automate.customannotations.FrameworkAnnotation; 6 | import com.automate.entity.TestData; 7 | import com.automate.enums.CategoryType; 8 | import com.automate.pages.LoginPage; 9 | import lombok.AccessLevel; 10 | import lombok.NoArgsConstructor; 11 | import org.testng.Assert; 12 | import org.testng.annotations.BeforeMethod; 13 | import org.testng.annotations.Test; 14 | 15 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 16 | public final class LoginTest extends BaseTest { 17 | 18 | LoginPage loginPage; 19 | 20 | @BeforeMethod 21 | public void initialize() { 22 | loginPage = new LoginPage(); 23 | } 24 | 25 | @FrameworkAnnotation(author = "User-1", category = {CategoryType.REGRESSION, CategoryType.SMOKE}) 26 | @Test(description = "Incorrect Username and Password combination") 27 | public void invalidLogin(TestData data) { 28 | loginPage.setUsername(data.getLoginData().getLoginUsername()) 29 | .setPassword(data.getLoginData().getLoginPassword()) 30 | .tapOnLogin(); 31 | String invalidLoginErrorMessage = loginPage.getErrorText(); 32 | 33 | Assert.assertEquals(invalidLoginErrorMessage, StringConstants.INVALID_LOGIN_ERROR_MESSAGE, "Assertion for Invalid login error"); 34 | } 35 | 36 | @FrameworkAnnotation(author = "User-2", category = {CategoryType.REGRESSION, CategoryType.SANITY}) 37 | @Test(description = "Correct Username and Password combination") 38 | public void validLogin(TestData data) { 39 | String productPageTitle = loginPage.setUsername(data.getLoginData().getLoginUsername()) 40 | .setPassword(data.getLoginData().getLoginPassword()) 41 | .tapOnLogin() 42 | .getProductPageTitle(); 43 | 44 | Assert.assertEquals(productPageTitle, StringConstants.PRODUCT_PAGE_TITLE, "Assertion for valid login"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/resources/app/Android.SauceLabs.Mobile.Sample.app.2.7.1.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thangarajtk/appium-mobileAutomationFramework/07e00ade6654e5e7bfba00bc95a2aed20960df40/src/test/resources/app/Android.SauceLabs.Mobile.Sample.app.2.7.1.apk -------------------------------------------------------------------------------- /src/test/resources/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "app_package": "com.swaglabsmobileapp", 3 | "app_activity": "com.swaglabsmobileapp.MainActivity", 4 | "appium_url": "http://127.0.0.1:4723/wd/hub", 5 | "avd_launch_timeout": 180000, 6 | "bundle_id": "", 7 | "url": "https://www.google.com/" 8 | } 9 | -------------------------------------------------------------------------------- /src/test/resources/config/config.properties: -------------------------------------------------------------------------------- 1 | # Provides an option to record screen during execution and save it in .mp4 format 2 | record_screen=no 3 | # Provides an option to start appium server programmatically 4 | start_appium_server=yes 5 | # Provides an option to override extent report 6 | override_reports=yes 7 | # Provides an option to capture screenshot for passed steps 8 | passed_step_screenshots=no 9 | # Provides an option to capture screenshot for failed steps 10 | failed_step_screenshots=yes 11 | # Provides an option to capture screenshot for skipped steps 12 | skipped_step_screenshots=no 13 | # Provides an option to retry failed tests 14 | retry_failed_tests=no 15 | retry_count=1 16 | # Provides an option to override appium server log 17 | override_server_log=yes 18 | -------------------------------------------------------------------------------- /src/test/resources/data/testdata.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thangarajtk/appium-mobileAutomationFramework/07e00ade6654e5e7bfba00bc95a2aed20960df40/src/test/resources/data/testdata.xlsx -------------------------------------------------------------------------------- /src/test/resources/executables/chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thangarajtk/appium-mobileAutomationFramework/07e00ade6654e5e7bfba00bc95a2aed20960df40/src/test/resources/executables/chromedriver.exe -------------------------------------------------------------------------------- /testng_android_Emulator.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /testng_android_ParallelExecution.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /testng_android_RealDevice.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------