mpfaffenberger / mpfaffenberger/code_puppy
tools/browser/: 8 find_by_* tools repeat the same 50-line locator scaffold (and ~30 page-guard/except blocks across the package) — extract a locator-query helper
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 814
- Forks
- 278
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 76
Description
Severity: Medium (DRY; pure boilerplate multiplication)
The clone family
Every element-discovery tool in tools/browser/browser_locators.py is the same function with a different one-line locator expression:
find_by_role(13-63)find_by_text(66-115)find_by_label(118-178)find_by_placeholder(181-237)find_by_test_id(240-293)run_xpath_query(296-349)find_buttons(352-400)find_links(403-454)
jscpd flags 136-146 ↔ 199-209 and 259-268 ↔ 317-326, but those are just the visible tips — each function repeats the full scaffold verbatim:
group_id = generate_group_id("browser_<x>", ...)
emit_info(f"BROWSER <X> <emoji> ...", message_group=group_id)
try:
browser_manager = get_session_browser_manager()
page = await browser_manager.get_current_page()
if not page:
return {"success": False, "error": "No active browser page available"}
locator = page.get_by_<x>(...) # <-- the only real difference
await locator.first.wait_for(state="visible", timeout=timeout)
count = await locator.count()
elements = []
for i in range(min(count, 10)):
element = locator.nth(i)
if await element.is_visible():
...collect a few attributes...
emit_success(f"Found {count} ...", message_group=group_id)
return {"success": True, ..., "count": count, "elements": elements}
except Exception as e:
return {"success": False, "error": str(e), ...}
Then register_find_by_* (457-640) adds 8 more wrappers whose bodies are return await find_by_x(...) — ~180 further lines of pass-through.
Evidence of drift (where bugs hide)
find_by_rolewaits for visibility;find_buttons/find_linksdon't (wait_formissing) yet still accept atimeoutparam that is silently unused — callers passingtimeout=to those two get no timeout behavior at all.- Result-cap inconsistency: 10 elements for the find_by_* family, 20 for buttons/links; some truncate text to 100 chars (
run_xpath_query), others return full text. find_buttons/find_links:text_filter.lower() not in text.lower()raisesTypeErroriftext_content()returnsNone— fixed nowhere because there's no single place to fix it.- Across the whole package the same scaffold repeats:
"No active browser page available"appears 30 times in 6 files;except Exception as e: return {"success": False, "error": str(e), ...}44 times.
Proposed extraction
One generic query helper in browser_locators.py:
async def _query_locator(
*,
tool_name: str, # for group id + emit messages
make_locator: Callable[[Page], Locator],
describe: Callable[[Locator], Awaitable[dict]], # per-element attribute collector
result_key: str,
extra_result: dict | None = None,
timeout: int = 10000,
limit: int = 10,
wait_visible: bool = True,
) -> Dict[str, Any]: ...
Each tool becomes ~5 lines: build the locator lambda + an attribute collector. The page-guard/try-except/emit scaffold lives once; and a package-level @browser_tool decorator (or async with browser_page(tool_name) as (page, group_id): context manager) can absorb the same boilerplate in browser_interactions/scripts/navigation as a follow-up. The 8 register_* pass-throughs can collapse into a loop over a registration table, or register the underlying functions directly.
This file is 640 lines (over the repo's 600-line cap, cf. #436); the extraction would roughly halve it while making the timeout/visibility inconsistencies impossible.
Filed by Zen Reviewer A (code-puppy-60635a) — DRY review round
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in tools/browser/browser_locators.py with the eight locator functions and the register_find_by_* wrappers, comparing their timeout, visibility, result-limit, and text-handling behavior. Extract the shared query scaffold without changing the locator-specific results, then verify all registrations and the documented inconsistencies are handled consistently; the package-wide page-guard extraction is described as follow-up work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- tooling
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100