appium_failure_helper 1.1.0 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0fbacdea5fe5540838204bcabcc355e38b455afbfe49f65d4628955d8f84f226
4
- data.tar.gz: 526f397b33ea3cdd87831c35275dfbd313e9d81dd60d6c363ad717972d63f096
3
+ metadata.gz: 206d70f79fcfb3cf01291da330e02da83218080d511f02ec899c9778e12ccec0
4
+ data.tar.gz: 2dbc0c9b407e44f786353b6914faf8c9baa0c2014fe3f84fd1e3532f45837dad
5
5
  SHA512:
6
- metadata.gz: 0274b45b6d5a796525574666e82c6bf60b8c99eb9ac269c0ae31f11c08c411f798b893cc33f4ff86d9fa493db951bd9947d7d19ef62691b29226952f9b78abde
7
- data.tar.gz: 6fa2f563edab7b2e5b6da5de909b8e39c89438773d40e32a767191ef6423009e71797899a0393d2db0b51f5688c165d221ddd2df6c458d611913a15cdf40c241
6
+ metadata.gz: c2a12c07420ec30127d4297258407f5e90f33dbcb395143a8b6332ec97b29ac704e2022ec86e661d1416706bbc6549b6dfdc77f70265ed7dc065f7e74ea9eff3
7
+ data.tar.gz: c52d7bb4ac02b470063866bb959a9067342d24f4699f9aba856b8a6d8a30fd388ed6aca9434dd397136cb07faec4adc567751856182ede3196cbbd0348b3126f
@@ -1,27 +1,33 @@
1
- # lib/appium_failure_helper/analyzer.rb
2
1
  module AppiumFailureHelper
3
2
  module Analyzer
4
-
5
3
  def self.triage_error(exception)
6
- case exception
7
- when Selenium::WebDriver::Error::NoSuchElementError, Selenium::WebDriver::Error::TimeoutError
8
- :locator_issue # O elemento não foi encontrado a tempo.
9
- when Selenium::WebDriver::Error::ElementNotInteractableError
10
- :visibility_issue # Encontrado, mas não clicável/visível.
11
- when Selenium::WebDriver::Error::StaleElementReferenceError
12
- :stale_element_issue # A página mudou, o elemento "envelheceu".
13
- when RSpec::Expectations::ExpectationNotMetError
14
- :assertion_failure # É um bug funcional, a asserção falhou.
15
- when NoMethodError, NameError, ArgumentError, TypeError
16
- :ruby_code_issue # Erro de sintaxe ou lógica no código de teste.
17
- when Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
18
- :session_startup_issue # Problema na conexão/inicialização com o Appium.
19
- when Selenium::WebDriver::Error::WebDriverError
20
- return :app_crash_issue if exception.message.include?('session deleted because of page crash')
21
- :unknown_appium_issue
22
- else
23
- :unknown_issue
24
- end
4
+ rspec_error_class = defined?(RSpec::Expectations::ExpectationNotMetError) ? RSpec::Expectations::ExpectationNotMetError : Class.new
5
+
6
+ result = case exception
7
+ when Selenium::WebDriver::Error::NoSuchElementError,
8
+ Selenium::WebDriver::Error::TimeoutError,
9
+ Selenium::WebDriver::Error::UnknownCommandError
10
+ :locator_issue
11
+ when Selenium::WebDriver::Error::ElementNotInteractableError
12
+ :visibility_issue
13
+ when Selenium::WebDriver::Error::StaleElementReferenceError
14
+ :stale_element_issue
15
+ when rspec_error_class
16
+ :assertion_failure
17
+ when NoMethodError, NameError, ArgumentError, TypeError
18
+ :ruby_code_issue
19
+ when Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
20
+ :session_startup_issue
21
+ when Selenium::WebDriver::Error::WebDriverError
22
+ if exception.message.include?('session deleted because of page crash')
23
+ :app_crash_issue
24
+ else
25
+ :unknown_appium_issue
26
+ end
27
+ else
28
+ :unknown_issue
29
+ end
30
+ return result
25
31
  end
26
32
 
27
33
  def self.extract_failure_details(exception)
@@ -34,36 +40,31 @@ module AppiumFailureHelper
34
40
  /(?:with the resource-id|with the accessibility-id) ['"]?(.+?)['"]?/i
35
41
  ]
36
42
  patterns.each do |pattern|
