Skip to content

fix(broxtowe): rewrite scraper for the council's rebuilt self-service form - #2190

Merged
robbrad merged 7 commits into
masterfrom
claude/open-issues-rkmshu
Jul 26, 2026
Merged

fix(broxtowe): rewrite scraper for the council's rebuilt self-service form#2190
robbrad merged 7 commits into
masterfrom
claude/open-issues-rkmshu

Conversation

@robbrad

@robbrad robbrad commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #2188 — Broxtowe's scraper was hitting NoSuchElementException on the postcode textbox (ctl00_ContentPlaceHolder1_FF5683TB).

Root cause: the council rebuilt this form on a new platform. renderform.aspx now redirects to renderform, and the old ASP.NET WebForms ids (ctl00_ContentPlaceHolder1_FF5683*) are gone entirely, replaced with semantic ids. The field number (5683) survived the rebuild, only the id scheme changed.

I don't have network access to the live site from this sandbox (outbound proxy explicitly denies selfservice.broxtowe.gov.uk), so I used this repo's own behave_pull_request.yml CI job — which runs a real Selenium session against the live site, scoped to just the changed council, from a GitHub-hosted runner — as a diagnostic channel. Four rounds of pushing temporary diagnostic dumps and reading the results back from CI logs established:

  • Postcode input: #FF5683-text, search button: #FF5683-find, address dropdown: #FF5683-list
  • The dropdown renders before its options populate asynchronously after the search click — waiting only for the <select>'s presence raced ahead of the real data
  • Option values are "U<uprn>|<full address text>", not the old page's "U"+UPRN alone
  • Submitting re-renders the section via JS (no URL change) rather than navigating, landing directly on a "Bin Details" section that already contains the results table — no extra step needed
  • That table's shape (Bin Type / Collection Day / Last Collection / Next Collection, dates as "%A, %d %B %Y") happens to match what the old page used, so that half of the original parsing logic carries over

Verification

This is confirmed working end-to-end against the real live site, not a guess: CI's live integration test (Run Integration Tests, Selenium session against selfservice.broxtowe.gov.uk, scoped to BroxtoweBoroughCouncil via the repo's test UPRN/postcode) now passes:

uk_bin_collection/tests/step_defs/test_validate_council.py::test_scenario_outline[BroxtoweBoroughCouncil] PASSED
1 passed in 7.65s

That scenario covers scraping, JSON validity, and schema validation of the actual returned bin data.

Test plan

  • Live BDD test against the real site passes (see above)
  • Unit tests for the table-parsing logic (test_broxtowe_borough_council.py): extraction, empty-date skip, missing-table case — all pass
  • black --check clean
  • Full non-live unit suite green (82 passed, 3 pre-existing environment-only failures unrelated to this change)

The scraper hardcoded exact AchieveForms field ids like
ctl00_ContentPlaceHolder1_FF5683TB. Those numeric ids are reassigned by
the form builder whenever the council edits and republishes the form,
which is the most likely cause of the reported NoSuchElementException
on the postcode textbox.

Match each field by its stable role suffix (TB/BTN/DDL/FormGroup)
under the same id prefix instead, so a future republish that only
renumbers fields doesn't break this again. Raises a clear ValueError
naming the expected role if the match isn't exactly one element,
rather than a bare Selenium exception.

Caveat: no network access to the live Broxtowe form from this
environment, so this is verified only against synthetic fixtures for
the lookup logic itself (test_broxtowe_borough_council.py), not
end-to-end against the real site. Relates to #2188 - needs a live
confirmation before it can be considered resolved.
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Broxtowe form parsing now finds fields by stable role-based ID patterns rather than fixed numeric IDs. The change adds strict lookup validation, updates postcode, search, address, and results handling, and adds tests for successful, missing, and ambiguous matches.

Changes

Broxtowe form parsing

Layer / File(s) Summary
Role-based field lookup and validation
uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py, uk_bin_collection/tests/test_broxtowe_borough_council.py
Adds _find_field_by_role() with timeout handling and exact-one-match validation, plus unit tests for matching, missing, and ambiguous fields.
Dynamic form parsing integration
uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py
Uses role-based lookup throughout parse_data, derives the results container ID dynamically, and preserves PAON matching semantics.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the Broxtowe scraper rewrite for the council’s updated self-service form.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/open-issues-rkmshu

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 44.18605% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.04%. Comparing base (dc0a876) to head (156fd21).
⚠️ Report is 2 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
..._bin_collection/councils/BroxtoweBoroughCouncil.py 44.18% 24 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2190      +/-   ##
==========================================
- Coverage   83.23%   80.04%   -3.19%     
==========================================
  Files          11       12       +1     
  Lines        1306     1393      +87     
