get_final_path_from_real_time normalises elapsed time onto the sigmoid's
input range with a 4.5 multiplier, and logistic_sigmoid(4.5) is 0.978, so
the bezier was never evaluated at its endpoint. move_mouse then looped on
`while time.monotonic() < end_time`, so it never asked for a sample at
movement_time either and the `t > movement_time` branch was unreachable
from there. The pointer was left short of the point move_to_element chose,
on every move.
Clamping the loop's last sample to movement_time is the smaller half of
this, but on its own it only recovers 0.05px of a 2.8px median offset,
since the sigmoid is what the rest of the shortfall comes from. So the
path now hands back the endpoint once t reaches movement_time rather than
only once it passes it, and the clamp is what makes that branch reachable.
Measured over 6000 simulated moves at 200px travel, against a 10x10
control: median offset 2.82px and 13.02% of clicks landing outside the
element before, 0.00px and 0.00% after.
Also comments the 4.5 where it is, since the two are only correct
together.
The cleanup after each task called close_all_other_tabs() with no arguments.
That defaults `exceptions` to the currently focused handle, so when a task died
with a Bing tab in focus the cleanup kept the Bing tab and closed the Rewards
one. Every later task then looked for Rewards controls on a search results page
and reported [SKIP] for a UI that was present and working the whole time. One
failure cost the rest of the run.
Measured in the container rather than reasoned about: with a Rewards tab and a
Bing tab open and the Bing one focused, close_all_other_tabs() leaves
['https://www.bing.com/search?q=weather'] behind and the Rewards handle is gone.
Two separate things have to hold before the next task can work, and they failed
independently. The right tab has to survive, so restore_main_tab names the
handle recorded at startup instead of trusting whatever happens to be focused.
The surviving tab also has to be showing the right page, which the tab fix alone
does not give you: run_search_batch navigates the main tab to bing.com itself,
so a failure part way through that batch strands the main tab on a search page
with no second tab involved at all.
Navigating only happens when the step did not finish, and is skipped when the
browser is already on the Rewards host, so a clean run navigates nowhere and a
panel that merely failed to render does not cost a page load.
Both recoveries swallow their own errors. Raising here would replace the real
failure with the tidy-up's, which is why the close_all_other_tabs call was
wrapped in the first place.
End to end in a real browser: two tabs with the Bing one focused become one tab,
the original handle, on rewards.bing.com/about (the logged out redirect), and a
main tab stranded on bing.com recovers too. Fifteen unit tests cover the tab
choice, the missing-main-tab fallback, the failure and skip paths, a clean run
navigating nowhere, and a driver too broken to do either.
Refs #63
Claude-Session: https://claude.ai/code/session_0113hp36oUBeMqG7twfuC8rE
Every failed task printed the same reason, because the blanket catch in
complete_all_tasks treated NoSuchElementException and TimeoutException as one
finding. A wait that expires says nothing about whether the section was there,
since wait_for_element swallowed whatever the getter raised and returned False.
A market that does not ship visual search and a points panel that was on screen
and slow both arrived as TimeoutException
wait_for_element now keeps the last reason the getter gave and raises
ElementNeverAppeared when nothing was in the DOM for the whole wait.
_container_by_id raises ElementNotReady when the id is present but no visible
copy has content yet, which is the hydrating case it used to report as missing.
Both subclass what they refine, so claim_bonus_points and
complete_bing_daily_set keep working untouched
A task that was reached and then ran out of time is now a [FAIL] rather than a
[SKIP], because it may have left points behind. Absence stays a [SKIP]
56 tests pass, 20 of them new. A mutation check backs them, forcing every
expired wait to count as absence fails 2, and collapsing the two branches back
into one [SKIP] fails 4
Refs #52
Matching on bing.com/search alone was too narrow. Turn referrals into rewards is a real daily set activity that awards points and it points at a rewards url, so the filter skipped it and the run came up short without saying why.
Three shapes now count, bing.com/search, bing.com/rewards and rewards.bing.com. The bing app promo from #45 sits on bingapp.microsoft.com and stays out of all three, which is what keeps that fix intact. Five tests, one per shape plus a mixed panel.
#50 landed a tests directory using stdlib unittest, discovered with
python -m unittest discover -s tests. The multi-account checks were a
standalone script with their own runner, so discovery would have walked
straight past them.
Same cases, rewritten as TestCases. The layer that starts Edge twice is
behind REWARDS_BROWSER_TESTS rather than a --browser argument, since
discovery does not pass arguments through.
Claude-Session: https://claude.ai/code/session_019PWUtibJn81iDbxZ4hzeU3
Five layers, cheapest first: which accounts a configuration produces, the
flags each one hands Edge, the run loop's ordering and exit codes, one
account failing every way it can without ending the batch, and two real
Edge profiles holding two independent, persistent identities.
Layers 1 to 4 need nothing installed beyond selenium and run in a second.
Layer 5 starts Edge twice and reaches bing.com, so it is opt in behind
--browser. It keys on bing.com's own MUID rather than an injected cookie:
a cookie added through webdriver is not written to the profile the way a
Set-Cookie is, so it would prove nothing about a sign-in surviving.
Claude-Session: https://claude.ai/code/session_019PWUtibJn81iDbxZ4hzeU3
Four bugs that reached real runs are covered: a point value the parser could not read, the wrong row of the breakdown panel, a container that looks right but is empty, and a label that matches two different buttons.
Stdlib unittest with small fakes for the selenium calls the selectors make, so this adds no dependency and needs no browser. Run with python -m unittest discover -s tests.