Module: AppiumFailureHelper::Analyzer

Defined in:
lib/appium_failure_helper/analyzer.rb

Class Method Summary collapse

Class Method Details

.extract_failure_details(exception) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/appium_failure_helper/analyzer.rb', line 27

def self.extract_failure_details(exception)
  message = exception.message.to_s
  info = {}
  patterns = [
    /using "([^"]+)" with value "([^"]+)"/,
    /element with locator ['"]?(#?\w+)['"]?/i,
    /(?:could not be found|cannot find element) using (.+?)=['"]?([^'"]+)['"]?/i,
    /no such element: Unable to locate element: {"method":"([^"]+)","selector":"([^"]+)"}/i,
  ]
  patterns.each do |pattern|
    match = message.match(pattern)
    if match
      if match.captures.size == 2
        info[:selector_type], info[:selector_value] = match.captures.map(&:strip)
      else
        info[:selector_value] = match.captures.first.strip
        info[:selector_type] = 'logical_name'
      end
      return info
    end
  end
  info
end

.find_de_para_match(failed_info, element_map) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/appium_failure_helper/analyzer.rb', line 51

def self.find_de_para_match(failed_info, element_map)
  failed_value = (failed_info || {})[:selector_value].to_s
  return nil if failed_value.empty?
  logical_name_key = failed_value.gsub(/^#/, '')
  if element_map.key?(logical_name_key)
    return { logical_name: logical_name_key, correct_locator: element_map[logical_name_key] }
  end
  cleaned_failed_locator = failed_value.gsub(/[:\-\/@=\[\]'"()]/, ' ').gsub(/\s+/, ' ').downcase.strip
  element_map.each do |name, locator_info|
    mapped_locator = (locator_info || {})['valor'].to_s
    cleaned_mapped_locator = mapped_locator.gsub(/[:\-\/@=\[\]'"()]/, ' ').gsub(/\s+/, ' ').downcase.strip
    distance = DidYouMean::Levenshtein.distance(cleaned_failed_locator, cleaned_mapped_locator)
    max_len = [cleaned_failed_locator.length, cleaned_mapped_locator.length].max
    next if max_len.zero?
    similarity_score = 1.0 - (distance.to_f / max_len)
    if similarity_score > 0.85
      return { logical_name: name, correct_locator: locator_info }
    end
  end
  nil
end

.find_similar_elements(failed_info, all_page_suggestions) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/appium_failure_helper/analyzer.rb', line 73

def self.find_similar_elements(failed_info, all_page_suggestions)
  failed_locator_value = (failed_info || {})[:selector_value]
  failed_locator_type = (failed_info || {})[:selector_type]
  return [] unless failed_locator_value && failed_locator_type
  normalized_failed_type = failed_locator_type.to_s.downcase.include?('id') ? 'id' : failed_locator_type.to_s
  cleaned_failed_locator = failed_locator_value.to_s.gsub(/[:\-\/@=\[\]'"()]/, ' ').gsub(/\s+/, ' ').downcase.strip
  similarities = []
  all_page_suggestions.each do |suggestion|
    candidate_locator = (suggestion[:locators] || []).find { |loc| loc[:strategy] == normalized_failed_type }
    next unless candidate_locator
    cleaned_candidate_locator = candidate_locator[:locator].gsub(/[:\-\/@=\[\]'"()]/, ' ').gsub(/\s+/, ' ').downcase.strip
    distance = DidYouMean::Levenshtein.distance(cleaned_failed_locator, cleaned_candidate_locator)
    max_len = [cleaned_failed_locator.length, cleaned_candidate_locator.length].max
    next if max_len.zero?
    similarity_score = 1.0 - (distance.to_f / max_len)
    if similarity_score > 0.85
      similarities << { name: suggestion[:name], locators: suggestion[:locators], score: similarity_score, attributes: suggestion[:attributes] }
    end
  end
  similarities.sort_by { |s| -s[:score] }.first(5)
end

.triage_error(exception) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/appium_failure_helper/analyzer.rb', line 3

def self.triage_error(exception)
  case exception
  when Selenium::WebDriver::Error::NoSuchElementError, 
       Selenium::WebDriver::Error::TimeoutError,
       Selenium::WebDriver::Error::UnknownCommandError
    :locator_issue
  when Selenium::WebDriver::Error::ElementNotInteractableError
    :visibility_issue
  when Selenium::WebDriver::Error::StaleElementReferenceError
    :stale_element_issue
  when defined?(RSpec::Expectations::ExpectationNotMetError) ? RSpec::Expectations::ExpectationNotMetError : Class.new
    :assertion_failure
  when NoMethodError, NameError, ArgumentError, TypeError
    :ruby_code_issue
  when Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
    :session_startup_issue
  when Selenium::WebDriver::Error::WebDriverError
    return :app_crash_issue if exception.message.include?('session deleted because of page crash')
    :unknown_appium_issue
  else
    :unknown_issue
  end
end