mirror of
https://github.com/User0332/rewards-farmer.git
synced 2026-09-16 01:31:36 +00:00
275 lines
8.5 KiB
Python
275 lines
8.5 KiB
Python
"""Tests for getting back to a known page after a task fails.
|
|||
|
|
|
||
|
|
The bug (#63): a task that dies partway through leaves the browser wherever it
|
||
|
|
stopped, which for the search and visual search tasks is a Bing page rather than
|
||
|
|
the Rewards site. Every later task begins by looking for controls that only
|
||
|
|
exist on rewards.bing.com, so one failure made all of them report [SKIP] for a
|
||
|
|
UI that was present and working the whole time.
|
||
|
|
|
||
|
|
The stand-ins here are local rather than in fakes.py: that module fakes the DOM
|
||
|
|
lookups the selectors do, and what these need is a driver that remembers where
|
||
|
|
it navigated. No browser, so they run anywhere.
|
||
|
|
|
||
|
|
python -m unittest discover -s tests
|
||
|
|
"""
|
||
|
|
|
||
|
|
import logging
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import unittest
|
||
|
|
|
||
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||
|
|
|
||
|
|
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
|
||
|
|
|
||
|
|
import rewards_tasks
|
||
|
|
|
||
|
|
HOME = rewards_tasks.REWARDS_HOME_URL
|
||
|
|
BING_RESULTS = "https://www.bing.com/search?q=weather"
|
||
|
|
|
||
|
|
|
||
|
|
MAIN_TAB = "main-tab"
|
||
|
|
BING_TAB = "bing-tab"
|
||
|
|
|
||
|
|
|
||
|
|
class RecordingDriver:
|
||
|
|
def __init__(self, url=HOME, get_raises=None, handles=None, current_handle=MAIN_TAB):
|
||
|
|
self.current_url = url
|
||
|
|
self.visited = []
|
||
|
|
self.get_raises = get_raises
|
||
|
|
self.window_handles = list(handles) if handles is not None else [MAIN_TAB]
|
||
|
|
self.current_window_handle = current_handle
|
||
|
|
|
||
|
|
def get(self, url):
|
||
|
|
if self.get_raises is not None:
|
||
|
|
raise self.get_raises
|
||
|
|
|
||
|
|
self.visited.append(url)
|
||
|
|
self.current_url = url
|
||
|
|
|
||
|
|
|
||
|
|
class RecordingTabUtils:
|
||
|
|
def __init__(self):
|
||
|
|
self.closes = 0
|
||
|
|
self.focuses = 0
|
||
|
|
self.kept = []
|
||
|
|
|
||
|
|
def close_all_other_tabs(self, exceptions=None):
|
||
|
|
self.closes += 1
|
||
|
|
self.kept.append(exceptions)
|
||
|
|
|
||
|
|
def ensure_focus(self):
|
||
|
|
self.focuses += 1
|
||
|
|
|
||
|
|
|
||
|
|
class StubTasks(rewards_tasks.RewardsTaskUtils):
|
||
|
|
"""RewardsTaskUtils with the six tasks replaced, and nothing else stubbed.
|
||
|
|
|
||
|
|
complete_all_tasks and return_to_rewards_home are the real ones, which is
|
||
|
|
the whole point.
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __init__(self, driver, failures=None):
|
||
|
|
self.driver = driver
|
||
|
|
self.tab_utils = RecordingTabUtils()
|
||
|
|
self.main_window = MAIN_TAB
|
||
|
|
self.failures = failures or {}
|
||
|
|
self.ran = []
|
||
|
|
|
||
|
|
def _task(self, name, strands_browser_at=None):
|
||
|
|
self.ran.append(name)
|
||
|
|
|
||
|
|
if name not in self.failures:
|
||
|
|
# A task that finishes switches back to the Rewards tab itself, so
|
||
|
|
# only a failure part way through leaves the browser elsewhere.
|
||
|
|
return
|
||
|
|
|
||
|
|
if strands_browser_at is not None:
|
||
|
|
self.driver.current_url = strands_browser_at
|
||
|
|
|
||
|
|
raise self.failures[name]
|
||
|
|
|
||
|
|
def complete_bing_daily_set(self):
|
||
|
|
self._task("Bing daily set")
|
||
|
|
|
||
|
|
def complete_explore_on_bing_tasks(self):
|
||
|
|
self._task("Explore on Bing", strands_browser_at=BING_RESULTS)
|
||
|
|
|
||
|
|
def complete_visual_search(self):
|
||
|
|
self._task("Visual search", strands_browser_at=BING_RESULTS)
|
||
|
|
|
||
|
|
def complete_misc_cards(self):
|
||
|
|
self._task("Misc cards")
|
||
|
|
|
||
|
|
def complete_required_searches(self):
|
||
|
|
self._task("Required searches")
|
||
|
|
|
||
|
|
def claim_bonus_points(self):
|
||
|
|
self._task("Bonus points")
|
||
|
|
|
||
|
|
|
||
|
|
ALL_TASKS = [
|
||
|
|
"Bing daily set", "Explore on Bing", "Visual search",
|
||
|
|
"Misc cards", "Required searches", "Bonus points",
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
class FailedTaskRecoveryTests(unittest.TestCase):
|
||
|
|
def test_a_failure_on_a_bing_page_navigates_back(self):
|
||
|
|
driver = RecordingDriver()
|
||
|
|
tasks = StubTasks(driver, {"Visual search": TimeoutException("no file input")})
|
||
|
|
|
||
|
|
with self.assertLogs(rewards_tasks.logger, level=logging.WARNING):
|
||
|
|
tasks.complete_all_tasks()
|
||
|
|
|
||
|
|
self.assertEqual(driver.visited, [HOME])
|
||
|
|
self.assertEqual(driver.current_url, HOME)
|
||
|
|
|
||
|
|
def test_the_later_tasks_still_run(self):
|
||
|
|
# The point of the fix: one failure used to cost every task after it.
|
||
|
|
driver = RecordingDriver()
|
||
|
|
tasks = StubTasks(driver, {"Explore on Bing": TimeoutException("slow panel")})
|
||
|
|
|
||
|
|
with self.assertLogs(rewards_tasks.logger, level=logging.WARNING):
|
||
|
|
tasks.complete_all_tasks()
|
||
|
|
|
||
|
|
self.assertEqual(tasks.ran, ALL_TASKS)
|
||
|
|
|
||
|
|
def test_a_skip_recovers_too(self):
|
||
|
|
# NoSuchElementException is the [SKIP] path rather than [FAIL], and it
|
||
|
|
# strands the browser just the same.
|
||
|
|
driver = RecordingDriver()
|
||
|
|
tasks = StubTasks(driver, {"Visual search": NoSuchElementException("no sidebar")})
|
||
|
|
|
||
|
|
with self.assertLogs(rewards_tasks.logger, level=logging.WARNING):
|
||
|
|
tasks.complete_all_tasks()
|
||
|
|
|
||
|
|
self.assertEqual(driver.visited, [HOME])
|
||
|
|
|
||
|
|
def test_a_clean_run_never_navigates(self):
|
||
|
|
# Six extra page loads a run would be a real cost for no benefit.
|
||
|
|
driver = RecordingDriver()
|
||
|
|
tasks = StubTasks(driver)
|
||
|
|
|
||
|
|
tasks.complete_all_tasks()
|
||
|
|
|
||
|
|
self.assertEqual(driver.visited, [])
|
||
|
|
self.assertEqual(tasks.ran, ALL_TASKS)
|
||
|
|
|
||
|
|
def test_no_redundant_navigation_when_already_home(self):
|
||
|
|
# Plenty of failures happen on the Rewards page itself, e.g. a panel
|
||
|
|
# that never renders. Reloading it would only cost time.
|
||
|
|
driver = RecordingDriver()
|
||
|
|
tasks = StubTasks(driver, {"Misc cards": TimeoutException("no cards")})
|
||
|
|
|
||
|
|
with self.assertLogs(rewards_tasks.logger, level=logging.WARNING):
|
||
|
|
tasks.complete_all_tasks()
|
||
|
|
|
||
|
|
self.assertEqual(driver.visited, [])
|
||
|
|
|
||
|
|
def test_tabs_are_still_tidied_after_a_failure(self):
|
||
|
|
driver = RecordingDriver()
|
||
|
|
tasks = StubTasks(driver, {"Visual search": TimeoutException("no file input")})
|
||
|
|
|
||
|
|
with self.assertLogs(rewards_tasks.logger, level=logging.WARNING):
|
||
|
|
tasks.complete_all_tasks()
|
||
|
|
|
||
|
|
self.assertEqual(tasks.tab_utils.closes, len(ALL_TASKS))
|
||
|
|
|
||
|
|
def test_the_rewards_tab_is_the_one_kept(self):
|
||
|
|
# The actual bug: close_all_other_tabs() with no argument keeps whatever
|
||
|
|
# is focused, and after a task dies on a Bing tab that is the Bing tab,
|
||
|
|
# so the cleanup closed the Rewards tab and kept the search results.
|
||
|
|
driver = RecordingDriver(handles=[MAIN_TAB, BING_TAB], current_handle=BING_TAB)
|
||
|
|
tasks = StubTasks(driver, {"Visual search": TimeoutException("no file input")})
|
||
|
|
|
||
|
|
with self.assertLogs(rewards_tasks.logger, level=logging.WARNING):
|
||
|
|
tasks.complete_all_tasks()
|
||
|
|
|
||
|
|
self.assertTrue(all(kept == [MAIN_TAB] for kept in tasks.tab_utils.kept))
|
||
|
|
|
||
|
|
|
||
|
|
class RestoreMainTabTests(unittest.TestCase):
|
||
|
|
def test_names_the_main_tab_rather_than_the_focused_one(self):
|
||
|
|
driver = RecordingDriver(handles=[MAIN_TAB, BING_TAB], current_handle=BING_TAB)
|
||
|
|
tasks = StubTasks(driver)
|
||
|
|
|
||
|
|
tasks.restore_main_tab()
|
||
|
|
|
||
|
|
self.assertEqual(tasks.tab_utils.kept, [[MAIN_TAB]])
|
||
|
|
|
||
|
|
def test_falls_back_when_the_main_tab_is_gone(self):
|
||
|
|
# A task can close it, and a stale handle would only raise.
|
||
|
|
driver = RecordingDriver(handles=[BING_TAB], current_handle=BING_TAB)
|
||
|
|
tasks = StubTasks(driver)
|
||
|
|
|
||
|
|
tasks.restore_main_tab()
|
||
|
|
|
||
|
|
self.assertEqual(tasks.tab_utils.kept, [[BING_TAB]])
|
||
|
|
|
||
|
|
def test_no_windows_left_is_not_an_error(self):
|
||
|
|
driver = RecordingDriver(handles=[], current_handle=MAIN_TAB)
|
||
|
|
tasks = StubTasks(driver)
|
||
|
|
|
||
|
|
tasks.restore_main_tab()
|
||
|
|
|
||
|
|
self.assertEqual(tasks.tab_utils.kept, [])
|
||
|
|
|
||
|
|
def test_a_failure_to_tidy_is_survivable(self):
|
||
|
|
driver = RecordingDriver(handles=[MAIN_TAB])
|
||
|
|
tasks = StubTasks(driver)
|
||
|
|
|
||
|
|
def boom(exceptions=None):
|
||
|
|
raise WebDriverException("browser gone")
|
||
|
|
|
||
|
|
tasks.tab_utils.close_all_other_tabs = boom
|
||
|
|
|
||
|
|
with self.assertLogs(rewards_tasks.logger, level=logging.WARNING) as logged:
|
||
|
|
tasks.restore_main_tab()
|
||
|
|
|
||
|
|
self.assertTrue(any("Could not tidy" in line for line in logged.output))
|
||
|
|
|
||
|
|
|
||
|
|
class ReturnToRewardsHomeTests(unittest.TestCase):
|
||
|
|
def test_navigates_and_refocuses(self):
|
||
|
|
driver = RecordingDriver(url=BING_RESULTS)
|
||
|
|
tasks = StubTasks(driver)
|
||
|
|
|
||
|
|
tasks.return_to_rewards_home()
|
||
|
|
|
||
|
|
self.assertEqual(driver.visited, [HOME])
|
||
|
|
self.assertEqual(tasks.tab_utils.focuses, 1)
|
||
|
|
|
||
|
|
def test_already_home_is_left_alone(self):
|
||
|
|
driver = RecordingDriver(url=HOME + "?foo=1")
|
||
|
|
tasks = StubTasks(driver)
|
||
|
|
|
||
|
|
tasks.return_to_rewards_home()
|
||
|
|
|
||
|
|
self.assertEqual(driver.visited, [])
|
||
|
|
self.assertEqual(tasks.tab_utils.focuses, 0)
|
||
|
|
|
||
|
|
def test_a_driver_that_cannot_navigate_is_survivable(self):
|
||
|
|
# Recovery is best effort. Raising here would replace the real failure
|
||
|
|
# with the tidy-up's own, and take the rest of the run down with it.
|
||
|
|
driver = RecordingDriver(url=BING_RESULTS, get_raises=WebDriverException("browser gone"))
|
||
|
|
tasks = StubTasks(driver)
|
||
|
|
|
||
|
|
with self.assertLogs(rewards_tasks.logger, level=logging.WARNING) as logged:
|
||
|
|
tasks.return_to_rewards_home()
|
||
|
|
|
||
|
|
self.assertTrue(any("Could not return" in line for line in logged.output))
|
||
|
|
|
||
|
|
def test_a_failing_recovery_does_not_end_the_run(self):
|
||
|
|
driver = RecordingDriver(get_raises=WebDriverException("browser gone"))
|
||
|
|
tasks = StubTasks(driver, {"Explore on Bing": TimeoutException("slow panel")})
|
||
|
|
|
||
|
|
with self.assertLogs(rewards_tasks.logger, level=logging.WARNING):
|
||
|
|
tasks.complete_all_tasks()
|
||
|
|
|
||
|
|
self.assertEqual(tasks.ran, ALL_TASKS)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|