appium_failure_helper 0.4.2 → 0.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: c15543514bd288d39e5dd2d8d5d07b23aa9c21b78704b99c8e5eeca7400b08ed
4
- data.tar.gz: c41bfaac49d288ca60d99eb6708b6e4eb71074494f0ee615251ccf429920dca7
3
+ metadata.gz: c7344cd53f36a48ed0689b608758d04724a1f2dc122197171e5b875bb08615fb
4
+ data.tar.gz: acc66c6049fe82c85dc769591b7a81befda696cc5c4b02e8b8bd99ec60b643ac
5
5
  SHA512:
6
- metadata.gz: 16773e8d4f28fdf2e453e80e739641756821a0efd9bb2c9df7917e67791b469b56e066a132992d38d361e68c9a66e7030fbae74fa7b9ae37253e620890ec8ac3
7
- data.tar.gz: 73dff6f6a7045ba2749e6aa3157cf141b9c5f8bb315de50ff9820cee1dde3e8b83c485fcfd8e19bd6260c4f77bae629aea62903dea2120240cb0e95b050075c6
6
+ metadata.gz: 44d7f03b734112353b8c5c7f0db157f125d54e7d9065471e5caba380c44d851f6a8eb757d410ec0f6280b412fc6b442eeb3b303143e98c0599b7d5adcf3d7792
7
+ data.tar.gz: 48469708fd272ac232494b32bccbf1a99c4cc277a60cca1296eaf6d29128e55794a16987939a1f89fc3a9d8c2ec37ab9a56686a4edc80e4e4cd73de6948031cb
data/README.md CHANGED
@@ -4,35 +4,34 @@ Este módulo Ruby foi projetado para ser uma ferramenta de diagnóstico intelige
4
4
 
5
5
  ## Funcionalidades Principais
6
6
 
7
- * **Análise de Falha Automatizada:** Captura o estado da aplicação no momento da falha.
8
- * **Captura de Artefatos:** Salva um **screenshot** da tela e o **XML completo do `page_source`** em uma pasta dedicada por falha.
9
- * **Geração de Localizadores Inteligente:** Percorre a árvore de elementos e gera um **relatório YAML** com sugestões de XPaths otimizados para cada elemento.
7
+ * **Diagnóstico de Falha Automatizado:** No momento de uma falha, a ferramenta captura automaticamente o estado da aplicação e gera um conjunto de artefatos de depuração.
8
+
9
+ * **Captura de Artefatos:** Salva um **screenshot** da tela, o **XML completo do `page_source`**, e um relatório de análise em uma pasta com carimbo de data e hora para cada falha.
10
+
11
+ * **Geração de Localizadores Inteligente:** Percorre a árvore de elementos e gera um relatório YAML com sugestões de XPaths otimizados para cada elemento.
12
+
10
13
  * **Lógica de XPath Otimizada:** Utiliza as melhores práticas para cada plataforma (**Android e iOS**), priorizando os localizadores mais estáveis e combinando atributos para alta especificidade.
11
- * **Organização de Saída:** Cria uma pasta com um carimbo de data/hora para cada falha (`/failure_AAAA_MM_DD_HHMMSS`), mantendo os arquivos organizados.
12
- * **Contexto de Elementos:** O relatório YAML agora inclui o **XPath do elemento pai (`parent_locator`)**, fornecendo contexto crucial para a depuração e construção de Page Objects.
13
14
 
14
- ## Como Funciona
15
+ * **Tratamento de Dados:** Trunca valores de atributos muito longos para evitar quebras no relatório e garante que não haja elementos duplicados.
15
16
 
16
- A lógica do `AppiumFailureHelper` é ativada por um evento de falha em seu framework de testes (ex: Cucumber `After` hook). O método `handler_failure` executa as seguintes etapas:
17
+ * **Sistema de Logging:** Usa a biblioteca padrão `Logger` do Ruby para fornecer feedback detalhado e limpo no console.
17
18
 
18
- 1. Cria um diretório de saída exclusivo.
19
- 2. Captura o screenshot e o `page_source` do driver.
20
- 3. Determina a plataforma do dispositivo a partir das capacidades do driver.
21
- 4. Itera sobre cada nó do `page_source` e, para cada um, chama a lógica de geração de XPath e de nome.
22
- 5. A lógica de XPath utiliza um conjunto de estratégias priorizadas para cada plataforma, como **combinação de atributos** (`@resource-id` e `@text`) e o uso de `starts-with()` para elementos dinâmicos.
23
- 6. Salva um arquivo `.yaml` estruturado, contendo o nome sugerido, o tipo (`xpath`) e o localizador para cada elemento.
19
+ * **Múltiplos Relatórios:** Gera dois arquivos YAML: um **focado** no elemento que falhou e um **completo** com todos os elementos da tela.
24
20
 
