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 +4 -4
- data/appium_failure_helper-1.4.0.gem +0 -0
- data/lib/appium_failure_helper/element_repository.rb +13 -34
- data/lib/appium_failure_helper/handler.rb +121 -31
- data/lib/appium_failure_helper/version.rb +1 -1
- data/release_gem.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 43096445c0c64d280ed41cc96d50ddd088c660bfac0441b8ff4e9ddc34e9107d
|
4
|
+
data.tar.gz: a2b4d0c6ebe03d445ae1e39d5dd35da188b01b4835b1a9a16055709b201f0864
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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(
|
42
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
53
|
+
Utils.logger.info("Número de elementos carregados: #{ELEMENTS.size}")
|
54
|
+
ELEMENTS
|
60
55
|
end
|
61
56
|
|
62
|
-
def self.
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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']
|
31
|
-
screenshot_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
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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
|
data/release_gem.rb
CHANGED
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
|
+
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
|