Module: OmniAI::Tools::Browser::SelectorGenerator::ContextualSelectors
- Included in:
- OmniAI::Tools::Browser::SelectorGenerator
- Defined in:
- lib/omniai/tools/browser/selector_generator/contextual_selectors.rb
Overview
Context-aware selector generation for complex elements
Instance Method Summary collapse
-
#build_parent_selector(element, parent_class) ⇒ Object
Build selector with parent class context.
-
#build_position_selector(element, index, parent_context = "") ⇒ Object
Build nth-of-type selector.
-
#element_has_significant_class?(element) ⇒ Boolean
Check if element has significant class.
-
#find_label_for_element(element) ⇒ Object
Find label element associated with this element.
-
#find_significant_parent(element) ⇒ Object
Find parent with meaningful class (not generic like ‘row’ or ‘col’).
-
#find_similar_siblings(element) ⇒ Object
Find sibling elements of same type with similar attributes.
- #generate_contextual_selectors(element) ⇒ Object
-
#generic_class?(class_name) ⇒ Boolean
Common generic class names to ignore.
-
#label_based_selectors(element) ⇒ Object
Generate selectors based on label associations.
-
#most_specific_class(element) ⇒ Object
Get most specific (longest) class name.
-
#parent_class_selectors(element) ⇒ Object
Generate selectors based on parent container classes.
-
#parent_context_prefix(element) ⇒ Object
Get parent context for more specific position selectors.
-
#position_based_selectors(element) ⇒ Object
Generate position-based selectors for similar elements.
-
#same_key_attributes?(elem1, elem2) ⇒ Boolean
Check if two elements have same key attributes.
-
#significant_class?(class_name) ⇒ Boolean
Check if class name is likely to be meaningful/specific.
-
#stable_id?(element) ⇒ Boolean
Check if element has stable (non-React) ID.
Instance Method Details
#build_parent_selector(element, parent_class) ⇒ Object
Build selector with parent class context
29 30 31 32 33 34 35 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 29 def build_parent_selector(element, parent_class) base = ".#{parent_class} #{element.name}" return ["#{base}[placeholder=\"#{element['placeholder']}\"]"] if element["placeholder"] return ["#{base}[type=\"#{element['type']}\"]"] if element["type"] [base] end |
#build_position_selector(element, index, parent_context = "") ⇒ Object
Build nth-of-type selector
103 104 105 106 107 108 109 110 111 112 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 103 def build_position_selector(element, index, parent_context = "") nth = ":nth-of-type(#{index})" base = "#{parent_context}#{element.name}#{nth}" return ["#{parent_context}#{element.name}[type=\"#{element['type']}\"]#{nth}"] if element["type"] if element["placeholder"] return ["#{parent_context}#{element.name}[placeholder=\"#{element['placeholder']}\"]#{nth}"] end [base] end |
#element_has_significant_class?(element) ⇒ Boolean
Check if element has significant class
49 50 51 52 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 49 def element_has_significant_class?(element) classes = element["class"]&.split || [] classes.any? { |c| significant_class?(c) } end |
#find_label_for_element(element) ⇒ Object
Find label element associated with this element
88 89 90 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 88 def find_label_for_element(element) element.document.at_css("label[for=\"#{element['id']}\"]") end |
#find_significant_parent(element) ⇒ Object
Find parent with meaningful class (not generic like ‘row’ or ‘col’)
38 39 40 41 42 43 44 45 46 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 38 def find_significant_parent(element) parent = element.parent while parent && parent.name != "body" return parent if element_has_significant_class?(parent) parent = parent.parent end nil end |
#find_similar_siblings(element) ⇒ Object
Find sibling elements of same type with similar attributes
115 116 117 118 119 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 115 def find_similar_siblings(element) return [] unless element.parent element.parent.css(element.name).select { |sibling| same_key_attributes?(element, sibling) } end |
#generate_contextual_selectors(element) ⇒ Object
9 10 11 12 13 14 15 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 9 def generate_contextual_selectors(element) selectors = [] selectors.concat(parent_class_selectors(element)) selectors.concat(label_based_selectors(element)) selectors.concat(position_based_selectors(element)) selectors end |
#generic_class?(class_name) ⇒ Boolean
Common generic class names to ignore
63 64 65 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 63 def generic_class?(class_name) %w[row col container wrapper inner outer main].include?(class_name.downcase) end |
#label_based_selectors(element) ⇒ Object
Generate selectors based on label associations
74 75 76 77 78 79 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 74 def label_based_selectors(element) return [] unless stable_id?(element) label = find_label_for_element(element) label ? ["#{element.name}##{element['id']}"] : [] end |
#most_specific_class(element) ⇒ Object
Get most specific (longest) class name
68 69 70 71 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 68 def most_specific_class(element) classes = element["class"]&.split || [] classes.select { |c| significant_class?(c) }.max_by(&:length) end |
#parent_class_selectors(element) ⇒ Object
Generate selectors based on parent container classes
18 19 20 21 22 23 24 25 26 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 18 def parent_class_selectors(element) significant_parent = find_significant_parent(element) return [] unless significant_parent parent_class = most_specific_class(significant_parent) return [] unless parent_class build_parent_selector(element, parent_class) end |
#parent_context_prefix(element) ⇒ Object
Get parent context for more specific position selectors
129 130 131 132 133 134 135 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 129 def parent_context_prefix(element) parent = find_significant_parent(element) return "" unless parent parent_class = most_specific_class(parent) parent_class ? ".#{parent_class} " : "" end |
#position_based_selectors(element) ⇒ Object
Generate position-based selectors for similar elements
93 94 95 96 97 98 99 100 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 93 def position_based_selectors(element) siblings = find_similar_siblings(element) return [] unless siblings.size > 1 index = siblings.index(element) + 1 parent_context = parent_context_prefix(element) build_position_selector(element, index, parent_context) end |
#same_key_attributes?(elem1, elem2) ⇒ Boolean
Check if two elements have same key attributes
122 123 124 125 126 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 122 def same_key_attributes?(elem1, elem2) return false unless elem1.name == elem2.name elem1.name == "input" ? elem1["type"] == elem2["type"] : true end |
#significant_class?(class_name) ⇒ Boolean
Check if class name is likely to be meaningful/specific
55 56 57 58 59 60 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 55 def significant_class?(class_name) return false if class_name.length < 4 return false if generic_class?(class_name) class_name.match?(/[a-z]+[-_]?[a-z]+/i) end |
#stable_id?(element) ⇒ Boolean
Check if element has stable (non-React) ID
82 83 84 85 |
# File 'lib/omniai/tools/browser/selector_generator/contextual_selectors.rb', line 82 def stable_id?(element) id = element["id"] id && !id.empty? && !id.match?(/^:r[0-9a-z]+:$/i) end |