appium_failure_helper 1.4.0 → 1.5.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b1148ccbdea9b7b8446bf489a8f325e6dd4255cbeeba75ed58469f21169a126
4
- data.tar.gz: 646ef386ee881d2f9cccf4ca3e85f8a79bd3cd132221c537e6db3f07c719f91f
3
+ metadata.gz: 43096445c0c64d280ed41cc96d50ddd088c660bfac0441b8ff4e9ddc34e9107d
4
+ data.tar.gz: a2b4d0c6ebe03d445ae1e39d5dd35da188b01b4835b1a9a16055709b201f0864
5
5
  SHA512:
6
- metadata.gz: f345294b82fad66aa0b47e3455a85063caef1f292225f9954d5bd10a0370f29e9ba51c15d7caeed219c4e587b84f115e4e762e7b16f9408bacdc91dbce8aaf55
7
- data.tar.gz: 505c2e690da3d723c5afb43a7afb1f219bca682e45e1c8c14068c98072c1331c90fecbe764312fe4ad3071d95df57106068b4feea5c4ca68c736b5fcbcd647dd
6
+ metadata.gz: f27c3e50dbef777ef84b756738e32ca50779a970e01e8cbd086a13c5a65a15d6c22c3f27e51e357b78555d3ae8cf5619b9e9fa36131c5f6761d54707b3fe8d93
7
+ data.tar.gz: a1dc3d6684a2d5afc79c6a7c956075fdeb5a90afd6d5ac4b02ae8a1129e16c18fcdae7cfd2f66794cab9e86ec65880ff7c5cb0edfddbef0b8ab6326ff163e445
Binary file
@@ -1,6 +1,7 @@
1
1
  # lib/appium_failure_helper/element_repository.rb
2
2
  module AppiumFailureHelper
3
3
  module ElementRepository
4
+ ELEMENTS = {}
4
5
  def self.load_all
5
6
  config = AppiumFailureHelper.configuration
6
7
  base_path = config.elements_path
@@ -38,48 +39,26 @@ module AppiumFailureHelper
38
39
  map
39
40
  end
40
41
 
41
- def self.load_all_from_yaml(base_path)
42
- elements_map = {}
43
- glob_path = File.join(base_path, '**', '*.yaml')
44
- files_found = Dir.glob(glob_path)
45
- files_found.each do |file|
46
- next if file.include?('reports_failure')
42
+ def self.load_all_from_yaml(dir_path = 'features/elements')
43
+ Dir.glob("#{dir_path}/*.yaml").each do |file|
47
44
  begin
48
- data = YAML.load_file(file)
49
- if data.is_a?(Hash)
50
- data.each do |k, v|
51
- data[k] = normalize_element(v)
52
- end
53
- elements_map.merge!(data)
45
+ yaml_data = YAML.load_file(file)
46
+ yaml_data.each do |key, value|
47
+ ELEMENTS[key.to_sym] = normalize_element(value)
54
48
  end
55
49
  rescue => e
56
50
  Utils.logger.warn("Aviso: Erro ao carregar o arquivo YAML #{file}: #{e.message}")
57
51
  end
58
52
  end
59
- elements_map
53
+ Utils.logger.info("Número de elementos carregados: #{ELEMENTS.size}")
54
+ ELEMENTS
60
55
  end
61
56
 
62
- def self.normalize_yaml_hash_keys(obj)
63
- case obj
64
- when Hash
65
- result = {}
66
- obj.each do |k, v|
67
- k_s = k.to_s
68
- v_n = normalize_yaml_hash_keys(v)
69
- # Se v_n é um Hash com chaves :value ou 'valor' -> faça unificação para 'value'
70
- if v_n.is_a?(Hash)
71
- if v_n.key?('valor') && !v_n.key?('value')
72
- v_n['value'] = v_n.delete('valor')
73
- end
74
- # também converte :tipoBusca para 'tipoBusca' (string)
75
- end
76
- result[k_s] = v_n
77
- end
78
- result
79
- when Array
80
- obj.map { |el| normalize_yaml_hash_keys(el) }
81
- else
82
- obj
57
+ def self.normalize_element(element_hash)
58
+ # Ajuste para garantir keys simbólicas e paths padrão
59
+ element_hash.transform_keys(&:to_sym).tap do |h|
60
+ h[:selector_type] ||= h[:type] || 'unknown'
61
+ h[:selector_value] ||= h[:value] || 'unknown'
83
62
  end
84
63
  end
85
64
  end
@@ -12,54 +12,44 @@ module AppiumFailureHelper
12
12
  end
13
13
 
14
14
  def call
15
+ report_data = {}
15
16
  begin
16
17
  unless @driver && @driver.session_id
17
18
  Utils.logger.error("Helper não executado: driver nulo ou sessão encerrada.")
