Skip to content

Commit

Permalink
Track usage of page locators
Browse files Browse the repository at this point in the history
  • Loading branch information
jacebrowning committed Dec 30, 2020
1 parent 4979357 commit 7f40c93
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 26 deletions.
1 change: 1 addition & 0 deletions 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)

Expand Down
93 changes: 74 additions & 19 deletions pomace/models.py
Expand Up @@ -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
Expand Down Expand Up @@ -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
)
Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions pomace/tests/conftest.py
Expand Up @@ -10,9 +10,15 @@
from pomace import shared


class MockElement(str):
@property
def outer_html(self):
return f"<mockhtml>{self}</>"


class MockLinks:
def find_by_partial_text(self, value):
return [f"<mockelement: links.partial_text={value}>"]
return [MockElement(f"mockelement:links.partial_text={value}")]


class MockBrowser:
Expand All @@ -22,7 +28,7 @@ class MockBrowser:
html = "Hello, world!"

def find_by_name(self, value):
return [f"<mockelement: name={value}>"]
return [MockElement(f"mockelement:name={value}")]

links = MockLinks()

Expand Down
29 changes: 25 additions & 4 deletions pomace/tests/test_models.py
Expand Up @@ -40,21 +40,25 @@ def it_orders_by_uses(expect):

def describe_find():
def it_returns_callable(expect, mockbrowser, locator):
expect(locator.find()) == "<mockelement: name=email>"
expect(locator.find()) == "mockelement:name=email"

def it_can_find_links_by_partial_text(expect, mockbrowser, locator):
locator.mode = "partial_text"
expect(locator.find()) == "<mockelement: links.partial_text=email>"
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


Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion 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"
Expand Down

0 comments on commit 7f40c93

Please sign in to comment.