|
29 | 29 | from .customlocator import CustomLocator
|
30 | 30 |
|
31 | 31 |
|
| 32 | +class Finder(): |
| 33 | + def __int__(self): |
| 34 | + """Placeholder to Finder class instantation method """ |
| 35 | + pass |
| 36 | + |
| 37 | + def pre_find_action(self): |
| 38 | + """Placeholder for the pre_find_action method""" |
| 39 | + pass |
| 40 | + |
| 41 | + def find(self): |
| 42 | + """Placeholder for the find method""" |
| 43 | + pass |
| 44 | + |
| 45 | + def _is_webelement(self, element): |
| 46 | + # Hook for unit tests |
| 47 | + return isinstance(element, (WebElement, EventFiringWebElement)) |
| 48 | + |
| 49 | + |
| 50 | + def _parse_locator(self, locator): |
| 51 | + if re.match(r"\(*//", locator): |
| 52 | + return "xpath", locator |
| 53 | + index = self._get_locator_separator_index(locator) |
| 54 | + if index != -1: |
| 55 | + prefix = locator[:index].strip() |
| 56 | + if prefix in self._strategies: |
| 57 | + return prefix, locator[index + 1 :].lstrip() |
| 58 | + return "default", locator |
| 59 | + |
| 60 | + |
| 61 | +class FinderList(): |
| 62 | + def __iter__(self): |
| 63 | + pass |
| 64 | + |
| 65 | + def __getitem__(self, item): |
| 66 | + pass |
| 67 | + |
| 68 | + def __len__(self): |
| 69 | + pass |
| 70 | + |
| 71 | +class DefaultFinder(Finder): |
| 72 | + def find(self, locator, tag=None, first_only=True, required=True, parent=None): |
| 73 | + element_type = "Element" if not tag else tag.capitalize() |
| 74 | + if parent and not self._is_webelement(parent): |
| 75 | + raise ValueError( |
| 76 | + f"Parent must be Selenium WebElement but it was {type(parent)}." |
| 77 | + ) |
| 78 | + if self._is_webelement(locator): |
| 79 | + return locator |
| 80 | + prefix, criteria = self._parse_locator(locator) |
| 81 | + strategy = self._strategies[prefix] |
| 82 | + tag, constraints = self._get_tag_and_constraints(tag) |
| 83 | + elements = strategy(criteria, tag, constraints, parent=parent or self.driver) |
| 84 | + if required and not elements: |
| 85 | + raise ElementNotFound(f"{element_type} with locator '{locator}' not found.") |
| 86 | + if first_only: |
| 87 | + if not elements: |
| 88 | + return None |
| 89 | + return elements[0] |
| 90 | + return elements |
| 91 | + |
| 92 | + |
| 93 | + def _get_tag_and_constraints(self, tag): |
| 94 | + if tag is None: |
| 95 | + return None, {} |
| 96 | + tag = tag.lower() |
| 97 | + constraints = {} |
| 98 | + if tag == "link": |
| 99 | + tag = "a" |
| 100 | + if tag == "partial link": |
| 101 | + tag = "a" |
| 102 | + elif tag == "image": |
| 103 | + tag = "img" |
| 104 | + elif tag == "list": |
| 105 | + tag = "select" |
| 106 | + elif tag == "radio button": |
| 107 | + tag = "input" |
| 108 | + constraints["type"] = "radio" |
| 109 | + elif tag == "checkbox": |
| 110 | + tag = "input" |
| 111 | + constraints["type"] = "checkbox" |
| 112 | + elif tag == "text field": |
| 113 | + tag = "input" |
| 114 | + constraints["type"] = [ |
| 115 | + "date", |
| 116 | + "datetime-local", |
| 117 | + "email", |
| 118 | + "month", |
| 119 | + "number", |
| 120 | + "password", |
| 121 | + "search", |
| 122 | + "tel", |
| 123 | + "text", |
| 124 | + "time", |
| 125 | + "url", |
| 126 | + "week", |
| 127 | + "file", |
| 128 | + ] |
| 129 | + elif tag == "file upload": |
| 130 | + tag = "input" |
| 131 | + constraints["type"] = "file" |
| 132 | + elif tag == "text area": |
| 133 | + tag = "textarea" |
| 134 | + return tag, constraints |
| 135 | + |
| 136 | + |
| 137 | +class Strategies: |
| 138 | + def __init__(self): |
| 139 | + strategies = { |
| 140 | + "identifier": ElementFinder._find_by_identifier, |
| 141 | + "id": ElementFinder._find_by_id, |
| 142 | + "name": ElementFinder._find_by_name, |
| 143 | + "xpath": ElementFinder._find_by_xpath, |
| 144 | + "dom": ElementFinder._find_by_dom, |
| 145 | + "link": ElementFinder._find_by_link_text, |
| 146 | + "partial link": ElementFinder._find_by_partial_link_text, |
| 147 | + "css": ElementFinder._find_by_css_selector, |
| 148 | + "class": ElementFinder._find_by_class_name, |
| 149 | + "jquery": ElementFinder._find_by_jquery_selector, |
| 150 | + "sizzle": ElementFinder._find_by_jquery_selector, |
| 151 | + "tag": ElementFinder._find_by_tag_name, |
| 152 | + "scLocator": ElementFinder._find_by_sc_locator, |
| 153 | + "data": ElementFinder._find_by_data_locator, |
| 154 | + "default": ElementFinder._find_by_default, |
| 155 | + } |
| 156 | + self._strategies = NormalizedDict( |
| 157 | + initial=strategies, caseless=True, spaceless=True |
| 158 | + ) |
| 159 | + self._default_strategies = list(strategies) |
| 160 | + def register(self, strategy_name, strategy_keyword, persist=False): |
| 161 | + strategy = CustomLocator(self.ctx, strategy_name, strategy_keyword) |
| 162 | + if strategy.name in self._strategies: |
| 163 | + raise RuntimeError( |
| 164 | + f"The custom locator '{strategy.name}' cannot be registered. " |
| 165 | + "A locator of that name already exists." |
| 166 | + ) |
| 167 | + self._strategies[strategy.name] = strategy.find |
| 168 | + if is_falsy(persist): |
| 169 | + # Unregister after current scope ends |
| 170 | + events.on("scope_end", "current", self.unregister, strategy.name) |
| 171 | + |
| 172 | + def unregister(self, strategy_name): |
| 173 | + if strategy_name in self._default_strategies: |
| 174 | + raise RuntimeError( |
| 175 | + f"Cannot unregister the default strategy '{strategy_name}'." |
| 176 | + ) |
| 177 | + if strategy_name not in self._strategies: |
| 178 | + raise RuntimeError( |
| 179 | + f"Cannot unregister the non-registered strategy '{strategy_name}'." |
| 180 | + ) |
| 181 | + del self._strategies[strategy_name] |
| 182 | + |
| 183 | + |
| 184 | + |
32 | 185 | class ElementFinder(ContextAware):
|
33 | 186 | def __init__(self, ctx):
|
34 | 187 | ContextAware.__init__(self, ctx)
|
|
0 commit comments