appium_failure_helper 0.4.0 → 0.4.2

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: 995c0139fe92b684bf7133a42cc97c17620f59b6a827aa105d2360157c6c861f
4
- data.tar.gz: cd712e634e60764c25d781f72caee2f6c4f2d7ee101e67c46a8eb021ac8cbd5b
3
+ metadata.gz: c15543514bd288d39e5dd2d8d5d07b23aa9c21b78704b99c8e5eeca7400b08ed
4
+ data.tar.gz: c41bfaac49d288ca60d99eb6708b6e4eb71074494f0ee615251ccf429920dca7
5
5
  SHA512:
6
- metadata.gz: d44e926ad1ae38a55f61b8dc8efa009385b91bbef5755339e08493fff6b3d0292f1524c51ead0f2753c5a480cb536cdf9abd8dc51ec9446bb126a2732ed2395b
7
- data.tar.gz: 8c1855b1d74c7b0b574659b19e4dba1a0c35bac085c293a2033f536b6dfbb05765de513cedccbe4ad5780618eb2a43ca8e0278974f9f2efb09f31041ecc10b22
6
+ metadata.gz: 16773e8d4f28fdf2e453e80e739641756821a0efd9bb2c9df7917e67791b469b56e066a132992d38d361e68c9a66e7030fbae74fa7b9ae37253e620890ec8ac3
7
+ data.tar.gz: 73dff6f6a7045ba2749e6aa3157cf141b9c5f8bb315de50ff9820cee1dde3e8b83c485fcfd8e19bd6260c4f77bae629aea62903dea2120240cb0e95b050075c6
@@ -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
- 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)
57
+ seen_elements = {}
58
+ suggestions = []
63
59
 
64
- parent_node = node.parent
65
- parent_xpath = parent_node ? parent_node.path : nil
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
- name: name,
69
- type: 'xpath',
70
- locator: xpath,
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.generate_android_xpath(tag, attrs)
104
+ self.generate_android_xpaths(tag, attrs)
98
105
  when 'ios'
99
- self.generate_ios_xpath(tag, attrs)
106
+ self.generate_ios_xpaths(tag, attrs)
100
107
  else
101
- self.generate_unknown_xpath(tag, attrs)
108
+ self.generate_unknown_xpaths(tag, attrs)
102
109
  end
103
110
  end
104
111
 
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
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.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
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.generate_unknown_xpath(tag, attrs)
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
- 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}"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AppiumFailureHelper
4
- VERSION = "0.4.0"
4
+ VERSION = "0.4.2"
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.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Nascimento