├── .editorconfig
├── .gitignore
├── LICENSE
├── README.md
├── checkstyle
├── checkstyle-suppression.xml
└── checkstyle.xml
├── pom.xml
└── src
├── main
└── java
│ └── org
│ └── example
│ ├── constants
│ └── FrameworkConstants.java
│ ├── customexceptions
│ ├── DriverInitializationException.java
│ ├── FrameworkException.java
│ ├── JsonFileUsageException.java
│ └── PropertyFileUsageException.java
│ ├── driver
│ ├── Drivers.java
│ ├── factory
│ │ └── DriverFactory.java
│ └── manager
│ │ ├── AndroidManager.java
│ │ ├── DeviceManager.java
│ │ ├── DriverManager.java
│ │ ├── IosManager.java
│ │ └── PlatformManager.java
│ ├── enums
│ ├── ConfigJson.java
│ ├── ConfigProperties.java
│ ├── MobileBrowserName.java
│ ├── MobileFindBy.java
│ ├── MobilePlatformName.java
│ └── WaitStrategy.java
│ ├── factories
│ └── WaitFactory.java
│ ├── pageobjects
│ ├── LoginPage.java
│ ├── ProductPage.java
│ └── screen
│ │ └── ScreenActions.java
│ └── utils
│ ├── AppiumServerManager.java
│ ├── configloader
│ ├── JsonParser.java
│ └── PropertyUtils.java
│ ├── screenrecording
│ ├── ScreenRecordingService.java
│ └── ScreenRecordingUtils.java
│ └── screenshot
│ ├── ScreenshotService.java
│ └── ScreenshotUtils.java
└── test
├── java
└── org
│ └── example
│ ├── runner
│ └── RunnerTest.java
│ └── stepdefinitions
│ ├── Hooks.java
│ └── LoginSteps.java
└── resources
├── app
└── Android.SauceLabs.Mobile.Sample.app.2.7.1.apk
├── config
├── config.json
└── config.properties
├── cucumber.properties
└── feature
└── Login.feature
/.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 | /.idea
2 | /server-logs
3 | /screen-recordings
4 | /target
--------------------------------------------------------------------------------
/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-bdd
2 |
3 | Mobile automation framework using appium - BDD
4 |
5 |
6 |
7 |
8 | ## :rocket: Quick Start - Appium set up on Windows (Android):
9 |
10 | 1) Install [Java JDK8](https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html)
11 | and [IntelliJ IDEA](https://www.jetbrains.com/idea/download/)
12 | 2) Install [NodeJS](https://nodejs.org/en/download/)
13 | 3) Install [Android studio](https://developer.android.com/studio)
14 | 4) Install Appium Server (v2.x) using npm (CLI) command `npm install -g appium@next`
15 | ```
16 | Appium server version 2.0.0-beta.55
17 | ```
18 | This will install the Appium server only, but no drivers. Install the latest versions of the XCUITest and UiAutomator2 drivers, after installing Appium you would run the following commands:
19 | ```
20 | appium driver install uiautomator2
21 | appium driver install xcuitest
22 | ```
23 | ```
24 | Command to check the installed appium version: `appium --version`
25 | ```
26 |
27 | 5) Add below Android SDK path in the environment variable
28 |
29 | ```
30 | - ANDROID_HOME =
31 | - %ANDROID_HOME%\tools
32 | - %ANDROID_HOME%\tools\bin
33 | - %ANDROID_HOME%\platform-tools
34 | ```
35 |
36 | 6) Install [Appium desktop](https://github.com/appium/appium-desktop/releases/)
37 | 7) Install [Appium Inspector](https://github.com/appium/appium-inspector/releases)
38 |
39 | ## :rocket: Quick Start - Appium set up on MAC (Android):
40 |
41 | 1) Install Homebrew
42 | 2) Install [NodeJS](https://nodejs.org/en/download/)
43 | 3) Install [Java JDK8](https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html)
44 | and [IntelliJ IDEA](https://www.jetbrains.com/idea/download/)
45 | 4) Install Appium server using npm (CLI) or Appium desktop client
46 | 5) Install [Android studio](https://developer.android.com/studio)
47 | 6) Install [Appium Inspector](https://github.com/appium/appium-inspector/releases)
48 | 7) Set JAVA_HOME and ANDROID_HOME environment variables
49 |
50 | ## :pushpin: Appium Doctor to verify the installations
51 |
52 | 1) Install appium-doctor using command `npm install -g appium-doctor`
53 | 2) To view the list of available options `appium-doctor --help`
54 |
55 | ```
56 | To check Android set up `appium-doctor --android`
57 | To check ios set up `appium-doctor --ios`
58 | ```
59 |
60 | ## :pushpin: Creating Android Virtual Device (Emulator) from Android Studio:
61 |
62 | 1) Open Android Studio.
63 | 2) Click on Tools -> AVD Manager -> Create Virtual Device -> Select the device and OS version -> Finish.
64 | 3) Once Virtual device is created, click on Launch this AVD in the emulator.
65 | 4) Command to view the list of devices attached `adb devices`
66 |
67 | ## :pushpin: Android Real Device Set up:
68 |
69 | 1) Connect Android real device to the machine(Desktop/Laptop)
70 | 2) Turn on the developer options in android mobile
71 | 3) Enable USB debugging
72 | 4) Run command `adb devices` in cmd prompt to check whether the device is recognised
73 |
74 | ## :pushpin: Mirror android/ios device to your desktop
75 |
76 | 1) Download [Vysor](https://www.vysor.io/)
77 |
78 | ## :pushpin: Start Android Emulator from Command line
79 |
80 | 1) Open command prompt, go to ``
81 |
82 | ```
83 | Command to stard AVD: `emulator -avd `
84 | Command to stop/kill AVD: `adb -e emu kill`
85 | ```
86 |
87 | ## :pushpin: Pushing the App (.apk file) to Android Emulator:
88 |
89 | 1) Copy the .apk file and paste it in the path - ``
90 | 2) Open the cmd terminal from the directory where APK file is placed and enter command `adb install `
91 |
92 | ## :pushpin: Android - Finding appPackage and appActivity:
93 |
94 | If the app is already installed on your device then we can make use of appPackage and appActivity to launch the app
95 |
96 | Option 1 :
97 | 1) Open the app on the device, for which appPackage and appActivity is required.
98 | 2) Open powershell and enter command `adb shell dumpsys window | grep -E 'mCurrentFocus|mFocusedApp'`
99 | NOTE: This command may not work for newer Android OS (10 or 11). In that case, use command:
100 | `adb shell "dumpsys activity activities | grep mResumedActivity"`
101 |
102 | Option 2 :
103 | Install APK info app to retrieve appPackage and appActivity for the app installed in your device
104 |
105 | ## :pushpin: Inspecting Elements
106 |
107 | ### uiautomatorviewer
108 |
109 | 1) Go to the path - `\tools\bin\`
110 | 2) click on `uiautomatorviewer`
111 | 3) On the UI Automator Viewer, click on Device Screenshot (uiautomator dump). Ui automator will capture the screenshot
112 | of current open screen in the device.
113 |
114 |
115 |
116 | ### Appium Inspector
117 |
118 | 1) Start the Appium Server and connect with Real device/Emulator.
119 | 2) Open Appium Inspector app and provide the appium server details and Desired Capabilities.
120 |
121 |
122 |
123 | 3) Click on Start session which will start the appium inspector with layout shown below.
124 |
125 |
126 |
127 | ## :pushpin: Inspecting Element for mobile web browser
128 |
129 | ```
130 | Type url `chrome://inspect/#devices` in the desktop chrome browser and start inspecting element
131 | ```
132 |
133 |
134 |
135 | ## :pushpin: Launching Android Emulator Automatically
136 |
137 | Add below lines in the Desired capabilities
138 |
139 | ```
140 | capability.setCapability(AndroidMobileCapabilityType.AVD, "Pixel_3a");
141 | capability.setCapability(AndroidMobileCapabilityType.AVD_LAUNCH_TIMEOUT, "180000");
142 | ```
143 |
144 | ## :pushpin: Auto Discovery of compatible ChromeDriver
145 |
146 | Start appium server using command `appium --allow-insecure chromedriver_autodownload`
147 |
148 | ## :pushpin: Auto download of compatible ChromeDriver programmatically
149 |
150 | Add below line in the `AppiumServiceBuilder`
151 |
152 | ```
153 | AppiumServiceBuilder builder = new AppiumServiceBuilder();
154 | builder.withArgument(GeneralServerFlag.ALLOW_INSECURE, "chromedriver_autodownload");
155 | ```
156 |
157 | ## :pushpin: Start Appium server programmatically
158 |
159 | Use `AppiumServiceBuilder` and `AppiumDriverLocalService` to start the server programmatically Set environment
160 | variable `APPIUM_HOME = \node_modules\appium\build\lib` where `main.js` file is present
161 |
162 | ## :pushpin: Running tests through Test Runner (JUnit)
163 |
164 | :point_right: Run `RunCucumberTest` in the
165 | path `appium-mobile-automation-framework-bdd\src\test\java\org\example\runner\RunCucumberTest.java`
166 |
167 | ## :pushpin: Running tests in parallel on multiple devices through Maven CLI
168 |
169 | :point_right: Run test using following command
170 |
171 | ```
172 | mvn clean test -DplatformName= -DdeviceName= -Dudid= -DsystemPort= -DchromedriverPort=
173 |
174 | Example:
175 | mvn clean test -DplatformName=android -DdeviceName=Pixel_4 -Dudid=emulator-5554 -DsystemPort=7070 -DchromedriverPort=7071
176 | ```
177 |
178 | ## :pushpin: Config Json for updating DesiredCapabilities
179 |
180 |
181 |
182 | ## :pushpin: Cucumber-html-reports
183 |
184 |
185 |
--------------------------------------------------------------------------------
/checkstyle/checkstyle-suppression.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/checkstyle/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 |
2 |
3 |
5 | 4.0.0
6 |
7 | org.example
8 | appium-mobile-automation-framework-bdd
9 | 1.0-SNAPSHOT
10 | appium-mobile-automation-framework-bdd
11 | http://maven.apache.org
12 |
13 |
14 | UTF-8
15 | 11
16 | 11
17 | 7.10.1
18 | 8.3.0
19 | 2.14.0
20 | 1.18.24
21 | 2.11.0
22 | 20220924
23 | 25.0.2
24 | 5.7.4
25 | 3.8.1
26 | 3.0.0-M5
27 | 3.2.0
28 | checkstyle/checkstyle.xml
29 | checkstyle/checkstyle-suppression.xml
30 |
31 |
32 |
33 |
34 |
35 | io.cucumber
36 | cucumber-junit
37 | ${cucumber.version}
38 | test
39 |
40 |
41 |
42 |
43 | io.cucumber
44 | cucumber-java
45 | ${cucumber.version}
46 |
47 |
48 |
49 |
50 | io.cucumber
51 | cucumber-picocontainer
52 | ${cucumber.version}
53 |
54 |
55 |
56 |
57 | io.cucumber
58 | gherkin
59 | ${gherkin.version}
60 |
61 |
62 |
63 |
64 | io.appium
65 | java-client
66 | ${appium-java-client.version}
67 |
68 |
69 |
70 |
71 | commons-io
72 | commons-io
73 | ${commons-io.version}
74 |
75 |
76 |
77 |
78 | commons-codec
79 | commons-codec
80 | 1.15
81 |
82 |
83 |
84 |
85 | org.json
86 | json
87 | ${json.version}
88 |
89 |
90 |
91 |
92 | com.fasterxml.jackson.core
93 | jackson-databind
94 | ${jackson.version}
95 |
96 |
97 |
98 |
99 | com.fasterxml.jackson.core
100 | jackson-core
101 | ${jackson.version}
102 |
103 |
104 |
105 |
106 | org.projectlombok
107 | lombok
108 | ${lombok.version}
109 |
110 |
111 |
112 | net.masterthought
113 | maven-cucumber-reporting
114 | ${maven-cucumber-reporting.version}
115 |
116 |
117 |
118 |
119 | org.apache.maven.plugins
120 | maven-checkstyle-plugin
121 | ${maven-checkstyle-plugin.version}
122 |
123 |
124 | org.seleniumhq.selenium
125 | selenium-support
126 | 4.5.0
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | org.apache.maven.plugins
135 | maven-compiler-plugin
136 | ${maven-compiler-plugin.version}
137 |
138 | 11
139 | 11
140 |
141 |
142 |
143 |
144 | org.apache.maven.plugins
145 | maven-surefire-plugin
146 | ${maven-surefire-plugin.version}
147 |
148 |
149 |
150 | org.apache.maven.plugins
151 | maven-checkstyle-plugin
152 | ${maven-checkstyle-plugin.version}
153 |
154 |
155 | verify-style
156 | verify
157 |
158 | check
159 |
160 |
161 |
162 |
163 | ${checkstyle.config}
164 | ${checkstyle.suppress}
165 | UTF-8
166 | UTF-8
167 | true
168 | true
169 | true
170 | true
171 |
172 |
173 |
174 |
175 | net.masterthought
176 | maven-cucumber-reporting
177 | ${maven-cucumber-reporting.version}
178 |
179 |
180 | execution
181 | test
182 |
183 | generate
184 |
185 |
186 | Mobile - Test Automation Framework
187 | ${project.build.directory}/cucumber-maven-html-report
188 | ${project.build.directory}/cucumber-json-output
189 |
190 | **/cucumber.json
191 |
192 | true
193 |
194 | false
195 |
197 | false
198 |
199 | false
200 |
201 | ${project.version}
202 | ${os.name}
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
--------------------------------------------------------------------------------
/src/main/java/org/example/constants/FrameworkConstants.java:
--------------------------------------------------------------------------------
1 | package org.example.constants;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.NoArgsConstructor;
5 | import org.example.enums.ConfigProperties;
6 | import org.example.utils.configloader.PropertyUtils;
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 RESOURCES_PATH = "src/test/resources";
17 | public static final String APPIUM_SERVER_HOST = "127.0.0.1";
18 | public static final int APPIUM_SERVER_PORT = 4723;
19 | public static final String APPIUM_JS_PATH = System.getenv("APPIUM_JS");
20 | public static final String ANDROID_APK_PATH =
21 | PROJECT_PATH + File.separator + RESOURCES_PATH + File.separator + "app" + File.separator +
22 | "Android.SauceLabs.Mobile.Sample.app.2.7.1.apk";
23 | public static final String CONFIG_PROPERTIES_PATH =
24 | PROJECT_PATH + File.separator + RESOURCES_PATH + File.separator + "config" + File.separator + "config.properties";
25 | public static final String CONFIG_JSON_PATH =
26 | PROJECT_PATH + File.separator + RESOURCES_PATH + File.separator + "config" + File.separator + "config.json";
27 | public static final long EXPLICIT_WAIT = 15;
28 | public static final String IOS_APP_PATH = "";
29 | public static final String SCREENSHOT_PATH = PROJECT_PATH + File.separator + "screenshots";
30 | public static final String NODEJS_PATH = System.getenv("NODE_HOME") + File.separator + "node.exe";
31 |
32 | private static final String SERVER_LOGS_PATH = PROJECT_PATH + File.separator + "server-logs";
33 | private static final String SCREEN_RECORDINGS_PATH = PROJECT_PATH + File.separator + "screen-recordings";
34 |
35 | public static String getAppiumServerLogsPath() {
36 | if (PropertyUtils.getPropertyValue(ConfigProperties.OVERRIDE_SERVER_LOG).equalsIgnoreCase("yes"))
37 | return SERVER_LOGS_PATH + File.separator + "server.log";
38 | else return SERVER_LOGS_PATH + File.separator + getCurrentDateTime() + File.separator + "server.log";
39 | }
40 |
41 | public static String getScreenRecordingsPath() {
42 | var screenRecordingsDir = new File(SCREEN_RECORDINGS_PATH);
43 | if (!screenRecordingsDir.exists())
44 | screenRecordingsDir.mkdir();
45 | return SCREEN_RECORDINGS_PATH;
46 | }
47 |
48 | private static String getCurrentDateTime() {
49 | DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss");
50 | LocalDateTime localDateTime = LocalDateTime.now();
51 | return dateTimeFormatter.format(localDateTime);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/org/example/customexceptions/DriverInitializationException.java:
--------------------------------------------------------------------------------
1 | package org.example.customexceptions;
2 |
3 | public class DriverInitializationException extends FrameworkException {
4 |
5 | public DriverInitializationException(String message) {
6 | super(message);
7 | }
8 |
9 | public DriverInitializationException(String message, Throwable t) {
10 | super(message, t);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/org/example/customexceptions/FrameworkException.java:
--------------------------------------------------------------------------------
1 | package org.example.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/org/example/customexceptions/JsonFileUsageException.java:
--------------------------------------------------------------------------------
1 | package org.example.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/org/example/customexceptions/PropertyFileUsageException.java:
--------------------------------------------------------------------------------
1 | package org.example.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/org/example/driver/Drivers.java:
--------------------------------------------------------------------------------
1 | package org.example.driver;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.NoArgsConstructor;
5 | import org.example.driver.factory.DriverFactory;
6 | import org.example.driver.manager.DriverManager;
7 | import org.example.enums.MobilePlatformName;
8 |
9 | import java.util.Objects;
10 |
11 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
12 | public final class Drivers {
13 |
14 | public static void initializeDriver(MobilePlatformName mobilePlatformName) {
15 | DriverManager.setAppiumDriver(DriverFactory.getDriver(mobilePlatformName));
16 | }
17 |
18 | public static void quitDriver() {
19 | if (Objects.nonNull(DriverManager.getDriver())) {
20 | DriverManager.getDriver().quit();
21 | DriverManager.unload();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/org/example/driver/factory/DriverFactory.java:
--------------------------------------------------------------------------------
1 | package org.example.driver.factory;
2 |
3 | import io.appium.java_client.AppiumDriver;
4 | import lombok.AccessLevel;
5 | import lombok.NoArgsConstructor;
6 | import org.example.driver.manager.AndroidManager;
7 | import org.example.driver.manager.IosManager;
8 | import org.example.enums.MobilePlatformName;
9 |
10 | import java.util.EnumMap;
11 | import java.util.Map;
12 | import java.util.function.Supplier;
13 |
14 | import static org.example.enums.MobilePlatformName.ANDROID;
15 | import static org.example.enums.MobilePlatformName.IOS;
16 |
17 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
18 | public final class DriverFactory {
19 |
20 | private static final Map> DRIVER_TYPE_MAP =
21 | new EnumMap<>(MobilePlatformName.class);
22 |
23 | static {
24 | DRIVER_TYPE_MAP.put(ANDROID, AndroidManager::createAndroidDriver);
25 | DRIVER_TYPE_MAP.put(IOS, IosManager::createIOSDriver);
26 | }
27 |
28 | public static AppiumDriver getDriver(MobilePlatformName mobilePlatformName) {
29 | return DRIVER_TYPE_MAP.get(mobilePlatformName).get();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/org/example/driver/manager/AndroidManager.java:
--------------------------------------------------------------------------------
1 | package org.example.driver.manager;
2 |
3 | import io.appium.java_client.AppiumDriver;
4 | import io.appium.java_client.android.AndroidDriver;
5 | import io.appium.java_client.android.options.UiAutomator2Options;
6 | import lombok.AccessLevel;
7 | import lombok.NoArgsConstructor;
8 | import org.example.constants.FrameworkConstants;
9 | import org.example.customexceptions.DriverInitializationException;
10 | import org.example.enums.ConfigJson;
11 |
12 | import java.net.URL;
13 | import java.time.Duration;
14 | import java.util.Optional;
15 |
16 | import static org.example.utils.configloader.JsonParser.getConfig;
17 |
18 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
19 | public final class AndroidManager {
20 |
21 | public static AppiumDriver createAndroidDriver() {
22 | try {
23 | UiAutomator2Options options = new UiAutomator2Options();
24 | options.setDeviceName(Optional.ofNullable(System.getProperty("deviceName"))
25 | .orElse(getConfig(ConfigJson.ANDROID_DEVICE_NAME)))
26 | .setUdid(Optional.ofNullable(System.getProperty("udid"))
27 | .orElse(getConfig(ConfigJson.ANDROID_UDID)))
28 | .setApp(FrameworkConstants.ANDROID_APK_PATH)
29 | .setAppPackage(getConfig(ConfigJson.ANDROID_APP_PACKAGE))
30 | .setAppActivity(getConfig(ConfigJson.ANDROID_APP_ACTIVITY))
31 | .setSystemPort(Integer.parseInt(Optional.ofNullable(System.getProperty("systemPort"))
32 | .orElse(getConfig(
33 | ConfigJson.ANDROID_SYSTEM_PORT))));
34 | if (getConfig(ConfigJson.ANDROID_EMULATOR).equalsIgnoreCase("yes")) {
35 | options.setAvd(Optional.ofNullable(System.getProperty("deviceName"))
36 | .orElse(getConfig(ConfigJson.ANDROID_DEVICE_NAME)))
37 | .setAvdLaunchTimeout(Duration.ofMillis(Long.parseLong(getConfig(ConfigJson.AVD_LAUNCH_TIMEOUT))));
38 | }
39 | return new AndroidDriver(new URL(getConfig(ConfigJson.APPIUM_URL)), options);
40 | } catch (Exception e) {
41 | throw new DriverInitializationException("Failed to initialize driver. Please check the desired capabilities", e);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/org/example/driver/manager/DeviceManager.java:
--------------------------------------------------------------------------------
1 | package org.example.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 DEVICE_NAME = new ThreadLocal<>();
10 |
11 | public static String getDeviceName() {
12 | return DEVICE_NAME.get();
13 | }
14 |
15 | public static void setDeviceName(String device) {
16 | DEVICE_NAME.set(device);
17 | }
18 |
19 | public static void removeDeviceName() {
20 | DEVICE_NAME.remove();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/org/example/driver/manager/DriverManager.java:
--------------------------------------------------------------------------------
1 | package org.example.driver.manager;
2 |
3 | import io.appium.java_client.AppiumDriver;
4 | import lombok.AccessLevel;
5 | import lombok.NoArgsConstructor;
6 |
7 | import java.util.Objects;
8 |
9 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
10 | public final class DriverManager {
11 |
12 | private static final ThreadLocal DRIVER_THREAD_LOCAL = new ThreadLocal<>();
13 |
14 | public static AppiumDriver getDriver() {
15 | return DRIVER_THREAD_LOCAL.get();
16 | }
17 |
18 | public static void setAppiumDriver(AppiumDriver driver) {
19 | if (Objects.nonNull(driver))
20 | DRIVER_THREAD_LOCAL.set(driver);
21 | }
22 |
23 | public static void unload() {
24 | DRIVER_THREAD_LOCAL.remove();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/org/example/driver/manager/IosManager.java:
--------------------------------------------------------------------------------
1 | package org.example.driver.manager;
2 |
3 | import io.appium.java_client.AppiumDriver;
4 | import io.appium.java_client.ios.IOSDriver;
5 | import io.appium.java_client.remote.AutomationName;
6 | import io.appium.java_client.remote.IOSMobileCapabilityType;
7 | import io.appium.java_client.remote.MobileCapabilityType;
8 | import lombok.AccessLevel;
9 | import lombok.NoArgsConstructor;
10 | import org.example.constants.FrameworkConstants;
11 | import org.example.customexceptions.DriverInitializationException;
12 | import org.example.enums.ConfigJson;
13 | import org.openqa.selenium.Platform;
14 | import org.openqa.selenium.remote.DesiredCapabilities;
15 |
16 | import java.net.URL;
17 | import java.util.Optional;
18 |
19 | import static org.example.utils.configloader.JsonParser.getConfig;
20 | import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME;
21 |
22 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
23 | public final class IosManager {
24 |
25 | public static AppiumDriver createIOSDriver() {
26 | try {
27 | var capability = new DesiredCapabilities();
28 | capability.setCapability(PLATFORM_NAME, Platform.IOS);
29 | capability.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.IOS_XCUI_TEST);
30 | capability.setCapability(MobileCapabilityType.DEVICE_NAME, Optional.ofNullable(System.getProperty("deviceName"))
31 | .orElse(getConfig(ConfigJson.IOS_DEVICE_NAME)));
32 | capability.setCapability(MobileCapabilityType.UDID, Optional.ofNullable(System.getProperty("udid"))
33 | .orElse(getConfig(ConfigJson.IOS_UDID)));
34 | capability.setCapability(MobileCapabilityType.APP, FrameworkConstants.IOS_APP_PATH);
35 | capability.setCapability(IOSMobileCapabilityType.BUNDLE_ID, getConfig(ConfigJson.IOS_BUNDLE_ID));
36 | capability.setCapability(IOSMobileCapabilityType.WDA_LOCAL_PORT, Optional.ofNullable(System.getProperty("wdaLocalPort"))
37 | .orElse(getConfig(
38 | ConfigJson.IOS_WDA_LOCAL_PORT))); // To set different port for each thread - This port is used to communicate with WebDriverAgent driver
39 | // capability.setCapability(CapabilityType.BROWSER_NAME, MobileBrowserName.SAFARI);
40 | capability.setCapability("webkitDebugProxyPort", Optional.ofNullable(System.getProperty("webkitDebugProxyPort"))
41 | .orElse(getConfig(ConfigJson.IOS_WEBKIT_DEBUG_PROXY_PORT))); // For web view/Safari browser testing on real device
42 |
43 | return new IOSDriver(new URL(getConfig(ConfigJson.APPIUM_URL)), capability);
44 | } catch (Exception e) {
45 | throw new DriverInitializationException("Failed to initialize driver. Please check the desired capabilities", e);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/org/example/driver/manager/PlatformManager.java:
--------------------------------------------------------------------------------
1 | package org.example.driver.manager;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.NoArgsConstructor;
5 |
6 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
7 | public final class PlatformManager {
8 |
9 | private static final ThreadLocal PLATFORM_NAME = new ThreadLocal<>();
10 |
11 | public static String getPlatformName() {
12 | return PLATFORM_NAME.get();
13 | }
14 |
15 | public static void setPlatformName(String platform) {
16 | PLATFORM_NAME.set(platform);
17 | }
18 |
19 | public static void removePlatformName() {
20 | PLATFORM_NAME.remove();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/org/example/enums/ConfigJson.java:
--------------------------------------------------------------------------------
1 | package org.example.enums;
2 |
3 | public enum ConfigJson {
4 | ANDROID_APP_ACTIVITY, ANDROID_APP_PACKAGE, ANDROID_DEVICE_NAME,
5 | ANDROID_UDID, ANDROID_SYSTEM_PORT, ANDROID_CHROMEDRIVER_PORT, ANDROID_EMULATOR,
6 | APPIUM_URL, AVD_LAUNCH_TIMEOUT,
7 | IOS_DEVICE_NAME, IOS_UDID, IOS_WDA_LOCAL_PORT, IOS_WEBKIT_DEBUG_PROXY_PORT,
8 | IOS_BUNDLE_ID,
9 | PLATFORM
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/org/example/enums/ConfigProperties.java:
--------------------------------------------------------------------------------
1 | package org.example.enums;
2 |
3 | public enum ConfigProperties {
4 | RECORD_SCREEN,
5 | START_APPIUM_SERVER,
6 | OVERRIDE_SERVER_LOG
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/org/example/enums/MobileBrowserName.java:
--------------------------------------------------------------------------------
1 | package org.example.enums;
2 |
3 | public enum MobileBrowserName {
4 | CHROME,
5 | SAFARI
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/org/example/enums/MobileFindBy.java:
--------------------------------------------------------------------------------
1 | package org.example.enums;
2 |
3 | public enum MobileFindBy {
4 | XPATH, CSS, ID, NAME, CLASS, ACCESSIBILITY_ID
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/org/example/enums/MobilePlatformName.java:
--------------------------------------------------------------------------------
1 | package org.example.enums;
2 |
3 | public enum MobilePlatformName {
4 | ANDROID,
5 | IOS
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/org/example/enums/WaitStrategy.java:
--------------------------------------------------------------------------------
1 | package org.example.enums;
2 |
3 | public enum WaitStrategy {
4 | CLICKABLE, PRESENCE, VISIBLE, NONE
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/org/example/factories/WaitFactory.java:
--------------------------------------------------------------------------------
1 | package org.example.factories;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.NoArgsConstructor;
5 | import org.example.constants.FrameworkConstants;
6 | import org.example.driver.manager.DriverManager;
7 | import org.example.enums.WaitStrategy;
8 | import org.openqa.selenium.By;
9 | import org.openqa.selenium.WebElement;
10 | import org.openqa.selenium.support.ui.ExpectedConditions;
11 | import org.openqa.selenium.support.ui.WebDriverWait;
12 |
13 | import java.time.Duration;
14 | import java.util.EnumMap;
15 | import java.util.Map;
16 | import java.util.function.Function;
17 |
18 | import static org.example.enums.WaitStrategy.CLICKABLE;
19 | import static org.example.enums.WaitStrategy.NONE;
20 | import static org.example.enums.WaitStrategy.PRESENCE;
21 | import static org.example.enums.WaitStrategy.VISIBLE;
22 |
23 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
24 | public final class WaitFactory {
25 |
26 | private static final Map> WAIT_STRATEGY_FUNCTION_MAP =
27 | new EnumMap<>(WaitStrategy.class);
28 |
29 | private static final Function CLICKABLE_BY_WEB_ELEMENT_FUNCTION =
30 | by -> new WebDriverWait(DriverManager.getDriver(), Duration.ofSeconds(FrameworkConstants.EXPLICIT_WAIT))
31 | .until(ExpectedConditions.elementToBeClickable(by));
32 | private static final Function PRESENCE_BY_WEB_ELEMENT_FUNCTION =
33 | by -> new WebDriverWait(DriverManager.getDriver(), Duration.ofSeconds(FrameworkConstants.EXPLICIT_WAIT))
34 | .until(ExpectedConditions.presenceOfElementLocated(by));
35 | private static final Function VISIBLE_BY_WEB_ELEMENT_FUNCTION =
36 | by -> new WebDriverWait(DriverManager.getDriver(), Duration.ofSeconds(FrameworkConstants.EXPLICIT_WAIT))
37 | .until(ExpectedConditions.visibilityOfElementLocated(by));
38 | private static final Function NONE_BY_WEB_ELEMENT_FUNCTION = by -> DriverManager.getDriver().findElement(by);
39 |
40 | private static final Map> WAIT_STRATEGY_MOBILE_ELEMENT_FUNCTION_MAP =
41 | new EnumMap<>(WaitStrategy.class);
42 |
43 | private static final Function CLICKABLE_MOBILE_ELEMENT_FUNCTION =
44 | mobileElement -> new WebDriverWait(DriverManager.getDriver(), Duration.ofSeconds(FrameworkConstants.EXPLICIT_WAIT))
45 | .until(ExpectedConditions.elementToBeClickable(mobileElement));
46 | private static final Function VISIBLE_MOBILE_ELEMENT_FUNCTION =
47 | mobileElement -> new WebDriverWait(DriverManager.getDriver(), Duration.ofSeconds(FrameworkConstants.EXPLICIT_WAIT))
48 | .until(ExpectedConditions.visibilityOf(mobileElement));
49 | private static final Function NONE_MOBILE_ELEMENT_FUNCTION = mobileElement -> mobileElement;
50 |
51 | static {
52 | WAIT_STRATEGY_FUNCTION_MAP.put(CLICKABLE, CLICKABLE_BY_WEB_ELEMENT_FUNCTION);
53 | WAIT_STRATEGY_FUNCTION_MAP.put(PRESENCE, PRESENCE_BY_WEB_ELEMENT_FUNCTION);
54 | WAIT_STRATEGY_FUNCTION_MAP.put(VISIBLE, VISIBLE_BY_WEB_ELEMENT_FUNCTION);
55 | WAIT_STRATEGY_FUNCTION_MAP.put(NONE, NONE_BY_WEB_ELEMENT_FUNCTION);
56 | WAIT_STRATEGY_MOBILE_ELEMENT_FUNCTION_MAP.put(CLICKABLE, CLICKABLE_MOBILE_ELEMENT_FUNCTION);
57 | WAIT_STRATEGY_MOBILE_ELEMENT_FUNCTION_MAP.put(VISIBLE, VISIBLE_MOBILE_ELEMENT_FUNCTION);
58 | WAIT_STRATEGY_MOBILE_ELEMENT_FUNCTION_MAP.put(NONE, NONE_MOBILE_ELEMENT_FUNCTION);
59 | }
60 |
61 | public static WebElement explicitlyWaitForElementLocatedBy(WaitStrategy waitStrategy, By by) {
62 | return WAIT_STRATEGY_FUNCTION_MAP.get(waitStrategy).apply(by);
63 | }
64 |
65 | public static WebElement explicitlyWaitForElement(WaitStrategy waitStrategy, WebElement mobileElement) {
66 | return WAIT_STRATEGY_MOBILE_ELEMENT_FUNCTION_MAP.get(waitStrategy).apply(mobileElement);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/org/example/pageobjects/LoginPage.java:
--------------------------------------------------------------------------------
1 | package org.example.pageobjects;
2 |
3 | import io.appium.java_client.pagefactory.AndroidFindBy;
4 | import io.appium.java_client.pagefactory.iOSXCUITFindBy;
5 | import org.example.enums.WaitStrategy;
6 | import org.example.pageobjects.screen.ScreenActions;
7 | import org.openqa.selenium.WebElement;
8 |
9 | public class LoginPage extends ScreenActions {
10 |
11 | @AndroidFindBy(accessibility = "test-Username")
12 | @iOSXCUITFindBy(accessibility = "test-Username")
13 | private static WebElement txtFieldUsername;
14 |
15 | @AndroidFindBy(accessibility = "test-Password")
16 | @iOSXCUITFindBy(accessibility = "test-Password")
17 | private static WebElement txtFieldPassword;
18 |
19 | @AndroidFindBy(accessibility = "test-LOGIN")
20 | @iOSXCUITFindBy(accessibility = "test-LOGIN")
21 | private static WebElement btnLogin;
22 |
23 | @AndroidFindBy(xpath = "//android.view.ViewGroup[@content-desc='test-Error message']/android.widget.TextView")
24 | private static WebElement errorMessage;
25 |
26 | public boolean isLoginPageDisplayed() {
27 | return isElementDisplayed(txtFieldUsername);
28 | }
29 |
30 | public LoginPage setUsername(String username) {
31 | enter(txtFieldUsername, username);
32 | return this;
33 | }
34 |
35 | public LoginPage setPassword(String password) {
36 | enter(txtFieldPassword, password);
37 | return this;
38 | }
39 |
40 | public ProductPage tapOnLogin() {
41 | click(btnLogin);
42 | return new ProductPage();
43 | }
44 |
45 | public ProductPage login(String username, String password) {
46 | setUsername(username)
47 | .setPassword(password)
48 | .tapOnLogin();
49 |
50 | return new ProductPage();
51 | }
52 |
53 | public String getErrorText() {
54 | return getText(errorMessage, WaitStrategy.VISIBLE);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/org/example/pageobjects/ProductPage.java:
--------------------------------------------------------------------------------
1 | package org.example.pageobjects;
2 |
3 | import io.appium.java_client.pagefactory.AndroidFindBy;
4 | import org.example.enums.WaitStrategy;
5 | import org.example.pageobjects.screen.ScreenActions;
6 | import org.openqa.selenium.WebElement;
7 |
8 | public 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 WebElement productPageTitle;
12 |
13 | public String getProductPageTitle() {
14 | return getText(productPageTitle, WaitStrategy.VISIBLE);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/org/example/pageobjects/screen/ScreenActions.java:
--------------------------------------------------------------------------------
1 | package org.example.pageobjects.screen;
2 |
3 | import com.google.common.collect.ImmutableMap;
4 | import com.google.common.collect.Ordering;
5 | import io.appium.java_client.AppiumBy;
6 | import io.appium.java_client.android.AndroidDriver;
7 | import io.appium.java_client.android.PowerACState;
8 | import io.appium.java_client.pagefactory.AppiumFieldDecorator;
9 | import org.example.driver.manager.DriverManager;
10 | import org.example.enums.MobileFindBy;
11 | import org.example.enums.WaitStrategy;
12 | import org.example.factories.WaitFactory;
13 | import org.openqa.selenium.Keys;
14 | import org.openqa.selenium.WebElement;
15 | import org.openqa.selenium.interactions.Actions;
16 | import org.openqa.selenium.support.PageFactory;
17 |
18 | import java.util.EnumMap;
19 | import java.util.HashMap;
20 | import java.util.List;
21 | import java.util.Map;
22 | import java.util.concurrent.TimeUnit;
23 | import java.util.function.Consumer;
24 | import java.util.function.Function;
25 |
26 | import static org.example.enums.MobileFindBy.ACCESSIBILITY_ID;
27 | import static org.example.enums.MobileFindBy.CLASS;
28 | import static org.example.enums.MobileFindBy.CSS;
29 | import static org.example.enums.MobileFindBy.ID;
30 | import static org.example.enums.MobileFindBy.NAME;
31 | import static org.example.enums.MobileFindBy.XPATH;
32 |
33 | public class ScreenActions {
34 |
35 | private final Map> mobileFindByFunctionMap = new EnumMap(MobileFindBy.class);
36 | private final Function findByXpath =
37 | mobileElement -> DriverManager.getDriver().findElement(AppiumBy.xpath(mobileElement));
38 | private final Function findByCss =
39 | mobileElement -> DriverManager.getDriver().findElement(AppiumBy.cssSelector(mobileElement));
40 | private final Function findById =
41 | mobileElement -> DriverManager.getDriver().findElement(AppiumBy.id(mobileElement));
42 | private final Function findByName =
43 | mobileElement -> DriverManager.getDriver().findElement(AppiumBy.name(mobileElement));
44 | private final Function findByAccessibilityId =
45 | mobileElement -> DriverManager.getDriver().findElement(AppiumBy.accessibilityId(mobileElement));
46 | private final Function findByClassName =
47 | mobileElement -> DriverManager.getDriver().findElement(AppiumBy.className(mobileElement));
48 |
49 | protected ScreenActions() {
50 | PageFactory.initElements(new AppiumFieldDecorator(DriverManager.getDriver()), this);
51 | }
52 |
53 | private WebElement getMobileElement(String mobileElement, MobileFindBy mobileFindBy) {
54 | if (mobileFindByFunctionMap.isEmpty()) {
55 | mobileFindByFunctionMap.put(XPATH, findByXpath);
56 | mobileFindByFunctionMap.put(CSS, findByCss);
57 | mobileFindByFunctionMap.put(ID, findById);
58 | mobileFindByFunctionMap.put(NAME, findByName);
59 | mobileFindByFunctionMap.put(ACCESSIBILITY_ID, findByAccessibilityId);
60 | mobileFindByFunctionMap.put(CLASS, findByClassName);
61 | }
62 | return mobileFindByFunctionMap.get(mobileFindBy).apply(mobileElement);
63 | }
64 |
65 | protected void waitForPageLoad(int waitTime) {
66 | DriverManager.getDriver().manage().timeouts().pageLoadTimeout(waitTime, TimeUnit.SECONDS);
67 | }
68 |
69 | protected String getTextFromAttribute(WaitStrategy waitStrategy, WebElement element) {
70 | return WaitFactory.explicitlyWaitForElement(waitStrategy, element).getAttribute("text");
71 | }
72 |
73 | protected String getText(WebElement element, WaitStrategy waitStrategy) {
74 | return WaitFactory.explicitlyWaitForElement(waitStrategy, element).getText();
75 | }
76 |
77 | protected boolean isElementDisplayed(WebElement element) {
78 | return element.isDisplayed();
79 | }
80 |
81 | protected void doClear(WebElement element) {
82 | element.clear();
83 | }
84 |
85 | protected void getServerStatus() {
86 | DriverManager.getDriver().getStatus();
87 | }
88 |
89 | protected String getElementAttribute(WebElement element, String attributeName) {
90 | return element.getAttribute(attributeName);
91 | }
92 |
93 | protected WebElement getActiveElement() {
94 | return DriverManager.getDriver().switchTo().activeElement();
95 | }
96 |
97 | protected void moveMouseToElement(WebElement element, int x_offset, int y_offset) {
98 | new Actions(DriverManager.getDriver())
99 | .moveToElement(element, x_offset, y_offset)
100 | .perform();
101 | }
102 |
103 | protected void doubleClickOnElement(WebElement element) {
104 | new Actions(DriverManager.getDriver())
105 | .moveToElement(element)
106 | .doubleClick()
107 | .perform();
108 | }
109 |
110 | protected void click(WebElement element) {
111 | element.click();
112 | }
113 |
114 | protected void click(String element, MobileFindBy elementType) {
115 | click(getMobileElement(element, elementType));
116 | }
117 |
118 | protected void enter(WebElement element, String value) {
119 | WaitFactory.explicitlyWaitForElement(WaitStrategy.VISIBLE, element);
120 | doClear(element);
121 | element.sendKeys(value);
122 | }
123 |
124 | protected void enterValueAndPressEnter(WebElement element, String value) {
125 | doClear(element);
126 | element.sendKeys(value, Keys.ENTER);
127 | }
128 |
129 | protected void enter(String element, MobileFindBy elementType, String value) {
130 | enter(getMobileElement(element, elementType), value);
131 | }
132 |
133 | protected boolean isTextPresent(String containsText) {
134 | return DriverManager.getDriver().getPageSource().contains(containsText);
135 | }
136 |
137 | protected void powerStateAndroid(PowerACState powerACState) {
138 | Consumer powerStateConsumer = state ->
139 | ((AndroidDriver) DriverManager.getDriver()).setPowerAC(state);
140 | powerStateConsumer.accept(powerACState);
141 | }
142 |
143 | /**
144 | * Swipe Down
145 | */
146 | protected void swipeDown() {
147 | DriverManager.getDriver().executeScript("mobile:scroll", ImmutableMap.of("direction", "down"));
148 | }
149 |
150 | /**
151 | * Swipe Up
152 | */
153 | protected void swipeUP() {
154 | DriverManager.getDriver().executeScript("mobile:scroll", ImmutableMap.of("direction", "up"));
155 | }
156 |
157 | /**
158 | * Accept Alert
159 | */
160 | protected void acceptAlert() {
161 | DriverManager.getDriver().executeScript("mobile:acceptAlert");
162 | }
163 |
164 | /**
165 | * Dismiss Alert
166 | */
167 | protected void dismissAlert() {
168 | DriverManager.getDriver().executeScript("mobile:dismissAlert");
169 | }
170 |
171 | /**
172 | * Scroll to specific location
173 | *
174 | * @param element element
175 | * @param value location
176 | */
177 | protected void scrollToLocation(WebElement element, int value) {
178 | HashMap scrollElement = new HashMap<>();
179 | scrollElement.put("startX", 0.50);
180 | scrollElement.put("startY", 0.95);
181 | scrollElement.put("endX", 0.50);
182 | scrollElement.put("endY", 0.01);
183 | scrollElement.put("duration", 3.0);
184 | DriverManager.getDriver().executeScript("mobile: swipe", scrollElement);
185 | }
186 |
187 | protected boolean checkListIsSorted(List listToSort) {
188 | if (!listToSort.isEmpty()) {
189 | return Ordering.natural().isOrdered(listToSort);
190 | }
191 | return false;
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/src/main/java/org/example/utils/AppiumServerManager.java:
--------------------------------------------------------------------------------
1 | package org.example.utils;
2 |
3 | import io.appium.java_client.service.local.AppiumDriverLocalService;
4 | import io.appium.java_client.service.local.AppiumServiceBuilder;
5 | import io.appium.java_client.service.local.flags.GeneralServerFlag;
6 | import lombok.AccessLevel;
7 | import lombok.NoArgsConstructor;
8 | import org.example.constants.FrameworkConstants;
9 | import org.example.enums.ConfigProperties;
10 | import org.example.utils.configloader.PropertyUtils;
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() {
22 | var isServerRunning = false;
23 | try {
24 | ServerSocket serverSocket = new ServerSocket(FrameworkConstants.APPIUM_SERVER_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 | !AppiumServerManager.checkIfServerIsRunning()) {
35 | //Build the Appium service
36 | AppiumServiceBuilder builder = new AppiumServiceBuilder();
37 | builder.usingDriverExecutable(new File(FrameworkConstants.NODEJS_PATH));
38 | builder.withAppiumJS(new File(FrameworkConstants.APPIUM_JS_PATH));
39 | builder.withIPAddress(FrameworkConstants.APPIUM_SERVER_HOST);
40 | builder.usingPort(FrameworkConstants.APPIUM_SERVER_PORT);
41 | builder.withArgument(GeneralServerFlag.SESSION_OVERRIDE);
42 | builder.withArgument(GeneralServerFlag.ALLOW_INSECURE, "chromedriver_autodownload");
43 | builder.withLogFile(new File(FrameworkConstants.getAppiumServerLogsPath()));
44 | //Start the server with the builder
45 | service = AppiumDriverLocalService.buildService(builder);
46 | service.start();
47 | service.clearOutPutStreams();
48 | }
49 | }
50 |
51 | public static void stopAppiumServer() {
52 | if (PropertyUtils.getPropertyValue(ConfigProperties.START_APPIUM_SERVER).equalsIgnoreCase("yes")) {
53 | service.stop();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/org/example/utils/configloader/JsonParser.java:
--------------------------------------------------------------------------------
1 | package org.example.utils.configloader;
2 |
3 | import com.fasterxml.jackson.core.type.TypeReference;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import lombok.AccessLevel;
6 | import lombok.NoArgsConstructor;
7 | import org.example.constants.FrameworkConstants;
8 | import org.example.customexceptions.JsonFileUsageException;
9 | import org.example.enums.ConfigJson;
10 |
11 | import java.io.File;
12 | import java.io.IOException;
13 | import java.util.HashMap;
14 | import java.util.Map;
15 | import java.util.Objects;
16 |
17 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
18 | public final class JsonParser {
19 |
20 | private static Map map;
21 |
22 | static void readJson(String jsonPath) {
23 | try {
24 | map = new ObjectMapper().readValue(new File(jsonPath),
25 | new TypeReference>() {
26 | });
27 | } catch (IOException e) {
28 | throw new JsonFileUsageException("Exception occurred while reading Json file in the specified path");
29 | }
30 | }
31 |
32 | public static String getConfig(ConfigJson key) {
33 | readJson(FrameworkConstants.CONFIG_JSON_PATH);
34 | if (Objects.isNull(map.get(key.name().toLowerCase()))) {
35 | throw new JsonFileUsageException("Property name - " + key + " is not found. Please check the config.json");
36 | }
37 | return map.get(key.name().toLowerCase());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/org/example/utils/configloader/PropertyUtils.java:
--------------------------------------------------------------------------------
1 | package org.example.utils.configloader;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.NoArgsConstructor;
5 | import org.example.constants.FrameworkConstants;
6 | import org.example.customexceptions.PropertyFileUsageException;
7 | import org.example.enums.ConfigProperties;
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() {
20 | try (var input = new FileInputStream(FrameworkConstants.CONFIG_PROPERTIES_PATH)) {
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();
29 | if (Objects.isNull(property.getProperty(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/org/example/utils/screenrecording/ScreenRecordingService.java:
--------------------------------------------------------------------------------
1 | package org.example.utils.screenrecording;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.NoArgsConstructor;
5 | import org.example.enums.ConfigProperties;
6 | import org.example.utils.configloader.PropertyUtils;
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 |
--------------------------------------------------------------------------------
/src/main/java/org/example/utils/screenrecording/ScreenRecordingUtils.java:
--------------------------------------------------------------------------------
1 | package org.example.utils.screenrecording;
2 |
3 | import io.appium.java_client.screenrecording.CanRecordScreen;
4 | import lombok.AccessLevel;
5 | import lombok.NoArgsConstructor;
6 | import org.apache.commons.codec.binary.Base64;
7 | import org.example.constants.FrameworkConstants;
8 | import org.example.driver.manager.DriverManager;
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 (var outputStream = new FileOutputStream(filePathToWrite)) {
29 | outputStream.write(Base64.decodeBase64(recordedVideoFile));
30 | } catch (IOException e) {
31 | e.printStackTrace();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/org/example/utils/screenshot/ScreenshotService.java:
--------------------------------------------------------------------------------
1 | package org.example.utils.screenshot;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.NoArgsConstructor;
5 |
6 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
7 | public final class ScreenshotService {
8 |
9 | public static byte[] getScreenshotAsBytes() {
10 | return ScreenshotUtils.captureScreenshotAsBytes();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/org/example/utils/screenshot/ScreenshotUtils.java:
--------------------------------------------------------------------------------
1 | package org.example.utils.screenshot;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.NoArgsConstructor;
5 | import lombok.SneakyThrows;
6 | import org.apache.commons.io.FileUtils;
7 | import org.example.constants.FrameworkConstants;
8 | import org.example.driver.manager.DriverManager;
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 | var source = ((TakesScreenshot) DriverManager.getDriver()).getScreenshotAs(OutputType.FILE);
21 | var destination = new File(FrameworkConstants.SCREENSHOT_PATH + File.separator + testName + ".png");
22 | FileUtils.copyFile(source, destination);
23 | }
24 |
25 | public static byte[] captureScreenshotAsBytes() {
26 | return DriverManager.getDriver().getScreenshotAs(OutputType.BYTES);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/java/org/example/runner/RunnerTest.java:
--------------------------------------------------------------------------------
1 | package org.example.runner;
2 |
3 | import io.cucumber.junit.Cucumber;
4 | import io.cucumber.junit.CucumberOptions;
5 | import org.example.driver.Drivers;
6 | import org.example.driver.manager.DriverManager;
7 | import org.example.enums.ConfigJson;
8 | import org.example.enums.MobilePlatformName;
9 | import org.junit.AfterClass;
10 | import org.junit.BeforeClass;
11 | import org.junit.runner.RunWith;
12 |
13 | import java.util.Objects;
14 | import java.util.Optional;
15 |
16 | import static org.example.utils.AppiumServerManager.startAppiumServer;
17 | import static org.example.utils.AppiumServerManager.stopAppiumServer;
18 | import static org.example.utils.configloader.JsonParser.getConfig;
19 |
20 | @RunWith(Cucumber.class)
21 | @CucumberOptions(plugin = {"pretty",
22 | "summary",
23 | "html:target/cucumber-html-output/cucumber-html-report.html",
24 | "json:target/cucumber-json-output/cucumber.json"},
25 | features = {"src/test/resources/feature"},
26 | glue = {"org.example.stepdefinitions"},
27 | monochrome = true,
28 | tags = "@Login"
29 | )
30 | public class RunnerTest {
31 |
32 | @BeforeClass
33 | public static void setUp() {
34 | startAppiumServer();
35 | if (Objects.isNull(DriverManager.getDriver())) {
36 | Drivers.initializeDriver(MobilePlatformName.valueOf(
37 | Optional.ofNullable(System.getProperty("platformName"))
38 | .orElse(getConfig(ConfigJson.PLATFORM)).toUpperCase()));
39 | }
40 | }
41 |
42 | @AfterClass
43 | public static void tearDown() {
44 | if (Objects.nonNull(DriverManager.getDriver())) {
45 | Drivers.quitDriver();
46 | }
47 | stopAppiumServer();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/test/java/org/example/stepdefinitions/Hooks.java:
--------------------------------------------------------------------------------
1 | package org.example.stepdefinitions;
2 |
3 | import io.cucumber.java.After;
4 | import io.cucumber.java.Before;
5 | import io.cucumber.java.Scenario;
6 | import org.example.utils.screenrecording.ScreenRecordingService;
7 | import org.example.utils.screenshot.ScreenshotService;
8 |
9 | public class Hooks {
10 |
11 | @Before
12 | public void setUp() {
13 | ScreenRecordingService.startRecording();
14 | }
15 |
16 | @After
17 | public void tearDown(Scenario scenario) {
18 | if (scenario.isFailed()) {
19 | scenario.attach(ScreenshotService.getScreenshotAsBytes(),
20 | "image/png", scenario.getName());
21 | }
22 | ScreenRecordingService.stopRecording(scenario.getName());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/org/example/stepdefinitions/LoginSteps.java:
--------------------------------------------------------------------------------
1 | package org.example.stepdefinitions;
2 |
3 | import io.cucumber.java.en.Then;
4 | import io.cucumber.java.en.When;
5 | import org.example.pageobjects.LoginPage;
6 | import org.example.pageobjects.ProductPage;
7 | import org.junit.Assert;
8 |
9 | public class LoginSteps {
10 |
11 | @When("^User enter username as \"([^\"]*)\"$")
12 | public void userEnterUsernameAs(String username) {
13 | new LoginPage().setUsername(username);
14 | }
15 |
16 | @When("^User enter password as \"([^\"]*)\"$")
17 | public void userEnterPasswordAs(String password) {
18 | new LoginPage().setPassword(password);
19 | }
20 |
21 | @When("^clicks on login$")
22 | public void clickOnLogin() {
23 | new LoginPage().tapOnLogin();
24 | }
25 |
26 | @Then("^login should fail with an error \"([^\"]*)\"$")
27 | public void loginShouldFailWithAnError(String err) {
28 | Assert.assertEquals(new LoginPage().getErrorText(), err);
29 | }
30 |
31 | @Then("^User should see Products page with title \"([^\"]*)\"$")
32 | public void userShouldSeeProductsPageWithTitle(String title) {
33 | Assert.assertEquals(new ProductPage().getProductPageTitle(), title);
34 | }
35 |
36 | @When("User enter username: {string} and password: {string}")
37 | public void userEnterUsernameAndPassword(String username, String password) {
38 | new LoginPage().setUsername(username).setPassword(password);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/test/resources/app/Android.SauceLabs.Mobile.Sample.app.2.7.1.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Thangarajtk/appium-mobile-automation-framework-bdd/ad317e1002d7d6e72372326eb4e0deea55512b69/src/test/resources/app/Android.SauceLabs.Mobile.Sample.app.2.7.1.apk
--------------------------------------------------------------------------------
/src/test/resources/config/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "platform": "android",
3 | "android_app_package": "com.swaglabsmobileapp",
4 | "android_app_activity": "com.swaglabsmobileapp.MainActivity",
5 | "avd_launch_timeout": 180000,
6 | "android_device_name": "Pixel_3a_API_30",
7 | "android_udid": "emulator-5554",
8 | "android_system_port": 7878,
9 | "android_chromedriver_port": 8787,
10 | "android_emulator": "yes",
11 | "ios_device_name": "",
12 | "ios_udid": "",
13 | "ios_bundle_id": "",
14 | "ios_webkit_debug_proxy_port": "",
15 | "ios_wda_local_port": "",
16 | "appium_url": "http://127.0.0.1:4723"
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/resources/config/config.properties:
--------------------------------------------------------------------------------
1 | # Provides an option to record screen during execution and save it in .mp4 format
2 | record_screen=yes
3 | # Provides an option to start appium server programmatically
4 | start_appium_server=yes
5 | # Provides an option to override appium server log
6 | override_server_log=yes
7 |
--------------------------------------------------------------------------------
/src/test/resources/cucumber.properties:
--------------------------------------------------------------------------------
1 | cucumber.publish.quiet=true
2 |
--------------------------------------------------------------------------------
/src/test/resources/feature/Login.feature:
--------------------------------------------------------------------------------
1 | @Login
2 | Feature: Login test
3 |
4 | @InvalidUsername
5 | Scenario Outline: Login with invalid username
6 | When User enter username: "" and password: ""
7 | And clicks on login
8 | Then login should fail with an error ""
9 | Examples:
10 | | username | password | error_message |
11 | | invalidUsername | secret_sauce | Username and password do not match any user in this service. |
12 |
13 | @InvalidPassword
14 | Scenario Outline: Login with invalid password
15 | When User enter username as ""
16 | And User enter password as ""
17 | And clicks on login
18 | Then login should fail with an error ""
19 | Examples:
20 | | username | password | error_message |
21 | | standard_user | invalidPassword | Username and password do not match any user in this service. |
22 |
23 | @ValidLogin
24 | Scenario Outline: Login with valid username and password
25 | When User enter username as ""
26 | And User enter password as ""
27 | And clicks on login
28 | Then User should see Products page with title ""
29 | Examples:
30 | | username | password | title |
31 | | standard_user | secret_sauce | PRODUCTS |
32 |
--------------------------------------------------------------------------------