Files

263 lines
8.7 KiB
Python
Raw Permalink Normal View History

"""Tests for what a run says about a task that did not complete.
The reported reason used to be a guess. Every wait that expired and every
lookup that missed produced "not available in this UI variant", so a section
that was on the page and slow read exactly like one this market does not ship,
which is #52. These pin down which failures are absence and which are not.
None of them need a browser.
python -m unittest discover -s tests
"""
import logging
import os
import sys
import types
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
from element_selectors import ElementNotReady
from fakes import FakeDriver
from rewards_tasks import ElementNeverAppeared, task_failure_report
# Long enough for one poll, short enough that the suite stays quick.
# WebDriverWait sleeps 0.5s between attempts, so a wait that expires costs
# about that regardless of the timeout asked for.
BRIEF = 0.05
def make_tasks():
"""A RewardsTaskUtils without the browser its __init__ opens."""
tasks = rewards_tasks.RewardsTaskUtils.__new__(rewards_tasks.RewardsTaskUtils)
tasks.driver = FakeDriver()
tasks.tab_utils = types.SimpleNamespace(close_all_other_tabs=lambda: None)
return tasks
class WaitClassification(unittest.TestCase):
"""wait_for_element has to say why it gave up, not just that it did."""
def test_a_getter_that_never_finds_anything_is_absence(self):
def missing():
raise NoSuchElementException("no button containing 'visual search streak'")
with self.assertRaises(ElementNeverAppeared):
make_tasks().wait_for_element(missing, timeout=BRIEF)
def test_a_section_that_is_still_rendering_is_not_absence(self):
# The id is in the page, no visible copy has content yet. Waiting
# longer is the answer here, skipping the task is not.
def not_ready():
raise ElementNotReady("'moreactivities' is present but no visible copy has content yet")
with self.assertRaises(TimeoutException) as caught:
make_tasks().wait_for_element(not_ready, timeout=BRIEF)
self.assertNotIsInstance(caught.exception, ElementNeverAppeared)
def test_a_getter_that_rejects_what_it_finds_is_not_absence(self):
# complete_bing_daily_set holds out for all three activities and
# returns False until they are there. The panel itself is open.
with self.assertRaises(TimeoutException) as caught:
make_tasks().wait_for_element(lambda: [], timeout=BRIEF)
self.assertNotIsInstance(caught.exception, ElementNeverAppeared)
def test_an_element_that_arrives_late_is_still_returned(self):
attempts = []
def slow():
attempts.append(None)
if len(attempts) < 2:
raise NoSuchElementException("not yet")
return "the element"
self.assertEqual(make_tasks().wait_for_element(slow, timeout=5), "the element")
# A single lucky first attempt would prove nothing about the retry.
self.assertGreater(len(attempts), 1)
def test_the_getters_own_reason_survives(self):
def missing():
raise NoSuchElementException("no button containing 'points breakdown'")
with self.assertRaises(ElementNeverAppeared) as caught:
make_tasks().wait_for_element(missing, timeout=BRIEF)
self.assertIn("points breakdown", str(caught.exception))
class ExistingTimeoutHandlers(unittest.TestCase):
"""The new type has to stay catchable where TimeoutException was."""
def test_it_is_still_a_timeout(self):
self.assertTrue(issubclass(ElementNeverAppeared, TimeoutException))
def test_having_no_bonus_points_is_still_only_a_warning(self):
# There is no Claim button when there is nothing to claim, so this
# path reaches the wait expecting to be disappointed. If the new type
# escaped its `except TimeoutException`, an ordinary run would start
# reporting a failed task every day.
def bonus_button():
pass
def claim_button():
pass
tasks = make_tasks()
tasks.switch_to_dashboard = lambda: None
tasks.elements = types.SimpleNamespace(
get_bonus_button_on_dashboard=bonus_button,
get_claim_bonus_points_button=claim_button,
)
def wait_for_then_click(getter, timeout=10):
if getter is claim_button:
raise ElementNeverAppeared("nothing matched during the 10s wait")
tasks.wait_for_then_click = wait_for_then_click
with self.assertLogs(rewards_tasks.logger, level=logging.WARNING) as captured:
tasks.claim_bonus_points()
self.assertIn("no bonus points to claim", "\n".join(captured.output).lower())
class FailureReport(unittest.TestCase):
def test_a_section_this_variant_does_not_ship_is_skipped(self):
tag, reason = task_failure_report(
NoSuchElementException("no element with id 'moreactivities'")
)
self.assertEqual(tag, "SKIP")
self.assertIn("not available in this UI variant", reason)
def test_a_wait_that_never_saw_the_element_is_skipped(self):
tag, reason = task_failure_report(ElementNeverAppeared("nothing matched"))
self.assertEqual(tag, "SKIP")
self.assertIn("not available in this UI variant", reason)
def test_a_section_that_never_finished_rendering_is_not_skipped(self):
tag, reason = task_failure_report(
ElementNotReady("'moreactivities' is present but no visible copy has content yet")
)
self.assertEqual(tag, "FAIL")
self.assertNotIn("not available", reason)
def test_an_expired_wait_is_not_skipped(self):
# The line in #52, reported for a panel that was on screen the whole
# time: "[SKIP] Required searches: not available in this UI variant
# (TimeoutException)".
tag, reason = task_failure_report(TimeoutException("Message: "))
self.assertEqual(tag, "FAIL")
self.assertNotIn("not available", reason)
def test_the_exception_name_is_kept(self):
# It is the difference between a lookup that missed and a wait that
# expired, and someone pasting a log should not lose it.
self.assertIn("ElementNeverAppeared", task_failure_report(ElementNeverAppeared("x"))[1])
self.assertIn("TimeoutException", task_failure_report(TimeoutException("x"))[1])
def test_anything_else_keeps_its_own_message(self):
tag, reason = task_failure_report(WebDriverException("chrome not reachable"))
self.assertEqual(tag, "FAIL")
self.assertIn("WebDriverException", reason)
self.assertIn("chrome not reachable", reason)
class TaskLoop(unittest.TestCase):
"""complete_all_tasks, with the six tasks replaced by recorded calls."""
STEPS = (
("Bing daily set", "complete_bing_daily_set"),
("Explore on Bing", "complete_explore_on_bing_tasks"),
("Visual search", "complete_visual_search"),
("Misc cards", "complete_misc_cards"),
("Required searches", "complete_required_searches"),
("Bonus points", "claim_bonus_points"),
)
def setUp(self):
self.ran = []
def _tasks(self, failures=None):
failures = failures or {}
tasks = make_tasks()
for _, attribute in self.STEPS:
def step(name=attribute):
self.ran.append(name)
if name in failures:
raise failures[name]
setattr(tasks, attribute, step)
return tasks
def _run(self, failures=None):
with self.assertLogs(rewards_tasks.logger, level=logging.INFO) as captured:
self._tasks(failures).complete_all_tasks()
return "\n".join(captured.output)
def test_a_failing_task_does_not_stop_the_ones_after_it(self):
self._run({"complete_visual_search": WebDriverException("chrome not reachable")})
self.assertEqual(self.ran, [attribute for _, attribute in self.STEPS])
def test_absence_and_an_expired_wait_read_differently(self):
output = self._run({
"complete_visual_search": ElementNeverAppeared("nothing matched"),
"complete_required_searches": TimeoutException("Message: "),
})
self.assertIn("[SKIP] Visual search: not available in this UI variant", output)
self.assertIn("[FAIL] Required searches: on the page but not ready in time", output)
self.assertIn("[OK] Bing daily set", output)
def test_a_section_that_never_rendered_is_not_called_unavailable(self):
output = self._run({
"complete_misc_cards": ElementNotReady(
"'moreactivities' is present but no visible copy has content yet"
),
})
self.assertIn("[FAIL] Misc cards: on the page but not ready in time", output)
self.assertNotIn("Misc cards: not available", output)
def test_a_task_this_variant_does_not_ship_is_still_skipped(self):
output = self._run({
"complete_explore_on_bing_tasks": NoSuchElementException(
"no Explore on Bing section in this UI variant"
),
})
self.assertIn("[SKIP] Explore on Bing: not available in this UI variant", output)
def test_an_unexpected_failure_still_reports_what_went_wrong(self):
output = self._run({"complete_misc_cards": WebDriverException("chrome not reachable")})
self.assertIn("[FAIL] Misc cards: WebDriverException", output)
self.assertIn("chrome not reachable", output)
if __name__ == "__main__":
unittest.main()