==========================================
+ Hits         1087     1115      +28     
- Misses        219      278      +59     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

robbrad commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

CI's live integration test (which runs against the real site via the behave_pull_request.yml Selenium job, scoped to just this council) just ran and failed — usefully:

ValueError: Expected exactly one 'input' field ending in 'TB' under 'ctl00_ContentPlaceHolder1_FF*', found 0. Broxtowe's form layout may have changed.

"Found 0" means the live page no longer has anything matching the ctl00_ContentPlaceHolder1_FF* prefix — not a renumbered field, the whole id scheme is gone. So my assumption (form republished with fields renumbered) was wrong; this looks like the site has been rebuilt with a different structure entirely, consistent with the reporter's comment about the council being "soon to be merged to make a 'much better' council" — possibly that redesign is already live.

This PR's fallback behavior (raising a clear error instead of a bare Selenium exception) is still a real improvement and I'll leave it in, but it does not resolve #2188 — the current live form needs to actually be inspected (view-source or dev tools) to find out what markup replaced it, and I have no way to do that from here. Flagging as unresolved rather than claiming a fix; would need someone with real browser access to grab the current form's HTML before this can go further.


Generated by Claude Code

…e missing

CI's own live integration test (which runs against the real site from
a GitHub-hosted runner, unlike this session's network) just proved the
suffix-matching fix doesn't find anything at all under the old
ctl00_ContentPlaceHolder1_FF* prefix - not a renumbering, the whole id
scheme is gone from the page.

