appium_failure_helper 0.5.0 → 0.6.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: c7344cd53f36a48ed0689b608758d04724a1f2dc122197171e5b875bb08615fb
4
- data.tar.gz: acc66c6049fe82c85dc769591b7a81befda696cc5c4b02e8b8bd99ec60b643ac
3
+ metadata.gz: cb96de86c564a4b997d4d1a4320e831e3d4f28a3f8d7e97623b347d855588303
4
+ data.tar.gz: 2462b34e6f3c01ea63a8a68d9c1814fd4138dc2c0dbcdbef0aecb8d6ba82fece
5
5
  SHA512:
6
- metadata.gz: 44d7f03b734112353b8c5c7f0db157f125d54e7d9065471e5caba380c44d851f6a8eb757d410ec0f6280b412fc6b442eeb3b303143e98c0599b7d5adcf3d7792
7
- data.tar.gz: 48469708fd272ac232494b32bccbf1a99c4cc277a60cca1296eaf6d29128e55794a16987939a1f89fc3a9d8c2ec37ab9a56686a4edc80e4e4cd73de6948031cb
6
+ metadata.gz: ac755fa2ec04c3038c27c9ded178be9b6499de3c7a9cc70c96a5dfebd2fd07d6304eb236720cc4e4dfb1f31807b731c50512973af875596925eef72a9290f1b0
7
+ data.tar.gz: 410501dec6ffa017effc75796cafb04b83a371bcd6f4b5b466b60f70339193b64f743112870e88cade771ed0ef70f4b0626fd4a5fc845173225d7cb4952052a5
data/README.md CHANGED
@@ -1,32 +1,130 @@
1
1
  # Appium Failure Helper
2
2
 
