appium_failure_helper 0.4.0 → 0.4.1
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/lib/appium_failure_helper/capture.rb +99 -88
- data/lib/appium_failure_helper/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c223153b4956f8098f653d255a4487bb3814bbd0ed8306cd4c01ccba478b4d9
|
4
|
+
data.tar.gz: 23ca5a6e67e1630473b37bd3ecaa1cab0b878ea35790e686bcd2eff6842462c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19483fead216b475eadac1f809e8bdd0ef2feafc46adfa41a396d18bb3572feb3a03b7768beacea1199046a4d7ffb982c2f9d8f9e4af13b1eee5fa035ff3a947
|
7
|
+
data.tar.gz: 2834dc179101c611fdce1262a20144f8684b86b3dea3515e3d2e4dccf983bbfa9e3e73b57fa1266d36c4a1b7deb4e93b35d7456ec75a7d9dbff5ec70cab542ab
|
@@ -30,6 +30,8 @@ module AppiumFailureHelper
|
|
30
30
|
'XCUIElementTypeOther' => 'elm',
|
31
31
|
'XCUIElementTypeCell' => 'cell',
|
32
32
|
}.freeze
|
33
|
+
|
34
|
+
MAX_VALUE_LENGTH = 100
|
33
35
|
|
34
36
|
def self.handler_failure(driver)
|
35
37
|
begin
|
@@ -52,26 +54,26 @@ module AppiumFailureHelper
|
|
52
54
|
|
53
55
|
platform = driver.capabilities['platformName']&.downcase || 'unknown'
|
54
56
|
|
55
|
-
|
56
|
-
|
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)
|
57
|
+
seen_elements = {}
|
58
|
+
suggestions = []
|
63
59
|
|
64
|
-
|
65
|
-
|
60
|
+
doc.xpath('//*').each do |node|
|
61
|
+
next if node.name == 'hierarchy'
|
62
|
+
attrs = node.attributes.transform_values(&:value)
|
63
|
+
|
64
|
+
unique_key = "#{node.name}|#{attrs['resource-id']}|#{attrs['content-desc']}|#{attrs['text']}"
|
65
|
+
|
66
|
+
unless seen_elements[unique_key]
|
67
|
+
name = self.suggest_name(node.name, attrs)
|
68
|
+
locators = self.xpath_generator(node.name, attrs, platform)
|
66
69
|
|
67
|
-
{
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
parent_locator: parent_xpath
|
72
|
-
}
|
73
|
-
end.compact
|
70
|
+
suggestions << { name: name, locators: locators }
|
71
|
+
seen_elements[unique_key] = true
|
72
|
+
end
|
73
|
+
end
|
74
74
|
|
75
|
+
yaml_path = "#{output_folder}/element_suggestions_#{timestamp}.yaml"
|
76
|
+
File.open(yaml_path, 'w') do |f|
|
75
77
|
f.write(YAML.dump(suggestions))
|
76
78
|
end
|
77
79
|
|
@@ -83,99 +85,108 @@ module AppiumFailureHelper
|
|
83
85
|
|
84
86
|
private
|
85
87
|
|
88
|
+
def self.truncate(value)
|
89
|
+
return value unless value.is_a?(String)
|
90
|
+
value.size > MAX_VALUE_LENGTH ? "#{value[0...MAX_VALUE_LENGTH]}..." : value
|
91
|
+
end
|
92
|
+
|
86
93
|
def self.suggest_name(tag, attrs)
|
87
94
|
type = tag.split('.').last
|
88
95
|
pfx = PREFIX[tag] || PREFIX[type] || 'elm'
|
89
96
|
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
|
97
|
+
name = truncate(name.strip.gsub(/[^0-9a-z]/, '').split.map(&:capitalize).join)
|
91
98
|
"#{pfx}#{name}"
|
92
99
|
end
|
93
100
|
|
94
101
|
def self.xpath_generator(tag, attrs, platform)
|
95
102
|
case platform
|
96
103
|
when 'android'
|
97
|
-
self.
|
104
|
+
self.generate_android_xpaths(tag, attrs)
|
98
105
|
when 'ios'
|
99
|
-
self.
|
106
|
+
self.generate_ios_xpaths(tag, attrs)
|
100
107
|
else
|
101
|
-
self.
|
108
|
+
self.generate_unknown_xpaths(tag, attrs)
|
102
109
|
end
|
103
110
|
end
|
104
111
|
|
105
|
-
def self.
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
}
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
112
|
+
def self.generate_android_xpaths(tag, attrs)
|
113
|
+
locators = []
|
114
|
+
|
115
|
+
# Estratégia 1: Combinação de atributos
|
116
|
+
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'])}']" }
|
118
|
+
elsif attrs['resource-id'] && !attrs['resource-id'].empty? && attrs['content-desc'] && !attrs['content-desc'].empty?
|
119
|
+
locators << { strategy: 'resource_id_and_content_desc', locator: "//#{tag}[@resource-id='#{attrs['resource-id']}' and @content-desc='#{self.truncate(attrs['content-desc'])}']" }
|
120
|
+
end
|
121
|
+
|
122
|
+
# Estratégia 2: ID único
|
123
|
+
if attrs['resource-id'] && !attrs['resource-id'].empty?
|
124
|
+
locators << { strategy: 'resource_id', locator: "//#{tag}[@resource-id='#{attrs['resource-id']}']" }
|
125
|
+
end
|
126
|
+
|
127
|
+
# Estratégia 3: starts-with para IDs dinâmicos
|
128
|
+
if attrs['resource-id'] && attrs['resource-id'].include?(':id/')
|
129
|
+
id_part = attrs['resource-id'].split(':id/').last
|
130
|
+
locators << { strategy: 'starts_with_resource_id', locator: "//#{tag}[starts-with(@resource-id, '#{id_part}')]" }
|
139
131
|
end
|
132
|
+
|
133
|
+
# Estratégia 4: Texto e content-desc como identificadores
|
134
|
+
if attrs['text'] && !attrs['text'].empty?
|
135
|
+
locators << { strategy: 'text', locator: "//#{tag}[@text='#{self.truncate(attrs['text'])}']" }
|
136
|
+
end
|
137
|
+
if attrs['content-desc'] && !attrs['content-desc'].empty?
|
138
|
+
locators << { strategy: 'content_desc', locator: "//#{tag}[@content-desc='#{self.truncate(attrs['content-desc'])}']" }
|
139
|
+
end
|
140
|
+
|
141
|
+
# Fallback genérico (sempre adicionado)
|
142
|
+
locators << { strategy: 'generic_tag', locator: "//#{tag}" }
|
143
|
+
|
144
|
+
locators
|
140
145
|
end
|
141
146
|
|
142
|
-
def self.
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
147
|
+
def self.generate_ios_xpaths(tag, attrs)
|
148
|
+
locators = []
|
149
|
+
|
150
|
+
# Estratégia 1: Combinação de atributos
|
151
|
+
if attrs['accessibility-id'] && !attrs['accessibility-id'].empty? && attrs['label'] && !attrs['label'].empty?
|
152
|
+
locators << { strategy: 'accessibility_id_and_label', locator: "//#{tag}[@accessibility-id='#{attrs['accessibility-id']}' and @label='#{self.truncate(attrs['label'])}']" }
|
153
|
+
end
|
154
|
+
|
155
|
+
# Estratégia 2: ID único
|
156
|
+
if attrs['accessibility-id'] && !attrs['accessibility-id'].empty?
|
157
|
+
locators << { strategy: 'accessibility_id', locator: "//#{tag}[@accessibility-id='#{attrs['accessibility-id']}']" }
|
166
158
|
end
|
159
|
+
|
160
|
+
# Estratégia 3: label, name ou value
|
161
|
+
if attrs['label'] && !attrs['label'].empty?
|
162
|
+
locators << { strategy: 'label', locator: "//#{tag}[@label='#{self.truncate(attrs['label'])}']" }
|
163
|
+
end
|
164
|
+
if attrs['name'] && !attrs['name'].empty?
|
165
|
+
locators << { strategy: 'name', locator: "//#{tag}[@name='#{self.truncate(attrs['name'])}']" }
|
166
|
+
end
|
167
|
+
|
168
|
+
# Fallback genérico (sempre adicionado)
|
169
|
+
locators << { strategy: 'generic_tag', locator: "//#{tag}" }
|
170
|
+
|
171
|
+
locators
|
167
172
|
end
|
168
173
|
|
169
|
-
def self.
|
174
|
+
def self.generate_unknown_xpaths(tag, attrs)
|
175
|
+
locators = []
|
170
176
|
if attrs['resource-id'] && !attrs['resource-id'].empty?
|
171
|
-
"//#{tag}[@resource-id='#{attrs['resource-id']}']"
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
"//#{tag}[@text='#{attrs['text']}']"
|
176
|
-
else
|
177
|
-
"//#{tag}"
|
177
|
+
locators << { strategy: 'resource_id', locator: "//#{tag}[@resource-id='#{attrs['resource-id']}']" }
|
178
|
+
end
|
179
|
+
if attrs['content-desc'] && !attrs['content-desc'].empty?
|
180
|
+
locators << { strategy: 'content_desc', locator: "//#{tag}[@content-desc='#{self.truncate(attrs['content-desc'])}']" }
|
178
181
|
end
|
182
|
+
if attrs['text'] && !attrs['text'].empty?
|
183
|
+
locators << { strategy: 'text', locator: "//#{tag}[@text='#{self.truncate(attrs['text'])}']" }
|
184
|
+
end
|
185
|
+
|
186
|
+
# Fallback genérico (sempre adicionado)
|
187
|
+
locators << { strategy: 'generic_tag', locator: "//#{tag}" }
|
188
|
+
|
189
|
+
locators
|
179
190
|
end
|
180
191
|
end
|
181
|
-
end
|
192
|
+
end
|