Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use ... as placeholder in have.texts like collection conditions #528

Open
yashaka opened this issue May 22, 2024 · 2 comments
Open

use ... as placeholder in have.texts like collection conditions #528

yashaka opened this issue May 22, 2024 · 2 comments
Assignees

Comments

@yashaka
Copy link
Owner

yashaka commented May 22, 2024

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 yashaka self-assigned this May 22, 2024
yashaka added a commit that referenced this issue May 23, 2024
+ texts like conditions now accepts int and floats as text item with a TODO: implement same for other text conditions
+ some commented not needed anymore code was left to temporary log in commit some decisions made
github-actions bot added a commit that referenced this issue May 23, 2024
+ texts like conditions now accepts int and floats as text item with a TODO: implement same for other text conditions
+ some commented not needed anymore code was left to temporary log in commit some decisions made
@yashaka
Copy link
Owner Author

yashaka commented May 23, 2024

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
  • have._text_patterns(*regex_patterns).with_regex
    • like have.texts but with regex patterns as expected, i.e. no list globs support
  • 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 or texts_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 added a commit that referenced this issue May 24, 2024
+ [#528] DOCS: docstrings for have.*_like collection conditions and also more verbose parameter names
+ Fix misleading absence of waiting in slicing behavior
github-actions bot added a commit that referenced this issue May 24, 2024
+ [#528] DOCS: docstrings for have.*_like collection conditions and also more verbose parameter names
+ Fix misleading absence of waiting in slicing behavior
@yashaka
Copy link
Owner Author

yashaka commented Jun 23, 2024

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"!

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=...))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant