Skip to content

Commit d5af829

Browse files
committed
Implemented some more features and improved documentation
1 parent 378246d commit d5af829

File tree

6 files changed

+87
-9
lines changed

6 files changed

+87
-9
lines changed

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,37 @@ I added two examples.
7777
2. [test_todo_mvc.py](examples/test_todo_mvc.py): are re-implemented test cases from the [Robot Framework TodoMVC](https://docs.robotframework.org/docs/examples/todo) example. It's IMHO developer friendly, better readable and less code.
7878

7979

80+
## Comparison
81+
82+
Comparison of the syntax with other frameworks.
83+
84+
**Pylenium**
85+
```python
86+
py.get("a[href='/about']").should().have_text("About")
87+
```
88+
```python
89+
find("a[href='/about']").expect.text.be("About")
90+
```
91+
**SeleniumBase**
92+
```python
93+
self.assert_text_not_visible("Thanks for your purchase.", "#app .success")
94+
```
95+
```python
96+
find("#app .success").expect.text.not_be("Thanks for your purchase.")
97+
```
98+
**Selene**
99+
```python
100+
browser.all('#rso>div').should(have.size_greater_than(5)) \
101+
.first.should(have.text('Selenium automates browsers'))
102+
```
103+
```python
104+
div = find("#rso>div")
105+
div.expect.count.greater_than(5).be(True)
106+
div.first.expect.text.be("Selenium automates browsers")
107+
```
108+
109+
References: https://www.nextgenerationautomation.com/post/python-test-automation-frameworks
110+
80111
## Developer area
81112
### Run the tests
82113
```shell

doc/uielement.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,12 @@ ui_element.expect.enabled.be(True)
6666
# Element is selected
6767
ui_element.expect.selected.be(True)
6868

69-
# Element text equals a string
69+
# Element text equals a case sensitive string
7070
ui_element.expect.text.be("Hello")
7171

72+
# Element text equals a lower case string
73+
ui_element.expect.text.map(str.lower).be("hello")
74+
7275
# Element text contains a string
7376
ui_element.expect.text.contains("World").be(True)
7477

@@ -81,6 +84,9 @@ ui_element.expect.text.matches("d\sW").be(True)
8184
# Element's CSS property equals a value
8285
ui_element.expect.css("display").be("block")
8386

87+
# Element has classes "main" and "nav"
88+
ui_element.expect.classes("main", "nav").be(True)
89+
8490
# Element's "max-length" attribute is greater or equal than 3
8591
ui_element.expect.attribute("max-length").greater_equal_than(3).be(True)
8692

@@ -97,6 +103,17 @@ ui_element.expect.fully_visible.be(True)
97103
path = ui_element.take_screenshot()
98104
```
99105

106+
The advantage of this chained assertion API is,
107+
that you can easily perform multiple assertions on the same property.
108+
```python
109+
text = ui_element.expect.text
110+
111+
text.not_be("Wrong")
112+
text.starts_with("Hello").be(True)
113+
text.map(str.upper).ends_with("WELCOME").be(True)
114+
text.length.between(10, 20).be(True)
115+
```
116+
100117
## Waiting for conditions
101118

102119
Instead of `expect`, use write `wait_for` to prevent raising `AssertionError`.

paf/assertion.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from paf.common import Rect
77
from paf.control import Control, RetryException
8-
from paf.types import Supplier, Predicate, Number
8+
from paf.types import Supplier, Predicate, Number, Mapper
99

1010
ACTUAL_TYPE = TypeVar("ACTUAL_TYPE")
1111

@@ -95,6 +95,12 @@ def be(self, expected: any) -> bool:
9595

9696

9797
class QuantityAssertion(Generic[ACTUAL_TYPE], BinaryAssertion[ACTUAL_TYPE]):
98+
def map(self, mapper: Mapper):
99+
return self.__class__(
100+
parent=self,
101+
actual=lambda: mapper(self._actual()),
102+
subject=lambda: "mapped",
103+
)
98104

99105
def not_be(self, expected: any) -> bool:
100106
return self._test_sequence(lambda actual: actual != expected, lambda: f" not to be {Format.param(expected)}")

paf/page.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ def _find(self, by: Locator):
3737
def name(self):
3838
return self.__class__.__name__
3939

40+
@property
41+
def webdriver(self):
42+
return self._webdriver
43+
4044

4145
class FinderPage(BasePage):
4246
def find(self, by: Locator):
@@ -65,10 +69,6 @@ def expect(self):
6569
def wait_for(self):
6670
return PageAssertion(self, self._webdriver, raise_exception=False)
6771

68-
@property
69-
def webdriver(self):
70-
return self._webdriver
71-
7272
def scroll_by(self, x: int = 0, y: int = 0):
7373
actions = ActionChains(self._webdriver)
7474
actions.scroll_by_amount(x, y).perform()

paf/uielement.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from paf.assertion import StringAssertion, Format, BinaryAssertion, QuantityAssertion, RectAssertion
1717
from paf.common import HasParent, Locator, Point, Rect, Property, Formatter
1818
from paf.control import Control
19+
from paf.dom import Attribute
1920
from paf.locator import By
2021
from paf.types import Mapper, Consumer, R
2122
from paf.xpath import XPath
@@ -59,6 +60,10 @@ def type(self, value: str):
5960
def clear(self):
6061
pass
6162

63+
@abstractmethod
64+
def submit(self):
65+
pass
66+
6267

6368
T = TypeVar('T')
6469

@@ -284,6 +289,10 @@ def clear(self):
284289
self._action_sequence(lambda web_element: web_element.clear())
285290
return self
286291

292+
def submit(self):
293+
self._action_sequence(lambda web_element: web_element.submit())
294+
return self
295+
287296
def __str__(self):
288297
return self.name
289298

@@ -387,18 +396,24 @@ def _map(web_element: WebElement):
387396

388397
return self._map_web_element_property(StringAssertion, _map, "tag name")
389398

390-
def attribute(self, attribute: str):
399+
def attribute(self, attribute: str|Attribute):
400+
if isinstance(attribute, Attribute):
401+
attribute = attribute.value
402+
391403
def _map(web_element: WebElement):
392404
return web_element.get_attribute(attribute)
393405

394-
return self._map_web_element_property(StringAssertion, _map, f"attribute({attribute}")
406+
return self._map_web_element_property(StringAssertion, _map, f"attribute({attribute})")
395407

396408
def css(self, property_name: str):
397409
def _map(web_element: WebElement):
398410
return web_element.value_of_css_property(property_name)
399411

400412
return self._map_web_element_property(StringAssertion, _map, f"css({property_name}")
401413

414+
def classes(self, *classes):
415+
return self.attribute(Attribute.CLASS).has_words(*classes)
416+
402417
@property
403418
def visible(self):
404419
return self._visible(False)

test/test_uielement.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import inject
22
import pytest
3+
from selenium.webdriver.remote.webdriver import WebDriver, BaseWebDriver
34
from selenium.webdriver.support.color import Color
45

56
from paf.control import Control
@@ -29,6 +30,7 @@ def test_finder_page(finder: FinderPage):
2930
p = finder.find(By.id("para1"))
3031
assert p.name_path == 'UiElement(By.id(para1))[0]'
3132
assert p.name == p.name_path
33+
assert isinstance(finder.webdriver, BaseWebDriver)
3234

3335

3436
# def test_rect():
@@ -41,15 +43,22 @@ def test_finder_page(finder: FinderPage):
4143
# assert rect.width == 962
4244
# assert rect.height == 27
4345

44-
def test_text_assertions(finder: FinderPage):
46+
def test_assertions(finder: FinderPage):
4547
finder.open("https://testpages.herokuapp.com/styled/basic-web-page-test.html")
4648

4749
p = finder.find("#para1")
4850

51+
# tag name
4952
p.expect.tag_name.be("p")
5053

54+
# classes
55+
p.expect.classes("main").be(True)
56+
p.expect.classes("sub").be(False)
57+
5158
text = p.expect.text
5259
text.be("A paragraph of text")
60+
text.map(str.upper).be("A PARAGRAPH OF TEXT")
61+
text.not_be("Bla")
5362
text.contains("paragraph").be(True)
5463
text.has_words("paragraph", "text").be(True)
5564
text.starts_with("A").be(True)

0 commit comments

Comments
 (0)