**Approach (brief)** I’d build a helper that accepts an ordered list of selector tuples (strategy, value), tries each with a short configurable timeout, logs the successful selector, caches the resolved locator (optional), and raises a descriptive error if none match.**Python pseudocode**python
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class MultiLocator:
def __init__(self, driver, timeout=2, poll=0.2, cache=True, logger=None):
self.driver = driver
self.timeout = timeout
self.poll = poll
self.cache = {} if cache else None
self.logger = logger or (lambda msg: print(msg))
def _by(self, strategy):
mapping = {'id': By.ID, 'css': By.CSS_SELECTOR, 'xpath': By.XPATH, 'data-test-id': By.CSS_SELECTOR}
return mapping[strategy]
def find(self, selectors, parent=None):
key = tuple(selectors)
if self.cache is not None and key in self.cache:
self.logger(f"cache hit: {self.cache[key][0]} -> {self.cache[key][1]}")
return self.cache[key][1]
root = parent or self.driver
last_exceptions = []
for strat, val in selectors:
# support data-test-id -> [data-test-id="value"]
if strat == 'data-test-id':
val = f'[data-test-id="{val}"]'
by = self._by(strat)
try:
elem = WebDriverWait(root, self.timeout, self.poll).until(EC.presence_of_element_located((by, val)))
self.logger(f"found with {strat}: {val}")
if self.cache is not None:
self.cache[key] = ( (strat, val), elem )
return elem
except Exception as e:
last_exceptions.append((strat, val, str(e)))
raise LookupError(f"No selector matched. Tried: {last_exceptions}")
**Key concepts & reasoning**- Try selectors in order; short per-strategy timeout avoids long waits.- Map human-friendly strategies (data-test-id) to CSS.- Cache successful selector -> element to speed repeated lookups; validate stale element handling in tests.**Performance & caching considerations**- Short timeouts reduce overall latency but risk false negatives for slow pages; tune per app.- Cache entries can speed repeat finds but must handle StaleElementReferenceException (evict or re-find).- Parallel tests: avoid global shared cache; keep per-driver or thread-local cache.- Alternative: first try fast DOM queries (css/id) before xpath.**Edge cases**- Multiple matches: return first or expose find_all.- Shadow DOM / iframes need special handling (switch frames or shadow roots).