18
- return
19
+ return {}
19
20
  end
20
21
 
21
22
  FileUtils.mkdir_p(@output_folder)
22
-
23
23
 
24
24
  triage_result = Analyzer.triage_error(@exception)
25
-
25
+ screenshot_b64 = begin
26
+ @driver.screenshot_as(:base64)
27
+ rescue
28
+ nil
29
+ end
26
30
  report_data = {
27
31
  exception: @exception,
28
32
  triage_result: triage_result,
29
33
  timestamp: @timestamp,
30
- platform: @driver.capabilities['platformName'] || @driver.capabilities[:platform_name] || 'unknown',
31
- screenshot_base64: @driver.screenshot_as(:base64)
34
+ platform: (@driver.capabilities['platformName'] rescue @driver.capabilities[:platform_name]) || 'unknown',
35
+ screenshot_base64: screenshot_b64
32
36
  }
33
37
 
34
38
  if triage_result == :locator_issue
35
39
  page_source = @driver.page_source rescue nil
36
- doc = Nokogiri::XML(page_source)
40
+ doc = Nokogiri::XML(page_source) rescue nil
37
41
 
38
- # Inicializa failed_info primeiro
39
- failed_info = Analyzer.extract_failure_details(@exception) || {}
40
- failed_info = SourceCodeAnalyzer.extract_from_exception(@exception) || {} if failed_info.empty?
41
- failed_info = { selector_type: 'unknown', selector_value: 'unknown' } if failed_info.empty?
42
+ failed_info = fetch_failed_element
42
43
 
43
- # Monta o report_data
44
44
  report_data[:page_source] = page_source
45
45
  report_data[:failed_element] = failed_info
46
46
 
47
- # Analisa elementos similares, alternative xpaths, de_para, code search etc
48
- unless failed_info.empty?
49
- page_analyzer = PageAnalyzer.new(page_source, report_data[:platform].to_s)
50
- all_page_elements = page_analyzer.analyze || []
47
+ unless failed_info.nil? || failed_info.empty?
48
+ page_analyzer = PageAnalyzer.new(page_source, report_data[:platform].to_s) rescue nil
49
+ all_page_elements = page_analyzer ? (page_analyzer.analyze || []) : []
51
50
  similar_elements = Analyzer.find_similar_elements(failed_info, all_page_elements) || []
52
-
53
- alternative_xpaths = []
54
- if !similar_elements.empty?
55
- target_suggestion = similar_elements.first
56
- if target_suggestion[:attributes] && (target_path = target_suggestion[:attributes][:path])
57
- target_node = doc.at_xpath(target_path)
58
- alternative_xpaths = XPathFactory.generate_for_node(target_node) if target_node
59
- end
60
- end
61
-
62
- unified_element_map = ElementRepository.load_all
51
+ alternative_xpaths = generate_alternative_xpaths(similar_elements, doc)
52
+ unified_element_map = ElementRepository.load_all rescue {}
63
53
  de_para_result = Analyzer.find_de_para_match(failed_info, unified_element_map)
64
54
  code_search_results = CodeSearcher.find_similar_locators(failed_info) || []
65
55
 
@@ -72,16 +62,116 @@ module AppiumFailureHelper
72
62
  )
73
63
  end
74
64
 
75
- # Gera o relatório
76
65
  ReportGenerator.new(@output_folder, report_data).generate_all
77
66
  Utils.logger.info("Relatórios gerados com sucesso em: #{@output_folder}")
78
-
79
- report_data
80
- return
81
67
  end
68
+
82
69
  rescue => e
83
70
  Utils.logger.error("Erro fatal na GEM de diagnóstico: #{e.message}\n#{e.backtrace.join("\n")}")
