From 92159cbc1352518fdf5280862fdbe365d8783649 Mon Sep 17 00:00:00 2001 From: Ethan Stoner Date: Wed, 26 Aug 2026 14:38:46 -0700 Subject: [PATCH 1/3] wait for the breakdown panel's content, not just its container check_selectors reports FAILED for two selectors that are fine. The wait before them is satisfied by a placeholder. get_sidebar_section returns the first section whose id starts with react-aria, and that section is in the DOM as soon as the panel opens, holding a "Loading..." placeholder. So `wait_until(get_sidebar_section() is not None)` returns immediately, and the two selectors that read the panel's text then read "Loading..." and raise. The report's own output shows it: the section that resolves OK has the text "Loading...", and the two entries under it fail. Before, on a healthy en-US account: OK get_sidebar_section 'Loading...' FAILED get_points_earned_from_searches_on_points_breakdown FAILED get_close_button_on_points_breakdown OK=10 ABSENT=1 FAILED=2 After: OK get_sidebar_section "Points breakdown | Today's points | 480 | To" OK get_points_earned_from_searches_on_points_breakdown (25, 25) OK get_close_button_on_points_breakdown OK=12 ABSENT=1 FAILED=0 This matters more than a cosmetic miscount. The README asks people to paste this output into bug reports and says FAILED is what needs fixing, so a false FAILED sends both the reporter and whoever triages it after selectors that work. The bot itself was never affected, read_search_points reaches the same selector through wait_for_element and so does wait. Waiting on the content rather than the container keeps a genuine breakage reporting FAILED; it just costs the timeout first. --- src/check_selectors.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/check_selectors.py b/src/check_selectors.py index 7c5fc55..218b2be 100644 --- a/src/check_selectors.py +++ b/src/check_selectors.py @@ -184,6 +184,18 @@ def main(): driver.execute_script("arguments[0].click();", elements.get_points_breakdown_button()) wait_until(lambda: elements.get_sidebar_section() is not None, 30) + # The section exists before it has content: the panel renders a + # "Loading..." placeholder inside it first, and that satisfies the + # presence check above immediately. Waiting only for the section leaves + # the two selectors below reading an empty panel, so they report FAILED + # for markup that is fine, on a page that is merely slow. Wait for the + # content itself. A selector that really is broken still reports FAILED, + # it just costs the timeout first. + wait_until( + lambda: elements.get_points_earned_from_searches_on_points_breakdown() is not None, + 30, + ) + report.check("get_sidebar_section", elements.get_sidebar_section) report.check( "get_points_earned_from_searches_on_points_breakdown", From efe4b07eb81285524675803bc8d5bf469b27400b Mon Sep 17 00:00:00 2001 From: mardausdennis <71312763+mardausdennis@users.noreply.github.com> Date: Thu, 27 Aug 2026 00:38:58 +0200 Subject: [PATCH 2/3] wait for the daily set panel to fill, and give the breakdown time to render complete_bing_daily_set indexed [0] [1] [2] on whatever wait_for_element returned first. The panel hydrates progressively, so that can be a single activity, and the task died with IndexError before touching the other two. Wait for the full set, fall back to what is there, and re-read per index since a click can re-render the panel. read_search_points ran into the default 10s timeout because it starts after the earlier tasks navigated away, so the earn page re-renders from scratch first. That skipped the whole search task while points were still available. --- src/rewards_tasks.py | 46 +++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/rewards_tasks.py b/src/rewards_tasks.py index 55773db..c616da3 100644 --- a/src/rewards_tasks.py +++ b/src/rewards_tasks.py @@ -58,24 +58,39 @@ class RewardsTaskUtils: elem = self.wait_for_element(element_getter, timeout) self.move_to_and_click(elem) - def complete_bing_daily_set(self): + def complete_bing_daily_set(self, expected_activities: int = 3): self.switch_to_earn_page() self.wait_for_then_click(self.elements.get_open_daily_set_button) - daily_set_links = self.wait_for_element(self.elements.get_daily_set_elements) + # The panel hydrates progressively, so the first non-empty snapshot can + # hold fewer than 3 activities. wait_for_element returns on the first + # truthy result, so a 1-element list satisfied it and indexing [1] and + # [2] then raised IndexError, taking the whole task down. Wait for the + # full set instead, and if it never fills, work with what is there. + def full_activity_list(): + activities = self.elements.get_daily_set_elements() - self.move_to_and_click(daily_set_links[0]) - time.sleep(random.uniform(2, 3)) - self.driver.switch_to.window(self.driver.current_window_handle) # refocus on the main tab + return activities if len(activities) >= expected_activities else False - self.move_to_and_click(daily_set_links[1]) - time.sleep(random.uniform(2, 3)) - self.driver.switch_to.window(self.driver.current_window_handle) + try: + daily_set_links = self.wait_for_element(full_activity_list, timeout=30) + except TimeoutException: + daily_set_links = self.elements.get_daily_set_elements() - self.move_to_and_click(daily_set_links[2]) - time.sleep(random.uniform(2, 3)) - self.driver.switch_to.window(self.driver.current_window_handle) + print(f"[WARNING] Daily set panel only shows {len(daily_set_links)} of {expected_activities} activities") + + # Re-read the panel per index: clicking an activity can re-render it and + # stale the captured references. + for index in range(len(daily_set_links)): + activities = self.elements.get_daily_set_elements() + + if index >= len(activities): + break + + self.move_to_and_click(activities[index]) + time.sleep(random.uniform(2, 3)) + self.driver.switch_to.window(self.driver.current_window_handle) # refocus on the main tab self.tab_utils.close_all_other_tabs() @@ -198,9 +213,14 @@ class RewardsTaskUtils: def read_search_points(self): """Open the points breakdown, read the Bing search row, close it again.""" self.switch_to_earn_page() - self.wait_for_then_click(self.elements.get_points_breakdown_button) - close_btn = self.wait_for_element(self.elements.get_close_button_on_points_breakdown) + # 30s rather than the default 10s: this runs after the earlier tasks have + # navigated away, so the earn page re-renders from scratch first and the + # breakdown button regularly needs longer than 10s to appear. Timing out + # here skipped the entire search task while points were still available. + self.wait_for_then_click(self.elements.get_points_breakdown_button, timeout=30) + + close_btn = self.wait_for_element(self.elements.get_close_button_on_points_breakdown, timeout=15) points_earned, max_pts = self.elements.get_points_earned_from_searches_on_points_breakdown() From 414a6a589fbf34561ccd934d7212d6d912275d54 Mon Sep 17 00:00:00 2001 From: Carl Furtado Date: Wed, 26 Aug 2026 23:54:17 -0400 Subject: [PATCH 3/3] ensure image name is visual_search.jpg in readme --- .gitignore | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2cb64b9..0deacf8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.png +*.jpg *.txt !nouns.txt Todo.md diff --git a/README.md b/README.md index 7d77dab..7e49df7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ cd rewards-farmer You should also have an Ollama account created (for the LLM), the `ollama` tool installed, and you should have signed in to the Ollama CLI via the command line using `ollama signin`. This project will use a minimal amount of Ollama cloud usage using `gemma4:cloud`. If you wish to use a different model, please change the `model` parameter in the `get_ollama_response` function in `src/llm_utils.py`. -You must also provide an image for the script to upload to complete the visual search task. Currently, this image is named `keypress_times.png` and is located in the root directory of the project (yes, I used a random image from my keyboard analysis to do this). You may provide an image of your own, just ensure that the absolute path of the image is placed in the `VISUAL_SEARCH_IMAGE_PATH` constant at the top of `rewards_tasks.py`. +You must also provide an image for the script to upload to complete the visual search task. Currently, this image is named `visual_search.jpg` and is located in the root directory of the project. You may provide an image of your own, just ensure that the absolute path of the image is placed in the `VISUAL_SEARCH_IMAGE_PATH` constant at the top of `rewards_tasks.py`. Activate the virtual environment & install dependencies (you may have to use `python -m poetry` instead of `poetry`). You must have Python 3.12+ and Poetry installed.