appium_failure_helper 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a20267977f0afde8bb695938eebce5b191dc3cba987e5c282a6035e07d881621
4
- data.tar.gz: abc7bbfb5dbaa920db3c4830bf93089c39874bc3c1d093e9490ac65e3f58e9de
3
+ metadata.gz: 43096445c0c64d280ed41cc96d50ddd088c660bfac0441b8ff4e9ddc34e9107d
4
+ data.tar.gz: a2b4d0c6ebe03d445ae1e39d5dd35da188b01b4835b1a9a16055709b201f0864
5
5
  SHA512:
6
- metadata.gz: 9f890998e4660b52c93b034e6237e73b11022430807dba957ed6bfaff38245eee55fda60a1dcf04191ac955e967d9a5a174976d1345196c3b049970f7602c05a
7
- data.tar.gz: c0514430f06deae0ede268aedcc2b3ca56fce697e26b0f612ac4bdace28afda15d0c14dffce72b5d94898cb3c7384a17d78a5bca6e822b4ac3fc3047d0c02ae0
6
+ metadata.gz: f27c3e50dbef777ef84b756738e32ca50779a970e01e8cbd086a13c5a65a15d6c22c3f27e51e357b78555d3ae8cf5619b9e9fa36131c5f6761d54707b3fe8d93
7
+ data.tar.gz: a1dc3d6684a2d5afc79c6a7c956075fdeb5a90afd6d5ac4b02ae8a1129e16c18fcdae7cfd2f66794cab9e86ec65880ff7c5cb0edfddbef0b8ab6326ff163e445
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/README.md CHANGED
@@ -1,144 +1,155 @@
1
- # Appium Failure Helper: Diagnóstico Inteligente de Falhas
2
-
3
- ![Build Status](https://img.shields.io/badge/build-passing-brightgreen)
4
- ![Gem Version](https://img.shields.io/badge/gem-v3.0.0-blue)
5
- ![License](https://img.shields.io/badge/license-MIT-lightgrey)
6
-
7
- Uma GEM de diagnóstico para testes Appium em Ruby, projetada para transformar falhas de automação em insights acionáveis. Quando um teste falha por não encontrar um elemento, esta ferramenta gera um relatório HTML detalhado, identificando a causa provável do erro e acelerando drasticamente o tempo de depuração.
8
-
9
- ## ✨ Principais Funcionalidades
10
-
11
- * **Diagnóstico por Triagem de Erros:** Identifica inteligentemente o *tipo* de falha (`NoSuchElementError`, `TimeoutError`, `Erro de Código Ruby`, `Falha de Asserção`, etc.) e gera um relatório específico e útil para cada cenário.
12
- * **Análise de Código-Fonte:** Para erros "silenciosos" (onde a mensagem não contém o seletor), a GEM inspeciona o `stack trace` para encontrar o arquivo e a linha exatos do erro, extraindo o seletor diretamente do código-fonte.
13
- * **Análise de Atributos Ponderados:** Em vez de uma simples comparação de strings, a GEM "desmonta" o seletor que falhou e o compara atributo por atributo com os elementos na tela, dando pesos diferentes para `resource-id`, `text`, etc., para encontrar o "candidato mais provável".
14
- * **Relatórios Ricos e Interativos:** Gera um relatório HTML completo com:
15
- * Screenshot da falha.
16
- * Diagnóstico claro da causa provável, com sugestões acionáveis.
17
- * Abas com "Análise Avançada" e um "Dump Completo" de todos os elementos da tela.
18
- * **Altamente Configurável:** Permite a customização de caminhos para se adaptar a diferentes estruturas de projeto.
19
-
20
- ## 🚀 Instalação
21
-
22
- Adicione esta linha ao `Gemfile` do seu projeto de automação:
23
-
24
- ```ruby
25
- gem 'appium_failure_helper', git: 'URL_DO_SEU_REPOSITORIO_GIT' # Exemplo de instalação via Git
26
- ```
27
-
28
- E então execute no seu terminal:
29
-
30
- ```sh
31
- bundle install
32
- ```
33
-
34
- ## 🛠️ Uso e Configuração
35
-
36
- A integração ideal envolve 3 passos:
37
-
38
- ### Passo 1: Configurar a GEM (Opcional)
39
-
40
- No seu arquivo de inicialização (ex: `features/support/env.rb`), carregue a GEM e, se necessário, configure os caminhos onde seus elementos estão mapeados. Se nenhuma configuração for fornecida, a ferramenta usará os valores padrão.
41
-
42
- ```ruby
43
- # features/support/env.rb
44
- require 'appium_failure_helper'
45
-
46
- AppiumFailureHelper.configure do |config|
47
- # Padrão: 'features/elements'
48
- config.elements_path = 'caminho/para/sua/pasta/de/elementos'
49
-
50
- # Padrão: 'elementLists.rb'
51
- config.elements_ruby_file = 'meu_arquivo_de_elementos.rb'
52
- end
53
- ```
54
-
55
- ### Passo 2: Enriquecer as Exceções (Altamente Recomendado)
56
-
57
- Para que a GEM consiga extrair o máximo de detalhes de uma falha (especialmente de erros genéricos como `TimeoutError` ou `NoSuchElementError` sem detalhes), é crucial que a exceção que ela recebe seja rica em informações. A melhor maneira de garantir isso é ajustar seus métodos de busca de elementos.
58
-
59
- Crie ou ajuste um arquivo de helpers (ex: `features/support/appiumCustom.rb`) com a seguinte estrutura:
60
-
61
- ```ruby
62
- # features/support/appiumCustom.rb
63
-
64
- # Métodos públicos que seus Page Objects irão chamar
65
- def find(el)
66
- find_element_with_enriched_error(el)
67
- end
68
-
69
- def clickElement(el)
70
- find_element_with_enriched_error(el).click
71
- end
72
-
73
- def waitForElementExist(el, timeout = 10)
74
- wait = Selenium::WebDriver::Wait.new(timeout: timeout)
75
- begin
76
- wait.until { $driver.find_elements(el['tipoBusca'], el['value']).size > 0 }
77
- rescue Selenium::WebDriver::Error::TimeoutError => e
78
- # Relança o erro com uma mensagem rica que a GEM entende
79
- new_message = "Timeout de #{timeout}s esperando pelo elemento: using \"#{el['tipoBusca']}\" with value \"#{el['value']}\""
80
- raise e.class, new_message
81
- end
82
- end
83
-
84
- private # --- Helper Interno ---
85
-
86
- # Este método é o coração da solução. Ele captura erros e os enriquece.
87
- def find_element_with_enriched_error(el)
88
- begin
89
- return $driver.find_element(el['tipoBusca'], el['value'])
90
- rescue Selenium::WebDriver::Error::NoSuchElementError => e
91
- # Cria uma nova mensagem explícita no formato "using... with value..."
92
- new_message = "using \"#{el['tipoBusca']}\" with value \"#{el['value']}\""
93
-
94
- # Recria a exceção original com a nova mensagem.
95
- new_exception = e.class.new(new_message)
96
- new_exception.set_backtrace(e.backtrace) # Preserva o stack trace
97
- raise new_exception
98
- end
99
- end
100
- ```
101
-
102
- ### Passo 3: Integrar com o Cucumber
103
-
104
- Finalmente, no seu `hooks.rb`, acione a GEM no hook `After` em caso de falha.
105
-
106
- ```ruby
107
- # features/support/hooks.rb
108
-
109
- After do |scenario|
110
- if scenario.failed? && $driver&.session_id
111
- AppiumFailureHelper.handler_failure($driver, scenario.exception)
112
- end
113
- end
114
- ```
115
-
116
- ## 📄 O Relatório Gerado
117
-
118
- A cada falha, uma nova pasta é criada em `reports_failure/`, contendo o relatório `.html` e outros artefatos. O relatório pode ter dois formatos principais:
119
-
120
- 1. **Relatório de Diagnóstico Simples:** Gerado para erros que não são de seletor (ex: falha de conexão, erro de código Ruby, falha de asserção). Ele mostra um diagnóstico direto, a mensagem de erro original e o `stack trace`.
121
-
122
- 2. **Relatório Detalhado (para problemas de seletor):**
123
- * **Coluna da Esquerda:** Mostra o "Elemento com Falha" (extraído da exceção ou do código), "Sugestões Encontradas no Código" e o "Screenshot".
124
- * **Coluna da Direita:** Contém abas interativas:
125
- * **Análise Avançada:** Apresenta o "candidato mais provável" encontrado na tela e uma análise comparativa de seus atributos (`resource-id`, `text`, etc.), com uma sugestão acionável.
126
- * **Dump Completo:** Uma lista de todos os elementos da tela e seus possíveis seletores.
127
-
128
- ## 🏛️ Arquitetura do Código
129
-
130
- A GEM é dividida em módulos com responsabilidades únicas para facilitar a manutenção e a extensibilidade (Handler, Analyzer, ReportGenerator, XPathFactory, etc.).
131
-
132
- ## 🔄 Fluxo Interno da GEM
133
-
134
- Abaixo o fluxo de como os módulos conversam entre si durante o diagnóstico de uma falha:
135
-
136
- ![Fluxo Interno](img\fluxo_appium_failure_helper.png)
137
-
138
- ## 🤝 Como Contribuir
139
-
140
- Pull Requests são bem-vindos. Para bugs ou sugestões, por favor, abra uma *Issue* no repositório.
141
-
142
- ## 📜 Licença
143
-
144
- Este projeto é distribuído sob a licença MIT.
1
+
2
+ # Appium Failure Helper: Diagnóstico Inteligente de Falhas
3
+
4
+ ![Build Status](https://img.shields.io/badge/build-passing-brightgreen)
5
+ ![Gem Version](https://badge.fury.io/rb/appium_failure_helper.svg)
6
+ ![License](https://img.shields.io/badge/license-MIT-lightgrey)
7
+
8
+ Uma GEM de diagnóstico para testes Appium em Ruby, projetada para transformar falhas de automação em **insights acionáveis**. Quando um teste falha por não encontrar um elemento, a ferramenta gera um relatório HTML detalhado, identificando a causa provável e acelerando drasticamente o tempo de depuração.
9
+
10
+ ---
11
+
12
+ ## Principais Funcionalidades
13
+
14
+ - **Diagnóstico Inteligente de Falhas:** Identifica automaticamente o tipo de erro (`NoSuchElementError`, `TimeoutError`, falha de asserção ou erro de código Ruby) e gera relatórios personalizados para cada caso.
15
+ - **Análise de Código-Fonte:** Para erros "silenciosos", inspeciona o `stack trace` e extrai o seletor diretamente do código, apontando arquivo e linha exatos.
16
+ - **Comparação Avançada de Atributos:** Compara atributo por atributo (`resource-id`, `text`, etc.) para encontrar o candidato mais provável na tela, evitando análises superficiais.
17
+ - **Relatórios Interativos:** HTML completo com:
18
+ - Screenshot da falha
19
+ - Diagnóstico claro e sugestões acionáveis
20
+ - Abas com "Análise Avançada" e "Dump Completo" de todos os elementos da tela
21
+ - **Configuração Flexível:** Personalize caminhos e arquivos de elementos para se adaptar a diferentes estruturas de projeto.
22
+
23
+ ---
24
+
25
+ ## 🚀 Instalação
26
+
27
+ Adicione ao `Gemfile` do seu projeto de automação:
28
+
29
+ ```ruby
30
+ gem 'appium_failure_helper', git: 'URL_DO_SEU_REPOSITORIO_GIT'
31
+ ```
32
+
33
+ Depois execute:
34
+
35
+ ```sh
36
+ bundle install
37
+ ```
38
+
39
+ ---
40
+
41
+ ## 🛠️ Uso e Configuração
42
+
43
+ ### 1️⃣ Configuração Inicial (Opcional)
44
+
45
+ No arquivo de inicialização (`features/support/env.rb`), configure os caminhos de elementos se necessário:
46
+
47
+ ```ruby
48
+ require 'appium_failure_helper'
49
+
50
+ AppiumFailureHelper.configure do |config|
51
+ config.elements_path = 'features/elements' # Pasta de elementos
52
+ config.elements_ruby_file = 'elementLists.rb' # Arquivo Ruby de elementos
53
+ end
54
+ ```
55
+
56
+ ---
57
+
58
+ ### 2️⃣ Enriquecer Exceções (Altamente Recomendado)
59
+
60
+ Para extrair o máximo de informações de falhas, ajuste seus métodos de busca de elementos:
61
+
62
+ ```ruby
63
+ def find(el)
64
+ find_element_with_enriched_error(el)
65
+ end
66
+
67
+ def clickElement(el)
68
+ find_element_with_enriched_error(el).click
69
+ end
70
+
71
+ def waitForElementExist(el, timeout = 10)
72
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
73
+ begin
74
+ wait.until { $driver.find_elements(el['tipoBusca'], el['value']).size > 0 }
75
+ rescue Selenium::WebDriver::Error::TimeoutError => e
76
+ raise e.class, "Timeout de #{timeout}s esperando pelo elemento: using \"\#{el['tipoBusca']}\" with value \"\#{el['value']}\""
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def find_element_with_enriched_error(el)
83
+ $driver.find_element(el['tipoBusca'], el['value'])
84
+ rescue Selenium::WebDriver::Error::NoSuchElementError => e
85
+ new_exception = e.class.new("using \"\#{el['tipoBusca']}\" with value \"\#{el['value']}\"")
86
+ new_exception.set_backtrace(e.backtrace)
87
+ raise new_exception
88
+ end
89
+ ```
90
+
91
+ ---
92
+
93
+ ### 3️⃣ Integração com Cucumber
94
+
95
+ No `hooks.rb`, acione a GEM após cada cenário com falha:
96
+
97
+ ```ruby
98
+ After do |scenario|
99
+ if scenario.failed? && $driver&.session_id
100
+ AppiumFailureHelper.handler_failure($driver, scenario.exception)
101
+ end
102
+ end
103
+ ```
104
+
105
+ ---
106
+
107
+ ## 📄 Relatório Gerado
108
+
109
+ A cada falha, a GEM cria uma pasta em `reports_failure/` com:
110
+
111
+ 1. **Relatório Simples:** Para falhas genéricas, mostrando erro, stack trace e diagnóstico direto.
112
+ 2. **Relatório Detalhado:** Para problemas de seletor:
113
+ - **Coluna Esquerda:** Elemento com falha, seletores sugeridos e screenshot.
114
+ - **Coluna Direita:** Abas interativas:
115
+ - **Análise Avançada:** Mostra o candidato mais provável, atributos comparados e sugestões acionáveis.
116
+ - **Dump Completo:** Lista todos os elementos e possíveis seletores da tela.
117
+
118
+ ---
119
+
120
+ ## 🏛️ Arquitetura
121
+
122
+ - **Handler:** Captura falhas e aciona o fluxo de análise.
123
+ - **SourceCodeAnalyzer:** Extrai seletores diretamente do código-fonte.
124
+ - **PageAnalyzer:** Analisa o `page_source` e sugere nomes e locators alternativos.
125
+ - **XPathFactory:** Gera estratégias de localização (diretas, combinatórias, parent-based, relativas, parciais, booleanas e posicionais).
126
+ - **ReportGenerator:** Cria relatórios HTML, XML e YAML ricos e interativos.
127
+
128
+ ---
129
+
130
+ ## 🔄 Fluxo Interno da GEM
131
+
132
+ ```
133
+ Falha Appium
134
+
135
+ ├─► SourceCodeAnalyzer → {selector_type, selector_value}
136
+
137
+ └─► PageAnalyzer → [{name, locators, attributes}, ...]
138
+
139
+ └─► XPathFactory → [estratégias alternativas]
140
+
141
+
142
+ ReportGenerator HTML / XML / YAML
143
+ ```
144
+
145
+ ---
146
+
147
+ ## 🤝 Contribuindo
148
+
149
+ Pull requests e issues são bem-vindos! Abra uma *Issue* para bugs ou sugestões.
150
+
151
+ ---
152
+
153
+ ## 📜 Licença
154
+
155
+ MIT License
Binary file
@@ -6,6 +6,9 @@ module AppiumFailureHelper
6
6
  Selenium::WebDriver::Error::TimeoutError,
7
7
  Selenium::WebDriver::Error::UnknownCommandError
8
8
  :locator_issue
9
+ when Selenium::WebDriver::Error::TimeoutError,
10
+ Appium::Core::Wait::TimeoutError
11
+ :locator_issue
9
12
  when Selenium::WebDriver::Error::ElementNotInteractableError
10
13
  :visibility_issue
11
14
  when Selenium::WebDriver::Error::StaleElementReferenceError
@@ -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(base_path)
42
- elements_map = {}
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
- data = YAML.load_file(file)
49
- if data.is_a?(Hash)
50
- data.each do |k, v|
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
- elements_map
53
+ Utils.logger.info("Número de elementos carregados: #{ELEMENTS.size}")
54
+ ELEMENTS
60
55
  end
61
56
 
62
- def self.normalize_yaml_hash_keys(obj)
63
- case obj
64
- when Hash
65
- result = {}
66
- obj.each do |k, v|
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,72 +12,166 @@ 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'] || @driver.capabilities[:platform_name] || 'unknown',
31
- screenshot_base64: @driver.screenshot_as(: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
- page_source = @driver.page_source
36
- doc = Nokogiri::XML(page_source)
39
+ page_source = @driver.page_source rescue nil
40
+ doc = Nokogiri::XML(page_source) rescue nil
37
41
 
38
- failed_info = Analyzer.extract_failure_details(@exception) || {}
39
- if failed_info.empty?
40
- failed_info = SourceCodeAnalyzer.extract_from_exception(@exception) || {}
41
- end
42
-
43
- if failed_info.empty?
44
- report_data[:triage_result] = :unidentified_locator_issue
45
- else
46
- page_analyzer = PageAnalyzer.new(page_source, report_data[:platform].to_s)
47
- all_page_elements = page_analyzer.analyze || []
42
+ failed_info = fetch_failed_element
43
+
44
+ report_data[:page_source] = page_source
45
+ report_data[:failed_element] = failed_info
46
+
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 || []) : []
48
50
  similar_elements = Analyzer.find_similar_elements(failed_info, all_page_elements) || []
49
-
50
- alternative_xpaths = []
51
- if !similar_elements.empty?
52
- target_suggestion = similar_elements.first
53
- if target_suggestion[:attributes] && (target_path = target_suggestion[:attributes][:path])
54
- target_node = doc.at_xpath(target_path)
55
- alternative_xpaths = XPathFactory.generate_for_node(target_node) if target_node
56
- end
57
- end
58
-
59
- unified_element_map = ElementRepository.load_all
51
+ alternative_xpaths = generate_alternative_xpaths(similar_elements, doc)
52
+ unified_element_map = ElementRepository.load_all rescue {}
60
53
  de_para_result = Analyzer.find_de_para_match(failed_info, unified_element_map)
61
54
  code_search_results = CodeSearcher.find_similar_locators(failed_info) || []
62
55
 
63
- report_data.merge!({
64
- page_source: page_source,
65
- failed_element: failed_info,
56
+ report_data.merge!(
66
57
  similar_elements: similar_elements,
67
58
  alternative_xpaths: alternative_xpaths,
68
59
  de_para_analysis: de_para_result,
69
60
  code_search_results: code_search_results,
70
61
  all_page_elements: all_page_elements
71
- })
62
+ )
72
63
  end
64
+
65
+ ReportGenerator.new(@output_folder, report_data).generate_all
66
+ Utils.logger.info("Relatórios gerados com sucesso em: #{@output_folder}")
73
67
  end
74
68
 
75
- ReportGenerator.new(@output_folder, report_data).generate_all
76
- Utils.logger.info("Relatórios gerados com sucesso em: #{@output_folder}")
77
-
78
69
  rescue => e
79
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
80
173
  end
174
+ alternative_xpaths
81
175
  end
82
176
  end
83
- end
177
+ end
@@ -1,3 +1,3 @@
1
1
  module AppiumFailureHelper
2
- VERSION = "1.3.0"
2
+ VERSION = "1.5.0"
3
3
  end
data/release_gem.rb CHANGED
@@ -1,9 +1,16 @@
1
- #!/usr/bin/env ruby
2
1
  require 'fileutils'
3
-
2
+ require 'selenium-webdriver'
3
+ require_relative 'lib/appium_failure_helper'
4
4
  # Caminho do arquivo de versão
5
5
  VERSION_FILE = 'lib/appium_failure_helper/version.rb'
6
6
 
7
+ # Executa os testes e retorna true se todos passarem
8
+ def tests_pass?
9
+ puts "Rodando testes..."
10
+ system('bundle exec rspec')
11
+ $?.success?
12
+ end
13
+
7
14
  # Lê a versão atual da gem
8
15
  def current_version
9
16
  content = File.read(VERSION_FILE)
@@ -53,20 +60,24 @@ def git_commit_and_tag(new_version)
53
60
  `git add .`
54
61
  `git commit -m "Bump version to #{new_version.join('.')}"`
55
62
  `git tag v#{new_version.join('.')}`
63
+ `git push && git push --tags`
56
64
  end
57
65
 
58
66
  # Publicar a GEM
59
- def push_gem
67
+ def push_gem(new_version)
60
68
  `gem build appium_failure_helper.gemspec`
61
- `gem push appium_failure_helper-#{current_version.join('.')}.gem`
69
+ `gem push appium_failure_helper-#{new_version.join('.')}.gem`
62
70
  end
63
71
 
64
72
  # Fluxo principal
65
- version = current_version
66
- type = change_type
67
- new_version = increment_version(version, type)
68
- update_version_file(new_version)
69
- git_commit_and_tag(new_version)
70
- push_gem
71
-
72
- puts "GEM publicada com sucesso! Nova versão: #{new_version.join('.')}"
73
+ if tests_pass?
74
+ version = current_version
75
+ type = change_type
76
+ new_version = increment_version(version, type)
77
+ update_version_file(new_version)
78
+ git_commit_and_tag(new_version)
79
+ push_gem(new_version)
80
+ puts "GEM publicada com sucesso! Nova versão: #{new_version.join('.')}"
81
+ else
82
+ puts "Testes falharam! Commit e push cancelados."
83
+ 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.3.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Nascimento
@@ -90,11 +90,11 @@ executables: []
90
90
  extensions: []
91
91
  extra_rdoc_files: []
92
92
  files:
93
+ - ".rspec"
93
94
  - LICENSE.txt
94
95
  - README.md
95
96
  - Rakefile
96
- - appium_failure_helper-1.2.3.gem
97
- - appium_failure_helper-1.2.4.gem
97
+ - appium_failure_helper-1.4.0.gem
98
98
  - elements/cadastro.yaml
99
99
  - elements/login.yaml
100
100
  - img/fluxo_appium_failure_helper.png
Binary file
Binary file