71
+ report_data = { exception: @exception, triage_result: :error } if report_data.nil? || report_data.empty?
72
+ ensure
73
+ return report_data
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def fetch_failed_element
80
+ msg = @exception&.message.to_s
81
+
82
+ # 1) pattern: using "type" with value "value"
83
+ if (m = msg.match(/using\s+["']?([^"']+)["']?\s+with\s+value\s+["']([^"']+)["']/i))
84
+ return { selector_type: m[1], selector_value: m[2] }
85
+ end
86
+
87
+ # 2) JSON-like: {"method":"id","selector":"btn"}
88
+ if (m = msg.match(/"method"\s*:\s*"([^"]+)"[\s,}].*"selector"\s*:\s*"([^"]+)"/i))
89
+ return { selector_type: m[1], selector_value: m[2] }
90
+ end
91
+
92
+ # 3) generic quoted token "value" or 'value'
93
+ if (m = msg.match(/["']([^"']+)["']/))
94
+ maybe_value = m[1]
95
+ # try lookup in repo by that value
96
+ unified_map = ElementRepository.load_all rescue {}
97
+ found = find_in_element_repository_by_value(maybe_value, unified_map)
98
+ if found
99
+ return found
100
+ end
101
+
102
+ # guess type from message heuristics
103
+ guessed_type = msg[/\b(xpath|id|accessibility id|css)\b/i] ? $&.downcase : nil
104
+ return { selector_type: guessed_type || 'unknown', selector_value: maybe_value }
105
+ end
106
+
107
+ # 4) try SourceCodeAnalyzer
108
+ begin
109
+ code_info = SourceCodeAnalyzer.extract_from_exception(@exception) rescue {}
110
+ unless code_info.nil? || code_info.empty?
111
+ return code_info
112
+ end
113
+ rescue => _; end
114
+
115
+ # 5) fallback: try to inspect unified map for likely candidates (keys or inner values)
116
+ unified_map = ElementRepository.load_all rescue {}
117
+ # try to match any key that looks like an identifier present in the message
118
+ unified_map.each do |k, v|
119
+ k_str = k.to_s.downcase
120
+ if msg.downcase.include?(k_str)
121
+ return normalize_repo_element(v)
122
+ end
123
+ # inspect value fields
124
+ vals = []
125
+ if v.is_a?(Hash)
126
+ vals << v['valor'] if v.key?('valor')
127
+ vals << v['value'] if v.key?('value')
128
+ vals << v[:valor] if v.key?(:valor)
129
+ vals << v[:value] if v.key?(:value)
130
+ end
131
+ vals.compact!
132
+ vals.each do |vv|
133
+ if vv.to_s.downcase == vv.to_s.downcase && msg.downcase.include?(vv.to_s.downcase)
134
+ return normalize_repo_element(v)
135
+ end
136
+ end
137
+ end
138
+
139
+ # final fallback
140
+ debug_log("fetch_failed_element: fallback unknown")
141
+ { selector_type: 'unknown', selector_value: 'unknown' }
142
+ end
143
+
144
+ def find_in_element_repository_by_value(value, map = {})
145
+ return nil if value.nil? || value.to_s.strip.empty?
146
+ normalized_value = value.to_s.downcase.strip
147
+ map.each do |k, v|
148
+ entry = v.is_a?(Hash) ? v : (v.respond_to?(:to_h) ? v.to_h : nil)
149
+ next unless entry
150
+ entry_val = entry['valor'] || entry['value'] || entry[:valor] || entry[:value] || entry['locator'] || entry[:locator]
151
+ next unless entry_val
152
+ return normalize_repo_element(entry) if entry_val.to_s.downcase.strip == normalized_value
153
+ end
154
+ nil
155
+ end
156
+
157
+ def normalize_repo_element(entry)
158
+ return nil unless entry.is_a?(Hash)
159
+ tipo = entry['tipoBusca'] || entry[:tipoBusca] || entry['type'] || entry[:type] || entry['search_type'] || entry[:search]
160
+ valor = entry['valor'] || entry[:value] || entry[:locator] || entry[:valor_final] || entry[:value_final]
161
+ return nil unless valor
162
+ { selector_type: (tipo || 'unknown'), selector_value: valor.to_s }
163
+ end
164
+
165
+ def generate_alternative_xpaths(similar_elements, doc)
166
+ alternative_xpaths = []
167
+ if !similar_elements.empty?
168
+ target_suggestion = similar_elements.first
169
+ if target_suggestion[:attributes] && (target_path = target_suggestion[:attributes][:path])
170
+ target_node = doc.at_xpath(target_path) rescue nil
171
+ alternative_xpaths = XPathFactory.generate_for_node(target_node) if target_node
172
+ end
84
173
  end
174
+ alternative_xpaths
85
175
  end
86
176
  end
87
- end
177
+ end
@@ -1,3 +1,3 @@
1
1
  module AppiumFailureHelper
2
- VERSION = "1.4.0"
2
+ VERSION = "1.5.0"
3
3
  end
data/release_gem.rb CHANGED
@@ -60,6 +60,7 @@ def git_commit_and_tag(new_version)
60
60
  `git add .`
61
61
  `git commit -m "Bump version to #{new_version.join('.')}"`
62
62
  `git tag v#{new_version.join('.')}`
63
+ `git push && git push --tags`
63
64
  end
64
65
 
65
66
  # Publicar a GEM
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appium_failure_helper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Nascimento
@@ -94,6 +94,7 @@ files:
94
94
  - LICENSE.txt
95
95
  - README.md
96
96
  - Rakefile
97
+ - appium_failure_helper-1.4.0.gem
97
98
  - elements/cadastro.yaml
98
99
  - elements/login.yaml
99
100
  - img/fluxo_appium_failure_helper.png