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 +4 -4
- data/lib/appium_failure_helper/analyzer.rb +35 -38
- data/lib/appium_failure_helper/element_repository.rb +7 -1
- data/lib/appium_failure_helper/handler.rb +28 -24
- data/lib/appium_failure_helper/report_generator.rb +72 -38
- data/lib/appium_failure_helper/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 206d70f79fcfb3cf01291da330e02da83218080d511f02ec899c9778e12ccec0
|
4
|
+
data.tar.gz: 2dbc0c9b407e44f786353b6914faf8c9baa0c2014fe3f84fd1e3532f45837dad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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>
|