use ... as placeholder in have.texts like collection conditions
yashaka opened this issue · 2 comments
yashaka commented
Something like:
browser = session_browser.with_(timeout=0.25)
li = browser.all('li')
GivenPage(browser.driver).opened_with_body(
'''
<ul>Hello:
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>X</li>
</ul>
'''
)
X = 'X'
# THEN
li.should(have.texts_like(..., 3, 4, ..., 7, 8, ...).where(one_or_more=...))
yashaka commented
List of conditions added (still marked as experimental with _
prefix):
have._exact_texts_like(*exact_texts_or_list_globs: Union[str, int, float])
have._exact_texts_like(*exact_texts_or_list_globs: Union[str, int, float]).where(**globs_to_override)
have._texts_like(*contained_texts_or_list_globs: Union[str, int, float])
have._texts_like(*contained_texts_or_list_globs: Union[str, int, float]).where(**glob_to_override)
have._texts_like(*regex_patterns_or_list_globs: Union[str, int, float]).with_regex
- is an alias to
have._text_patterns_like
- is an alias to
have._text_patterns(*regex_patterns).with_regex
- like
have.texts
but with regex patterns as expected, i.e. no list globs support
- like
have._texts_like(*texts_with_wildcards_or_list_globs: Union[str, int, float]).with_wildcards
have._texts_like(*texts_with_wildcards_or_list_globs: Union[str, int, float]).where_wildcards(**to_override)
- corresponding
have.no.*
versions of same conditions
Where:
- default list globs are:
[...]
matches zero or one item of any text in the list...
matches exactly one item of any text in the list(...,)
matches one or more items of any text in the list[(...,)]
matches zero or more items of any text in the list
- all globs can be mixed in the same list of expected items in any order
- regex patterns can't use
^
(start of text) and$
(end of text)
because they are implicit, and if added explicitly will break the match - supported wildcards can be overridden and defaults are:
*
matches zero or more of any characters in a text item?
matches exactly one of any character in a text item
Warning:
- Actual implementation does not compare each list item separately, it merges all expected items into one regex pattern and matches it with merged text of all visible elements collection texts, and so it may be tricky to analyze the error message in case of failure. To keep life simpler, try to reduce the usage of such conditions to the simplest cases, preferring wildcards to regex patterns, trying even to avoid wildcards if possible, in the perfect end, sticking just to
exact_texts_like
ortexts_like
conditions with only one explicitly (for readability) customized list glob, choosing...
as the simplest glob placeholder, for example:browser.all('li').should(have._exact_texts_like(1, 2, 'Three', ...).where(one_or_more=...))
to assert actual texts<li>1</li><li>2</li><li>Three</li><li>4</li><li>5</li>
in the list.
Examples of usage:
from selene import browser, have
...
# GivenPage(browser.driver).opened_with_body(
# '''
# <ul>Hello:
# <li>1) One!!!</li>
# <li>2) Two!!!</li>
# <li>3) Three!!!</li>
# <li>4) Four!!!</li>
# <li>5) Five!!!</li>
# </ul>
# '''
# )
browser.all('li').should(have._exact_texts_like(
'1) One!!!', '2) Two!!!', ..., ..., ... # = exactly one
))
browser.all('li').should(have._texts_like(
'\d\) One!+', '\d.*', ..., ..., ...
).with_regex)
browser.all('li').should(have._texts_like(
'?) One*', '?) Two*', ..., ..., ...
).with_wildcards)
browser.all('li').should(have._texts_like(
'_) One**', '_) Two*', ..., ..., ...
).where_wildcards(zero_or_more='**', exactly_one='_'))
browser.all('li').should(have._texts_like(
'One', 'Two', ..., ..., ... # matches each text by contains
)) # kind of "with implicit * wildcards" in the beginning and the end of each text
browser.all('li').should(have._texts_like(
..., ..., ..., 'Four', 'Five'
))
browser.all('li').should(have._texts_like(
'One', ..., ..., 'Four', ... # = exactly one
))
browser.all('li').should(have._texts_like(
'One', 'Two', (..., ) # = one or more
))
browser.all('li').should(have._texts_like(
[(..., )], 'One', 'Two', [(..., )] # = ZERO or more ;)
))
browser.all('li').should(have._texts_like(
[...], 'One', 'Two', 'Three', 'Four', [...] # = zero or ONE ;)
))
# If you don't need so much "globs"...
# (here goes, actually, the 💡RECOMMENDED💡 way to use it in most cases...
# to keep things simpler for easier support and more explicit for readability)
# – you can use the simplest glob item with explicitly customized meaning:
browser.all('li').should(have._exact_texts_like(
..., 'One', 'Two', ... # = zero OR MORE
).where(zero_or_more=...)) # – because the ... meaning was overridden
# Same works for other conditions that end with `_like`
browser.all('li').should(have._exact_texts_like(
..., '1) One!!!', '2) Two!!!', ...
).where(zero_or_more=...))
yashaka commented
Here are the critique of previous globs:
- ... as placeholder for "exactly one" is
- less consistent with common expectation from real life ... meaning
- yet is consistent with kind of "standard python usage of ..."
- yet not consistent with numpy arrays slicing meaning
- in most cases we need "one or more", why not to use the most consice placeholder for it –
...
?- we could not use it before, because in order to mark "exactly one" we would need to "limit"
...
somehow, for example by surrounding it with some "fences", and my first idea was to use()
for that, but()
forces to use coma so we get(...,)
that is not obvious as "exactly one", because 'coma' means that something will go after it :). That's why I had to choose(...,)
as "one or more", then keeping...
as "exactly one". But why not to use{}
for "fencing"? then we don't have this "confusion with,
meaning"!
- we could not use it before, because in order to mark "exactly one" we would need to "limit"
So, why not define default list globs as:
[{...}]
matches zero or one item of any text in the list{...}
matches exactly one item of any text in the list...
matches one or more items of any text in the list (consistent with numpy slicing style)[...]
matches zero or more items of any text in the list
Examples of usage:
from selene import browser, have
...
# GivenPage(browser.driver).opened_with_body(
# '''
# <ul>Hello:
# <li>1) One!!!</li>
# <li>2) Two!!!</li>
# <li>3) Three!!!</li>
# <li>4) Four!!!</li>
# <li>5) Five!!!</li>
# </ul>
# '''
# )
browser.all('li').should(have._exact_texts_like(
'1) One!!!', '2) Two!!!', {...}, {...}, {...} # = exactly one
))
browser.all('li').should(have._texts_like(
'\d\) One!+', '\d.*', {...}, {...}, {...}
).with_regex)
browser.all('li').should(have._texts_like(
'?) One*', '?) Two*', {...}, {...}, {...}
).with_wildcards)
browser.all('li').should(have._texts_like(
'_) One**', '_) Two*', {...}, {...}, {...}
).where_wildcards(zero_or_more='**', exactly_one='_'))
browser.all('li').should(have._texts_like(
'One', 'Two', {...}, {...}, {...} # matches each text by contains
)) # kind of "with implicit * wildcards" in the beginning and the end of each text
browser.all('li').should(have._texts_like(
{...}, {...}, {...}, 'Four', 'Five'
))
browser.all('li').should(have._texts_like(
'One', {...}, {...}, 'Four', {...} # = exactly one
))
browser.all('li').should(have._texts_like(
'One', 'Two', ... # = one or more
))
browser.all('li').should(have._texts_like(
[...], 'One', 'Two', [...] # = ZERO or more ;)
))
browser.all('li').should(have._texts_like(
[{...}], 'One', 'Two', 'Three', 'Four', [{...}] # = zero or ONE ;)
))
# If you don't need so much "globs"...
# (here goes, actually, the 💡RECOMMENDED💡 way to use it in most cases...
# to keep things simpler for easier support and more explicit for readability)
# – you can use the simplest glob item with explicitly customized meaning:
browser.all('li').should(have._exact_texts_like(
..., 'One', 'Two', ... # = one OR MORE
).where(zero_or_more=...)) # – because the ... meaning was overridden
# Same works for other conditions that end with `_like`
browser.all('li').should(have._exact_texts_like(
..., '1) One!!!', '2) Two!!!', ...
).where(zero_or_more=...))