selenium-query 0.1.4__tar.gz → 0.1.5__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.
- {selenium_query-0.1.4 → selenium_query-0.1.5}/PKG-INFO +1 -3
- {selenium_query-0.1.4 → selenium_query-0.1.5}/pyproject.toml +1 -4
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_asserter_getter.py +2 -2
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_basics/action_getter.py +9 -3
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_basics/base_getter.py +25 -9
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_basics/generic_value_getter.py +1 -1
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_mapper_getter.py +15 -8
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/getter.py +32 -1
- {selenium_query-0.1.4 → selenium_query-0.1.5}/README.md +0 -0
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/__init__.py +0 -0
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_any_all_getters.py +0 -0
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_basics/__init__.py +0 -0
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_basics/boolean_getter.py +0 -0
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_filter_getter.py +0 -0
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_negation_transmitter.py +0 -0
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_order_getter.py +0 -0
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_types_and_tools.py +0 -0
- {selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_values_getter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: selenium-query
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Python equivalent of jQuery, to write selenium tests.
|
|
5
5
|
License: GPL-3.0-or-later
|
|
6
6
|
Author: Frédéric Zinelli
|
|
@@ -13,8 +13,6 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Provides-Extra: pmt
|
|
17
|
-
Requires-Dist: pmt-tools (>=0.1.2) ; extra == "pmt"
|
|
18
16
|
Requires-Dist: selenium (>=4.36)
|
|
19
17
|
Description-Content-Type: text/markdown
|
|
20
18
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "selenium-query"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.5"
|
|
4
4
|
description = "Python equivalent of jQuery, to write selenium tests."
|
|
5
5
|
license = { text = "GPL-3.0-or-later" }
|
|
6
6
|
authors = [
|
|
@@ -12,9 +12,6 @@ dependencies = [
|
|
|
12
12
|
"selenium (>=4.36)"
|
|
13
13
|
]
|
|
14
14
|
|
|
15
|
-
[project.optional-dependencies]
|
|
16
|
-
pmt = [ "pmt-tools>=0.1.2" ]
|
|
17
|
-
|
|
18
15
|
|
|
19
16
|
[tool.setuptools]
|
|
20
17
|
packages = ["selenium_query"]
|
|
@@ -75,7 +75,7 @@ class AsserterGetter(Getter, accessor="check"):
|
|
|
75
75
|
def exists(self, exp=True):
|
|
76
76
|
""" Usable for non existing list of elements """
|
|
77
77
|
actual = super().exists() # Already handle the negation!
|
|
78
|
-
msg = self._with_msg(f" {actual} should { '
|
|
78
|
+
msg = self._with_msg(f" {actual} should { 'not '*self._negate }be {exp}\n\nOn: {self}")
|
|
79
79
|
assert actual == exp, msg
|
|
80
80
|
return self
|
|
81
81
|
|
|
@@ -84,7 +84,7 @@ class AsserterGetter(Getter, accessor="check"):
|
|
|
84
84
|
""" Usable for non existing list of elements """
|
|
85
85
|
actual = super().count() # Already handle the negation!
|
|
86
86
|
msg = self._with_msg(
|
|
87
|
-
f" {actual} should { '
|
|
87
|
+
f" {actual} should { 'not '*self._negate }be {n}\n\nWrong number of children on: {self}"
|
|
88
88
|
)
|
|
89
89
|
assert actual == n, msg
|
|
90
90
|
return self
|
|
@@ -42,6 +42,12 @@ class ActionGetter(BaseGetter):
|
|
|
42
42
|
Selenium default is 250 ms.
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
|
+
AUTO_SHIFT: ClassVar[float] = 0
|
|
46
|
+
"""
|
|
47
|
+
Systematic shift value to use for `scroll_to` (helps when the targeted element is inside an
|
|
48
|
+
iframe, for example).
|
|
49
|
+
"""
|
|
50
|
+
|
|
45
51
|
|
|
46
52
|
@staticmethod
|
|
47
53
|
@contextmanager
|
|
@@ -249,7 +255,7 @@ class ActionGetter(BaseGetter):
|
|
|
249
255
|
)
|
|
250
256
|
|
|
251
257
|
def pause(self, seconds: Union[float, int, None]=None):
|
|
252
|
-
return self._apply("pause", self._duplicate_value(seconds or self.DURATION))
|
|
258
|
+
return self._apply("pause", self._duplicate_value(seconds or self.DURATION/1000))
|
|
253
259
|
|
|
254
260
|
def release(self, other:Optional[GetterOrCss]=None, *, current=False):
|
|
255
261
|
return self._apply("release", self._this_or_that(other, current))
|
|
@@ -291,16 +297,16 @@ class ActionGetter(BaseGetter):
|
|
|
291
297
|
WARNING: this action CANNOT be chained with others, because it is applied directly with JS.
|
|
292
298
|
"""
|
|
293
299
|
elt = self if other is None else self._getter_or_css(other)
|
|
300
|
+
|
|
294
301
|
# Cannot scroll to different elements, considering how the actions are handled, so forbid it:
|
|
295
302
|
elt.check("Getters.scroll_to usage requires instances with exactly one element").count(1)
|
|
296
|
-
|
|
297
303
|
assert not self._actions and not ActionGetter._context_action, (
|
|
298
304
|
"scroll_to(...) cannot be chained with other actions, because it interacts directly with "
|
|
299
305
|
"the page, through the JS executor."
|
|
300
306
|
)
|
|
301
307
|
|
|
302
308
|
rect = elt.get.rect
|
|
303
|
-
center_y = rect['y'] + rect['height']/2 + shift
|
|
309
|
+
center_y = rect['y'] + rect['height']/2 + shift + self.AUTO_SHIFT
|
|
304
310
|
|
|
305
311
|
self.run_js(f"""
|
|
306
312
|
var centerY={ center_y }, H=innerHeight, Y=scrollY
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Kind of jQuery like usage, but for Selenium logistic/elements...
|
|
3
3
|
"""
|
|
4
|
-
|
|
4
|
+
import datetime as dt
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
6
|
from functools import wraps
|
|
7
|
-
from typing import Callable, ClassVar, List, TYPE_CHECKING, Tuple
|
|
7
|
+
from typing import Callable, ClassVar, List, TYPE_CHECKING, Tuple, Union
|
|
8
8
|
|
|
9
9
|
from selenium.webdriver.common.by import By
|
|
10
10
|
from selenium.webdriver.remote.webdriver import WebDriver
|
|
@@ -73,11 +73,13 @@ class BaseGetter:
|
|
|
73
73
|
return ( self.__class__.from_(self, [elt]) for elt in self.elements )
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
def __getitem__(self, idx:int):
|
|
77
|
-
|
|
76
|
+
def __getitem__(self, idx:Union[int,slice]):
|
|
77
|
+
if isinstance(idx, int):
|
|
78
|
+
return self.__class__.from_(self, [self.elements[idx]])
|
|
79
|
+
return self.__class__.from_(self, self.elements[idx])
|
|
78
80
|
|
|
79
81
|
|
|
80
|
-
def find(self, css_selector:str, *, in_page=False) -> 'Getter':
|
|
82
|
+
def find(self, css_selector:str, *, in_page=False, as_:str="") -> 'Getter':
|
|
81
83
|
"""
|
|
82
84
|
Find all children element matching the given css selector.
|
|
83
85
|
If @in_page is true, starts searching from the top of the page instead of starting
|
|
@@ -96,7 +98,8 @@ class BaseGetter:
|
|
|
96
98
|
children = elt.find_elements(By.CSS_SELECTOR, css_selector)
|
|
97
99
|
elements.extend(children)
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
designation = f'"{ as_ or css_selector }"'
|
|
102
|
+
full_css_path = f'{self._full_css} -> {designation}' if self._full_css else designation
|
|
100
103
|
|
|
101
104
|
return self.__class__.from_(self, elements, css_selector, full_css_path)
|
|
102
105
|
|
|
@@ -127,17 +130,29 @@ class BaseGetter:
|
|
|
127
130
|
return getattr(self.driver, method)(code, *args)
|
|
128
131
|
|
|
129
132
|
|
|
130
|
-
def wait_for_element(self, css_selector):
|
|
133
|
+
def wait_for_element(self, css_selector, as_=""):
|
|
131
134
|
if self._actions:
|
|
132
135
|
raise ValueError(f"Cannot use `wait_until(...)` on objects with defined ActionChains.")
|
|
133
136
|
|
|
137
|
+
date = None
|
|
138
|
+
delta = dt.timedelta(seconds=0.05)
|
|
139
|
+
|
|
134
140
|
def finder(_):
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
nonlocal date
|
|
142
|
+
|
|
143
|
+
elt = self.find(css_selector, in_page=True, as_=as_)
|
|
144
|
+
found = elt.exists() and elt
|
|
145
|
+
if found:
|
|
146
|
+
if not date:
|
|
147
|
+
date = dt.datetime.now()
|
|
148
|
+
if dt.datetime.now() - date > delta:
|
|
149
|
+
return found
|
|
150
|
+
return False
|
|
137
151
|
|
|
138
152
|
return self.waiter.until(finder)
|
|
139
153
|
|
|
140
154
|
|
|
155
|
+
|
|
141
156
|
def wait_until(self, bool_provider):
|
|
142
157
|
if self._actions:
|
|
143
158
|
raise ValueError(f"Cannot use `wait_until(...)` on objects with defined ActionChains.")
|
|
@@ -157,6 +172,7 @@ class BaseGetter:
|
|
|
157
172
|
"""
|
|
158
173
|
for elt in self.elements:
|
|
159
174
|
elt.clear()
|
|
175
|
+
return self
|
|
160
176
|
|
|
161
177
|
|
|
162
178
|
#-----------------------------------------------------
|
{selenium_query-0.1.4 → selenium_query-0.1.5}/selenium_query/_basics/generic_value_getter.py
RENAMED
|
@@ -28,7 +28,7 @@ def should_contain(exp:Expected, act:Actual, with_neg:bool):
|
|
|
28
28
|
return f"{act!r} should { ' not'*with_neg }contain {exp!r}"
|
|
29
29
|
|
|
30
30
|
def should_be(exp:Expected, act:Actual, with_neg:bool):
|
|
31
|
-
return f"{act!r} should { '
|
|
31
|
+
return f"{act!r} should { 'not '*with_neg }be {exp!r}"
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
|
|
@@ -3,7 +3,7 @@ from types import MethodType
|
|
|
3
3
|
from typing import Any, Dict, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
from ._types_and_tools import MAPPER_GETTER_FOLLOW_UP, TGetter
|
|
6
|
+
from ._types_and_tools import MAPPER_GETTER_FOLLOW_UP, _GetterSubClassesAsProperties, TGetter
|
|
7
7
|
from ._basics.base_getter import forbid_negation
|
|
8
8
|
from .getter import Getter
|
|
9
9
|
|
|
@@ -94,13 +94,18 @@ class MapperGetter(Getter, accessor="map"):
|
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
@classmethod
|
|
97
|
-
def from_(cls, getter:Getter, _css_selector=None, _full_css=None):
|
|
98
|
-
|
|
97
|
+
def from_(cls, getter:Getter, elements=None, _css_selector=None, _full_css=None, merged=False):
|
|
98
|
+
if elements is not None and not merged:
|
|
99
|
+
raise ValueError("Cannot pass an @elements list to MapperGetter.from_, unless @merge is True")
|
|
100
|
+
elements_as_getters = elements or [*getter]
|
|
101
|
+
return super().from_(getter, elements_as_getters, _css_selector, _full_css)
|
|
99
102
|
|
|
100
103
|
|
|
101
|
-
def __call__(self, css_selector:str=None):
|
|
104
|
+
def __call__(self, css_selector:str=None, as_=""):
|
|
102
105
|
if css_selector is not None:
|
|
103
|
-
self.elements = [g.find(css_selector) for g in self.elements]
|
|
106
|
+
self.elements = [g.find(css_selector, as_=as_) for g in self.elements]
|
|
107
|
+
if as_:
|
|
108
|
+
raise ValueError("Cannot use @as_ without @css_selector argument")
|
|
104
109
|
return self
|
|
105
110
|
|
|
106
111
|
|
|
@@ -113,11 +118,13 @@ class MapperGetter(Getter, accessor="map"):
|
|
|
113
118
|
|
|
114
119
|
|
|
115
120
|
def __getattribute__(self, k:str):
|
|
116
|
-
_getattr = super().__getattribute__
|
|
117
121
|
if k in _FORBIDDEN_FOLLOW_UP:
|
|
118
122
|
raise ValueError(f"Cannot use {k} on MapperGetter objects.")
|
|
119
123
|
|
|
120
|
-
|
|
124
|
+
_getattr = super().__getattribute__
|
|
125
|
+
|
|
126
|
+
sub_class:_GetterSubClassesAsProperties = _getattr('subclass')
|
|
127
|
+
if sub_class.is_getter_property(k):
|
|
121
128
|
return MapperTransmitter(driver=None, waiter=None, _source=self, _prop=k)
|
|
122
129
|
|
|
123
130
|
if k in MAPPER_GETTER_FOLLOW_UP:
|
|
@@ -147,7 +154,7 @@ class MapperGetter(Getter, accessor="map"):
|
|
|
147
154
|
def _apply_caller(self, bound_methods:List[MethodType]):
|
|
148
155
|
def call(*a, zip_args=True, non_existent_at_idxs=(), **kw):
|
|
149
156
|
"""
|
|
150
|
-
If @zip_args=False, directly transfer *args and
|
|
157
|
+
If @zip_args=False, directly transfer *args and **kwargs to the bound methods, without
|
|
151
158
|
applying any zipping/mapping logic on values given as lists.
|
|
152
159
|
|
|
153
160
|
If @non_existent_at_idxs is given, it must be a tuple of indices. The bound method at these
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import ClassVar
|
|
1
|
+
from typing import ClassVar, List
|
|
2
2
|
|
|
3
3
|
from ._basics.base_getter import BaseGetter
|
|
4
4
|
from ._basics.boolean_getter import BooleanGetter
|
|
@@ -25,8 +25,39 @@ class Getter(ActionGetter, BooleanGetter, BaseGetter):
|
|
|
25
25
|
if accessor:
|
|
26
26
|
setattr(cls.subclass, accessor, cls)
|
|
27
27
|
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def merge_cls(cls, *getters:'Getter', mapped=False, as_=None) -> 'Getter':
|
|
31
|
+
"""
|
|
32
|
+
Merge collections of Getters together. Any MApperGetter object is "flattened" with the
|
|
33
|
+
other elements.
|
|
34
|
+
|
|
35
|
+
If @mapped is True, Build a MapperGetter if the source elements.
|
|
36
|
+
If @designation is given, it will replace the internal path/css selectors informations.
|
|
37
|
+
"""
|
|
38
|
+
flat: List[Getter] = []
|
|
39
|
+
for g in getters:
|
|
40
|
+
if isinstance(g, cls.subclass.map):
|
|
41
|
+
flat.extend(g.elements)
|
|
42
|
+
else:
|
|
43
|
+
flat.append(g)
|
|
44
|
+
|
|
45
|
+
if mapped:
|
|
46
|
+
return cls.subclass.map.from_(flat[0], flat, as_, as_, merged=True)
|
|
47
|
+
|
|
48
|
+
elements = [e for g in flat for e in g.elements]
|
|
49
|
+
return cls.from_(flat[0], elements, as_, as_)
|
|
50
|
+
|
|
51
|
+
def merge(self, *others:'Getter', mapped=False, as_=None) -> 'Getter':
|
|
52
|
+
"""
|
|
53
|
+
Merge collections of Getters together.
|
|
54
|
+
"""
|
|
55
|
+
return self.merge_cls(self, *others, as_=as_, mapped=mapped)
|
|
56
|
+
|
|
57
|
+
|
|
28
58
|
#------------------------------------------------------------------------------------------
|
|
29
59
|
|
|
60
|
+
|
|
30
61
|
@property
|
|
31
62
|
def all(self):
|
|
32
63
|
return self.subclass.all.from_(self)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|