37
- match = message.match(pattern)
38
- if match
39
- info[:selector_value] = match.captures.last.strip.gsub(/['"]/, '')
40
- info[:selector_type] = match.captures.size > 1 ? match.captures[0].strip.gsub(/['"]/, '') : 'id'
41
- return info
42
- end
43
+ match = message.match(pattern)
44
+ if match
45
+ info[:selector_value] = match.captures.last.strip.gsub(/['"]/, '')
46
+ info[:selector_type] = match.captures.size > 1 ? match.captures[0].strip.gsub(/['"]/, '') : 'id'
47
+ return info
48
+ end
43
49
  end
44
50
  info
45
51
  end
46
-
52
+
47
53
  def self.find_de_para_match(failed_info, element_map)
48
54
  failed_value = failed_info[:selector_value].to_s
49
55
  return nil if failed_value.empty?
50
-
51
56
  logical_name_key = failed_value.gsub(/^#/, '')
52
-
53
57
  if element_map.key?(logical_name_key)
54
58
  return { logical_name: logical_name_key, correct_locator: element_map[logical_name_key] }
55
59
  end
56
-
57
60
  cleaned_failed_locator = failed_value.gsub(/[:\-\/@=\[\]'"()]/, ' ').gsub(/\s+/, ' ').downcase.strip
58
-
59
61
  element_map.each do |name, locator_info|
60
- mapped_locator = locator_info['valor'].to_s
62
+ mapped_locator = locator_info['valor'].to_s || locator_info['value'].to_s
61
63
  cleaned_mapped_locator = mapped_locator.gsub(/[:\-\/@=\[\]'"()]/, ' ').gsub(/\s+/, ' ').downcase.strip
62
64
  distance = DidYouMean::Levenshtein.distance(cleaned_failed_locator, cleaned_mapped_locator)
63
65
  max_len = [cleaned_failed_locator.length, cleaned_mapped_locator.length].max
64
66
  next if max_len.zero?
65
67
  similarity_score = 1.0 - (distance.to_f / max_len)
66
-
67
68
  if similarity_score > 0.85
68
69
  return { logical_name: name, correct_locator: locator_info }
69
70
  end
@@ -75,20 +76,16 @@ module AppiumFailureHelper
75
76
  failed_locator_value = failed_info[:selector_value]
76
77
  failed_locator_type = failed_info[:selector_type]
77
78
  return [] unless failed_locator_value && failed_locator_type
78
-
79
79
  normalized_failed_type = failed_locator_type.to_s.downcase.include?('id') ? 'id' : failed_locator_type.to_s
80
80
  cleaned_failed_locator = failed_locator_value.to_s.gsub(/[:\-\/@=\[\]'"()]/, ' ').gsub(/\s+/, ' ').downcase.strip
81
81
  similarities = []
82
-
83
82
  all_page_suggestions.each do |suggestion|
84
83
  candidate_locator = suggestion[:locators].find { |loc| loc[:strategy] == normalized_failed_type }
85
84
  next unless candidate_locator
86
-
87
85
  cleaned_candidate_locator = candidate_locator[:locator].gsub(/[:\-\/@=\[\]'"()]/, ' ').gsub(/\s+/, ' ').downcase.strip
88
86
  distance = DidYouMean::Levenshtein.distance(cleaned_failed_locator, cleaned_candidate_locator)
89
87
  max_len = [cleaned_failed_locator.length, cleaned_candidate_locator.length].max
90
88
  next if max_len.zero?
91
-
92
89
  similarity_score = 1.0 - (distance.to_f / max_len)
93
90
  if similarity_score > 0.85
94
91
  similarities << { name: suggestion[:name], locators: suggestion[:locators], score: similarity_score, attributes: suggestion[:attributes] }
@@ -24,7 +24,13 @@ module AppiumFailureHelper
24
24
  return map
25
25
  end
26
26
  instance.elements.each do |key, value|
27
- map[key.to_s] = { 'tipoBusca' => value[0], 'valor' => value[1] }
27
+ valor = value[1]
28
+ if valor.is_a?(Hash)
29
+ valor_final = valor['valor'] || valor['value'] || valor
30
+ else
31
+ valor_final = valor
32
+ end
33
+ map[key.to_s] = { 'tipoBusca' => value[0], 'valor' => valor_final }
28
34
  end
29
35
  rescue => e
30
36
  Utils.logger.warn("AVISO: Erro ao processar o arquivo #{file_path}: #{e.message}")
@@ -16,6 +16,7 @@ module AppiumFailureHelper
16
16
  begin
17
17
  unless @driver && @driver.session_id
18
18
  Utils.logger.error("Helper não executado: driver nulo ou sessão encerrada.")
19
+ Utils.logger.error("Exceção original: #{@exception.message}")
19
20
  return
20
21
  end
21
22
 
@@ -40,35 +41,38 @@ module AppiumFailureHelper
40
41
  failed_info = SourceCodeAnalyzer.extract_from_exception(@exception) || {}
41
42
  end
42
43
 
43
- page_analyzer = PageAnalyzer.new(page_source, report_data[:platform].to_s)
44
- all_page_elements = page_analyzer.analyze || []
45
- similar_elements = Analyzer.find_similar_elements(failed_info, all_page_elements) || []
46
-
47
- alternative_xpaths = []
48
- if !similar_elements.empty?
49
- target_suggestion = similar_elements.first
50
- if target_suggestion[:attributes] && (target_path = target_suggestion[:attributes][:path])
51
- target_node = doc.at_xpath(target_path)
52
- alternative_xpaths = XPathFactory.generate_for_node(target_node) if target_node
44
+ if failed_info.empty?
45
+ report_data[:triage_result] = :unidentified_locator_issue
46
+ else
47
+ page_analyzer = PageAnalyzer.new(page_source, report_data[:platform].to_s)
48
+ all_page_elements = page_analyzer.analyze || []
49
+ similar_elements = Analyzer.find_similar_elements(failed_info, all_page_elements) || []
50
+
51
+ alternative_xpaths = []
52
+ if !similar_elements.empty?
53
+ target_suggestion = similar_elements.first
54
+ if target_suggestion[:attributes] && (target_path = target_suggestion[:attributes][:path])
55
+ target_node = doc.at_xpath(target_path)
56
+ alternative_xpaths = XPathFactory.generate_for_node(target_node) if target_node
57
+ end
53
58
  end
54
- end
55
59
 
56
- unified_element_map = ElementRepository.load_all
57
- de_para_result = Analyzer.find_de_para_match(failed_info, unified_element_map)
58
- code_search_results = CodeSearcher.find_similar_locators(failed_info) || []
60
+ unified_element_map = ElementRepository.load_all
61
+ de_para_result = Analyzer.find_de_para_match(failed_info, unified_element_map)
62
+ code_search_results = CodeSearcher.find_similar_locators(failed_info) || []
59
63
 
60
- report_data.merge!({
61
- page_source: page_source,
62
- failed_element: failed_info,
63
- similar_elements: similar_elements,
64
- alternative_xpaths: alternative_xpaths,
65
- de_para_analysis: de_para_result,
66
- code_search_results: code_search_results,
67
- all_page_elements: all_page_elements
68
- })
64
+ report_data.merge!({
65
+ page_source: page_source,
66
+ failed_element: failed_info,
67
+ similar_elements: similar_elements,
68
+ alternative_xpaths: alternative_xpaths,
69
+ de_para_analysis: de_para_result,
70
+ code_search_results: code_search_results,
71
+ all_page_elements: all_page_elements
72
+ })
73
+ end
69
74
  end
70
75
 
71
- # A chamada correta, passando apenas 2 argumentos
72
76
  ReportGenerator.new(@output_folder, report_data).generate_all
73
77
  Utils.logger.info("Relatórios gerados com sucesso em: #{@output_folder}")
74
78
 
@@ -37,41 +37,52 @@ module AppiumFailureHelper
37
37
  end
38
38
  end
39
39
 
40
- def generate_html_report
41
- html_content = case @data[:triage_result]
42
- when :locator_issue
43
- build_full_report
44
- when :assertion_failure
45
- build_simple_diagnosis_report(
46
- title: "Falha de Asserção (Bug Funcional)",
47
- message: "A automação executou os passos corretamente, mas o resultado final verificado na tela não foi o esperado. Isso geralmente indica um bug funcional na aplicação, e não um problema com o seletor."
48
- )
49
- when :visibility_issue
50
- build_simple_diagnosis_report(
51
- title: "Elemento Oculto ou Não-Interagível",
52
- message: "O seletor encontrou o elemento no XML da página, mas ele não está visível ou habilitado para interação. Verifique se há outros elementos sobrepondo-o, se ele está desabilitado (disabled/enabled='false'), ou se é necessário aguardar uma animação."
53
- )
54
- when :stale_element_issue
55
- build_simple_diagnosis_report(
56
- title: "Referência de Elemento Antiga (Stale)",
57
- message: "O elemento foi encontrado, mas a página foi atualizada antes que a interação pudesse ocorrer. Isso é um problema de timing. A solução é encontrar o elemento novamente logo antes de interagir com ele."
58
- )
59
- when :session_startup_issue
60
- build_simple_diagnosis_report(
61
- title: "Falha na Conexão com o Servidor Appium",
62
- message: "Não foi possível criar uma sessão com o servidor. Verifique se o servidor Appium está rodando, se as 'capabilities' (incluindo prefixos 'appium:') e a URL de conexão estão corretas."
63
- )
64
- when :app_crash_issue
65
- build_simple_diagnosis_report(
66
- title: "Crash do Aplicativo",
67
- message: "A sessão foi encerrada inesperadamente, o que indica que o aplicativo travou. A causa raiz deve ser investigada nos logs do dispositivo (Logcat para Android, Console para iOS)."
68
- )
69
- else # :ruby_code_issue, :unknown_issue
70
- build_simple_diagnosis_report(
71
- title: "Erro no Código de Teste",
72
- message: "A falha foi causada por um erro de sintaxe ou lógica no próprio código de automação (ex: método não definido, variável nula). O problema não é no Appium ou no seletor, mas sim no script. Verifique o stack trace para encontrar o arquivo e a linha exatos."
73
- )
74
- end
40
+ def generate_html_report
41
+ if @data[:triage_result] == :locator_issue && @data[:failed_element].empty?
42
+ return build_simple_diagnosis_report(
43
+ title: "Falha na Análise do Seletor",
44
+ message: "A GEM identificou um erro de 'elemento não encontrado', mas não conseguiu extrair o seletor da mensagem de erro ou do código-fonte. Isso pode ocorrer com métodos de busca customizados ou seletores dinâmicos. Verifique o stack trace para encontrar a linha exata do erro e o método responsável."
45
+ )
46
+ end
47
+ html_content = case @data[:triage_result]
48
+ when :locator_issue
49
+ build_full_report
50
+ when :unidentified_locator_issue, :unidentified_timeout_issue
51
+ build_simple_diagnosis_report(
52
+ title: "Seletor Não Identificado",
53
+ message: "A falha ocorreu porque um elemento não foi encontrado, mas a GEM não conseguiu extrair o seletor exato da mensagem de erro ou do código-fonte. Isso geralmente acontece quando o seletor é construído dinamicamente ou está dentro de um método helper complexo. Verifique o stack trace para encontrar o método responsável (ex: 'tap_by_text')."
54
+ )
55
+ when :assertion_failure
56
+ build_simple_diagnosis_report(
57
+ title: "Falha de Asserção (Bug Funcional)",
58
+ message: "A automação executou os passos corretamente, mas o resultado final verificado na tela não foi o esperado. Isso geralmente indica um bug funcional na aplicação, e não um problema com o seletor."
59
+ )
60
+ when :visibility_issue
61
+ build_simple_diagnosis_report(
62
+ title: "Elemento Oculto ou Não-Interagível",
63
+ message: "O seletor encontrou o elemento no XML da página, mas ele não está visível ou habilitado para interação. Verifique se há outros elementos sobrepondo-o, se ele está desabilitado (disabled/enabled='false'), ou se é necessário aguardar uma animação."
64
+ )
65
+ when :stale_element_issue
66
+ build_simple_diagnosis_report(
67
+ title: "Referência de Elemento Antiga (Stale)",
68
+ message: "O elemento foi encontrado, mas a página foi atualizada antes que a interação pudesse ocorrer. Isso é um problema de timing. A solução é encontrar o elemento novamente logo antes de interagir com ele."
69
+ )
70
+ when :session_startup_issue
71
+ build_simple_diagnosis_report(
72
+ title: "Falha na Conexão com o Servidor Appium",
73
+ message: "Não foi possível criar uma sessão com o servidor. Verifique se o servidor Appium está rodando, se as 'capabilities' (incluindo prefixos 'appium:') e a URL de conexão estão corretas."
74
+ )
75
+ when :app_crash_issue
76
+ build_simple_diagnosis_report(
77
+ title: "Crash do Aplicativo",
78
+ message: "A sessão foi encerrada inesperadamente, o que indica que o aplicativo travou. A causa raiz deve ser investigada nos logs do dispositivo (Logcat para Android, Console para iOS)."
79
+ )
80
+ else # :ruby_code_issue, :unknown_issue
81
+ build_simple_diagnosis_report(
82
+ title: "Erro no Código de Teste",
83
+ message: "A falha foi causada por um erro de sintaxe ou lógica no próprio código de automação (ex: método não definido, variável nula). O problema não é no Appium ou no seletor, mas sim no script. Verifique o stack trace para encontrar o arquivo e a linha exatos."
84
+ )
85
+ end
75
86
 
76
87
  File.write("#{@output_folder}/report_#{@data[:timestamp]}.html", html_content)
77
88
  end
@@ -99,6 +110,32 @@ module AppiumFailureHelper
99
110
  code_search_html = "" # (Sua lógica code_search_html)
100
111
  failed_info_content = if failed_info && !failed_info.empty?; # ... (Sua lógica failed_info_content)
101
112
  else "<p class='text-sm text-gray-500'>O localizador exato não pôde ser extraído.</p>"; end
113
+ code_search_html = ""
114
+ unless code_search_results.empty?
115
+ suggestions_list = code_search_results.map do |match|
116
+ score_percent = (match[:score] * 100).round(1)
117
+ <<~SUGGESTION
118
+ <div class='border border-sky-200 bg-sky-50 p-3 rounded-lg mb-2'>
119
+ <p class='text-sm text-gray-600'>Encontrado em: <strong class='font-mono'>#{match[:file]}:#{match[:line_number]}</strong></p>
120
+ <pre class='bg-gray-800 text-white p-2 rounded mt-2 text-xs overflow-auto'><code>#{CGI.escapeHTML(match[:code])}</code></pre>
121
+ <p class='text-xs text-green-600 mt-1'>Similaridade: #{score_percent}%</p>
122
+ </div>
123
+ SUGGESTION
124
+ end.join
125
+ code_search_html = <<~HTML
126
+ <div class="bg-white p-4 rounded-lg shadow-md">
127
+ <h2 class="text-xl font-bold text-sky-700 mb-4">Sugestões Encontradas no Código</h2>
128
+ #{suggestions_list}
129
+ </div>
130
+ HTML
131
+ end
132
+
133
+ # --- LÓGICA RESTAURADA: ELEMENTO COM FALHA ---
134
+ failed_info_content = if failed_info && !failed_info.empty?
135
+ "<p class='text-sm text-gray-700 font-medium mb-2'>Tipo de Seletor: <span class='font-mono text-xs bg-red-100 p-1 rounded'>#{CGI.escapeHTML(failed_info[:selector_type].to_s)}</span></p><p class='text-sm text-gray-700 font-medium'>Valor Buscado: <span class='font-mono text-xs bg-red-100 p-1 rounded break-all'>#{CGI.escapeHTML(failed_info[:selector_value].to_s)}</span></p>"
136
+ else
137
+ "<p class='text-sm text-gray-500'>O localizador exato não pôde ser extraído.</p>"
138
+ end
102
139
 
103
140
  repair_strategies_content = if alternative_xpaths.empty?
104
141
  "<p class='text-gray-500'>Nenhuma estratégia de XPath alternativa pôde ser gerada para o elemento alvo.</p>"
@@ -209,9 +246,6 @@ module AppiumFailureHelper
209
246
  <h3 class="text-lg font-semibold text-indigo-700 mb-4">Estratégias de Localização Alternativas</h3>
210
247
  #{repair_strategies_content}
211
248
  </div>
212
- <div id="similar" class="tab-content">
213
- <div class="space-y-3 max-h-[700px] overflow-y-auto">#{similar_elements_content}</div>
214
- </div>
215
249
  <div id="all" class="tab-content">
216
250
  <h3 class="text-lg font-semibold text-gray-700 mb-4">Dump de Todos os Elementos da Tela</h3>
217
251
  <div class="max-h-[800px] overflow-y-auto space-y-2">#{all_elements_html.call(all_suggestions)}</div>
@@ -1,3 +1,3 @@
1
1
  module AppiumFailureHelper
2
- VERSION = "1.1.0"
2
+ VERSION = "1.1.1"
3
3
  end
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.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Nascimento