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,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Cache
4
+ attr_reader :cache
5
+
6
+ @@cache = {}
7
+ CACHE_TIMEOUT = 5 * 60 # 5 * 60 seconds
8
+ TIMEOUT_KEY = 'last_access_time'
9
+
10
+ SESSION_DETAILS = 'session_details'
11
+ SYSTEM_BARS = 'system_bars'
12
+ WINDOW_SIZE = 'window_size'
13
+ VIEWPORT = 'viewport'
14
+ SESSION_CAPABILITIES = 'session_capabilities'
15
+ CAPABILITIES = 'capabilities'
16
+ COMMAND_EXECUTOR_URL = 'command_executor_url'
17
+
18
+ def cache
19
+ @@cache
20
+ end
21
+
22
+ def self.set_cache(session_id, property, value)
23
+ raise TypeError, 'Argument session_id should be a String' unless session_id.is_a?(String)
24
+ raise TypeError, 'Argument property should be a String' unless property.is_a?(String)
25
+
26
+ session = @@cache.fetch(session_id, {})
27
+ session[TIMEOUT_KEY] = Time.now.to_i
28
+ session[property] = value
29
+ @@cache[session_id] = session
30
+ end
31
+
32
+ def self.get_cache(session_id, property)
33
+ cleanup_cache
34
+
35
+ raise TypeError, 'Argument session_id should be a String' unless session_id.is_a?(String)
36
+ raise TypeError, 'Argument property should be a String' unless property.is_a?(String)
37
+
38
+ session = @@cache.fetch(session_id, {})
39
+ session.fetch(property, nil)
40
+ end
41
+
42
+ def self.cleanup_cache
43
+ now = Time.now.to_i
44
+ session_ids = []
45
+
46
+ @@cache.each do |session_id, session|
47
+ timestamp = session[TIMEOUT_KEY]
48
+ session_ids << session_id if now - timestamp >= CACHE_TIMEOUT
49
+ end
50
+
51
+ session_ids.each { |session_id| @@cache.delete(session_id) }
52
+ end
53
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+ require 'appium_lib/version'
7
+ require_relative '../common/common'
8
+ require_relative '../exceptions/exceptions'
9
+ require_relative '../version'
10
+ require_relative '../environment'
11
+
12
+ CLIENT_INFO = "percy-appium-app/#{SDK_VERSION}"
13
+ ENV_INFO = ["appium/#{Appium::VERSION}", "ruby/#{RUBY_VERSION}"].freeze
14
+
15
+ PERCY_CLI_API = ENV['PERCY_CLI_API'] || 'http://localhost:5338'
16
+
17
+ class CLIWrapper
18
+ def initialize; end
19
+
20
+ def self.percy_enabled?
21
+ @percy_enabled ||= begin
22
+ uri = URI("#{PERCY_CLI_API}/percy/healthcheck")
23
+ response = Net::HTTP.get_response(uri)
24
+
25
+ raise CLIException, response.body unless response.is_a?(Net::HTTPSuccess)
26
+
27
+ data = JSON.parse(response.body)
28
+ raise CLIException, data['error'] unless data['success']
29
+
30
+ Environment.percy_build_id = data['build']['id']
31
+ Environment.percy_build_url = data['build']['url']
32
+ Environment.session_type = data.fetch('type', nil)
33
+
34
+ version = response['x-percy-core-version']
35
+ if version.split('.')[0] != '1'
36
+ log("Unsupported Percy CLI version, #{version}")
37
+ return false
38
+ end
39
+ return true unless version.split('.')[1].to_i < 27
40
+
41
+ log('Please upgrade to the latest CLI version for using this SDK. Minimum compatible version is 1.27.0-beta.0')
42
+ return false
43
+ rescue StandardError => e
44
+ log('Percy is not running, disabling screenshots')
45
+ log(e, on_debug: true)
46
+ return false
47
+ end
48
+ end
49
+
50
+ def post_screenshots(name, tag, tiles, external_debug_url = nil, ignored_elements_data = nil,
51
+ considered_elements_data = nil)
52
+ body = request_body(name, tag, tiles, external_debug_url, ignored_elements_data, considered_elements_data)
53
+ body['client_info'] = Environment.get_client_info
54
+ body['environment_info'] = Environment.get_env_info
55
+
56
+ uri = URI("#{PERCY_CLI_API}/percy/comparison")
57
+ http = Net::HTTP.new(uri.host, uri.port)
58
+ request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
59
+ request.body = body.to_json
60
+
61
+ response = http.request(request)
62
+ data = JSON.parse(response.body)
63
+
64
+ raise CLIException, data.fetch('error', 'UnknownException') if response.code != '200'
65
+
66
+ data
67
+ end
68
+
69
+ def self.post_failed_event(error)
70
+ body = {
71
+ 'clientInfo' => Environment.get_client_info(true),
72
+ 'message' => error,
73
+ 'errorKind' => 'sdk'
74
+ }
75
+
76
+ uri = URI("#{PERCY_CLI_API}/percy/events")
77
+ response = Net::HTTP.post(uri, body.to_json, 'Content-Type' => 'application/json')
78
+
79
+ # Handle errors
80
+ if response.code.to_i != 200
81
+ data = JSON.parse(response.body)
82
+ error_message = data.fetch('error', 'UnknownException')
83
+ raise CLIException, error_message
84
+ end
85
+
86
+ JSON.parse(response.body)
87
+ rescue StandardError => e
88
+ log(e.message, on_debug: true)
89
+ nil
90
+ end
91
+
92
+ def post_poa_screenshots(name, session_id, command_executor_url, capabilities, desired_capabilities, options = nil)
93
+ body = {
94
+ 'sessionId' => session_id,
95
+ 'commandExecutorUrl' => command_executor_url,
96
+ 'capabilities' => capabilities.dup, # In Ruby, you can duplicate the hash with `dup`
97
+ 'sessionCapabilities' => desired_capabilities.dup,
98
+ 'snapshotName' => name,
99
+ 'options' => options
100
+ }
101
+
102
+ body['client_info'] = Environment.get_client_info # Using class method without the underscore
103
+ body['environment_info'] = Environment.get_env_info
104
+
105
+ uri = URI("#{PERCY_CLI_API}/percy/automateScreenshot")
106
+ response = Net::HTTP.post(uri, body.to_json, 'Content-Type' => 'application/json')
107
+
108
+ # Handle errors
109
+ raise CLIException, "Error: #{response.message}" unless response.is_a?(Net::HTTPSuccess)
110
+
111
+ data = JSON.parse(response.body)
112
+
113
+ if response.code != '200'
114
+ error_message = data.fetch('error', 'UnknownException')
115
+ raise CLIException, error_message
116
+ end
117
+
118
+ data
119
+ end
120
+
121
+ def request_body(name, tag, tiles, external_debug_url, ignored_elements_data, considered_elements_data)
122
+ tiles = tiles.map(&:to_h)
123
+ {
124
+ 'name' => name,
125
+ 'tag' => tag,
126
+ 'tiles' => tiles,
127
+ 'ignored_elements_data' => ignored_elements_data,
128
+ 'external_debug_url' => external_debug_url,
129
+ 'considered_elements_data' => considered_elements_data
130
+ }
131
+ end
132
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/ignore_region.rb
4
+ require_relative 'region'
5
+
6
+ class IgnoreRegion < Region
7
+ # Inherits everything from Region; no additional code needed unless you want to extend or modify the behavior
8
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'appium_lib'
4
+ require_relative '../exceptions/exceptions'
5
+ require_relative '../metadata/driver_metadata'
6
+ require_relative '../lib/cli_wrapper'
7
+
8
+ IGNORE_ELEMENT_KEY = 'ignore_region_appium_elements'
9
+ IGNORE_ELEMENT_ALT_KEY = 'ignoreRegionAppiumElements'
10
+ CONSIDER_ELEMENT_KEY = 'consider_region_appium_elements'
11
+ CONSIDER_ELEMENT_ALT_KEY = 'considerRegionAppiumElements'
12
+
13
+ class PercyOnAutomate
14
+ def initialize(driver)
15
+ unless driver.is_a?(Appium::Core::Base::Driver)
16
+ raise DriverNotSupported, 'The provided driver instance is not supported.'
17
+ end
18
+
19
+ @driver = driver
20
+ @percy_options = PercyOptions.new(@driver.capabilities)
21
+ end
22
+
23
+ def screenshot(name, **options)
24
+ return nil unless @percy_options.enabled
25
+ raise TypeError, 'Argument name should be a string' unless name.is_a?(String)
26
+ raise KeyError, 'Please pass the last parameter as "options" key' unless options.key?(:options)
27
+
28
+ metadata = DriverMetadata.new(@driver)
29
+ options = options[:options] || {}
30
+
31
+ begin
32
+ options[IGNORE_ELEMENT_KEY] = options.delete(IGNORE_ELEMENT_ALT_KEY) if options.key?(IGNORE_ELEMENT_ALT_KEY)
33
+ options[CONSIDER_ELEMENT_KEY] = options.delete(CONSIDER_ELEMENT_ALT_KEY) if options.key?(CONSIDER_ELEMENT_ALT_KEY)
34
+
35
+ ignore_region_elements = options.fetch(IGNORE_ELEMENT_KEY, []).map(&:id)
36
+ consider_region_elements = options.fetch(CONSIDER_ELEMENT_KEY, []).map(&:id)
37
+ options.delete(IGNORE_ELEMENT_KEY)
38
+ options.delete(CONSIDER_ELEMENT_KEY)
39
+
40
+ additional_options = {
41
+ 'ignore_region_elements' => ignore_region_elements,
42
+ 'consider_region_elements' => consider_region_elements
43
+ }
44
+
45
+ CLIWrapper.new.post_poa_screenshots(
46
+ name,
47
+ metadata.session_id,
48
+ metadata.command_executor_url,
49
+ metadata.capabilities,
50
+ metadata.session_capabilities,
51
+ options.merge(additional_options)
52
+ )
53
+ rescue StandardError => e
54
+ log("Could not take Screenshot '#{name}'")
55
+ log(e.message, on_debug: true)
56
+ end
57
+ nil
58
+ end
59
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PercyOptions
4
+ IGNORE_ERRORS = 'ignoreErrors'
5
+ ENABLED = 'enabled'
6
+ PERCY_OPTIONS = ['percy:options', 'percyOptions'].freeze
7
+
8
+ def initialize(capabilities)
9
+ @capabilities = capabilities
10
+ @capabilities = @capabilities.as_json unless @capabilities.is_a?(Hash)
11
+ @percy_options = _parse_percy_options || {}
12
+ end
13
+
14
+ def _parse_percy_options
15
+ options = PERCY_OPTIONS.map { |key| @capabilities.fetch(key, nil) }
16
+ options = if options.any? { |element| !element.nil? }
17
+ options[0] || options[1]
18
+ else
19
+ {}
20
+ end
21
+
22
+ if options
23
+ options[IGNORE_ERRORS] = @capabilities.fetch("percy.#{IGNORE_ERRORS}", true) unless options.key?(IGNORE_ERRORS)
24
+ options[ENABLED] = @capabilities.fetch("percy.#{ENABLED}", true) unless options.key?(ENABLED)
25
+ end
26
+
27
+ options
28
+ end
29
+
30
+ def ignore_errors
31
+ @percy_options.fetch(IGNORE_ERRORS, true)
32
+ end
33
+
34
+ def enabled
35
+ @percy_options.fetch(ENABLED, true)
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/region.rb
4
+ class Region
5
+ attr_accessor :top, :bottom, :left, :right
6
+
7
+ def initialize(top, bottom, left, right)
8
+ raise ArgumentError, 'Only Positive integer is allowed!' if [top, bottom, left, right].any?(&:negative?)
9
+ raise ArgumentError, 'Invalid ignore region parameters!' if top >= bottom || left >= right
10
+
11
+ @top = top
12
+ @bottom = bottom
13
+ @left = left
14
+ @right = right
15
+ end
16
+
17
+ def valid?(screen_height, screen_width)
18
+ return false if @top >= screen_height || @bottom > screen_height || @left >= screen_width || @right > screen_width
19
+
20
+ true
21
+ end
22
+ end
data/percy/lib/tile.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Tile
4
+ attr_reader :filepath, :status_bar_height, :nav_bar_height, :header_height, :footer_height, :fullscreen, :sha
5
+
6
+ def initialize(status_bar_height, nav_bar_height, header_height, footer_height, filepath: nil, sha: nil,
7
+ fullscreen: false)
8
+ @filepath = filepath
9
+ @status_bar_height = status_bar_height
10
+ @nav_bar_height = nav_bar_height
11
+ @header_height = header_height
12
+ @footer_height = footer_height
13
+ @fullscreen = fullscreen
14
+ @sha = sha
15
+ end
16
+
17
+ def to_h
18
+ {
19
+ 'filepath' => @filepath,
20
+ 'status_bar_height' => @status_bar_height,
21
+ 'nav_bar_height' => @nav_bar_height,
22
+ 'header_height' => @header_height,
23
+ 'footer_height' => @footer_height,
24
+ 'fullscreen' => @fullscreen,
25
+ 'sha' => @sha
26
+ }
27
+ end
28
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require_relative 'metadata'
5
+ require_relative '../lib/cache'
6
+
7
+ class AndroidMetadata < Metadata
8
+ def initialize(driver)
9
+ super(driver)
10
+ @_bars = nil
11
+ @_viewport_rect = capabilities.to_json['viewportRect']
12
+ end
13
+
14
+ def device_screen_size
15
+ caps = capabilities
16
+ caps = caps.as_json unless caps.is_a?(Hash)
17
+ width, height = caps['deviceScreenSize'].split('x')
18
+ { 'width' => width.to_i, 'height' => height.to_i }
19
+ end
20
+
21
+ def get_system_bars
22
+ @_bars = Cache.get_cache(session_id, Cache::SYSTEM_BARS)
23
+ if @_viewport_rect
24
+ begin
25
+ @_bars = {
26
+ 'statusBar' => { 'height' => @_viewport_rect['top'] },
27
+ 'navigationBar' => {
28
+ 'height' => device_screen_size['height'] - @_viewport_rect['height'] - @_viewport_rect['top']
29
+ }
30
+ }
31
+ rescue StandardError
32
+ @_bars = nil
33
+ end
34
+ end
35
+ if @_bars.nil?
36
+ @_bars = driver.get_system_bars
37
+ Cache.set_cache(session_id, Cache::SYSTEM_BARS, @_bars)
38
+ end
39
+ @_bars
40
+ end
41
+
42
+ def status_bar
43
+ status_bar = get_system_bars['statusBar']
44
+ if status_bar['height'] == 1
45
+ response = value_from_devices_info('status_bar', _device_name.upcase, os_version)
46
+ return { 'height' => response }
47
+ end
48
+ status_bar
49
+ end
50
+
51
+ def navigation_bar
52
+ navigation_bar = get_system_bars['navigationBar']
53
+ if navigation_bar['height'] == 1
54
+ response = { 'height' => value_from_devices_info('nav_bar', _device_name.upcase, os_version) }
55
+ return response
56
+ end
57
+ navigation_bar
58
+ end
59
+
60
+ def viewport
61
+ capabilities.to_json['viewportRect']
62
+ end
63
+
64
+ def scale_factor
65
+ 1
66
+ end
67
+
68
+ def _device_name
69
+ if @device_name.nil?
70
+ desired_caps = capabilities.to_json['desired'] || {}
71
+ device_name = desired_caps['deviceName']
72
+ device = desired_caps['device']
73
+ device_name ||= device
74
+ device_model = capabilities.to_json['deviceModel']
75
+ @device_name = device_name || device_model
76
+ end
77
+ @device_name
78
+ end
79
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/cache'
4
+
5
+ class DriverMetadata
6
+ def initialize(driver)
7
+ @driver = driver
8
+ end
9
+
10
+ def session_id
11
+ @driver.session_id
12
+ end
13
+
14
+ def command_executor_url
15
+ url = Cache.get_cache(session_id, Cache::COMMAND_EXECUTOR_URL)
16
+ if url.nil?
17
+ url = @driver.instance_variable_get(:@bridge).instance_variable_get(:@http).instance_variable_get(:@server_url).to_s
18
+ Cache.set_cache(session_id, Cache::COMMAND_EXECUTOR_URL, url)
19
+ end
20
+ url
21
+ end
22
+
23
+ def capabilities
24
+ caps = Cache.get_cache(session_id, Cache::SESSION_CAPABILITIES)
25
+ if caps.nil?
26
+ caps = @driver.capabilities.dup # In Ruby, use dup to create a shallow copy of the hash
27
+ Cache.set_cache(session_id, Cache::SESSION_CAPABILITIES, caps)
28
+ end
29
+ caps
30
+ end
31
+
32
+ def session_capabilities
33
+ session_caps = Cache.get_cache(session_id, Cache::SESSION_CAPABILITIES)
34
+ if session_caps.nil?
35
+ session_caps = @driver.desired_capabilities.dup # Assuming there is a desired_capabilities method
36
+ Cache.set_cache(session_id, Cache::SESSION_CAPABILITIES, session_caps)
37
+ end
38
+ session_caps
39
+ end
40
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'metadata'
4
+ require_relative '../lib/cache'
5
+
6
+ class IOSMetadata < Metadata
7
+ attr_reader :_window_size
8
+
9
+ def initialize(driver)
10
+ super(driver)
11
+ @_viewport = {}
12
+ @_window_size = {}
13
+ end
14
+
15
+ def device_screen_size
16
+ vp = viewport
17
+ height = vp.fetch('top', 0) + vp.fetch('height', 0)
18
+ width = vp.fetch('width', 0)
19
+ if height.zero? && width.zero?
20
+ scale_factor = value_from_devices_info('scale_factor', device_name)
21
+ height = get_window_size['height'] * scale_factor
22
+ width = get_window_size['width'] * scale_factor
23
+ end
24
+ { 'width' => width, 'height' => height }
25
+ end
26
+
27
+ def status_bar
28
+ height = 0
29
+ view_port = viewport
30
+ if view_port.fetch('top', 0) != 0
31
+ height = view_port['top']
32
+ else
33
+ scale_factor = value_from_devices_info('scale_factor', device_name)
34
+ status_bar_height = value_from_devices_info('status_bar', device_name)
35
+ height = status_bar_height.to_i * scale_factor.to_i
36
+ end
37
+ { 'height' => height }
38
+ end
39
+
40
+ def navigation_bar
41
+ { 'height' => 0 }
42
+ end
43
+
44
+ def get_window_size
45
+ @_window_size = Cache.get_cache(session_id, Cache::WINDOW_SIZE)
46
+ unless @_window_size
47
+ @_window_size = driver.get_window_size
48
+ Cache.set_cache(session_id, Cache::WINDOW_SIZE, @_window_size)
49
+ end
50
+ @_window_size
51
+ end
52
+
53
+ def viewport
54
+ @_viewport = Cache.get_cache(session_id, Cache::VIEWPORT)
55
+ if @_viewport.nil? || (@_viewport.is_a?(Hash) && @_viewport.empty?)
56
+ begin
57
+ @_viewport = execute_script('mobile: viewportRect')
58
+ Cache.set_cache(session_id, Cache::VIEWPORT, @_viewport)
59
+ rescue StandardError
60
+ log('Could not use viewportRect; using static config', on_debug: true)
61
+ # setting `viewport` as empty hash so that it's not nil anymore
62
+ Cache.set_cache(session_id, Cache::VIEWPORT, {})
63
+ end
64
+ end
65
+ @_viewport || { 'top' => 0, 'height' => 0, 'width' => 0 }
66
+ end
67
+
68
+ def device_name
69
+ if @device_name.nil?
70
+ caps = capabilities
71
+ caps = caps.as_json unless caps.is_a?(Hash)
72
+ @device_name = caps['deviceName']
73
+ end
74
+ @device_name
75
+ end
76
+
77
+ def scale_factor
78
+ scale_factor = value_from_devices_info('scale_factor', device_name)
79
+ return viewport['width'] / get_window_size['width'] if scale_factor.zero?
80
+
81
+ scale_factor
82
+ end
83
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'pathname'
5
+ require_relative '../common/common'
6
+
7
+ DEVICE_INFO_FILE_PATH = File.join(File.dirname(__FILE__), '..', 'configs', 'devices.json')
8
+ DEVICE_INFO = JSON.parse(File.read(DEVICE_INFO_FILE_PATH))
9
+
10
+ class Metadata
11
+ attr_reader :driver, :device_info
12
+ attr_accessor :device_name, :os_version, :device_info
13
+
14
+ def initialize(driver)
15
+ @driver = driver
16
+ @device_name = nil
17
+ @os_version = nil
18
+ @device_info = {}
19
+ end
20
+
21
+ def capabilities
22
+ caps = driver.capabilities
23
+ caps = caps.as_json unless caps.is_a?(Hash)
24
+ caps
25
+ end
26
+
27
+ def session_id
28
+ driver.session_id
29
+ end
30
+
31
+ def os_name
32
+ capabilities['platformName']
33
+ end
34
+
35
+ def os_version
36
+ caps = capabilities
37
+ caps = caps.as_json unless caps.is_a?(Hash)
38
+
39
+ os_version = caps['os_version'] || caps['platformVersion'] || ''
40
+ os_version = @os_version || os_version
41
+ begin
42
+ os_version.to_f.to_i.to_s
43
+ rescue StandardError
44
+ ''
45
+ end
46
+ end
47
+
48
+ def remote_url
49
+ driver.instance_variable_get(:@bridge).instance_variable_get(:@http).instance_variable_get(:@server_url).to_s
50
+ end
51
+
52
+ def get_orientation(**kwargs)
53
+ orientation = kwargs[:orientation] || capabilities['orientation'] || 'PORTRAIT'
54
+ orientation = orientation.downcase
55
+ orientation = orientation == 'auto' ? _orientation : orientation
56
+ orientation.upcase
57
+ end
58
+
59
+ def _orientation
60
+ driver.orientation.downcase
61
+ end
62
+
63
+ def device_screen_size
64
+ raise NotImplementedError
65
+ end
66
+
67
+ def _device_name
68
+ raise NotImplementedError
69
+ end
70
+
71
+ def status_bar
72
+ raise NotImplementedError
73
+ end
74
+
75
+ def status_bar_height
76
+ status_bar['height']
77
+ end
78
+
79
+ def navigation_bar
80
+ raise NotImplementedError
81
+ end
82
+
83
+ def navigation_bar_height
84
+ navigation_bar['height']
85
+ end
86
+
87
+ def viewport
88
+ raise NotImplementedError
89
+ end
90
+
91
+ def execute_script(command)
92
+ driver.execute_script(command)
93
+ end
94
+
95
+ def value_from_devices_info(key, device_name, os_version = nil)
96
+ device_info = get_device_info(device_name)
97
+ device_info = device_info[os_version] || {} if os_version
98
+ device_info[key].to_i || 0
99
+ end
100
+
101
+ def get_device_info(device_name)
102
+ return @device_info unless @device_info.empty?
103
+
104
+ @device_info = DEVICE_INFO[device_name.downcase] || {}
105
+ log("#{device_name.downcase} does not exist in config.") if @device_info.empty?
106
+ @device_info
107
+ end
108
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../exceptions/exceptions'
4
+ require_relative 'android_metadata'
5
+ require_relative 'ios_metadata'
6
+
7
+ class MetadataResolver
8
+ def self.resolve(driver)
9
+ capabilities = driver.capabilities
10
+ capabilities = capabilities.as_json unless capabilities.is_a?(Hash)
11
+ platform_name = capabilities.fetch('platformName', '').downcase
12
+ case platform_name
13
+ when 'android'
14
+ AndroidMetadata.new(driver)
15
+ when 'ios'
16
+ IOSMetadata.new(driver)
17
+ else
18
+ raise PlatformNotSupported
19
+ end
20
+ end
21
+ end