fluent-selectors 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, Union
|
|
7
|
+
|
|
8
|
+
from fluent_checks import Check
|
|
9
|
+
from selenium.common import NoSuchElementException
|
|
10
|
+
from selenium.webdriver.common.by import By
|
|
11
|
+
from selenium.webdriver.remote.webdriver import WebDriver
|
|
12
|
+
from selenium.webdriver.remote.webelement import WebElement
|
|
13
|
+
|
|
14
|
+
type Locator = tuple[str, str]
|
|
15
|
+
|
|
16
|
+
SELF_LOCATOR: Locator = (By.XPATH, ".")
|
|
17
|
+
CHILDREN_LOCATOR: Locator = (By.XPATH, "./*")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Location:
|
|
22
|
+
x: int
|
|
23
|
+
y: int
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class Size:
|
|
28
|
+
x: float
|
|
29
|
+
y: float
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Selector(ABC):
|
|
33
|
+
def __init__(self, driver: WebDriver, *locators: Locator) -> None:
|
|
34
|
+
super().__init__()
|
|
35
|
+
self._driver: WebDriver = driver
|
|
36
|
+
self._locators: tuple[Locator, ...] = locators or (SELF_LOCATOR,)
|
|
37
|
+
self._locator: Locator = self._locators[-1]
|
|
38
|
+
|
|
39
|
+
@cached_property
|
|
40
|
+
def parent(self) -> Optional["Selector"]:
|
|
41
|
+
if len(self._locators) > 1:
|
|
42
|
+
return Selector(self._driver, *self._locators[:-1])
|
|
43
|
+
|
|
44
|
+
@cached_property
|
|
45
|
+
def parents(self) -> list["Selector"]:
|
|
46
|
+
if parent := self.parent:
|
|
47
|
+
return [parent, *parent.parents]
|
|
48
|
+
return []
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def _context(self) -> Union[WebDriver, WebElement, None]:
|
|
52
|
+
if self.parent:
|
|
53
|
+
return self.parent.element
|
|
54
|
+
return self._driver
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def element(self) -> Optional[WebElement]:
|
|
58
|
+
try:
|
|
59
|
+
context = self._context
|
|
60
|
+
if context:
|
|
61
|
+
return context.find_element(*self._locator)
|
|
62
|
+
return None
|
|
63
|
+
except NoSuchElementException:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def elements(self) -> list[WebElement]:
|
|
68
|
+
try:
|
|
69
|
+
context = self._context
|
|
70
|
+
if context:
|
|
71
|
+
return context.find_elements(*self._locator)
|
|
72
|
+
return []
|
|
73
|
+
except NoSuchElementException:
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
def select(self, locator: Locator) -> "Selector":
|
|
77
|
+
return Selector(self._driver, *self._locators, locator)
|
|
78
|
+
|
|
79
|
+
def child(self, index: int) -> "Selector":
|
|
80
|
+
locator: Locator = (By.XPATH, f"({CHILDREN_LOCATOR[1]})[{index + 1}]")
|
|
81
|
+
return Selector(self._driver, *self._locators, locator)
|
|
82
|
+
|
|
83
|
+
def children(self) -> list["Selector"]:
|
|
84
|
+
num_children = len(self.select(CHILDREN_LOCATOR).elements)
|
|
85
|
+
return [self.child(index) for index in range(num_children)]
|
|
86
|
+
|
|
87
|
+
def click(self):
|
|
88
|
+
if element := self.element:
|
|
89
|
+
element.click()
|
|
90
|
+
|
|
91
|
+
def type_text(self, text: str):
|
|
92
|
+
if element := self.element:
|
|
93
|
+
element.send_keys(text)
|
|
94
|
+
|
|
95
|
+
def clear(self):
|
|
96
|
+
if element := self.element:
|
|
97
|
+
element.clear()
|
|
98
|
+
|
|
99
|
+
def set_text(self, text: str):
|
|
100
|
+
self.clear()
|
|
101
|
+
self.type_text(text)
|
|
102
|
+
|
|
103
|
+
def upload_file(self, path: Path):
|
|
104
|
+
self.set_text(os.path.abspath(path))
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def text(self) -> Optional[str]:
|
|
108
|
+
if element := self.element:
|
|
109
|
+
return element.text
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def tag_name(self) -> Optional[str]:
|
|
113
|
+
if element := self.element:
|
|
114
|
+
return element.tag_name
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def accessible_name(self) -> Optional[str]:
|
|
118
|
+
if element := self.element:
|
|
119
|
+
return element.accessible_name
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def aria_role(self) -> Optional[str]:
|
|
123
|
+
if element := self.element:
|
|
124
|
+
return element.aria_role
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def id(self) -> Optional[str]:
|
|
128
|
+
if element := self.element:
|
|
129
|
+
return element.id
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def location(self) -> Optional[Location]:
|
|
133
|
+
if element := self.element:
|
|
134
|
+
location = element.location
|
|
135
|
+
return Location(location["x"], location["y"])
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def size(self) -> Optional[Size]:
|
|
139
|
+
if element := self.element:
|
|
140
|
+
size = element.size
|
|
141
|
+
return Size(size["width"], size["height"])
|
|
142
|
+
|
|
143
|
+
def scroll_into_view(self) -> None:
|
|
144
|
+
if element := self.element:
|
|
145
|
+
self._driver.execute_script("arguments[0].scrollIntoView(true);", element)
|
|
146
|
+
|
|
147
|
+
def attribute(self, name: str) -> Optional[str]:
|
|
148
|
+
if element := self.element:
|
|
149
|
+
return element.get_attribute(name)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def is_present(self) -> "IsPresentCheck":
|
|
153
|
+
return IsPresentCheck(self)
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def is_displayed(self) -> "IsDisplayedCheck":
|
|
157
|
+
return IsDisplayedCheck(self)
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def is_enabled(self) -> "IsEnabledCheck":
|
|
161
|
+
return IsEnabledCheck(self)
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def is_selected(self) -> "IsSelectedCheck":
|
|
165
|
+
return IsSelectedCheck(self)
|
|
166
|
+
|
|
167
|
+
def has_text(self, text: str) -> "HasTextCheck":
|
|
168
|
+
return HasTextCheck(self, text)
|
|
169
|
+
|
|
170
|
+
def has_exact_text(self, text: str) -> "HasExactTextCheck":
|
|
171
|
+
return HasExactTextCheck(self, text)
|
|
172
|
+
|
|
173
|
+
def has_attribute(self, name: str) -> "HasAttributeCheck":
|
|
174
|
+
return HasAttributeCheck(self, name)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class IsPresentCheck(Check):
|
|
178
|
+
def __init__(self, selector: Selector) -> None:
|
|
179
|
+
element: WebElement | None = selector.element
|
|
180
|
+
super().__init__(lambda: True if element is not None else False)
|
|
181
|
+
self._selector: Selector = selector
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class IsDisplayedCheck(Check):
|
|
185
|
+
def __init__(self, selector: Selector) -> None:
|
|
186
|
+
element: WebElement | None = selector.element
|
|
187
|
+
super().__init__(
|
|
188
|
+
lambda: element.is_displayed() if element is not None else False
|
|
189
|
+
)
|
|
190
|
+
self._selector: Selector = selector
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class IsEnabledCheck(Check):
|
|
194
|
+
def __init__(self, selector: Selector) -> None:
|
|
195
|
+
element: WebElement | None = selector.element
|
|
196
|
+
super().__init__(lambda: element.is_enabled() if element is not None else False)
|
|
197
|
+
self._selector: Selector = selector
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class IsSelectedCheck(Check):
|
|
201
|
+
def __init__(self, selector: Selector) -> None:
|
|
202
|
+
element: WebElement | None = selector.element
|
|
203
|
+
super().__init__(
|
|
204
|
+
lambda: element.is_selected() if element is not None else False
|
|
205
|
+
)
|
|
206
|
+
self._selector: Selector = selector
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class HasTextCheck(Check):
|
|
210
|
+
def __init__(self, selector: Selector, text: str) -> None:
|
|
211
|
+
element: WebElement | None = selector.element
|
|
212
|
+
super().__init__(lambda: text in element.text if element is not None else False)
|
|
213
|
+
self._selector: Selector = selector
|
|
214
|
+
self._text: str = text
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class HasExactTextCheck(Check):
|
|
218
|
+
def __init__(self, selector: Selector, text: str) -> None:
|
|
219
|
+
element: WebElement | None = selector.element
|
|
220
|
+
super().__init__(lambda: text == element.text if element is not None else False)
|
|
221
|
+
self._selector: Selector = selector
|
|
222
|
+
self._text: str = text
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class HasAttributeCheck(Check):
|
|
226
|
+
def __init__(self, selector: Selector, name: str) -> None:
|
|
227
|
+
element: WebElement | None = selector.element
|
|
228
|
+
super().__init__(
|
|
229
|
+
lambda: bool(element.get_attribute(self._name))
|
|
230
|
+
if element is not None
|
|
231
|
+
else False
|
|
232
|
+
)
|
|
233
|
+
self._selector: Selector = selector
|
|
234
|
+
self._name = name
|
|
File without changes
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: fluent-selectors
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for creating a readable, fluent API for Selenium browser automation
|
|
5
|
+
Keywords: fluent,selectors,testing,assertions,validation
|
|
6
|
+
Author: vantorrewannes
|
|
7
|
+
Author-email: vantorrewannes <vantorrewannes@gmail.com>
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Software Development :: Testing
|
|
13
|
+
Classifier: Topic :: Utilities
|
|
14
|
+
Requires-Dist: fluent-checks>=0.1.1
|
|
15
|
+
Requires-Dist: selenium>=4.35.0
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Project-URL: Bug Tracker, https://github.com/VantorreWannes/fluent-selectors/issues
|
|
18
|
+
Project-URL: Homepage, https://github.com/VantorreWannes/fluent-selectors
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Fluent Selectors
|
|
22
|
+
|
|
23
|
+
A Python library for creating a readable, fluent API for Selenium browser automation.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Install the package using pip:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install fluent-selectors
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
Here's a simple example of how to use `fluent-selectors`:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from selenium import webdriver
|
|
39
|
+
from selenium.webdriver.common.by import By
|
|
40
|
+
from fluent_selectors import Selector
|
|
41
|
+
|
|
42
|
+
# Initialize the WebDriver
|
|
43
|
+
driver = webdriver.Chrome()
|
|
44
|
+
driver.get("http://www.python.org")
|
|
45
|
+
|
|
46
|
+
# Create a Selector instance
|
|
47
|
+
s = Selector(driver)
|
|
48
|
+
|
|
49
|
+
# Select an element and interact with it
|
|
50
|
+
search_bar = s.select((By.NAME, "q"))
|
|
51
|
+
search_bar.set_text("pycon")
|
|
52
|
+
|
|
53
|
+
go_button = s.select((By.ID, "submit"))
|
|
54
|
+
go_button.click()
|
|
55
|
+
|
|
56
|
+
# Perform checks
|
|
57
|
+
s.select((By.CLASS_NAME, "list-recent-events")).is_displayed.is_true()
|
|
58
|
+
|
|
59
|
+
driver.quit()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
### `Selector(driver: WebDriver, *locators: Locator)`
|
|
65
|
+
|
|
66
|
+
The main class for selecting and interacting with elements.
|
|
67
|
+
|
|
68
|
+
- `select(locator: Locator) -> Selector`: Select a descendant of the current element.
|
|
69
|
+
- `child(index: int) -> Selector`: Select a child by its index.
|
|
70
|
+
- `children() -> list[Selector]`: Get a list of all children.
|
|
71
|
+
- `parent() -> Selector | None`: Get the parent selector.
|
|
72
|
+
- `parents() -> list[Selector]`: Get a list of all parent selectors.
|
|
73
|
+
|
|
74
|
+
### Element Actions
|
|
75
|
+
|
|
76
|
+
- `click()`: Clicks the element.
|
|
77
|
+
- `type_text(text: str)`: Types text into the element.
|
|
78
|
+
- `clear()`: Clears the text from the element.
|
|
79
|
+
- `set_text(text: str)`: Clears the element and then types text into it.
|
|
80
|
+
- `upload_file(path: Path)`: Uploads a file to a file input element.
|
|
81
|
+
- `scroll_into_view()`: Scrolls the element into view.
|
|
82
|
+
|
|
83
|
+
### Element Properties
|
|
84
|
+
|
|
85
|
+
- `element() -> WebElement | None`: The Selenium WebElement.
|
|
86
|
+
- `elements() -> list[WebElement]`: A list of Selenium WebElements.
|
|
87
|
+
- `text() -> str | None`: The text of the element.
|
|
88
|
+
- `tag_name() -> str | None`: The tag name of the element.
|
|
89
|
+
- `accessible_name() -> str | None`: The accessible name of the element.
|
|
90
|
+
- `aria_role() -> str | None`: The ARIA role of the element.
|
|
91
|
+
- `id() -> str | None`: The ID of the element.
|
|
92
|
+
- `location() -> Location | None`: The location of the element.
|
|
93
|
+
- `size() -> Size | None`: The size of the element.
|
|
94
|
+
- `attribute(name: str) -> str | None`: The value of an attribute.
|
|
95
|
+
|
|
96
|
+
### Checks
|
|
97
|
+
|
|
98
|
+
You can perform checks on selectors using the following properties. These checks are based on the [fluent-checks](https://github.com/VantorreWannes/fluent-checks) library.
|
|
99
|
+
|
|
100
|
+
- `is_present()`: Checks if the element is present in the DOM.
|
|
101
|
+
- `is_displayed()`: Checks if the element is visible.
|
|
102
|
+
- `is_enabled()`: Checks if the element is enabled.
|
|
103
|
+
- `is_selected()`: Checks if the element is selected.
|
|
104
|
+
- `has_text(text: str)`: Checks if the element's text contains the given text.
|
|
105
|
+
- `has_exact_text(text: str)`: Checks if the element's text exactly matches the given text.
|
|
106
|
+
- `has_attribute(name: str)`: Checks if the element has the given attribute.
|
|
107
|
+
|
|
108
|
+
Example of a check:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
s.select((By.ID, "my-element")).is_displayed.is_true()
|
|
112
|
+
s.select((By.ID, "my-element")).has_text("Hello").is_true()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
fluent_selectors/__init__.py,sha256=7orky_sH1eyagcM4c91PGBLmUNR8KhsU9shmLIDfppM,7047
|
|
2
|
+
fluent_selectors/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
fluent_selectors-0.1.0.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
|
|
4
|
+
fluent_selectors-0.1.0.dist-info/METADATA,sha256=eVsdTsOmTiklogXQzMgWVhdGDs0oQQF7ORzTVs6ZXyU,4094
|
|
5
|
+
fluent_selectors-0.1.0.dist-info/RECORD,,
|