fluent-selectors 0.1.0__tar.gz

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,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,97 @@
1
+ # Fluent Selectors
2
+
3
+ A Python library for creating a readable, fluent API for Selenium browser automation.
4
+
5
+ ## Installation
6
+
7
+ Install the package using pip:
8
+
9
+ ```bash
10
+ pip install fluent-selectors
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Here's a simple example of how to use `fluent-selectors`:
16
+
17
+ ```python
18
+ from selenium import webdriver
19
+ from selenium.webdriver.common.by import By
20
+ from fluent_selectors import Selector
21
+
22
+ # Initialize the WebDriver
23
+ driver = webdriver.Chrome()
24
+ driver.get("http://www.python.org")
25
+
26
+ # Create a Selector instance
27
+ s = Selector(driver)
28
+
29
+ # Select an element and interact with it
30
+ search_bar = s.select((By.NAME, "q"))
31
+ search_bar.set_text("pycon")
32
+
33
+ go_button = s.select((By.ID, "submit"))
34
+ go_button.click()
35
+
36
+ # Perform checks
37
+ s.select((By.CLASS_NAME, "list-recent-events")).is_displayed.is_true()
38
+
39
+ driver.quit()
40
+ ```
41
+
42
+ ## API
43
+
44
+ ### `Selector(driver: WebDriver, *locators: Locator)`
45
+
46
+ The main class for selecting and interacting with elements.
47
+
48
+ - `select(locator: Locator) -> Selector`: Select a descendant of the current element.
49
+ - `child(index: int) -> Selector`: Select a child by its index.
50
+ - `children() -> list[Selector]`: Get a list of all children.
51
+ - `parent() -> Selector | None`: Get the parent selector.
52
+ - `parents() -> list[Selector]`: Get a list of all parent selectors.
53
+
54
+ ### Element Actions
55
+
56
+ - `click()`: Clicks the element.
57
+ - `type_text(text: str)`: Types text into the element.
58
+ - `clear()`: Clears the text from the element.
59
+ - `set_text(text: str)`: Clears the element and then types text into it.
60
+ - `upload_file(path: Path)`: Uploads a file to a file input element.
61
+ - `scroll_into_view()`: Scrolls the element into view.
62
+
63
+ ### Element Properties
64
+
65
+ - `element() -> WebElement | None`: The Selenium WebElement.
66
+ - `elements() -> list[WebElement]`: A list of Selenium WebElements.
67
+ - `text() -> str | None`: The text of the element.
68
+ - `tag_name() -> str | None`: The tag name of the element.
69
+ - `accessible_name() -> str | None`: The accessible name of the element.
70
+ - `aria_role() -> str | None`: The ARIA role of the element.
71
+ - `id() -> str | None`: The ID of the element.
72
+ - `location() -> Location | None`: The location of the element.
73
+ - `size() -> Size | None`: The size of the element.
74
+ - `attribute(name: str) -> str | None`: The value of an attribute.
75
+
76
+ ### Checks
77
+
78
+ 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.
79
+
80
+ - `is_present()`: Checks if the element is present in the DOM.
81
+ - `is_displayed()`: Checks if the element is visible.
82
+ - `is_enabled()`: Checks if the element is enabled.
83
+ - `is_selected()`: Checks if the element is selected.
84
+ - `has_text(text: str)`: Checks if the element's text contains the given text.
85
+ - `has_exact_text(text: str)`: Checks if the element's text exactly matches the given text.
86
+ - `has_attribute(name: str)`: Checks if the element has the given attribute.
87
+
88
+ Example of a check:
89
+
90
+ ```python
91
+ s.select((By.ID, "my-element")).is_displayed.is_true()
92
+ s.select((By.ID, "my-element")).has_text("Hello").is_true()
93
+ ```
94
+
95
+ ## License
96
+
97
+ This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,44 @@
1
+ [project]
2
+ name = "fluent-selectors"
3
+ version = "0.1.0"
4
+ description = "A Python library for creating a readable, fluent API for Selenium browser automation"
5
+ readme = "README.md"
6
+ authors = [{ name = "vantorrewannes", email = "vantorrewannes@gmail.com" }]
7
+ requires-python = ">=3.12"
8
+ dependencies = ["fluent-checks>=0.1.1", "selenium>=4.35.0"]
9
+ classifiers = [
10
+ "Programming Language :: Python :: 3",
11
+ "Operating System :: OS Independent",
12
+ "Development Status :: 3 - Alpha",
13
+ "Intended Audience :: Developers",
14
+ "Topic :: Software Development :: Testing",
15
+ "Topic :: Utilities",
16
+ ]
17
+ keywords = ["fluent", "selectors", "testing", "assertions", "validation"]
18
+
19
+
20
+ [project.urls]
21
+ Homepage = "https://github.com/VantorreWannes/fluent-selectors"
22
+ "Bug Tracker" = "https://github.com/VantorreWannes/fluent-selectors/issues"
23
+
24
+
25
+ [build-system]
26
+ requires = ["uv_build>=0.8.13,<0.9.0"]
27
+ build-backend = "uv_build"
28
+
29
+ [tool.pytest.ini_options]
30
+ pythonpath = "."
31
+ testpaths = "tests"
32
+
33
+ [tool.ruff.lint]
34
+ select = ["E", "F", "I"]
35
+ fixable = ["ALL"]
36
+
37
+ [[tool.uv.index]]
38
+ name = "testpypi"
39
+ url = "https://test.pypi.org/simple/"
40
+ publish-url = "https://test.pypi.org/legacy/"
41
+ explicit = true
42
+
43
+ [dependency-groups]
44
+ dev = ["pytest>=8.4.1", "pytest-httpserver>=1.1.3", "webdriver-manager>=4.0.2"]
@@ -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