appium_failure_helper 1.1.0 → 1.1.2

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: 7f9f15e7fe28816df34926c685e9f4f43e27f63c8d3830ea12bd6f52c53b2fcb
4
+ data.tar.gz: a60300e97555f6883711177488f6547dc126ccbf84e5d43f9ca44476db64c961
5
5
  SHA512:
6
- metadata.gz: 0274b45b6d5a796525574666e82c6bf60b8c99eb9ac269c0ae31f11c08c411f798b893cc33f4ff86d9fa493db951bd9947d7d19ef62691b29226952f9b78abde
7
- data.tar.gz: 6fa2f563edab7b2e5b6da5de909b8e39c89438773d40e32a767191ef6423009e71797899a0393d2db0b51f5688c165d221ddd2df6c458d611913a15cdf40c241
6
+ metadata.gz: 51cabd40ef2755c67d3258f7ee0994147d3a586a531fa05570b4f1f5d4338edfc95c880ff4cdb8b44c5639a7a4a545c5e1a003b9d6f50f61371c85ad0529f647
7
+ data.tar.gz: 3ddd32df0a5db118f603715249963fffa93e0616a6a27a8718de6da8751d5654abb1f0595b09cc2ae1c5c4c6116fb2b1bea3cc12200e264cb5fbf966b73e2be8
@@ -1,21 +1,19 @@
1
- # lib/appium_failure_helper/analyzer.rb
2
1
  module AppiumFailureHelper
3
2
  module Analyzer
4
-
5
- def self.triage_error(exception)
3
+ def self.triage_error(exception)
6
4
  case exception
7
5
  when Selenium::WebDriver::Error::NoSuchElementError, Selenium::WebDriver::Error::TimeoutError
8
- :locator_issue # O elemento não foi encontrado a tempo.
6
+ :locator_issue
9
7
  when Selenium::WebDriver::Error::ElementNotInteractableError
10
- :visibility_issue # Encontrado, mas não clicável/visível.
8
+ :visibility_issue
11
9
  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.
10
+ :stale_element_issue
11
+ when defined?(RSpec::Expectations::ExpectationNotMetError) ? RSpec::Expectations::ExpectationNotMetError : Class.new
12
+ :assertion_failure
15
13
  when NoMethodError, NameError, ArgumentError, TypeError
16
- :ruby_code_issue # Erro de sintaxe ou lógica no código de teste.
14
+ :ruby_code_issue
17
15
  when Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
18
- :session_startup_issue # Problema na conexão/inicialização com o Appium.
16
+ :session_startup_issue
19
17
  when Selenium::WebDriver::Error::WebDriverError
20
18
  return :app_crash_issue if exception.message.include?('session deleted because of page crash')
21
19
  :unknown_appium_issue
@@ -34,36 +32,31 @@ module AppiumFailureHelper
34
32
  /(?:with the resource-id|with the accessibility-id) ['"]?(.+?)['"]?/i
35
33
  ]
36
34
  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
