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

Open
#462 0 comments 0 reactions 0 assignees View on GitHub

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_role waits for visibility; find_buttons/find_links don't (wait_for missing) yet still accept a timeout param that is silently unused — callers passing timeout= 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() raises TypeError if text_content() returns None — 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.