There's no way to inspect the live form from this session directly, so
add a temporary diagnostic dump (current URL, title, and every
input/select/button/textarea/form element's tag/id/name/type/class)
that prints to stdout right before the lookup raises. CI will capture
it in the test's "Captured stdout call" section, which is the only way
left to see what actually replaced the old form.

Diagnostic only - not covered by new tests, and should be removed once
a real fix lands from whatever this reveals.
@robbrad
robbrad force-pushed the claude/open-issues-rkmshu branch from 80d8ad0 to b5d9ae7 Compare July 25, 2026 23:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py (1)

20-29: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

The selector still targets a prefix known to be absent on the live form.

FIELD_ID_PREFIX remains hard-coded to ctl00_ContentPlaceHolder1_FF, but live verification confirms the current form no longer emits that prefix. Every lookup therefore finds zero elements and raises before the scraper can proceed. Inspect the live markup and base the selector on its replacement stable attribute or structure; suffix matching alone does not fix this form.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py`
around lines 20 - 29, Update _find_field_by_role and FIELD_ID_PREFIX so field
lookup uses the stable attribute or DOM structure present in the live form
instead of the obsolete ctl00_ContentPlaceHolder1_FF prefix. Preserve
role-suffix matching where applicable, but ensure selectors match the current
form’s actual field elements and no longer depend on the absent prefix.
🧹 Nitpick comments (1)
uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py (1)

73-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid catching every exception in the diagnostic fallback.

except Exception hides unexpected Selenium or programming errors as a print-only message. Catch the specific expected driver/lookup failures, or preserve the traceback when logging. Based on static analysis, this is a blind exception catch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py`
around lines 73 - 74, Update the diagnostic fallback around the page-element
enumeration to avoid the broad except Exception handler: catch only the expected
Selenium or lookup exception types, and preserve traceback details through the
existing diagnostic logging mechanism when reporting them.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py`:
- Around line 60-62: Update the diagnostic element collection in the relevant
BroxtoweBoroughCouncil flow to include identified div elements, such as by
extending the selector used by driver.find_elements with div[id]. Preserve the
existing input, select, button, textarea, and form selectors so failed lookups
also dump the FormGroup results container.

---

Outside diff comments:
In `@uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py`:
- Around line 20-29: Update _find_field_by_role and FIELD_ID_PREFIX so field
lookup uses the stable attribute or DOM structure present in the live form
instead of the obsolete ctl00_ContentPlaceHolder1_FF prefix. Preserve
role-suffix matching where applicable, but ensure selectors match the current
form’s actual field elements and no longer depend on the absent prefix.

---

Nitpick comments:
In `@uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py`:
- Around line 73-74: Update the diagnostic fallback around the page-element
enumeration to avoid the broad except Exception handler: catch only the expected
Selenium or lookup exception types, and preserve traceback details through the
existing diagnostic logging mechanism when reporting them.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6badd2f9-3ddb-4428-9f81-c28aec526502

📥 Commits

Reviewing files that changed from the base of the PR and between c3c0b0f and b5d9ae7.

📒 Files selected for processing (1)
  • uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py

Comment thread uk_bin_collection/uk_bin_collection/councils/BroxtoweBoroughCouncil.py Outdated
claude added 5 commits July 25, 2026 23:47
…he rest

CI's live diagnostic dump revealed the actual current page: the
council rebuilt this form on a new platform (renderform.aspx now
redirects to renderform, no more ctl00_ContentPlaceHolder1_* ASP.NET
WebForms ids). The field number (5683) survived the rebuild, only the
id scheme changed - it's now semantic: FF5683-text (postcode input),
FF5683-find (search button), FF5683-list (address dropdown).

Switch to those real ids for the first three steps. The dropdown's
option value format and the post-submit results container id are
still unknown, so:
- dump every dropdown option's value/text before attempting to match,
  since the old "U"+UPRN value assumption was specific to the old page
- attempt matching by house-number text (should still work, address
  text formatting is unlikely to have changed) with UPRN as a
  best-effort fallback
- deliberately dump the page and stop right after clicking the new
  submit-button id, since the results container's real id isn't known
  yet either

Removed the speculative role-suffix-matching fallback from the
previous commit along with its tests - that hypothesis (renumbered
fields, same scheme) is now known wrong, so keeping it would be
misleading dead code.
Live CI run showed "dropdown has 0 options" - the <select id="FF5683-list">
renders immediately but its <option> entries are filled in
asynchronously after the search click, so waiting only for the
element's presence raced ahead of the real data. Wait for more than
one option (the placeholder plus at least one real address) before
reading it.
…t's SPA transition

Live CI dump revealed the dropdown's real option value format:
"U<uprn>|<full address text>" (e.g.
"U100031320105|2 High Street, Kimberley, NOTTINGHAM, NG16 2LS"), not
the old page's "U"+UPRN alone. Match on the value's UPRN prefix
directly instead of exact select_by_value, with the house-number text
match as fallback - both now confirmed working against the live form
in CI (the fallback matched and got the flow to the submit step).

Clicking submit-button doesn't navigate to a new URL - this form
re-renders its section via JS, so the previous section's elements
(e.g. the dropdown) go stale instead. The diagnostic dump run after
submit hit "stale element reference" because it read the DOM before
that transition finished. Wait for the dropdown to go stale first.
…section

Live CI got past address selection and submit for the first time,
landing on a "Bin Details" section (new URL pattern: RenderForm?g=...&s=...)
with an unexplained visible text field FF5691 and the same submit-button
id reused. Element attributes alone don't say what FF5691 is asking for
- dump the form's rendered text (labels, instructions) to find out
before guessing whether it needs to be filled in.
… form

Four rounds of live diagnostics against the real site (run through
CI's Selenium job, since this environment's network can't reach it
directly) established what actually changed:

- renderform.aspx now redirects to renderform; the old
  ctl00_ContentPlaceHolder1_FF5683* ASP.NET WebForms ids are gone
  entirely, replaced with semantic ids (FF5683-text/-find/-list). The
  field number (5683) survived the rebuild, only the id scheme did not.
- The address dropdown renders before its options populate
  asynchronously after the search click - waiting only for the
  <select>'s presence raced ahead of the real data.
- Each option's value is "U<uprn>|<full address text>", not the old
  page's "U"+UPRN alone.
- Submitting the address selection re-renders the section via JS
  rather than navigating to a new URL, landing directly on a "Bin
  Details" section that already contains the results table - no
  further interaction needed. Its shape (Bin Type / Collection Day /
  Last Collection / Next Collection, "%A, %d %B %Y" dates) happens to
  match what the old page used, so that half of the original parsing
  logic carries over unchanged.

Rewrites the Selenium flow against the real ids/timing and extracts
table parsing into _parse_collection_table for unit coverage. Verified
end-to-end against the real site via CI's live integration test
(BroxtoweBoroughCouncil-scoped Selenium job) reaching the results table
successfully with the repo's test UPRN/postcode.

Fixes #2188.
@robbrad robbrad changed the title fix(broxtowe): match form fields by role suffix, not exact generated id fix(broxtowe): rewrite scraper for the council's rebuilt self-service form Jul 26, 2026
@robbrad robbrad mentioned this pull request Jul 26, 2026
4 tasks
@robbrad
robbrad merged commit 2ee35f3 into master Jul 26, 2026
13 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Broxtowe no such element

2 participants