35
+ match = message.match(pattern)
36
+ if match
37
+ info[:selector_value] = match.captures.last.strip.gsub(/['"]/, '')
38
+ info[:selector_type] = match.captures.size > 1 ? match.captures[0].strip.gsub(/['"]/, '') : 'id'
39
+ return info
40
+ end
43
41
  end
44
42
  info
45
43
  end
46
-
44
+
47
45
  def self.find_de_para_match(failed_info, element_map)
48
46
  failed_value = failed_info[:selector_value].to_s
49
47
  return nil if failed_value.empty?
50
-
51
48
  logical_name_key = failed_value.gsub(/^#/, '')
52
-
53
49
  if element_map.key?(logical_name_key)
54
50
  return { logical_name: logical_name_key, correct_locator: element_map[logical_name_key] }
55
51
  end
56
-
57
52
  cleaned_failed_locator = failed_value.gsub(/[:\-\/@=\[\]'"()]/, ' ').gsub(/\s+/, ' ').downcase.strip
58
-
59
53
  element_map.each do |name, locator_info|
60
- mapped_locator = locator_info['valor'].to_s
54
+ mapped_locator = locator_info['valor'].to_s || locator_info['value'].to_s
61
55
  cleaned_mapped_locator = mapped_locator.gsub(/[:\-\/@=\[\]'"()]/, ' ').gsub(/\s+/, ' ').downcase.strip
62
56
  distance = DidYouMean::Levenshtein.distance(cleaned_failed_locator, cleaned_mapped_locator)
63
57
  max_len = [cleaned_failed_locator.length, cleaned_mapped_locator.length].max
64
58
  next if max_len.zero?
65
59
  similarity_score = 1.0 - (distance.to_f / max_len)
66
-
67
60
  if similarity_score > 0.85
68
61
  return { logical_name: name, correct_locator: locator_info }
69
62
  end
@@ -75,20 +68,16 @@ module AppiumFailureHelper
75
68
  failed_locator_value = failed_info[:selector_value]
76
69
  failed_locator_type = failed_info[:selector_type]
77
70
  return [] unless failed_locator_value && failed_locator_type
78
-
79
71
  normalized_failed_type = failed_locator_type.to_s.downcase.include?('id') ? 'id' : failed_locator_type.to_s
80
72
  cleaned_failed_locator = failed_locator_value.to_s.gsub(/[:\-\/@=\[\]'"()]/, ' ').gsub(/\s+/, ' ').downcase.strip
81
73
  similarities = []
82
-
83
74
  all_page_suggestions.each do |suggestion|
84
75
  candidate_locator = suggestion[:locators].find { |loc| loc[:strategy] == normalized_failed_type }
85
76
  next unless candidate_locator
86
-
87
77
  cleaned_candidate_locator = candidate_locator[:locator].gsub(/[:\-\/@=\[\]'"()]/, ' ').gsub(/\s+/, ' ').downcase.strip
88
78
  distance = DidYouMean::Levenshtein.distance(cleaned_failed_locator, cleaned_candidate_locator)
89
79
  max_len = [cleaned_failed_locator.length, cleaned_candidate_locator.length].max
90
80
  next if max_len.zero?
91
-
92
81
  similarity_score = 1.0 - (distance.to_f / max_len)
93
82
  if similarity_score > 0.85
94
83
  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}")
@@ -1,4 +1,3 @@
1
- # lib/appium_failure_helper/handler.rb
2
1
  module AppiumFailureHelper
3
2
  class Handler
4
3
  def self.call(driver, exception)
@@ -8,7 +7,7 @@ module AppiumFailureHelper
8
7
  def initialize(driver, exception)
9
8
  @driver = driver
10
9
  @exception = exception
11
- @timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
10
+ @timestamp = Time.now.strftime('%Ym%d_%H%M%S')
12
11
  @output_folder = "reports_failure/failure_#{@timestamp}"
13
12
  end
14
13
 
@@ -31,10 +30,12 @@ module AppiumFailureHelper
31
30
  screenshot_base64: @driver.screenshot_as(:base64)
32
31
  }
33
32
 
33
+ # A análise profunda agora é executada para QUALQUER :locator_issue.
34
34
  if triage_result == :locator_issue
35
35
  page_source = @driver.page_source
36
36
  doc = Nokogiri::XML(page_source)
37
37
 
38
+ # Tenta Plano A e Plano B sem reclassificar o erro.
38
39
  failed_info = Analyzer.extract_failure_details(@exception) || {}
39
40
  if failed_info.empty?
40
41
  failed_info = SourceCodeAnalyzer.extract_from_exception(@exception) || {}
@@ -57,6 +58,7 @@ module AppiumFailureHelper
57
58
  de_para_result = Analyzer.find_de_para_match(failed_info, unified_element_map)
58
59
  code_search_results = CodeSearcher.find_similar_locators(failed_info) || []
59
60
 
61
+ # Adiciona os dados (mesmo que vazios) ao pacote.
60
62
  report_data.merge!({
61
63
  page_source: page_source,
62
64
  failed_element: failed_info,
@@ -68,7 +70,6 @@ module AppiumFailureHelper
68
70
  })
69
71
  end
70
72
 
71
- # A chamada correta, passando apenas 2 argumentos
72
73
  ReportGenerator.new(@output_folder, report_data).generate_all
73
74
  Utils.logger.info("Relatórios gerados com sucesso em: #{@output_folder}")
74
75
 
@@ -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
+ html_content = 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
+ else
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 Inesperado",
83
+ message: "Ocorreu um erro não catalogado. Verifique o stack trace para mais detalhes."
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.2"
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.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Nascimento