selenium-query 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,119 @@
1
+
2
+ from typing import Any, Callable, Optional, Sequence, Tuple
3
+
4
+ from selenium.webdriver.remote.webelement import WebElement
5
+
6
+ from .._types_and_tools import Expected, Actual
7
+ from .base_getter import BaseGetter
8
+
9
+
10
+
11
+
12
+ # Value getters:
13
+ def element_is_as_method(elt:WebElement, prop:str):
14
+ return getattr(elt, prop)()
15
+
16
+ def element_rect_value(elt:WebElement, prop:str):
17
+ return elt.rect[prop]
18
+
19
+ # is_ok:
20
+ def contains(exp:Expected, act:Actual):
21
+ return act is not None and exp in act
22
+
23
+ def contains_as_list(exp:Expected, act:Actual):
24
+ return act is not None and exp in act.split()
25
+
26
+ # feedback:
27
+ def should_contain(exp:Expected, act:Actual, with_neg:bool):
28
+ return f"{act!r} should { ' not'*with_neg }contain {exp!r}"
29
+
30
+ def should_be(exp:Expected, act:Actual, with_neg:bool):
31
+ return f"{act!r} should { ' not'*with_neg }be {exp!r}"
32
+
33
+
34
+
35
+ class ComparerGetter(BaseGetter):
36
+ """
37
+ Verify that all the elements are matching the given arguments (css, data, props, ...),
38
+ returning a boolean.
39
+ """
40
+
41
+ def id(self, txt:str):
42
+ expectations = ( ("id",txt), )
43
+ return self._check_elements(expectations, WebElement.get_attribute)
44
+
45
+ def has_class(self, txt:str):
46
+ expectations = ( ("class",txt), )
47
+ return self._check_elements(
48
+ expectations, WebElement.get_attribute,
49
+ is_ok = contains_as_list,
50
+ feedback = should_contain
51
+ )
52
+
53
+ def tag_name(self, txt:str):
54
+ expectations = ( ("tag_name",txt), )
55
+ return self._check_elements(expectations, getattr)
56
+
57
+ def text(self, txt:str):
58
+ expectations = ( ("text",txt), )
59
+ return self._check_elements(expectations, getattr)
60
+
61
+ def has_text(self, txt:str):
62
+ expectations = ( ("text",txt), )
63
+ return self._check_elements(expectations, getattr, is_ok=contains, feedback=should_contain)
64
+
65
+ def is_enabled(self, expected:bool=True):
66
+ expectations = ( ("is_enabled",expected), )
67
+ return self._check_elements(expectations, element_is_as_method)
68
+
69
+ def is_selected(self, expected:bool=True):
70
+ expectations = ( ("is_selected",expected), )
71
+ return self._check_elements(expectations, element_is_as_method)
72
+
73
+ def is_displayed(self, expected:bool=True):
74
+ expectations = ( ("is_displayed",expected), )
75
+ return self._check_elements(expectations, element_is_as_method)
76
+
77
+ def css(self, dct=None, **kwargs:Any):
78
+ expectations = self._merge_args(dct,kwargs).items()
79
+ return self._check_elements(expectations, WebElement.value_of_css_property)
80
+
81
+ def data(self, dct=None, **kwargs:Any):
82
+ expectations = {'data'+s: v for s,v in self._merge_args(dct,kwargs).items()}
83
+ return self.props(expectations)
84
+
85
+ def props(self, dct=None, **kwargs:Any):
86
+ expectations = self._merge_args(dct,kwargs).items()
87
+ return self._check_elements(expectations, WebElement.get_attribute)
88
+
89
+ def rect(self, *, x:float=None, y:float=None, width:float=None, height:float=None):
90
+ items = (('x',x), ('y',y), ('width',width), ('height',height))
91
+ expectations = tuple( (prop,exp) for prop,exp in items if exp is not None ),
92
+ return self._check_elements(expectations, element_rect_value)
93
+
94
+
95
+ #----------------------------------------------------------------------------
96
+
97
+
98
+ def _merge_args(self, dct:Optional[dict], kwargs:dict):
99
+ if dct:
100
+ kwargs.update(dct)
101
+ return kwargs
102
+
103
+ def _check_elements(
104
+ self,
105
+ ref_items: Sequence[Tuple[str,Expected]],
106
+ value_getter: Callable[[WebElement,str],Any],
107
+ *,
108
+ is_ok: Callable[[Expected,Actual],bool] = None,
109
+ feedback: Callable[[Any,Any],bool] = None,
110
+ ):
111
+ """
112
+ Method applying the logic of the child class to each of the properties/values/...
113
+
114
+ @ref_items: Sequence/Iterable (NOT a generator) of tuples `(prop_or_attr, expected_value)`.
115
+ @value_getter: Access the value for the given string on the WebElement.
116
+ @is_ok: if needed, compare expected value with the value coming from the value_getter.
117
+ @feedback: build a string for assertion messages (see AssertGetter).
118
+ """
119
+ raise NotImplementedError()
@@ -0,0 +1,53 @@
1
+ from typing import Any, Callable
2
+
3
+ from selenium.webdriver.remote.webelement import WebElement
4
+
5
+ from .getter import Getter
6
+
7
+
8
+ class FilterGetter(Getter, accessor="filter"):
9
+ """
10
+ Accessible through `Getter.filter`.
11
+ Transform the css, data, props, ... methods into filters: each of them now returns a
12
+ new FilterGetter instance whose the elements are only containing those which match the
13
+ desired values.
14
+
15
+ Basic usage:
16
+
17
+ Getter(...).filter.has_class('this') -> FilterGetter
18
+
19
+ FilterGetter instances can use custom predicates when called. The predicate takes a
20
+ Getter object as argument (one per `self.elements`):
21
+
22
+ Getter.filter(predicate) -> FilterGetter
23
+ """
24
+
25
+ def __call__(self, getter_ok:Callable[[Getter],bool]):
26
+ out = [g.elements[0] for g in self if getter_ok(g)]
27
+ return self.from_(self, elements=out)
28
+
29
+
30
+ def _check_elements(
31
+ self,
32
+ ref_items,
33
+ value_getter: Callable[[WebElement,str],Any],
34
+ *,
35
+ is_ok: Callable[[Any,Any],bool] = None,
36
+ **_,
37
+ ) -> 'FilterGetter':
38
+ """
39
+ The method now returns a new Getter instance whose the elements list only contains those
40
+ which match the criteria given to the calling method (css, data, props, ...).
41
+ """
42
+ is_ok = self._default_is_ok(is_ok)
43
+ out = [
44
+ element
45
+ for element in self.elements
46
+ if all( is_ok(exp, value_getter(element, prop)) for prop, exp in ref_items)
47
+ ]
48
+ return self.from_(self, elements=out)
49
+
50
+ @classmethod
51
+ def from_(cls, getter:'Getter', elements=None, _css_selector=None, _full_css=None):
52
+ return super().from_(getter, elements, _css_selector, (_full_css or getter._full_css)+"[filtered]")
53
+
@@ -0,0 +1,226 @@
1
+ from dataclasses import dataclass, field
2
+ from types import MethodType
3
+ from typing import Any, Dict, List, Optional, Tuple
4
+
5
+
6
+ from ._types_and_tools import MAPPER_GETTER_FOLLOW_UP, TGetter
7
+ from ._basics.base_getter import forbid_negation
8
+ from .getter import Getter
9
+
10
+
11
+
12
+ _FORBIDDEN_FOLLOW_UP = 'go', 'perform', 'scroll_to', 'map', 'not_'
13
+
14
+
15
+
16
+ @dataclass
17
+ class MapperTransmitter(Getter):
18
+ """
19
+ Extends Getter for auto completion purpose only: the actual interface is different.
20
+ """
21
+ _source: TGetter = None
22
+ _prop: str = None
23
+ _call_args: Optional[Tuple[Any]] = None
24
+
25
+
26
+ def __getattribute__(self, k:str):
27
+ _getattr = super().__getattribute__
28
+
29
+ if k in MAPPER_GETTER_FOLLOW_UP:
30
+ source: MapperGetter = _getattr('_source')
31
+ return source._apply(k, transmitter=self)
32
+
33
+ kls = _getattr('__class__')
34
+ is_mapper_getter_attribute = k in _getattr('__dict__') or k in kls.__dict__
35
+ if is_mapper_getter_attribute:
36
+ return _getattr(k)
37
+
38
+ raise ValueError(f"Cannot use `{k}` on { kls.__name__ } objects.")
39
+
40
+
41
+ def __call__(self, *a, **kw):
42
+ """
43
+ Relay to standardize TransmitterGetters for `.check(...)` or `.order_by(...)`.
44
+ """
45
+ self._call_args = a, kw
46
+ return self
47
+
48
+
49
+ def _apply(self, mapped:'Getter'):
50
+ out = getattr(mapped, self._prop)
51
+ if self._call_args:
52
+ a, kw = self._call_args
53
+ out = out(*a, **kw)
54
+ return out
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+ @dataclass(repr=False)
63
+ class MapperGetter(Getter, accessor="map"):
64
+ """
65
+ Allow to work with a Getter "behaving like a list": the attributes/methods calls are
66
+ transferred to the elements (as Getters) instead of being applied to the current Getter.
67
+
68
+ Some methods or attributes accesses are forbidden on MapperGetter objects:
69
+ 'go', 'perform', 'scroll_to', 'map'
70
+
71
+ Any subsequent "Getter" related method call (see `_GETTERS`) whose arguments are lists will
72
+ automatically map the lists elements to each internal `MapperGetter.elements` when transmitting
73
+ the methods call. The length of the lists and the one of `MapperGetter.elements` are check up
74
+ front and have to match, or ValueError is raised.
75
+
76
+ Other methods or property calls are working directly applying to the MapperGetter instance.
77
+
78
+ Examples:
79
+
80
+ G = Getter(...)
81
+ G.check.count(3)
82
+
83
+ mapped = G.map('li.that-class') # Equivalent to applying `find` to each element
84
+ mapped.count() == [1,4,6] # Gives the number of `li.that-class` elements
85
+ # held by each of the original G elements.
86
+
87
+ Note this class is really helpful only when testing hierarchical DOM structures, otherwise,
88
+ assertions with different expectations for each elements can already be done through
89
+ AsserterGetter objects, passing in lists or tuples, instead of values.
90
+ """
91
+
92
+ elements: List[Getter] = field(default_factory=list)
93
+ """ Override """
94
+
95
+
96
+ @classmethod
97
+ def from_(cls, getter:Getter, _css_selector=None, _full_css=None):
98
+ return super().from_(getter, [*getter], _css_selector, _full_css)
99
+
100
+
101
+ def __call__(self, css_selector:str=None):
102
+ if css_selector is not None:
103
+ self.elements = [g.find(css_selector) for g in self.elements]
104
+ return self
105
+
106
+
107
+
108
+ def __iter__(self):
109
+ yield from self.elements
110
+
111
+ def __getitem__(self, idx:int):
112
+ return self.elements[idx]
113
+
114
+
115
+ def __getattribute__(self, k:str):
116
+ _getattr = super().__getattribute__
117
+ if k in _FORBIDDEN_FOLLOW_UP:
118
+ raise ValueError(f"Cannot use {k} on MapperGetter objects.")
119
+
120
+ if _getattr('subclass').is_getter_property(k):
121
+ return MapperTransmitter(driver=None, waiter=None, _source=self, _prop=k)
122
+
123
+ if k in MAPPER_GETTER_FOLLOW_UP:
124
+ return super().__getattribute__('_apply')(k)
125
+
126
+ # Using "normal" methods/logic: just use the basic "get" logic
127
+ return _getattr(k)
128
+
129
+
130
+ @forbid_negation
131
+ def _apply(self, prop:str, transmitter:Optional[MapperTransmitter]=None):
132
+ if not self.elements:
133
+ raise ValueError(f"Cannot `_apply` on an empty MapperGetter instance.")
134
+
135
+ out = []
136
+ for g in self.elements:
137
+ if transmitter is not None:
138
+ g = transmitter._apply(g)
139
+ out.append(getattr(g, prop))
140
+
141
+ # If the values are actually bound methods, the next operation will be a method call:
142
+ if isinstance(out[0], MethodType):
143
+ return self._apply_caller(out)
144
+ return out
145
+
146
+
147
+ def _apply_caller(self, bound_methods:List[MethodType]):
148
+ def call(*a, zip_args=True, non_existent_at_idxs=(), **kw):
149
+ """
150
+ If @zip_args=False, directly transfer *args and ù**kwargs to the bound methods, without
151
+ applying any zipping/mapping logic on values given as lists.
152
+
153
+ If @non_existent_at_idxs is given, it must be a tuple of indices. The bound method at these
154
+ indices won't be called and their source Getter have to be non existent, otherwise an
155
+ error will be raised.
156
+ """
157
+ self._validate_non_existent_elements(bound_methods, non_existent_at_idxs)
158
+
159
+ if zip_args:
160
+ args = self._validate_and_standardize_args(a)
161
+ kwargs = self._validate_and_standardize_kwargs(kw)
162
+ out = [
163
+ method.__self__ if i in non_existent_at_idxs else method(*aa, **kkw)
164
+ for i,(method, aa, kkw) in enumerate(zip(bound_methods, args, kwargs))
165
+ ]
166
+
167
+ else:
168
+ out = [
169
+ method.__self__ if i in non_existent_at_idxs else method(*a,**kw)
170
+ for i,method in enumerate(bound_methods)
171
+ ]
172
+
173
+ if isinstance(out[0], Getter):
174
+ # elements are already Getters, no need to convert again:
175
+ return super(self.__class__, self).from_(self, elements=out)
176
+ return out
177
+
178
+ return call
179
+
180
+
181
+ #------------------------------------------------------------------------------------------------
182
+
183
+
184
+ def _validate_non_existent_elements(self, bound_methods, non_existent_at_idxs):
185
+ if non_existent_at_idxs and (existents := [
186
+ bound_methods[i].__self__
187
+ for i in non_existent_at_idxs if len(bound_methods[i].__self__.elements)
188
+ ]):
189
+ raise ValueError(
190
+ f"These Getters should have zero elements:\n "+'\n '.join(map(str,existents))
191
+ )
192
+
193
+
194
+ def _validate_and_standardize_args(self, args: Tuple[Any]):
195
+ wrong_indices = [
196
+ i for i,v in enumerate(args)
197
+ if isinstance(v, (list,tuple)) and len(v) != len(self.elements)
198
+ ]
199
+ if wrong_indices:
200
+ raise ValueError(
201
+ f"Mismatched list or tuple argument length at indices {wrong_indices} in {args} "
202
+ f"(expected length: {len(self.elements)})"
203
+ )
204
+
205
+ out_args = tuple(
206
+ tuple( arg[i] if isinstance(arg, (list,tuple)) else arg for arg in args)
207
+ for i in range(len(self.elements))
208
+ )
209
+ return out_args
210
+
211
+
212
+ def _validate_and_standardize_kwargs(self, kw: Dict[str,Any] ):
213
+ lists_or_tuples_kw = tuple((k,v) for k,v in kw.items() if isinstance(v,(list,tuple)))
214
+ if lists_or_tuples_kw and (wrong_keys := [
215
+ k for k,v in lists_or_tuples_kw if len(v) != len(self.elements)
216
+ ]):
217
+ raise ValueError(
218
+ f"Mismatched list or tuple value length for kwargs (keys: {wrong_keys}. "
219
+ f"Expected length: {len(self.elements)})"
220
+ )
221
+
222
+ out_kw = (kw,)*len(self.elements) if not lists_or_tuples_kw else tuple(
223
+ {k: v[i] if isinstance(v,(tuple,list)) else v for k,v in kw.items()}
224
+ for i in range(len(self.elements))
225
+ )
226
+ return out_kw
@@ -0,0 +1,40 @@
1
+ from dataclasses import dataclass
2
+
3
+ from ._types_and_tools import TGetter
4
+ from .getter import Getter
5
+
6
+
7
+ FORBIDDEN_NOT_FOLLOW_UP = set("""
8
+ map order_by
9
+ """.strip().split())
10
+
11
+
12
+ @dataclass
13
+ class NotTransmitter(Getter, accessor='not_'):
14
+ """
15
+ Extends Getter mostly for auto completion purpose.
16
+ """
17
+ source: TGetter = None
18
+
19
+
20
+ def __getattribute__(self, k:str):
21
+ src: TGetter = super().__getattribute__('source')
22
+ out = src.from_(src)
23
+
24
+ if k in FORBIDDEN_NOT_FOLLOW_UP:
25
+ raise ValueError(f"Cannot use `{k}` attribute access after the `not_` attribute.")
26
+
27
+ elif k in ('any', 'all'):
28
+ out._negate_outside = not out._negate_outside
29
+
30
+ elif k != 'not_':
31
+ out._negate = not out._negate
32
+
33
+ return getattr(out, k)
34
+
35
+
36
+ @classmethod
37
+ def from_(cls, getter, elements=None, _css_selector=None, _full_css=None):
38
+ out = super().from_(getter, elements, _css_selector, _full_css)
39
+ out.source = getter
40
+ return out
@@ -0,0 +1,70 @@
1
+ from typing import Any, Callable, List
2
+
3
+ from selenium.webdriver.remote.webelement import WebElement
4
+
5
+ from .getter import Getter
6
+ from ._values_getter import ValuesGetter
7
+
8
+
9
+
10
+
11
+ class OrderGetter(ValuesGetter, accessor="order_by"):
12
+ """
13
+ Enable to sort the inner `elements` in various ways, returning a new instance of
14
+ the original class.
15
+
16
+ Getter(...).order_by.id -> Getter
17
+ Getter(...).order_by.has_class("xxx") -> Getter
18
+
19
+ It is possible to sort the elements with a custom key function by calling the OrderGetter
20
+ instance. The key function takes a Getter argument:
21
+
22
+ Getter(...).order_by(custom_key_as_getter) -> Getter
23
+ """
24
+
25
+ _src_getter: Getter
26
+
27
+ # Autocompletion helpers: override the types WITHOUT assigning any actual value:
28
+ id: Getter
29
+ class_: Getter
30
+ tag_name: Getter
31
+ text: Getter
32
+ rect: Getter
33
+ is_enabled: Getter # WARNING: use as property!!
34
+ is_selected: Getter # WARNING: use as property!!
35
+ is_displayed: Getter # WARNING: use as property!!
36
+ css: Callable[[Getter],Getter]
37
+ data: Callable[[Getter],Getter]
38
+ props: Callable[[Getter],Getter]
39
+
40
+
41
+ @classmethod
42
+ def from_(cls, getter, elements=None, _css_selector=None, _full_css=None):
43
+ orderer = super().from_(getter, elements, _css_selector, _full_css)
44
+ orderer._src_getter = getter
45
+ return orderer
46
+
47
+
48
+ def __call__(self, keyer: Callable[[Getter],Any]):
49
+ keys = [ keyer(g) for g in self ]
50
+ return self.__order(keys)
51
+
52
+
53
+ def _get_values(self, names, value_getter):
54
+ keys = super()._get_values(names, value_getter)
55
+ return self.__order(keys)
56
+
57
+
58
+ def __order(self, keys:List[Any]):
59
+ # Avoid any trouble (output types changing when only one element) + speed up:
60
+ if len(self.elements) == 1:
61
+ return self._src_getter
62
+
63
+ # Standardization if some elements returned list and others returned bare values (this
64
+ # may happen because of the ValueGetter logistic), to avoid crashes during comparisons:
65
+ if 0 < sum(isinstance(v, list) for v in keys) < len(keys):
66
+ keys = [ v if isinstance(v, list) else [v] for v in keys ]
67
+
68
+ by_idx = sorted(range(len(self.elements)), key=keys.__getitem__)
69
+ ordered = [self.elements[i] for i in by_idx]
70
+ return self._src_getter.from_(self._src_getter, elements=ordered)
@@ -0,0 +1,93 @@
1
+ from functools import cached_property
2
+ from dataclasses import dataclass, fields
3
+ from contextlib import _GeneratorContextManager
4
+ from typing import Any, Dict, Literal, Type, TypeVar, Union, TYPE_CHECKING
5
+
6
+
7
+ if TYPE_CHECKING:
8
+ from ._any_all_getters import BoolAnyGetter, BoolAllGetter
9
+ from ._values_getter import ValuesGetter
10
+ from ._asserter_getter import AsserterGetter
11
+ from ._filter_getter import FilterGetter
12
+ from ._negation_transmitter import NotTransmitter
13
+ from ._order_getter import OrderGetter
14
+ from ._mapper_getter import MapperGetter
15
+ from .getter import Getter
16
+
17
+
18
+
19
+ Expected = Any
20
+ Actual = Any
21
+ GetterOrCss = Union['Getter',str]
22
+ Rect = Dict[Literal['x','y','width','height'],float]
23
+ WaitingContext = _GeneratorContextManager[None, None, None]
24
+
25
+
26
+ TGetter = TypeVar('TGetter', bound='Getter')
27
+
28
+
29
+
30
+ @dataclass
31
+ class _GetterSubClassesAsProperties:
32
+
33
+ all: Type['BoolAllGetter'] = None
34
+ any: Type['BoolAnyGetter'] = None
35
+ check: Type['AsserterGetter'] = None
36
+ filter: Type['FilterGetter'] = None
37
+ get: Type['ValuesGetter'] = None
38
+ order_by: Type['OrderGetter'] = None
39
+ map: Type['MapperGetter'] = None
40
+ not_: Type['NotTransmitter'] = None
41
+
42
+ @cached_property
43
+ def fields(self):
44
+ return {f.name for f in fields(self.__class__)}
45
+
46
+ def is_getter_property(self, prop:str):
47
+ return prop in self.fields
48
+
49
+
50
+
51
+
52
+ def center_of(g:'Getter'):
53
+ d = g.get.rect
54
+ return d['x'] + d['width']/2, d['y'] + d['height']/2
55
+
56
+
57
+
58
+ MAPPER_GETTER_FOLLOW_UP = set("""
59
+ exists
60
+ count
61
+ find
62
+ __getitem__
63
+ id
64
+ class_
65
+ has_class
66
+ tag_name
67
+ text
68
+ has_text
69
+ is_enabled
70
+ is_selected
71
+ is_displayed
72
+ css
73
+ data
74
+ props
75
+ rect
76
+ reset_actions
77
+ click
78
+ click_and_hold
79
+ context_click
80
+ double_click
81
+ drag_and_drop
82
+ drag_and_drop_by_offset
83
+ key_down
84
+ key_up
85
+ moved_by_offset
86
+ move_to
87
+ move_to_element
88
+ move_to_element_with_offset
89
+ pause
90
+ release
91
+ send_keys_to_element
92
+ send_keys
93
+ """.split())
@@ -0,0 +1,78 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Callable, Collection
3
+
4
+ from selenium.webdriver.remote.webelement import WebElement
5
+
6
+
7
+
8
+ from ._types_and_tools import Rect
9
+ from ._basics.base_getter import forbid_negation
10
+ from ._basics.generic_value_getter import element_is_as_method
11
+ from .getter import Getter
12
+
13
+
14
+
15
+
16
+
17
+
18
+ @dataclass(repr=False)
19
+ class ValueNameSetter:
20
+
21
+ value_getter: Callable[[WebElement,str],Any]
22
+ name: str = None
23
+
24
+ def __set_name__(self, _, prop):
25
+ self.name = (self.name or prop).rstrip("_") # Stripping because of `class_`
26
+
27
+ def __get__(self, obj:'ValuesGetter', kls):
28
+ return obj._get_values((self.name,), self.value_getter)
29
+
30
+
31
+
32
+
33
+ class ValuesGetter(Getter, accessor="get"):
34
+ """
35
+ Extract the corresponding value(s) from the current Getter. The output type may change,
36
+ depending on the way the call is done and on what Getter object.
37
+
38
+ Depending on the number of elements:
39
+ * If the getter has only one element, the output is the value's) matching that element.
40
+ * If there are several elements, the output is a list of this/those value(s).
41
+
42
+ Depending on the number of values asked for (may happen when using css, data or props):
43
+ * If there are several kind of values asked for, the corresponding output value is a dict
44
+ (hence, a list of dicts, is several elements).
45
+ * If only one kind of value is asked for, the output is the value itself (hence, a list of
46
+ values if several elements).
47
+ """
48
+
49
+ @forbid_negation
50
+ def _get_values(self, names:Collection[str], value_getter: Callable[[WebElement,str],Any]):
51
+ if len(names)==1:
52
+ name = names[0]
53
+ out = [ value_getter(element, name) for element in self.elements ]
54
+ else:
55
+ out =[
56
+ { prop: value_getter(element, prop) for prop in names }
57
+ for element in self.elements
58
+ ]
59
+ return out if len(self.elements) != 1 else out[0]
60
+
61
+ id: str = ValueNameSetter(WebElement.get_attribute)
62
+ class_: str = ValueNameSetter(WebElement.get_attribute)
63
+ tag_name: str = ValueNameSetter(getattr)
64
+ text: str = ValueNameSetter(getattr)
65
+ rect: Rect = ValueNameSetter(getattr)
66
+ is_enabled: bool = ValueNameSetter(element_is_as_method) # WARNING: use as property!!
67
+ is_selected: bool = ValueNameSetter(element_is_as_method) # WARNING: use as property!!
68
+ is_displayed: bool = ValueNameSetter(element_is_as_method) # WARNING: use as property!!
69
+
70
+ def css(self, *args:str):
71
+ return self._get_values(args, WebElement.value_of_css_property)
72
+
73
+ def data(self, *args:str):
74
+ args = tuple( "data-"+s for s in args )
75
+ return self._get_values(args, WebElement.get_attribute)
76
+
77
+ def props(self, *args:str):
78
+ return self._get_values(args, WebElement.get_attribute)