percy-appium-app 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +55 -0
  3. data/.github/dependabot.yml +14 -0
  4. data/.github/release-drafter.yml +28 -0
  5. data/.github/workflows/Semgrep.yml +48 -0
  6. data/.github/workflows/changelog.yml +11 -0
  7. data/.github/workflows/release.yml +22 -0
  8. data/.github/workflows/stale.yml +30 -0
  9. data/.github/workflows/test.yml +40 -0
  10. data/Gemfile +13 -0
  11. data/LICENSE +21 -0
  12. data/Makefile +3 -0
  13. data/README.md +1 -0
  14. data/Rakefile +1 -0
  15. data/percy/common/common.rb +19 -0
  16. data/percy/configs/devices.json +232 -0
  17. data/percy/environment.rb +25 -0
  18. data/percy/exceptions/exceptions.rb +13 -0
  19. data/percy/lib/app_percy.rb +55 -0
  20. data/percy/lib/cache.rb +53 -0
  21. data/percy/lib/cli_wrapper.rb +132 -0
  22. data/percy/lib/ignore_region.rb +8 -0
  23. data/percy/lib/percy_automate.rb +59 -0
  24. data/percy/lib/percy_options.rb +37 -0
  25. data/percy/lib/region.rb +22 -0
  26. data/percy/lib/tile.rb +28 -0
  27. data/percy/metadata/android_metadata.rb +79 -0
  28. data/percy/metadata/driver_metadata.rb +40 -0
  29. data/percy/metadata/ios_metadata.rb +83 -0
  30. data/percy/metadata/metadata.rb +108 -0
  31. data/percy/metadata/metadata_resolver.rb +21 -0
  32. data/percy/providers/app_automate.rb +159 -0
  33. data/percy/providers/generic_provider.rb +205 -0
  34. data/percy/providers/provider_resolver.rb +17 -0
  35. data/percy/screenshot.rb +23 -0
  36. data/percy/version.rb +5 -0
  37. data/percy-appium-app.gemspec +37 -0
  38. data/specs/android_metadata.rb +79 -0
  39. data/specs/app_automate.rb +124 -0
  40. data/specs/app_percy.rb +175 -0
  41. data/specs/cache.rb +56 -0
  42. data/specs/cli_wrapper.rb +135 -0
  43. data/specs/driver_metadata.rb +71 -0
  44. data/specs/generic_providers.rb +370 -0
  45. data/specs/ignore_regions.rb +51 -0
  46. data/specs/ios_metadata.rb +88 -0
  47. data/specs/metadata.rb +105 -0
  48. data/specs/metadata_resolver.rb +41 -0
  49. data/specs/mocks/mock_methods.rb +147 -0
  50. data/specs/percy_options.rb +114 -0
  51. data/specs/screenshot.rb +342 -0
  52. data/specs/tile.rb +33 -0
  53. metadata +194 -0
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative '../common/common'
5
+ require_relative '../lib/tile'
6
+ require_relative 'generic_provider'
7
+ require_relative '../environment'
8
+
9
+ class AppAutomate < GenericProvider
10
+ def self.supports(remote_url)
11
+ r_index = remote_url.rindex(ENV['AA_DOMAIN'].nil? ? 'browserstack' : ENV['AA_DOMAIN'])
12
+ if r_index
13
+ r_index > -1
14
+ else
15
+ false
16
+ end
17
+ end
18
+
19
+ def screenshot(name, **kwargs)
20
+ session_details = execute_percy_screenshot_begin(name)
21
+
22
+ if session_details
23
+ metadata.device_name = session_details['deviceName']
24
+ metadata.os_version = session_details['osVersion']
25
+ set_debug_url(session_details)
26
+ end
27
+
28
+ begin
29
+ response = super(name, **kwargs)
30
+ percy_screenshot_url = response.fetch('link', '')
31
+ execute_percy_screenshot_end(name, percy_screenshot_url, 'success')
32
+ rescue StandardError => e
33
+ execute_percy_screenshot_end(name, percy_screenshot_url, 'failure', e.message)
34
+ raise e
35
+ end
36
+ end
37
+
38
+ def set_debug_url(session_details)
39
+ build_hash = session_details['buildHash'].to_s
40
+ session_hash = session_details['sessionHash'].to_s
41
+ @debug_url = "https://app-automate.browserstack.com/dashboard/v2/builds/#{build_hash}/sessions/#{session_hash}"
42
+ end
43
+
44
+ def _get_tiles(**kwargs)
45
+ fullpage_ss = kwargs[:fullpage] || false
46
+ if ENV['PERCY_DISABLE_REMOTE_UPLOADS'] == 'true'
47
+ puts("Full page screenshots are only supported when 'PERCY_DISABLE_REMOTE_UPLOADS' is not set") if fullpage_ss
48
+ return super(**kwargs) unless fullpage_ss
49
+ end
50
+ screenshot_type = fullpage_ss ? 'fullpage' : 'singlepage'
51
+ screen_lengths = kwargs[:screen_lengths] || 4
52
+ scrollable_xpath = kwargs[:scollable_xpath]
53
+ scrollable_id = kwargs[:scrollable_id]
54
+ top_scrollview_offset = kwargs[:top_scrollview_offset]
55
+ bottom_scrollview_offset = kwargs[:top_scrollview_offset]
56
+
57
+ data = execute_percy_screenshot(
58
+ metadata.device_screen_size.fetch('height', 1),
59
+ screenshot_type,
60
+ screen_lengths,
61
+ scrollable_xpath,
62
+ scrollable_id,
63
+ metadata.scale_factor,
64
+ top_scrollview_offset,
65
+ bottom_scrollview_offset
66
+ )
67
+ tiles = []
68
+ status_bar_height = metadata.status_bar_height
69
+ nav_bar_height = metadata.navigation_bar_height
70
+
71
+ JSON.parse(data['result']).each do |tile_data|
72
+ tiles << Tile.new(
73
+ status_bar_height,
74
+ nav_bar_height,
75
+ tile_data['header_height'],
76
+ tile_data['footer_height'],
77
+ sha: tile_data['sha'].split('-')[0]
78
+ )
79
+ end
80
+
81
+ tiles
82
+ end
83
+
84
+ def execute_percy_screenshot_begin(name)
85
+ request_body = {
86
+ action: 'percyScreenshot',
87
+ arguments: {
88
+ state: 'begin',
89
+ percyBuildId: Environment.percy_build_id,
90
+ percyBuildUrl: Environment.percy_build_url,
91
+ name: name
92
+ }
93
+ }
94
+ command = "browserstack_executor: #{request_body.to_json}"
95
+ begin
96
+ response = metadata.execute_script(command)
97
+ JSON.parse(response)
98
+ rescue StandardError => e
99
+ log('Could not set session as Percy session')
100
+ log('Error occurred during begin call', on_debug: true)
101
+ log(e, on_debug: true)
102
+ nil
103
+ end
104
+ end
105
+
106
+ def execute_percy_screenshot_end(name, percy_screenshot_url, status, status_message = nil)
107
+ request_body = {
108
+ action: 'percyScreenshot',
109
+ arguments: {
110
+ state: 'end',
111
+ percyScreenshotUrl: percy_screenshot_url,
112
+ name: name,
113
+ status: status
114
+ }
115
+ }
116
+ request_body[:arguments][:statusMessage] = status_message if status_message
117
+ command = "browserstack_executor: #{request_body.to_json}"
118
+ begin
119
+ metadata.execute_script(command)
120
+ rescue StandardError => e
121
+ log('Error occurred during end call', on_debug: true)
122
+ log(e, on_debug: true)
123
+ end
124
+ end
125
+
126
+ def execute_percy_screenshot(device_height, screenshotType, screen_lengths, scrollable_xpath = nil,
127
+ scrollable_id = nil, scale_factor = 1, top_scrollview_offset = 0,
128
+ bottom_scrollview_offset = 0)
129
+ project_id = ENV['PERCY_ENABLE_DEV'] == 'true' ? 'percy-dev' : 'percy-prod'
130
+ request_body = {
131
+ action: 'percyScreenshot',
132
+ arguments: {
133
+ state: 'screenshot',
134
+ percyBuildId: Environment.percy_build_id,
135
+ screenshotType: screenshotType,
136
+ projectId: project_id,
137
+ scaleFactor: scale_factor,
138
+ options: {
139
+ numOfTiles: screen_lengths,
140
+ deviceHeight: device_height,
141
+ scrollableXpath: scrollable_xpath,
142
+ scrollableId: scrollable_id,
143
+ topScrollviewOffset: top_scrollview_offset,
144
+ bottomScrollviewOffset: bottom_scrollview_offset,
145
+ 'FORCE_FULL_PAGE' => ENV['FORCE_FULL_PAGE'] == 'true'
146
+ }
147
+ }
148
+ }
149
+ command = "browserstack_executor: #{request_body.to_json}"
150
+ begin
151
+ response = metadata.execute_script(command)
152
+ JSON.parse(response)
153
+ rescue StandardError => e
154
+ log('Error occurred during screenshot call', on_debug: true)
155
+ log(e, on_debug: true)
156
+ raise e
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'tempfile'
5
+ require 'pathname'
6
+ require 'base64'
7
+ require 'fileutils'
8
+ require_relative '../lib/cli_wrapper'
9
+ require_relative '../lib/tile'
10
+ require_relative '../common/common'
11
+
12
+ class GenericProvider
13
+ attr_accessor :driver, :metadata, :debug_url
14
+
15
+ def initialize(driver, metadata)
16
+ @driver = driver
17
+ @metadata = metadata
18
+ @debug_url = ''
19
+ end
20
+
21
+ def self.supports(_remote_url)
22
+ true
23
+ end
24
+
25
+ def screenshot(name, **kwargs)
26
+ tiles = _get_tiles(**kwargs)
27
+ tag = _get_tag(**kwargs)
28
+ ignore_regions = {
29
+ 'ignoreElementsData' => _find_regions(
30
+ xpaths: kwargs.fetch(:ignore_regions_xpaths, []),
31
+ accessibility_ids: kwargs.fetch(:ignore_region_accessibility_ids, []),
32
+ appium_elements: kwargs.fetch(:ignore_region_appium_elements, []),
33
+ custom_locations: kwargs.fetch(:custom_ignore_regions, [])
34
+ )
35
+ }
36
+ consider_regions = {
37
+ 'considerElementsData' => _find_regions(
38
+ xpaths: kwargs.fetch(:consider_regions_xpaths, []),
39
+ accessibility_ids: kwargs.fetch(:consider_region_accessibility_ids, []),
40
+ appium_elements: kwargs.fetch(:consider_region_appium_elements, []),
41
+ custom_locations: kwargs.fetch(:custom_consider_regions, [])
42
+ )
43
+ }
44
+
45
+ _post_screenshots(name, tag, tiles, get_debug_url, ignore_regions, consider_regions)
46
+ end
47
+
48
+ def _get_tag(**kwargs)
49
+ name = kwargs[:device_name] || metadata.device_name
50
+ os_name = metadata.os_name
51
+ os_version = metadata.os_version
52
+ width = metadata.device_screen_size['width'] || 1
53
+ height = metadata.device_screen_size['height'] || 1
54
+ orientation = metadata.get_orientation(**kwargs).downcase
55
+
56
+ {
57
+ 'name' => name,
58
+ 'os_name' => os_name,
59
+ 'os_version' => os_version,
60
+ 'width' => width,
61
+ 'height' => height,
62
+ 'orientation' => orientation
63
+ }
64
+ end
65
+
66
+ def _get_tiles(**kwargs)
67
+ fullpage_ss = kwargs[:fullpage] || false
68
+ if fullpage_ss
69
+ log('Full page screenshot is only supported on App Automate. Falling back to single page screenshot.')
70
+ end
71
+
72
+ png_bytes = driver.screenshot_as(:png)
73
+ directory = _get_dir
74
+ path = _write_screenshot(png_bytes, directory)
75
+
76
+ fullscreen = kwargs[:full_screen] || false
77
+ status_bar_height = kwargs[:status_bar_height] || metadata.status_bar_height
78
+ nav_bar_height = kwargs[:nav_bar_height] || metadata.navigation_bar_height
79
+ header_height = 0
80
+ footer_height = 0
81
+ [
82
+ Tile.new(status_bar_height, nav_bar_height, header_height, footer_height, filepath: path, fullscreen: fullscreen)
83
+ ]
84
+ end
85
+
86
+ def _find_regions(xpaths:, accessibility_ids:, appium_elements:, custom_locations:)
87
+ elements_array = []
88
+ get_regions_by_xpath(elements_array, xpaths)
89
+ get_regions_by_ids(elements_array, accessibility_ids)
90
+ get_regions_by_elements(elements_array, appium_elements)
91
+ get_regions_by_location(elements_array, custom_locations)
92
+ elements_array
93
+ end
94
+
95
+ def _post_screenshots(name, tag, tiles, debug_url, ignored_regions, considered_regions)
96
+ CLIWrapper.new.post_screenshots(name, tag, tiles, debug_url, ignored_regions, considered_regions)
97
+ end
98
+
99
+ def _write_screenshot(png_bytes, directory)
100
+ filepath = _get_path(directory)
101
+ File.open(filepath, 'wb') { |f| f.write(png_bytes) }
102
+ filepath
103
+ end
104
+
105
+ def get_region_object(selector, element)
106
+ scale_factor = metadata.scale_factor
107
+ location = hashed(element.location)
108
+ size = hashed(element.size)
109
+ coordinates = {
110
+ 'top' => location['y'] * scale_factor,
111
+ 'bottom' => (location['y'] + size['height']) * scale_factor,
112
+ 'left' => location['x'] * scale_factor,
113
+ 'right' => (location['x'] + size['width']) * scale_factor
114
+ }
115
+ { 'selector' => selector, 'coOrdinates' => coordinates }
116
+ end
117
+
118
+ def get_regions_by_xpath(elements_array, xpaths)
119
+ xpaths.each do |xpath|
120
+ element = driver.find_element(Appium::Core::Base::SearchContext::FINDERS[:xpath], xpath)
121
+ selector = "xpath: #{xpath}"
122
+ if element
123
+ region = get_region_object(selector, element)
124
+ elements_array << region
125
+ end
126
+ rescue Appium::Core::Error::NoSuchElementError => e
127
+ log("Appium Element with xpath: #{xpath} not found. Ignoring this xpath.")
128
+ log(e, on_debug: true)
129
+ end
130
+ end
131
+
132
+ def get_regions_by_ids(elements_array, ids)
133
+ ids.each do |id|
134
+ element = driver.find_element(Appium::Core::Base::SearchContext::FINDERS[:accessibility_id], id)
135
+ selector = "id: #{id}"
136
+ region = get_region_object(selector, element)
137
+ elements_array << region
138
+ rescue Appium::Core::Error::NoSuchElementError => e
139
+ log("Appium Element with id: #{id} not found. Ignoring this id.")
140
+ log(e, on_debug: true)
141
+ end
142
+ end
143
+
144
+ def get_regions_by_elements(elements_array, elements)
145
+ elements.each_with_index do |element, index|
146
+ class_name = element.attribute('class')
147
+ selector = "element: #{index} #{class_name}"
148
+ region = get_region_object(selector, element)
149
+ elements_array << region
150
+ rescue Appium::Core::Error::NoSuchElementError => e
151
+ log("Correct Element not passed at index #{index}")
152
+ log(e, on_debug: true)
153
+ end
154
+ end
155
+
156
+ def get_regions_by_location(elements_array, custom_locations)
157
+ custom_locations.each_with_index do |custom_location, index|
158
+ screen_width = metadata.device_screen_size['width']
159
+ screen_height = metadata.device_screen_size['height']
160
+ if custom_location.valid?(screen_height, screen_width)
161
+ region = {
162
+ selector: "custom ignore region: #{index}",
163
+ coOrdinates: {
164
+ top: custom_location.top,
165
+ bottom: custom_location.bottom,
166
+ left: custom_location.left,
167
+ right: custom_location.right
168
+ }
169
+ }
170
+ elements_array << region
171
+ else
172
+ log("Values passed in custom ignored region at index: #{index} are not valid")
173
+ end
174
+ end
175
+ end
176
+
177
+ def log(message, on_debug: false)
178
+ puts message if on_debug
179
+ end
180
+
181
+ def get_debug_url
182
+ debug_url
183
+ end
184
+
185
+ def get_device_name
186
+ ''
187
+ end
188
+
189
+ def _get_dir
190
+ dir_path = ENV['PERCY_TMP_DIR'] || nil
191
+ if dir_path
192
+ Pathname.new(dir_path).mkpath
193
+ return dir_path
194
+ end
195
+ Dir.mktmpdir
196
+ end
197
+
198
+ def _get_path(directory)
199
+ suffix = '.png'
200
+ prefix = 'percy-appium-'
201
+ file = Tempfile.new([prefix, suffix], directory)
202
+ file.close
203
+ file.path
204
+ end
205
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../exceptions/exceptions'
4
+ require_relative '../metadata/metadata_resolver'
5
+ require_relative 'app_automate'
6
+ require_relative 'generic_provider'
7
+
8
+ class ProviderResolver
9
+ def self.resolve(driver)
10
+ metadata = MetadataResolver.resolve(driver)
11
+ providers = [AppAutomate, GenericProvider]
12
+ providers.each do |provider|
13
+ return provider.new(driver, metadata) if provider.supports(metadata.remote_url)
14
+ end
15
+ raise UnknownProvider
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'common/common'
4
+ require_relative 'lib/app_percy'
5
+ require_relative 'lib/percy_automate'
6
+ require_relative 'lib/cli_wrapper'
7
+ require_relative 'environment'
8
+
9
+ def percy_screenshot(driver, name, **kwargs)
10
+ return nil unless CLIWrapper.percy_enabled?
11
+
12
+ app_percy = nil
13
+ provider_class = Environment.session_type == 'automate' ? PercyOnAutomate : AppPercy
14
+ app_percy = provider_class.new(driver)
15
+ app_percy.screenshot(name, **kwargs)
16
+ rescue StandardError => e
17
+ CLIWrapper.post_failed_event(e.to_s)
18
+ log("Could not take screenshot \"#{name}\"")
19
+ raise e if app_percy && !app_percy.percy_options.ignore_errors
20
+
21
+ log(e, on_debug: true)
22
+ nil
23
+ end
data/percy/version.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Percy
4
+ VERSION = '0.0.1'.freeze
5
+ end
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ percy = File.expand_path('../percy', __FILE__)
3
+ $LOAD_PATH.unshift(percy) unless $LOAD_PATH.include?(percy)
4
+ require 'version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'percy-appium-app'
8
+ spec.version = Percy::VERSION
9
+ spec.authors = ['BroswerStack']
10
+ spec.email = ['support@browserstack.com']
11
+ spec.summary = %q{Percy visual testing for Ruby Appium Mobile Apps}
12
+ spec.description = %q{}
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 2.6.0'
16
+
17
+
18
+ spec.metadata = {
19
+ 'bug_tracker_uri' => 'https://github.com/percy/percy-appium-ruby/issues',
20
+ 'source_code_uri' => 'https://github.com/percy/percy-appium-ruby',
21
+ }
22
+
23
+ spec.files = `git ls-files -z`.split("\x0")
24
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
25
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
26
+ spec.require_paths = ['percy']
27
+
28
+ spec.add_runtime_dependency 'appium_lib', '~> 12.0'
29
+
30
+ spec.add_development_dependency 'bundler', '~> 2.4'
31
+ spec.add_development_dependency 'minitest', '~> 5.20'
32
+ spec.add_development_dependency 'rake', '~> 13.0'
33
+ spec.add_development_dependency 'percy-style', '~> 0.7.0'
34
+ spec.add_development_dependency 'webmock', '~> 3.18'
35
+ spec.add_development_dependency 'webrick', '~> 1.3'
36
+
37
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/MethodLength
4
+
5
+ require 'minitest/autorun'
6
+ require 'minitest/mock'
7
+ require 'appium_lib'
8
+ require_relative 'mocks/mock_methods'
9
+ require_relative '../percy/metadata/android_metadata'
10
+
11
+ # Test suite for the AndroidMetadata class
12
+ class TestAndroidMetadata < Minitest::Test
13
+ def setup
14
+ @mock_webdriver = Minitest::Mock.new
15
+ @mock_webdriver.expect(:capabilities, get_android_capabilities)
16
+ @android_metadata = AndroidMetadata.new(@mock_webdriver)
17
+ end
18
+
19
+ def test_android_execute_script
20
+ command = 'some dummy command'
21
+ output = 'some output'
22
+ @mock_webdriver.expect(:execute_script, output, [command])
23
+
24
+ assert_equal(output, @android_metadata.execute_script(command))
25
+ @mock_webdriver.verify
26
+ end
27
+
28
+ def test_viewport
29
+ viewport = { 'left' => 0, 'top' => 84, 'width' => 1440, 'height' => 2708 }
30
+ android_capabilities = get_android_capabilities
31
+ @mock_webdriver.expect(:capabilities, android_capabilities.merge('viewportRect' => viewport))
32
+
33
+ assert(viewport, @android_metadata.viewport)
34
+
35
+ @mock_webdriver.verify
36
+ end
37
+
38
+ def test_get_system_bars
39
+ system_bars = {
40
+ 'statusBar' => { 'height' => 83 },
41
+ 'navigationBar' => { 'height' => 44 }
42
+ }
43
+ android_capabilities = get_android_capabilities
44
+ session_id = 'session_id_123'
45
+ @mock_webdriver.expect(:session_id, session_id)
46
+ @mock_webdriver.expect(:session_id, session_id)
47
+ @mock_webdriver.expect(:capabilities, android_capabilities.merge('viewportRect' => nil))
48
+ @mock_webdriver.expect(:get_system_bars, system_bars)
49
+
50
+ assert(system_bars, @android_metadata.get_system_bars)
51
+ @mock_webdriver.verify
52
+ end
53
+
54
+ def test_status_bar
55
+ @mock_webdriver.expect(:capabilities, get_android_capabilities)
56
+ @mock_webdriver.expect(:capabilities, get_android_capabilities)
57
+ @mock_webdriver.expect(:capabilities, get_android_capabilities)
58
+ @mock_webdriver.expect(:capabilities, get_android_capabilities)
59
+ mock_get_system_bars = { 'statusBar' => { 'height' => 1 } }
60
+ @android_metadata.stub(:get_system_bars, mock_get_system_bars) do
61
+ assert_equal(0, @android_metadata.status_bar_height)
62
+ end
63
+ end
64
+
65
+ def test_navigation_bar
66
+ @mock_webdriver.expect(:capabilities, get_android_capabilities)
67
+ @mock_webdriver.expect(:capabilities, get_android_capabilities)
68
+ @mock_webdriver.expect(:capabilities, get_android_capabilities)
69
+ @mock_webdriver.expect(:capabilities, get_android_capabilities)
70
+ mock_get_system_bars = { 'navigationBar' => { 'height' => 1 } }
71
+ @android_metadata.stub(:get_system_bars, mock_get_system_bars) do
72
+ assert_equal(0, @android_metadata.navigation_bar_height)
73
+ end
74
+ end
75
+
76
+ def test_scale_factor
77
+ assert_equal(1, @android_metadata.scale_factor)
78
+ end
79
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/MethodLength
4
+ # rubocop:disable Metrics/AbcSize
5
+
6
+ require 'minitest/autorun'
7
+ require 'minitest/mock'
8
+ require_relative '../percy/providers/app_automate'
9
+ require_relative '../percy/metadata/android_metadata'
10
+ require_relative 'mocks/mock_methods'
11
+
12
+ # Test suite for the AppAutomate class
13
+ class TestAppAutomate < Minitest::Test
14
+ COMPARISON_RESPONSE = { 'success' => true, 'link' => 'https://snapshots-url' }.freeze
15
+
16
+ def setup
17
+ @mock_webdriver = Minitest::Mock.new
18
+ @mock_webdriver.expect(:capabilities, get_android_capabilities)
19
+ @metadata = AndroidMetadata.new(@mock_webdriver)
20
+ @app_automate = AppAutomate.new(@mock_webdriver, @metadata)
21
+ end
22
+
23
+ def test_app_automate_get_debug_url
24
+ @app_automate.set_debug_url('deviceName' => 'Google Pixel 4', 'osVersion' => '12.0', 'buildHash' => 'abc',
25
+ 'sessionHash' => 'def')
26
+ debug_url = @app_automate.get_debug_url
27
+ assert_equal 'https://app-automate.browserstack.com/dashboard/v2/builds/abc/sessions/def', debug_url
28
+ end
29
+
30
+ def test_app_automate_supports_with_correct_url
31
+ app_automate_session = AppAutomate.supports('https://hub-cloud.browserstack.com/wd/hub')
32
+ assert_equal true, app_automate_session
33
+ end
34
+
35
+ def test_app_automate_supports_with_incorrect_url
36
+ app_automate_session = AppAutomate.supports('https://hub-cloud.generic.com/wd/hub')
37
+ assert_equal false, app_automate_session
38
+ end
39
+
40
+ def test_app_automate_supports_with_AA_DOMAIN
41
+ ENV['AA_DOMAIN'] = 'bsstag'
42
+ app_automate_session = AppAutomate.supports('bsstag.com')
43
+ assert_equal true, app_automate_session
44
+ ENV['AA_DOMAIN'] = nil
45
+ end
46
+
47
+ def test_app_automate_execute_percy_screenshot_begin
48
+ @mock_webdriver.expect(:execute_script, '{}', [String])
49
+ assert_empty @app_automate.execute_percy_screenshot_begin('Screebshot 1')
50
+ @mock_webdriver.verify
51
+ end
52
+
53
+ def test_app_automate_execute_percy_screenshot_end
54
+ @mock_webdriver.expect(:execute_script, '{}', [String])
55
+ assert_equal '{}',
56
+ @app_automate.execute_percy_screenshot_end('Screenshot 1', COMPARISON_RESPONSE['link'], 'success')
57
+ @mock_webdriver.verify
58
+ end
59
+
60
+ def test_app_automate_execute_percy_screenshot
61
+ @mock_webdriver.expect(:execute_script, '{"result": "result"}', [String])
62
+ @app_automate.execute_percy_screenshot(1080, 'singlepage', 5)
63
+ @mock_webdriver.verify
64
+ end
65
+
66
+ def test_execute_percy_screenshot_end_throws_error
67
+ @mock_webdriver.expect(:execute_script, proc { raise 'SomeException' }, [String])
68
+ @app_automate.execute_percy_screenshot_end('Screenshot 1', 'snapshot-url', 'success')
69
+ @mock_webdriver.verify
70
+ end
71
+
72
+ def test_execute_percy_screenshot_end
73
+ @app_automate.stub(:execute_percy_screenshot_begin, 'deviceName' => 'abc', 'osVersion' => '123') do
74
+ @app_automate.stub(:execute_percy_screenshot_end, nil) do
75
+ @app_automate.stub(:screenshot, 'link' => 'https://link') do
76
+ @app_automate.screenshot('name')
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def test_get_tiles
83
+ # Mocking Metadata's session_id method
84
+ metadata_mock = Minitest::Mock.new
85
+ metadata_mock.expect(:session_id, 'session_id_123')
86
+
87
+ # Mocking AndroidMetadata's methods
88
+ android_metadata_mock = Minitest::Mock.new
89
+ android_metadata_mock.expect(:device_screen_size, { 'width' => 1080, 'height' => 1920 })
90
+ android_metadata_mock.expect(:navigation_bar_height, 150)
91
+ android_metadata_mock.expect(:status_bar_height, 100)
92
+
93
+ Metadata.class_eval do
94
+ define_method(:session_id) do
95
+ metadata_mock.session_id
96
+ end
97
+ end
98
+
99
+ AndroidMetadata.class_eval do
100
+ define_method(:device_screen_size) do
101
+ android_metadata_mock.device_screen_size
102
+ end
103
+
104
+ define_method(:navigation_bar_height) do
105
+ android_metadata_mock.navigation_bar_height
106
+ end
107
+
108
+ define_method(:status_bar_height) do
109
+ android_metadata_mock.status_bar_height
110
+ end
111
+ end
112
+
113
+ @app_automate.stub(:execute_percy_screenshot, {
114
+ 'result' => '[{"sha":"sha-25568755","status_bar":null,"nav_bar":null,"header_height":120,"footer_height":80,"index":0}]'
115
+ }) do
116
+ result = @app_automate._get_tiles(fullpage: true)[0]
117
+ assert_equal('sha', result.sha)
118
+ assert_equal(100, result.status_bar_height)
119
+ assert_equal(150, result.nav_bar_height)
120
+ assert_equal(120, result.header_height)
121
+ assert_equal(80, result.footer_height)
122
+ end
123
+ end
124
+ end