25
- ## Uso
21
+ * **Relatório HTML Interativo:** Cria um relatório HTML visualmente agradável que une o screenshot, o XML e os localizadores sugeridos em uma única página para fácil acesso e análise.
26
22
 
27
- Para usar este helper, integre-o ao seu framework de testes. Um exemplo comum é utilizá-lo em um hook `After` do Cucumber, passando o objeto de driver do Appium.
23
+ ## Como Funciona
24
+
25
+ O `AppiumFailureHelper` deve ser integrado ao seu framework de testes. Um exemplo comum é utilizá-lo em um hook `After` do Cucumber, passando o objeto de driver do Appium e a exceção do cenário.
28
26
 
29
27
  **`features/support/hooks.rb`**
28
+
30
29
  ```ruby
31
30
  require 'appium_failure_helper'
32
31
 
33
32
  After do |scenario|
34
33
  if scenario.failed?
35
- AppiumFailureHelper::Capture.handler_failure(appium_driver)
34
+ AppiumFailureHelper::Capture.handler_failure(appium_driver, scenario.exception)
36
35
  end
37
36
  end
38
37
 
@@ -2,6 +2,7 @@ require 'nokogiri'
2
2
  require 'fileutils'
3
3
  require 'base64'
4
4
  require 'yaml'
5
+ require 'logger'
5
6
 
6
7
  module AppiumFailureHelper
7
8
  class Capture
@@ -32,59 +33,267 @@ module AppiumFailureHelper
32
33
  }.freeze
33
34
 
34
35
  MAX_VALUE_LENGTH = 100
36
+ @@logger = nil
35
37
 
36
- def self.handler_failure(driver)
38
+ def self.handler_failure(driver, exception)
37
39
  begin
40
+ self.setup_logger unless @@logger
41
+
42
+ # Remove a pasta reports_failure ao iniciar uma nova execução
43
+ FileUtils.rm_rf("reports_failure")
44
+ @@logger.info("Pasta 'reports_failure' removida para uma nova execução.")
45
+
38
46
  timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
39
- output_folder = "screenshots/failure_#{timestamp}"
47
+ output_folder = "reports_failure/failure_#{timestamp}"
40
48
 
41
49
  FileUtils.mkdir_p(output_folder)
50
+ @@logger.info("Pasta de saída criada: #{output_folder}")
42
51
 
52
+ # Captura o Base64 e salva o PNG
53
+ screenshot_base64 = driver.screenshot_as(:base64)
43
54
  screenshot_path = "#{output_folder}/screenshot_#{timestamp}.png"
44
55
  File.open(screenshot_path, 'wb') do |f|
45
- f.write(Base64.decode64(driver.screenshot_as(:base64)))
56
+ f.write(Base64.decode64(screenshot_base64))
46
57
  end
47
- puts "Screenshot saved to #{screenshot_path}"
58
+ @@logger.info("Screenshot salvo em #{screenshot_path}")
48
59
 
49
60
  page_source = driver.page_source
50
61
  xml_path = "#{output_folder}/page_source_#{timestamp}.xml"
51
62
  File.write(xml_path, page_source)
63
+ @@logger.info("Page source salvo em #{xml_path}")
52
64
 
53
65
  doc = Nokogiri::XML(page_source)
54
-
55
66
  platform = driver.capabilities['platformName']&.downcase || 'unknown'
56
67
 
57
- seen_elements = {}
58
- suggestions = []
68
+ failed_element_info = self.extract_info_from_exception(exception)
59
69
 
70
+ # --- Processamento de todos os elementos ---
71
+ seen_elements = {}
72
+ all_elements_suggestions = []
60
73
  doc.xpath('//*').each do |node|
61
74
  next if node.name == 'hierarchy'
62
75
  attrs = node.attributes.transform_values(&:value)
63
76
 
64
- unique_key = "#{node.name}|#{attrs['resource-id']}|#{attrs['content-desc']}|#{attrs['text']}"
77
+ unique_key = "#{node.name}|#{attrs['resource-id'].to_s}|#{attrs['content-desc'].to_s}|#{attrs['text'].to_s}"
65
78
 
66
79
  unless seen_elements[unique_key]
67
80
  name = self.suggest_name(node.name, attrs)
68
81
  locators = self.xpath_generator(node.name, attrs, platform)
69
82
 
70
- suggestions << { name: name, locators: locators }
83
+ all_elements_suggestions << { name: name, locators: locators }
71
84
  seen_elements[unique_key] = true
72
85
  end
73
86
  end
74
87
 
75
- yaml_path = "#{output_folder}/element_suggestions_#{timestamp}.yaml"
76
- File.open(yaml_path, 'w') do |f|
77
- f.write(YAML.dump(suggestions))
88
+ # --- Geração do Relatório FOCADO (1) ---
89
+ targeted_report = {
90
+ failed_element: failed_element_info,
91
+ similar_elements: [],
92
+ }
93
+
94
+ if failed_element_info && failed_element_info[:selector_value]
95
+ targeted_report[:similar_elements] = self.find_similar_elements(doc, failed_element_info, platform)
96
+ end
97
+
98
+ targeted_yaml_path = "#{output_folder}/failure_analysis_#{timestamp}.yaml"
99
+ File.open(targeted_yaml_path, 'w') do |f|
100
+ f.write(YAML.dump(targeted_report))
101
+ end
102
+ @@logger.info("Análise direcionada salva em #{targeted_yaml_path}")
103
+
104
+ # --- Geração do Relatório COMPLETO (2) ---
105
+ full_dump_yaml_path = "#{output_folder}/all_elements_dump_#{timestamp}.yaml"
106
+ File.open(full_dump_yaml_path, 'w') do |f|
107
+ f.write(YAML.dump(all_elements_suggestions))
78
108
  end
109
+ @@logger.info("Dump completo da página salvo em #{full_dump_yaml_path}")
110
+
111
+ # --- Geração do Relatório HTML (3) ---
112
+ html_report_path = "#{output_folder}/report_#{timestamp}.html"
113
+ html_content = self.generate_html_report(targeted_report, all_elements_suggestions, screenshot_base64, platform, timestamp)
114
+ File.write(html_report_path, html_content)
115
+ @@logger.info("Relatório HTML completo salvo em #{html_report_path}")
116
+
79
117
 
80
- puts "Element suggestions saved to #{yaml_path}"
81
118
  rescue => e
82
- puts "Error capturing failure details: #{e.message}\n#{e.backtrace.join("\n")}"
119
+ @@logger.error("Erro ao capturar detalhes da falha: #{e.message}\n#{e.backtrace.join("\n")}")
83
120
  end
84
121
  end
85
122
 
86
123
  private
87
124
 
125
+ def self.setup_logger
126
+ @@logger = Logger.new(STDOUT)
127
+ @@logger.level = Logger::INFO
128
+ @@logger.formatter = proc do |severity, datetime, progname, msg|
129
+ "#{datetime.strftime('%Y-%m-%d %H:%M:%S')} [#{severity}] #{msg}\n"
130
+ end
131
+ end
132
+
133
+ # --- LÓGICA DE GERAÇÃO DE HTML ---
134
+ def self.generate_html_report(targeted_report, all_suggestions, screenshot_base64, platform, timestamp)
135
+
136
+ # Helper para formatar localizadores
137
+ locators_html = lambda do |locators|
138
+ locators.map do |loc|
139
+ "<li class='flex justify-between items-center bg-gray-50 p-2 rounded-md mb-1 text-xs font-mono'><span class='font-bold text-indigo-600'>#{loc[:strategy].upcase.gsub('_', ' ')}:</span><span class='text-gray-700 ml-2 overflow-auto max-w-[70%]'>#{loc[:locator]}</span></li>"
140
+ end.join
141
+ end
142
+
143
+ # Helper para criar a lista de todos os elementos
144
+ all_elements_html = lambda do |elements|
145
+ elements.map do |el|
146
+ "<div class='border-b border-gray-200 py-3'><p class='font-semibold text-sm text-gray-800 mb-1'>#{el[:name]}</p><ul class='text-xs space-y-1'>#{locators_html.call(el[:locators])}</ul></div>"
147
+ end.join
148
+ end
149
+
150
+ # Template HTML usando um heredoc
151
+ <<~HTML_REPORT
152
+ <!DOCTYPE html>
153
+ <html lang="pt-BR">
154
+ <head>
155
+ <meta charset="UTF-8">
156
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
157
+ <title>Relatório de Falha Appium - #{timestamp}</title>
158
+ <script src="https://cdn.tailwindcss.com"></script>
159
+ <style>
160
+ body { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; }
161
+ .tab-content { display: none; }
162
+ .tab-content.active { display: block; }
163
+ .tab-button.active { background-color: #4f46e5; color: white; }
164
+ .tab-button:not(.active):hover { background-color: #e0e7ff; }
165
+ </style>
166
+ </head>
167
+ <body class="bg-gray-50 p-8">
168
+ <div class="max-w-7xl mx-auto">
169
+ <header class="mb-8 pb-4 border-b border-gray-300">
170
+ <h1 class="text-3xl font-bold text-gray-800">Diagnóstico de Falha Automatizada</h1>
171
+ <p class="text-sm text-gray-500">Relatório gerado em: #{timestamp} | Plataforma: #{platform.upcase}</p>
172
+ </header>
173
+
174
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
175
+ <div class="lg:col-span-1">
176
+ <div class="bg-white p-4 rounded-lg shadow-xl mb-6 border border-red-200">
177
+ <h2 class="text-xl font-bold text-red-600 mb-4">Elemento com Falha</h2>
178
+ <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">#{targeted_report[:failed_element][:selector_type] || 'Desconhecido'}</span></p>
179
+ <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">#{targeted_report[:failed_element][:selector_value] || 'N/A'}</span></p>
180
+ </div>
181
+
182
+ <div class="bg-white p-4 rounded-lg shadow-xl">
183
+ <h2 class="text-xl font-bold text-gray-800 mb-4">Screenshot da Falha</h2>
184
+ <img src="data:image/png;base64,#{screenshot_base64}" alt="Screenshot da Falha" class="w-full rounded-md shadow-lg border border-gray-200">
185
+ </div>
186
+ </div>
187
+
188
+ <div class="lg:col-span-2">
189
+ <div class="bg-white rounded-lg shadow-xl">
190
+ <div class="flex border-b border-gray-200">
191
+ <button class="tab-button active px-4 py-3 text-sm font-medium rounded-tl-lg" data-tab="similar">Sugestões de Reparo (#{targeted_report[:similar_elements].size})</button>
192
+ <button class="tab-button px-4 py-3 text-sm font-medium text-gray-600" data-tab="all">Dump Completo da Página (#{all_suggestions.size} Elementos)</button>
193
+ </div>
194
+
195
+ <div class="p-6">
196
+ <div id="similar" class="tab-content active">
197
+ <h3 class="text-lg font-semibold text-indigo-700 mb-4">Elementos Semelhantes (Melhores Alternativas)</h3>
198
+ #{"<p class='text-gray-500'>Nenhuma alternativa semelhante foi encontrada na página. O elemento pode ter sido removido ou o localizador está incorreto.</p>" if targeted_report[:similar_elements].empty?}
199
+ <div class="space-y-4">
200
+ #{targeted_report[:similar_elements].map { |el| "<div class='border border-indigo-100 p-3 rounded-lg bg-indigo-50'><p class='font-bold text-indigo-800 mb-2'>#{el[:name]}</p><ul>#{locators_html.call(el[:locators])}</ul></div>" }.join}
201
+ </div>
202
+ </div>
203
+
204
+ <div id="all" class="tab-content">
205
+ <h3 class="text-lg font-semibold text-indigo-700 mb-4">Dump Completo de Todos os Elementos da Tela</h3>
206
+ <div class="max-h-[600px] overflow-y-auto space-y-2">
207
+ #{all_elements_html.call(all_suggestions)}
208
+ </div>
209
+ </div>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ </div>
215
+
216
+ <script>
217
+ document.addEventListener('DOMContentLoaded', () => {
218
+ const tabs = document.querySelectorAll('.tab-button');
219
+ const contents = document.querySelectorAll('.tab-content');
220
+
221
+ tabs.forEach(tab => {
222
+ tab.addEventListener('click', () => {
223
+ const target = tab.getAttribute('data-tab');
224
+
225
+ tabs.forEach(t => t.classList.remove('active', 'text-white', 'text-gray-600', 'hover:bg-indigo-700'));
226
+ contents.forEach(c => c.classList.remove('active'));
227
+
228
+ tab.classList.add('active', 'text-white', 'bg-indigo-600');
229
+ document.getElementById(target).classList.add('active');
230
+ });
231
+ });
232
+ // Set initial active state for styling consistency
233
+ const activeTab = document.querySelector('.tab-button[data-tab="similar"]');
234
+ activeTab.classList.add('active', 'text-white', 'bg-indigo-600');
235
+ });
236
+ </script>
237
+ </body>
238
+ </html>
239
+ HTML_REPORT
240
+ end
241
+
242
+ # --- Métodos de Suporte Existentes ---
243
+
244
+ # ... (métodos setup_logger, extract_info_from_exception, find_similar_elements, etc.)
245
+
246
+ def self.extract_info_from_exception(exception)
247
+ message = exception.message
248
+ info = {}
249
+
250
+ patterns = [
251
+ /(?:could not be found|cannot find element) using (.+)=['"](.+)['"]/i,
252
+ /no such element: Unable to locate element: {"method":"([^"]+)","selector":"([^"]+)"}/i
253
+ ]
254
+
255
+ patterns.each do |pattern|
256
+ match = message.match(pattern)
257
+ if match
258
+ selector_type = match[1].strip
259
+ selector_value = match[2].strip
260
+
261
+ info[:selector_type] = selector_type
262
+ info[:selector_value] = selector_value.gsub(/['"]/, '')
263
+ return info
264
+ end
265
+ end
266
+ info
267
+ end
268
+
269
+ def self.find_similar_elements(doc, failed_info, platform)
270
+ similar_elements = []
271
+ doc.xpath('//*').each do |node|
272
+ next if node.name == 'hierarchy'
273
+ attrs = node.attributes.transform_values(&:value)
274
+
275
+ is_similar = case platform
276
+ when 'android'
277
+ (attrs['resource-id']&.include?(failed_info[:selector_value]) ||
278
+ attrs['text']&.include?(failed_info[:selector_value]) ||
279
+ attrs['content-desc']&.include?(failed_info[:selector_value]))
280
+ when 'ios'
281
+ (attrs['accessibility-id']&.include?(failed_info[:selector_value]) ||
282
+ attrs['label']&.include?(failed_info[:selector_value]) ||
283
+ attrs['name']&.include?(failed_info[:selector_value]))
284
+ else
285
+ false
286
+ end
287
+
288
+ if is_similar
289
+ name = self.suggest_name(node.name, attrs)
290
+ locators = self.xpath_generator(node.name, attrs, platform)
291
+ similar_elements << { name: name, locators: locators }
292
+ end
293
+ end
294
+ similar_elements
295
+ end
296
+
88
297
  def self.truncate(value)
89
298
  return value unless value.is_a?(String)
90
299
  value.size > MAX_VALUE_LENGTH ? "#{value[0...MAX_VALUE_LENGTH]}..." : value
@@ -93,9 +302,22 @@ module AppiumFailureHelper
93
302
  def self.suggest_name(tag, attrs)
94
303
  type = tag.split('.').last
95
304
  pfx = PREFIX[tag] || PREFIX[type] || 'elm'
96
- name = attrs['content-desc'] || attrs['text'] || attrs['resource-id'] || attrs['label'] || attrs['name'] || 'unknown' || type
97
- name = truncate(name.strip.gsub(/[^0-9a-z]/, '').split.map(&:capitalize).join)
98
- "#{pfx}#{name}"
305
+ name_base = nil
306
+
307
+ ['content-desc', 'text', 'resource-id', 'label', 'name'].each do |attr_key|
308
+ value = attrs[attr_key]
309
+ if value.is_a?(String) && !value.empty?
310
+ name_base = value
311
+ break
312
+ end
313
+ end
314
+
315
+ name_base ||= type
316
+
317
+ truncated_name = truncate(name_base)
318
+ sanitized_name = truncated_name.gsub(/[^a-zA-Z0-9\s]/, ' ').split.map(&:capitalize).join
319
+
320
+ "#{pfx}#{sanitized_name}"
99
321
  end
100
322
 
101
323
  def self.xpath_generator(tag, attrs, platform)
@@ -112,25 +334,21 @@ module AppiumFailureHelper
112
334
  def self.generate_android_xpaths(tag, attrs)
113
335
  locators = []
114
336
 
115
- # Estratégia 1: Combinação de atributos
116
337
  if attrs['resource-id'] && !attrs['resource-id'].empty? && attrs['text'] && !attrs['text'].empty?
117
- locators << { strategy: 'resource_id_and_text', locator: "//#{tag}[@resource-id\"#{attrs['resource-id']}\" and @text=\"#{self.truncate(attrs['text'])}\"]" }
338
+ locators << { strategy: 'resource_id_and_text', locator: "//#{tag}[@resource-id=\"#{attrs['resource-id']}\" and @text=\"#{self.truncate(attrs['text'])}\"]" }
118
339
  elsif attrs['resource-id'] && !attrs['resource-id'].empty? && attrs['content-desc'] && !attrs['content-desc'].empty?
119
340
  locators << { strategy: 'resource_id_and_content_desc', locator: "//#{tag}[@resource-id=\"#{attrs['resource-id']}\" and @content-desc=\"#{self.truncate(attrs['content-desc'])}\"]" }
120
341
  end
121
342
 
122
- # Estratégia 2: ID único
123
343
  if attrs['resource-id'] && !attrs['resource-id'].empty?
124
344
  locators << { strategy: 'resource_id', locator: "//#{tag}[@resource-id=\"#{attrs['resource-id']}\"]" }
125
345
  end
126
346
 
127
- # Estratégia 3: starts-with para IDs dinâmicos
128
347
  if attrs['resource-id'] && attrs['resource-id'].include?(':id/')
129
348
  id_part = attrs['resource-id'].split(':id/').last
130
349
  locators << { strategy: 'starts_with_resource_id', locator: "//#{tag}[starts-with(@resource-id, \"#{id_part}\")]" }
131
350
  end
132
351
 
133
- # Estratégia 4: Texto e content-desc como identificadores
134
352
  if attrs['text'] && !attrs['text'].empty?
135
353
  locators << { strategy: 'text', locator: "//#{tag}[@text=\"#{self.truncate(attrs['text'])}\"]" }
136
354
  end
@@ -138,7 +356,6 @@ module AppiumFailureHelper
138
356
  locators << { strategy: 'content_desc', locator: "//#{tag}[@content-desc=\"#{self.truncate(attrs['content-desc'])}\"]" }
139
357
  end
140
358
 
141
- # Fallback genérico (sempre adicionado)
142
359
  locators << { strategy: 'generic_tag', locator: "//#{tag}" }
143
360
 
144
361
  locators
@@ -147,17 +364,14 @@ module AppiumFailureHelper
147
364
  def self.generate_ios_xpaths(tag, attrs)
148
365
  locators = []
149
366
 
150
- # Estratégia 1: Combinação de atributos
151
367
  if attrs['accessibility-id'] && !attrs['accessibility-id'].empty? && attrs['label'] && !attrs['label'].empty?
152
368
  locators << { strategy: 'accessibility_id_and_label', locator: "//#{tag}[@accessibility-id=\"#{attrs['accessibility-id']}\" and @label=\"#{self.truncate(attrs['label'])}\"]" }
153
369
  end
154
370
 
155
- # Estratégia 2: ID único
156
371
  if attrs['accessibility-id'] && !attrs['accessibility-id'].empty?
157
372
  locators << { strategy: 'accessibility_id', locator: "//#{tag}[@accessibility-id=\"#{attrs['accessibility-id']}\"]" }
158
373
  end
159
374
 
160
- # Estratégia 3: label, name ou value
161
375
  if attrs['label'] && !attrs['label'].empty?
162
376
  locators << { strategy: 'label', locator: "//#{tag}[@label=\"#{self.truncate(attrs['label'])}\"]" }
163
377
  end
@@ -165,7 +379,6 @@ module AppiumFailureHelper
165
379
  locators << { strategy: 'name', locator: "//#{tag}[@name=\"#{self.truncate(attrs['name'])}\"]" }
166
380
  end
167
381
 
168
- # Fallback genérico (sempre adicionado)
169
382
  locators << { strategy: 'generic_tag', locator: "//#{tag}" }
170
383
 
171
384
  locators
@@ -183,10 +396,17 @@ module AppiumFailureHelper
183
396
  locators << { strategy: 'text', locator: "//#{tag}[@text=\"#{self.truncate(attrs['text'])}\"]" }
184
397
  end
185
398
 
186
- # Fallback genérico (sempre adicionado)
187
399
  locators << { strategy: 'generic_tag', locator: "//#{tag}" }
188
400
 
189
401
  locators
190
402
  end
403
+
404
+ def self.setup_logger
405
+ @@logger = Logger.new(STDOUT)
406
+ @@logger.level = Logger::INFO
407
+ @@logger.formatter = proc do |severity, datetime, progname, msg|
408
+ "#{datetime.strftime('%Y-%m-%d %H:%M:%S')} [#{severity}] #{msg}\n"
409
+ end
410
+ end
191
411
  end
192
- end
412
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AppiumFailureHelper
4
- VERSION = "0.4.2"
4
+ VERSION = "0.5.0"
5
5
  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: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Nascimento