3
- Este módulo Ruby foi projetado para ser uma ferramenta de diagnóstico inteligente para falhas em testes de automação mobile com **Appium**. Ele automatiza a captura de artefatos de depuração e a geração de sugestões de localizadores de elementos, eliminando a necessidade de usar o Appium Inspector.
3
+ [![Ruby](https://img.shields.io/badge/language-ruby-red.svg)](https://www.ruby-lang.org/)
4
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ [![Status](https://img.shields.io/badge/status-beta-yellow.svg)]()
4
6
 
5
- ## Funcionalidades Principais
7
+ **Appium Failure Helper** é um módulo Ruby destinado a automatizar diagnóstico de falhas em testes de automação mobile com **Appium**. O objetivo é reduzir tempo de triagem, fornecer localizadores confiáveis e coletar artefatos de depuração sem depender do Appium Inspector.
6
8
 
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.
9
+ ---
8
10
 
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.
11
+ ## Sumário
12
+ - [Visão Geral](#visão-geral)
13
+ - [Funcionalidades](#funcionalidades)
14
+ - [Arquitetura e Fluxo](#arquitetura-e-fluxo)
15
+ - [Instalação](#instalação)
16
+ - [Configuração (opcional)](#configuração-opcional)
17
+ - [API Pública / Integração](#api-pública--integração)
18
+ - [Exemplos de Uso](#exemplos-de-uso)
19
+ - [Cucumber (hook After)](#cucumber-hook-after)
20
+ - [RSpec (after :each)](#rspec-after-each)
21
+ - [Formato dos Artefatos Gerados](#formato-dos-artefatos-gerados)
22
+ - [Lógica de Geração de XPaths (detalhada)](#lógica-de-geração-de-xpaths-detalhada)
23
+ - [Tratamento de Dados e Deduplicação](#tratamento-de-dados-e-deduplicação)
24
+ - [Relatório HTML Interativo](#relatório-html-interativo)
25
+ - [Logging e Observabilidade](#logging-e-observabilidade)
26
+ - [Testes e Qualidade](#testes-e-qualidade)
27
+ - [Roadmap e Contribuição](#roadmap-e-contribuição)
28
+ - [Licença](#licença)
10
29
 
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.
30
+ ---
12
31
 
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.
32
+ ## Visão Geral
14
33
 
15
- * **Tratamento de Dados:** Trunca valores de atributos muito longos para evitar quebras no relatório e garante que não haja elementos duplicados.
34
+ No momento em que um teste falha, o módulo realiza, de forma atômica e thread-safe:
35
+ 1. captura de screenshot,
36
+ 2. extração do `page_source` completo (XML),
37
+ 3. varredura da árvore de elementos para gerar localizadores sugeridos,
38
+ 4. escrita de dois YAMLs (focado e completo) e um relatório HTML que agrega tudo.
16
39
 
17
- * **Sistema de Logging:** Usa a biblioteca padrão `Logger` do Ruby para fornecer feedback detalhado e limpo no console.
40
+ Todos os artefatos são salvos em uma pasta timestamped (formato `YYYY_MM_DD_HHMMSS`) dentro de `reports_failure/`.
18
41
 
19
- * **Múltiplos Relatórios:** Gera dois arquivos YAML: um **focado** no elemento que falhou e um **completo** com todos os elementos da tela.
42
+ ---
20
43
 
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.
44
+ ## Funcionalidades
22
45
 
23
- ## Como Funciona
46
+ - Captura automática de screenshot PNG.
47
+ - Export completo de `page_source` em XML.
48
+ - Geração de `failure_analysis_*.yaml` (focado no elemento que falhou).
49
+ - Geração de `all_elements_dump_*.yaml` (todos os elementos com localizadores sugeridos).
50
+ - Relatório HTML interativo que combine screenshot, XML formatado e lista de localizadores.
51
+ - Geração de XPaths otimizados para **Android** e **iOS**.
52
+ - Truncamento de atributos longos (configurável).
53
+ - Eliminação de elementos duplicados e normalização de atributos.
54
+ - Logging via `Logger` do Ruby (Níveis: DEBUG/INFO/WARN/ERROR).
55
+ - Configuração via bloco `configure` (opcional).
24
56
 
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.
57
+ ---
26
58
 
27
- **`features/support/hooks.rb`**
59
+ ## Arquitetura e Fluxo
60
+
61
+ 1. **Hook de Testes** (Cucumber/RSpec) → invoca `Capture.handler_failure(driver, exception)`
62
+ 2. **Capture.handler_failure**:
63
+ - estabelece pasta de saída com timestamp;
64
+ - chama `driver.screenshot` (salva PNG);
65
+ - chama `driver.page_source` (salva XML);
66
+ - percorre XML e cria árvore de elementos;
67
+ - para cada elemento gera candidate XPaths aplicando regras por plataforma;
68
+ - grava `failure_analysis_*.yaml` (prioriza elemento indicado) e `all_elements_dump_*.yaml`;
69
+ - monta `report_*.html` agregando tudo.
70
+ 3. Logs detalhados emitidos durante a execução.
71
+
72
+ ---
73
+
74
+ ## Instalação
75
+
76
+ **Como gem (exemplo):**
77
+
78
+ Adicione ao `Gemfile` do projeto:
79
+
80
+ ```ruby
81
+ gem 'appium_failure_helper', '~> 0.1.0'
82
+ ```
83
+
84
+ Depois:
85
+
86
+ ```bash
87
+ bundle install
88
+ ```
89
+
90
+ **Ou manual (para uso local):**
91
+
92
+ Coloque o diretório `appium_failure_helper/` dentro do `lib/` do projeto e faça:
93
+
94
+ ```ruby
95
+ require_relative 'lib/appium_failure_helper'
96
+ ```
97
+
98
+ ---
99
+
100
+ ## API Pública / Integração
101
+
102
+ ### `AppiumFailureHelper::Capture`
28
103
 
29
104
  ```ruby
105
+ # handler_failure(driver, exception, options = {})
106
+ # - driver: objeto de sessão Appium (Selenium::WebDriver / Appium::Driver)
107
+ # - exception: exceção capturada no momento da falha
108
+ # - options: hash com overrides (ex: output_dir:)
109
+ AppiumFailureHelper::Capture.handler_failure(appium_driver, scenario.exception)
110
+ ```
111
+
112
+ ### Configuração global
113
+
114
+ ```ruby
115
+ AppiumFailureHelper.configure do |c|
116
+ # ver bloco de configuração acima
117
+ end
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Exemplos de Uso
123
+
124
+ ### Cucumber (hook `After`)
125
+
126
+ ```ruby
127
+ # features/support/hooks.rb
30
128
  require 'appium_failure_helper'
31
129
 
32
130
  After do |scenario|
@@ -34,19 +132,159 @@ After do |scenario|
34
132
  AppiumFailureHelper::Capture.handler_failure(appium_driver, scenario.exception)
35
133
  end
36
134
  end
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Formato dos Artefatos Gerados
140
+
141
+ **Pasta:** `reports_failure/<TIMESTAMP>/`
37
142
 
143
+ Arquivos gerados (ex.: TIMESTAMP = `2025_09_23_173045`):
144
+
145
+ ```
146
+ screenshot_2025_09_23_173045.png
147
+ page_source_2025_09_23_173045.xml
148
+ failure_analysis_2025_09_23_173045.yaml
149
+ all_elements_dump_2025_09_23_173045.yaml
150
+ report_2025_09_23_173045.html
151
+ ```
152
+
153
+ ### Exemplo (simplificado) de `failure_analysis_*.yaml`
154
+
155
+ ```yaml
156
+ failed_element:
157
+ platform: android
158
+ summary:
159
+ class: android.widget.Button
160
+ resource_id: com.example:id/submit
161
+ text: "Enviar"
162
+ suggested_xpaths:
163
+ - "//android.widget.Button[@resource-id='com.example:id/submit']"
164
+ - "//android.widget.Button[contains(@text,'Enviar')]"
165
+ capture_metadata:
166
+ screenshot: screenshot_2025_09_23_173045.png
167
+ page_source: page_source_2025_09_23_173045.xml
168
+ timestamp: "2025-09-23T17:30:45Z"
169
+ tips: "Priorize resource-id; se ausente, use accessibility id (content-desc) e class+text como fallback."
170
+ ```
171
+
172
+ ### Exemplo (simplificado) de `all_elements_dump_*.yaml`
173
+
174
+ ```yaml
175
+ elements:
176
+ - id_hash: "a1b2c3..."
177
+ class: "android.widget.EditText"
178
+ resource_id: "com.example:id/input_email"
179
+ text: "example@example.com"
180
+ truncated_attributes:
181
+ hint: "Digite seu e-mail..."
182
+ suggested_xpaths:
183
+ - "//*[@resource-id='com.example:id/input_email']"
184
+ - "//android.widget.EditText[contains(@hint,'Digite seu e-mail')]"
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Lógica de Geração de XPaths (detalhada)
190
+
191
+ **Princípios gerais**
192
+ 1. Priorizar identificadores estáveis (resource-id no Android / accessibility id no iOS).
193
+ 2. Evitar XPaths com `index` como primeira opção (usado apenas como último recurso).
194
+ 3. Combinar atributos quando necessário para aumentar a especificidade e evitar colisões.
195
+ 4. Normalizar espaços e truncar textos longos.
196
+
197
+ **Estratégias por plataforma (ordem de preferência)**
198
+
199
+ - **Android**
200
+ 1. `resource-id` → `//*[@resource-id='com.pkg:id/id']`
201
+ 2. `content-desc` / `contentDescription` (accessibility) → `//*[@content-desc='x']`
202
+ 3. `class` + `text` → `//android.widget.TextView[@class='...' and contains(normalize-space(@text),'...')]`
203
+ 4. `class` + raça de atributos (combinações: enabled, clickable, package)
204
+ 5. fallback: `//android.widget.Button[position()=n]` (último recurso)
205
+
206
+ - **iOS**
207
+ 1. `accessibility id` (nome accessibility) → `//*[@name='Submit']`
208
+ 2. `label` / `value` → `//*[contains(@label,'...')]`
209
+ 3. `type` + `label` → `//XCUIElementTypeButton[@label='OK']`
210
+ 4. fallback: hierarquia / indices
211
+
212
+ **Exemplo de XPath combinado (alta especificidade):**
213
+
214
+ ```xpath
215
+ //android.widget.Button[@resource-id='com.example:id/submit' and contains(normalize-space(@text),'Enviar') and @clickable='true']
38
216
  ```
39
217
 
40
- **Observação:** O nome da sua variável de driver pode variar. No exemplo, `appium_driver` deve ser o objeto de driver do seu teste.
218
+ ---
219
+
220
+ ## Tratamento de Dados e Deduplicação
221
+
222
+ - **Truncamento**: atributos com comprimento acima de `attr_truncate_length` são truncados com sufixo `...` para evitar poluição do YAML.
223
+ - **Hash único por elemento**: é gerado um hash (sha1) baseado em conjunto de atributos relevantes (class+resource-id+content-desc+text) para identificar duplicados.
224
+ - **Remoção de nulos**: atributos vazios ou nulos são omitidos nos YAMLs.
225
+ - **Ordenação**: elementos no `all_elements_dump` são ordenados por prioridade de localizador (resource-id primeiro).
226
+
227
+ ---
228
+
229
+ ## Relatório HTML Interativo
230
+
231
+ O HTML gerado possui:
232
+ - Visualização inline do `screenshot` (img tag),
233
+ - Painel colapsável com o `page_source` (XML formatado e collapsible),
234
+ - Lista navegável de elementos com seus `suggested_xpaths` (botões para copiar),
235
+ - Ancoragem que permite focalizar: ao clicar em um XPath, realça o fragmento correspondente no XML (se possível),
236
+ - Metadados e link rápido para os YAMLs.
237
+
238
+ **Observação:** o HTML é gerado de forma estática — para realces dinâmicos é usado JavaScript simples embutido (sem dependências externas).
239
+
240
+ ---
241
+
242
+ ## Logging e Observabilidade
243
+
244
+ - Usa `Logger` padrão do Ruby:
245
+ - `DEBUG` para detalhamento completo (padrão em modo dev).
246
+ - `INFO` para resumo das ações realizadas.
247
+ - `WARN/ERROR` para problemas durante captura/escrita.
248
+ - Exemplos de mensagens:
249
+ - `[INFO] Creating failure report folder: reports_failure/2025_09_23_173045`
250
+ - `[DEBUG] Captured 4123 elements from page_source`
251
+ - `[ERROR] Failed to write screenshot: Permission denied`
252
+
253
+ ---
254
+
255
+ ## Testes e Qualidade
256
+
257
+ - Estrutura de testes sugerida: RSpec + fixtures com dumps de `page_source` para validar a geração de XPaths.
258
+ - Testes unitários para: truncamento, hash de deduplicação, geração de strategies, output YAML válido.
259
+ - CI: incluir step que valide YAML/HTML gerados (lint) e execute testes RSpec.
260
+
261
+ ---
262
+
263
+ ## Roadmap e Contribuição
264
+
265
+ **Funcionalidades previstas**
266
+ - Suporte a mapeamento visual (overlay) para apontar elemento sobre screenshot.
267
+ - Export para outros formatos (JSON/CSV).
268
+ - Integração com ferramentas de observabilidade (Sentry, Datadog).
269
+ - Modo headless para gerar relatórios offline em pipelines.
270
+
271
+ **Como contribuir**
272
+ 1. Fork no repositório.
273
+ 2. Crie branch com feature/bugfix.
274
+ 3. Abra PR com descrição técnica das mudanças e testes.
275
+ 4. Mantenha o estilo Ruby (RuboCop) e documentação atualizada.
276
+
277
+ ---
41
278
 
42
- ## Artefatos Gerados
279
+ ## Segurança e Privacidade
43
280
 
44
- Após uma falha, os seguintes arquivos serão gerados na pasta `screenshots/`:
281
+ - Evite capturar dados sensíveis em ambientes com PII. Implementar filtro por regex para mascarar dados (ex.: emails/telefones) antes de salvar YAMLs.
282
+ - Recomendado: executar limpeza em ambientes de produção.
45
283
 
46
- * `screenshot_20231027_153045.png`
284
+ ---
47
285
 
48
- * `page_source_20231027_153045.xml`
286
+ ## Licença
49
287
 
50
- * `element_suggestions_20231027_153045.yaml`
288
+ MIT — veja o arquivo `LICENSE` para os termos.
51
289
 
52
- O arquivo `.yaml` é um recurso valioso para inspecionar os elementos da tela e atualizar seus localizadores de forma eficiente.
290
+ ---
@@ -114,7 +114,6 @@ module AppiumFailureHelper
114
114
  File.write(html_report_path, html_content)
115
115
  @@logger.info("Relatório HTML completo salvo em #{html_report_path}")
116
116
 
117
-
118
117
  rescue => e
119
118
  @@logger.error("Erro ao capturar detalhes da falha: #{e.message}\n#{e.backtrace.join("\n")}")
120
119
  end
@@ -129,134 +128,24 @@ module AppiumFailureHelper
129
128
  "#{datetime.strftime('%Y-%m-%d %H:%M:%S')} [#{severity}] #{msg}\n"
130
129
  end
131
130
  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
131
 
244
- # ... (métodos setup_logger, extract_info_from_exception, find_similar_elements, etc.)
245
-
246
132
  def self.extract_info_from_exception(exception)
247
133
  message = exception.message
248
134
  info = {}
249
135
 
136
+ # Corrigido: Usando múltiplos padrões para extração robusta (resolvendo o problema do YAML vazio)
250
137
  patterns = [
251
- /(?:could not be found|cannot find element) using (.+)=['"](.+)['"]/i,
252
- /no such element: Unable to locate element: {"method":"([^"]+)","selector":"([^"]+)"}/i
138
+ /(?:could not be found|cannot find element) using (.+)=['"]?(.+)['"]?/i,
139
+ /no such element: Unable to locate element: {"method":"([^"]+)","selector":"([^"]+)"}/i,
140
+ /(?:An element with the selector |element with the selector |selector |element with the |element identified by )(.+?) (?:could not be found|was not found|not found|not be located)/i,
141
+ /(?:with the resource-id|with the accessibility-id) ['"](.+?)['"]/i
253
142
  ]
254
143
 
255
144
  patterns.each do |pattern|
256
145
  match = message.match(pattern)
257
146
  if match
258
- selector_type = match[1].strip
259
- selector_value = match[2].strip
147
+ selector_value = match.captures.last.strip
148
+ selector_type = match[1]&.strip || 'Unknown'
260
149
 
261
150
  info[:selector_type] = selector_type
262
151
  info[:selector_value] = selector_value.gsub(/['"]/, '')
@@ -272,15 +161,17 @@ module AppiumFailureHelper
272
161
  next if node.name == 'hierarchy'
273
162
  attrs = node.attributes.transform_values(&:value)
274
163
 
164
+ # Lógica aprimorada para comparação insensível a maiúsculas/minúsculas
165
+ selector_value = failed_info[:selector_value].to_s.downcase
275
166
  is_similar = case platform
276
167
  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]))
168
+ (attrs['resource-id']&.downcase&.include?(selector_value) ||
169
+ attrs['text']&.downcase&.include?(selector_value) ||
170
+ attrs['content-desc']&.downcase&.include?(selector_value))
280
171
  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]))
172
+ (attrs['accessibility-id']&.downcase&.include?(selector_value) ||
173
+ attrs['label']&.downcase&.include?(selector_value) ||
174
+ attrs['name']&.downcase&.include?(selector_value))
284
175
  else
285
176
  false
286
177
  end
@@ -400,13 +291,131 @@ module AppiumFailureHelper
400
291
 
401
292
  locators
402
293
  end
294
+
295
+ def self.generate_html_report(targeted_report, all_suggestions, screenshot_base64, platform, timestamp)
296
+
297
+ locators_html = lambda do |locators|
298
+ locators.map do |loc|
299
+ "<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>"
300
+ end.join
301
+ end
403
302
 
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"
303
+ all_elements_html = lambda do |elements|
304
+ elements.map do |el|
305
+ "<details class='border-b border-gray-200 py-3'><summary class='font-semibold text-sm text-gray-800 cursor-pointer'>#{el[:name]}</summary><ul class='text-xs space-y-1 mt-2'>#{locators_html.call(el[:locators])}</ul></details>"
306
+ end.join
409
307
  end
308
+
309
+ failed_info = targeted_report[:failed_element]
310
+ similar_elements = targeted_report[:similar_elements]
311
+
312
+ similar_elements_content = similar_elements.empty? ? "<p class='text-gray-500'>Nenhuma alternativa semelhante foi encontrada. O elemento pode ter sido removido.</p>" : 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
313
+
314
+ failed_info_content = if failed_info && failed_info[:selector_value]
315
+ "<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'>#{failed_info[:selector_type]}</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'>#{failed_info[:selector_value]}</span></p>"
316
+ else
317
+ "<p class='text-sm text-gray-500'>O localizador exato não pôde ser extraído da mensagem de erro.</p>"
318
+ end
319
+
320
+ # Template HTML usando um heredoc
321
+ <<~HTML_REPORT
322
+ <!DOCTYPE html>
323
+ <html lang="pt-BR">
324
+ <head>
325
+ <meta charset="UTF-8">
326
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
327
+ <title>Relatório de Falha Appium - #{timestamp}</title>
328
+ <script src="https://cdn.tailwindcss.com"></script>
329
+ <style>
330
+ body { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; }
331
+ .tab-content { display: none; }
332
+ .tab-content.active { display: block; }
333
+ .tab-button.active { background-color: #4f46e5; color: white; }
334
+ .tab-button:not(.active):hover { background-color: #e0e7ff; }
335
+ </style>
336
+ </head>
337
+ <body class="bg-gray-50 p-8">
338
+ <div class="max-w-7xl mx-auto">
339
+ <header class="mb-8 pb-4 border-b border-gray-300">
340
+ <h1 class="text-3xl font-bold text-gray-800">Diagnóstico de Falha Automatizada</h1>
341
+ <p class="text-sm text-gray-500">Relatório gerado em: #{timestamp} | Plataforma: #{platform.upcase}</p>
342
+ </header>
343
+
344
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
345
+ <!-- Coluna de Screenshots e Falha -->
346
+ <div class="lg:col-span-1">
347
+ <div class="bg-white p-4 rounded-lg shadow-xl mb-6 border border-red-200">
348
+ <h2 class="text-xl font-bold text-red-600 mb-4">Elemento com Falha</h2>
349
+ #{failed_info_content}
350
+ </div>
351
+
352
+ <div class="bg-white p-4 rounded-lg shadow-xl">
353
+ <h2 class="text-xl font-bold text-gray-800 mb-4">Screenshot da Falha</h2>
354
+ <img src="data:image/png;base64,#{screenshot_base64}" alt="Screenshot da Falha" class="w-full rounded-md shadow-lg border border-gray-200">
355
+ </div>
356
+ </div>
357
+
358
+ <!-- Coluna de Relatórios e Sugestões -->
359
+ <div class="lg:col-span-2">
360
+ <div class="bg-white rounded-lg shadow-xl">
361
+ <!-- Abas de Navegação -->
362
+ <div class="flex border-b border-gray-200">
363
+ <button class="tab-button active px-4 py-3 text-sm font-medium rounded-tl-lg" data-tab="similar">Sugestões de Reparo (#{similar_elements.size})</button>
364
+ <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>
365
+ </div>
366
+
367
+ <!-- Conteúdo das Abas -->
368
+ <div class="p-6">
369
+ <!-- Aba Sugestões de Reparo -->
370
+ <div id="similar" class="tab-content active">
371
+ <h3 class="text-lg font-semibold text-indigo-700 mb-4">Elementos Semelhantes (Alternativas para o Localizador Falho)</h3>
372
+ <div class="space-y-4">
373
+ #{similar_elements_content}
374
+ </div>
375
+ </div>
376
+
377
+ <!-- Aba Dump Completo -->
378
+ <div id="all" class="tab-content">
379
+ <h3 class="text-lg font-semibold text-indigo-700 mb-4">Dump Completo de Todos os Elementos da Tela</h3>
380
+ <div class="max-h-[600px] overflow-y-auto space-y-2">
381
+ #{all_elements_html.call(all_suggestions)}
382
+ </div>
383
+ </div>
384
+ </div>
385
+ </div>
386
+ </div>
387
+ </div>
388
+ </div>
389
+
390
+ <script>
391
+ document.addEventListener('DOMContentLoaded', () => {
392
+ const tabs = document.querySelectorAll('.tab-button');
393
+ const contents = document.querySelectorAll('.tab-content');
394
+
395
+ tabs.forEach(tab => {
396
+ tab.addEventListener('click', () => {
397
+ const target = tab.getAttribute('data-tab');
398
+
399
+ tabs.forEach(t => {
400
+ t.classList.remove('active', 'text-white', 'bg-indigo-600');
401
+ t.classList.add('text-gray-600');
402
+ });
403
+ contents.forEach(c => c.classList.remove('active'));
404
+
405
+ tab.classList.add('active', 'text-white', 'bg-indigo-600');
406
+ tab.classList.remove('text-gray-600');
407
+ document.getElementById(target).classList.add('active');
408
+ });
409
+ });
410
+
411
+ // Set initial active state for styling consistency
412
+ const activeTab = document.querySelector('.tab-button[data-tab="similar"]');
413
+ activeTab.classList.add('active', 'text-white', 'bg-indigo-600');
414
+ });
415
+ </script>
416
+ </body>
417
+ </html>
418
+ HTML_REPORT
410
419
  end
411
420
  end
412
421
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AppiumFailureHelper
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.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.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Nascimento