diff --git a/CHANGELOG.md b/CHANGELOG.md index b019431..5ffd6cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 0.6 (beta) - Renamed automatic page load function from `autopage()` to `auto()`. +- Updated page model schema to `locators: { inclusions, exclusions }`. # 0.5 (2020-12-28) diff --git a/pomace/models.py b/pomace/models.py index 2c3c5fa..e3142ba 100644 --- a/pomace/models.py +++ b/pomace/models.py @@ -45,19 +45,23 @@ def find(self) -> Optional[WebDriverElement]: log.debug(f"{self} unable to find element") return None else: - log.debug(f"{self} found element: {element}") + log.debug(f"{self} found element: {element.outer_html}") return element - def score(self, value: int): + def score(self, value: int) -> bool: previous = self.uses + if value > 0: self.uses = min(99, max(1, self.uses + value)) else: self.uses = max(-1, self.uses + value) - if self.uses > previous: - log.debug(f"Increased {self} uses to {self.uses}") - elif self.uses < previous: - log.debug(f"Decreased {self} uses to {self.uses}") + + if self.uses == previous: + return False + + result = "Increased" if self.uses > previous else "Decreased" + log.debug(f"{result} {self} uses to {self.uses}") + return True @datafile @@ -166,6 +170,56 @@ def clean(self, *, force: bool = False) -> int: return len(unused_locators) +@datafile +class Locators: + inclusions: List[Locator] + exclusions: List[Locator] + + @property + def sorted_inclusions(self) -> List[Locator]: + return [x for x in sorted(self.inclusions, reverse=True) if x] + + @property + def sorted_exclusions(self) -> List[Locator]: + return [x for x in sorted(self.exclusions, reverse=True) if x] + + def clean(self, page, *, force: bool = False) -> int: + unused_inclusion_locators = [] + unused_exclusion_locators = [] + remove_unused_locators = force + + for locator in self.inclusions: + if locator.uses <= 0: + unused_inclusion_locators.append(locator) + if locator.uses >= 99: + remove_unused_locators = True + + for locator in self.exclusions: + if locator.uses <= 0: + unused_exclusion_locators.append(locator) + if locator.uses >= 99: + remove_unused_locators = True + + count = len(unused_inclusion_locators) + len(unused_exclusion_locators) + log.debug(f"Found {count} unused locators for {page}") + if not remove_unused_locators: + return 0 + + if unused_inclusion_locators: + log.info(f"Cleaning up inclusion locators for {page}") + for locator in unused_inclusion_locators: + log.info(f"Removed unused {locator}") + self.inclusions.remove(locator) + + if unused_exclusion_locators: + log.info(f"Cleaning up exclusion locators for {page}") + for locator in unused_exclusion_locators: + log.info(f"Removed unused {locator}") + self.exclusions.remove(locator) + + return len(unused_inclusion_locators) + len(unused_exclusion_locators) + + @datafile( "./sites/{self.domain}/{self.path}/{self.variant}.yml", defaults=True, manual=True ) @@ -175,9 +229,7 @@ class Page: path: str = URL.ROOT variant: str = "default" - active_locators: List[Locator] = field(default_factory=lambda: [Locator()]) - inactive_locators: List[Locator] = field(default_factory=lambda: [Locator()]) - + locators: Locators = field(default_factory=lambda: Locators([], [])) actions: List[Action] = field(default_factory=lambda: [Action()]) @classmethod @@ -205,21 +257,24 @@ def active(self) -> bool: log.debug(f"Determining if {self!r} is active") if self.url != URL(shared.browser.url): - log.debug( - f"{self!r} is inactive - URL does not match: {shared.browser.url}" - ) + log.debug(f"{self!r} is inactive: URL not matched") return False log.debug("Checking that all expected elements can be found") - for locator in self.active_locators: - if locator and not locator.find(): - log.debug(f"{self!r} is inactive - Unable to find: {locator!r}") + for locator in self.locators.sorted_inclusions: + if locator.find(): + if locator.score(+1): + self.datafile.save() + else: + log.debug(f"{self!r} is inactive: {locator!r} found expected element") return False log.debug("Checking that no unexpected elements can be found") - for locator in self.inactive_locators: - if locator and locator.find(): - log.debug(f"{self!r} is inactive - Found unexpected: {locator!r}") + for locator in self.locators.sorted_exclusions: + if locator.find(): + if locator.score(+1): + self.datafile.save() + log.debug(f"{self!r} is inactive: {locator!r} found unexpected element") return False log.debug(f"{self!r} is active") @@ -290,7 +345,7 @@ def perform(self, name: str) -> Tuple["Page", bool]: return page, page != self def clean(self, *, force: bool = False) -> int: - count = 0 + count = self.locators.clean(self, force=force) unused_actions = [] remove_unused_actions = force diff --git a/pomace/tests/conftest.py b/pomace/tests/conftest.py index a1df3b7..d1cd5c1 100644 --- a/pomace/tests/conftest.py +++ b/pomace/tests/conftest.py @@ -10,9 +10,15 @@ from pomace import shared +class MockElement(str): + @property + def outer_html(self): + return f"{self}" + + class MockLinks: def find_by_partial_text(self, value): - return [f""] + return [MockElement(f"mockelement:links.partial_text={value}")] class MockBrowser: @@ -22,7 +28,7 @@ class MockBrowser: html = "Hello, world!" def find_by_name(self, value): - return [f""] + return [MockElement(f"mockelement:name={value}")] links = MockLinks() diff --git a/pomace/tests/test_models.py b/pomace/tests/test_models.py index 7ad979c..6a68bef 100644 --- a/pomace/tests/test_models.py +++ b/pomace/tests/test_models.py @@ -40,21 +40,25 @@ def it_orders_by_uses(expect): def describe_find(): def it_returns_callable(expect, mockbrowser, locator): - expect(locator.find()) == "" + expect(locator.find()) == "mockelement:name=email" def it_can_find_links_by_partial_text(expect, mockbrowser, locator): locator.mode = "partial_text" - expect(locator.find()) == "" + expect(locator.find()) == "mockelement:links.partial_text=email" def describe_score(): + def it_updates_uses(expect, locator): + expect(locator.score(+1)) == True + expect(locator.uses) == 1 + def it_tops_out_at_max_value(expect, locator): locator.score(+99) - locator.score(+1) + expect(locator.score(+1)) == False expect(locator.uses) == 99 def it_bottoms_out_at_min_value(expect, locator): locator.score(-1) - locator.score(-1) + expect(locator.score(-1)) == False expect(locator.uses) == -1 @@ -148,3 +152,20 @@ def it_rejects_missing_attributes(expect, page): def describe_contains(): def it_matches_partial_html(expect, page, mockbrowser): expect(page).contains("world") + + def describe_clean(): + def it_removes_unused_locators(expect, page): + page.locators.inclusions = [ + Locator("id", "foo", uses=0), + Locator("id", "bar", uses=-1), + Locator("id", "qux", uses=99), + ] + page.locators.exclusions = [ + Locator("id", "foo", uses=0), + Locator("id", "bar", uses=-1), + Locator("id", "qux", uses=99), + ] + + expect(page.clean()) == 4 + expect(len(page.locators.inclusions)) == 1 + expect(len(page.locators.exclusions)) == 1 diff --git a/pyproject.toml b/pyproject.toml index 4834865..9404979 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "pomace" -version = "0.6b1" +version = "0.6b2" description = "Dynamic page objects for browser automation." license = "MIT"