appium_failure_helper 0.3.0 → 0.4.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/LICENSE.txt +21 -0
- data/README.md +53 -0
- data/Rakefile +4 -0
- data/lib/appium_failure_helper/capture.rb +181 -0
- data/lib/appium_failure_helper/version.rb +5 -0
- data/lib/appium_failure_helper.rb +8 -0
- data/sig/appium_failure_helper.rbs +4 -0
- metadata +80 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 995c0139fe92b684bf7133a42cc97c17620f59b6a827aa105d2360157c6c861f
|
4
|
+
data.tar.gz: cd712e634e60764c25d781f72caee2f6c4f2d7ee101e67c46a8eb021ac8cbd5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d44e926ad1ae38a55f61b8dc8efa009385b91bbef5755339e08493fff6b3d0292f1524c51ead0f2753c5a480cb536cdf9abd8dc51ec9446bb126a2732ed2395b
|
7
|
+
data.tar.gz: 8c1855b1d74c7b0b574659b19e4dba1a0c35bac085c293a2033f536b6dfbb05765de513cedccbe4ad5780618eb2a43ca8e0278974f9f2efb09f31041ecc10b22
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 David Nascimento
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Appium Failure Helper
|
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.
|
4
|
+
|
5
|
+
## Funcionalidades Principais
|
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.
|
10
|
+
* **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
|
+
## Como Funciona
|
15
|
+
|
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
|
+
|
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.
|
24
|
+
|
25
|
+
## Uso
|
26
|
+
|
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.
|
28
|
+
|
29
|
+
**`features/support/hooks.rb`**
|
30
|
+
```ruby
|
31
|
+
require 'appium_failure_helper'
|
32
|
+
|
33
|
+
After do |scenario|
|
34
|
+
if scenario.failed?
|
35
|
+
AppiumFailureHelper::Capture.handler_failure(appium_driver)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
```
|
40
|
+
|
41
|
+
**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.
|
42
|
+
|
43
|
+
## Artefatos Gerados
|
44
|
+
|
45
|
+
Após uma falha, os seguintes arquivos serão gerados na pasta `screenshots/`:
|
46
|
+
|
47
|
+
* `screenshot_20231027_153045.png`
|
48
|
+
|
49
|
+
* `page_source_20231027_153045.xml`
|
50
|
+
|
51
|
+
* `element_suggestions_20231027_153045.yaml`
|
52
|
+
|
53
|
+
O arquivo `.yaml` é um recurso valioso para inspecionar os elementos da tela e atualizar seus localizadores de forma eficiente.
|
data/Rakefile
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'base64'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module AppiumFailureHelper
|
7
|
+
class Capture
|
8
|
+
PREFIX = {
|
9
|
+
'android.widget.Button' => 'btn',
|
10
|
+
'android.widget.TextView' => 'txt',
|
11
|
+
'android.widget.ImageView' => 'img',
|
12
|
+
'android.widget.EditText' => 'input',
|
13
|
+
'android.widget.CheckBox' => 'chk',
|
14
|
+
'android.widget.RadioButton' => 'radio',
|
15
|
+
'android.widget.Switch' => 'switch',
|
16
|
+
'android.widget.ViewGroup' => 'group',
|
17
|
+
'android.widget.View' => 'view',
|
18
|
+
'android.widget.FrameLayout' => 'frame',
|
19
|
+
'android.widget.LinearLayout' => 'linear',
|
20
|
+
'android.widget.RelativeLayout' => 'relative',
|
21
|
+
'android.widget.ScrollView' => 'scroll',
|
22
|
+
'android.webkit.WebView' => 'web',
|
23
|
+
'android.widget.Spinner' => 'spin',
|
24
|
+
'XCUIElementTypeButton' => 'btn',
|
25
|
+
'XCUIElementTypeStaticText' => 'txt',
|
26
|
+
'XCUIElementTypeTextField' => 'input',
|
27
|
+
'XCUIElementTypeImage' => 'img',
|
28
|
+
'XCUIElementTypeSwitch' => 'switch',
|
29
|
+
'XCUIElementTypeScrollView' => 'scroll',
|
30
|
+
'XCUIElementTypeOther' => 'elm',
|
31
|
+
'XCUIElementTypeCell' => 'cell',
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
def self.handler_failure(driver)
|
35
|
+
begin
|
36
|
+
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
37
|
+
output_folder = "screenshots/failure_#{timestamp}"
|
38
|
+
|
39
|
+
FileUtils.mkdir_p(output_folder)
|
40
|
+
|
41
|
+
screenshot_path = "#{output_folder}/screenshot_#{timestamp}.png"
|
42
|
+
File.open(screenshot_path, 'wb') do |f|
|
43
|
+
f.write(Base64.decode64(driver.screenshot_as(:base64)))
|
44
|
+
end
|
45
|
+
puts "Screenshot saved to #{screenshot_path}"
|
46
|
+
|
47
|
+
page_source = driver.page_source
|
48
|
+
xml_path = "#{output_folder}/page_source_#{timestamp}.xml"
|
49
|
+
File.write(xml_path, page_source)
|
50
|
+
|
51
|
+
doc = Nokogiri::XML(page_source)
|
52
|
+
|
53
|
+
platform = driver.capabilities['platformName']&.downcase || 'unknown'
|
54
|
+
|
55
|
+
yaml_path = "#{output_folder}/element_suggestions_#{timestamp}.yaml"
|
56
|
+
File.open(yaml_path, 'w') do |f|
|
57
|
+
suggestions = doc.xpath('//*').map do |node|
|
58
|
+
next if node.name == 'hierarchy'
|
59
|
+
attrs = node.attributes.transform_values(&:value)
|
60
|
+
|
61
|
+
name = self.suggest_name(node.name, attrs)
|
62
|
+
xpath = self.xpath_generator(node.name, attrs, platform)
|
63
|
+
|
64
|
+
parent_node = node.parent
|
65
|
+
parent_xpath = parent_node ? parent_node.path : nil
|
66
|
+
|
67
|
+
{
|
68
|
+
name: name,
|
69
|
+
type: 'xpath',
|
70
|
+
locator: xpath,
|
71
|
+
parent_locator: parent_xpath
|
72
|
+
}
|
73
|
+
end.compact
|
74
|
+
|
75
|
+
f.write(YAML.dump(suggestions))
|
76
|
+
end
|
77
|
+
|
78
|
+
puts "Element suggestions saved to #{yaml_path}"
|
79
|
+
rescue => e
|
80
|
+
puts "Error capturing failure details: #{e.message}\n#{e.backtrace.join("\n")}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def self.suggest_name(tag, attrs)
|
87
|
+
type = tag.split('.').last
|
88
|
+
pfx = PREFIX[tag] || PREFIX[type] || 'elm'
|
89
|
+
name = attrs['content-desc'] || attrs['text'] || attrs['resource-id'] || attrs['label'] || attrs['name'] || 'unknown' || type
|
90
|
+
name = name.strip.gsub(/[^0-9a-z]/, '').split.map(&:capitalize).join
|
91
|
+
"#{pfx}#{name}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.xpath_generator(tag, attrs, platform)
|
95
|
+
case platform
|
96
|
+
when 'android'
|
97
|
+
self.generate_android_xpath(tag, attrs)
|
98
|
+
when 'ios'
|
99
|
+
self.generate_ios_xpath(tag, attrs)
|
100
|
+
else
|
101
|
+
self.generate_unknown_xpath(tag, attrs)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.generate_android_xpath(tag, attrs)
|
106
|
+
strategies = [
|
107
|
+
# Estratégia 1: Combinação de resource-id e text/content-desc
|
108
|
+
-> {
|
109
|
+
if attrs['resource-id'] && !attrs['resource-id'].empty?
|
110
|
+
if attrs['text'] && !attrs['text'].empty?
|
111
|
+
return "//#{tag}[@resource-id='#{attrs['resource-id']}' and @text='#{attrs['text']}']"
|
112
|
+
elsif attrs['content-desc'] && !attrs['content-desc'].empty?
|
113
|
+
return "//#{tag}[@resource-id='#{attrs['resource-id']}' and @content-desc='#{attrs['content-desc']}']"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
nil
|
117
|
+
},
|
118
|
+
# Estratégia 2: resource-id único
|
119
|
+
-> { "//#{tag}[@resource-id='#{attrs['resource-id']}']" if attrs['resource-id'] && !attrs['resource-id'].empty? },
|
120
|
+
# Estratégia 3: starts-with para resource-id dinâmico
|
121
|
+
-> {
|
122
|
+
if attrs['resource-id'] && attrs['resource-id'].include?(':id/')
|
123
|
+
id_part = attrs['resource-id'].split(':id/').last
|
124
|
+
return "//#{tag}[starts-with(@resource-id, '#{id_part}')]"
|
125
|
+
end
|
126
|
+
nil
|
127
|
+
},
|
128
|
+
# Estratégia 4: text como identificador
|
129
|
+
-> { "//#{tag}[@text='#{attrs['text']}']" if attrs['text'] && !attrs['text'].empty? },
|
130
|
+
# Estratégia 5: content-desc como identificador
|
131
|
+
-> { "//#{tag}[@content-desc='#{attrs['content-desc']}']" if attrs['content-desc'] && !attrs['content-desc'].empty? },
|
132
|
+
# Estratégia 6: Fallback genérico
|
133
|
+
-> { "//#{tag}" }
|
134
|
+
]
|
135
|
+
|
136
|
+
strategies.each do |strategy|
|
137
|
+
result = strategy.call
|
138
|
+
return result if result
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.generate_ios_xpath(tag, attrs)
|
143
|
+
strategies = [
|
144
|
+
# Estratégia 1: Combinação de accessibility-id e label
|
145
|
+
-> {
|
146
|
+
if attrs['accessibility-id'] && !attrs['accessibility-id'].empty?
|
147
|
+
if attrs['label'] && !attrs['label'].empty?
|
148
|
+
return "//#{tag}[@accessibility-id='#{attrs['accessibility-id']}' and @label='#{attrs['label']}']"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
nil
|
152
|
+
},
|
153
|
+
# Estratégia 2: accessibility-id como identificador
|
154
|
+
-> { "//#{tag}[@accessibility-id='#{attrs['accessibility-id']}']" if attrs['accessibility-id'] && !attrs['accessibility-id'].empty? },
|
155
|
+
# Estratégia 3: label como identificador
|
156
|
+
-> { "//#{tag}[@label='#{attrs['label']}']" if attrs['label'] && !attrs['label'].empty? },
|
157
|
+
# Estratégia 4: name como identificador
|
158
|
+
-> { "//#{tag}[@name='#{attrs['name']}']" if attrs['name'] && !attrs['name'].empty? },
|
159
|
+
# Estratégia 5: Fallback genérico
|
160
|
+
-> { "//#{tag}" }
|
161
|
+
]
|
162
|
+
|
163
|
+
strategies.each do |strategy|
|
164
|
+
result = strategy.call
|
165
|
+
return result if result
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.generate_unknown_xpath(tag, attrs)
|
170
|
+
if attrs['resource-id'] && !attrs['resource-id'].empty?
|
171
|
+
"//#{tag}[@resource-id='#{attrs['resource-id']}']"
|
172
|
+
elsif attrs['content-desc'] && !attrs['content-desc'].empty?
|
173
|
+
"//#{tag}[@content-desc='#{attrs['content-desc']}']"
|
174
|
+
elsif attrs['text'] && !attrs['text'].empty?
|
175
|
+
"//#{tag}[@text='#{attrs['text']}']"
|
176
|
+
else
|
177
|
+
"//#{tag}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
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
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Nascimento
|
@@ -9,7 +9,77 @@ autorequire:
|
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
11
|
date: 2025-09-23 00:00:00.000000000 Z
|
12
|
-
dependencies:
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.15'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.15'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: appium_lib
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '13.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '13.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
13
83
|
description: Appium Failure Helper is a Ruby gem that provides utilities to capture
|
14
84
|
failures during Appium test executions. It extracts and saves relevant information
|
15
85
|
from the page source, including screenshots and element details, to aid in debugging
|
@@ -19,7 +89,14 @@ email:
|
|
19
89
|
executables: []
|
20
90
|
extensions: []
|
21
91
|
extra_rdoc_files: []
|
22
|
-
files:
|
92
|
+
files:
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/appium_failure_helper.rb
|
97
|
+
- lib/appium_failure_helper/capture.rb
|
98
|
+
- lib/appium_failure_helper/version.rb
|
99
|
+
- sig/appium_failure_helper.rbs
|
23
100
|
homepage: https://github.com/David-Nascimento/Appium_failure_helper
|
24
101
|
licenses:
|
25
102
|
- MIT
|