Skip to content

Commit

Permalink
[#505] REFACTOR: set _name on pom entity to use on logging + ...
Browse files Browse the repository at this point in the history
+ TODO: implement pom-descriptor-like decorators to name objects returned from methods
  • Loading branch information
yashaka committed Jul 24, 2024
1 parent 1716c33 commit c74063a
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 4 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ check vscode pylance, mypy, jetbrains qodana...

### DOING: draft Element descriptors POC?

#### TODO: make descriptor based PageObjects be used as descriptors on their own

#### TODO: implement pom-descriptor-like decorators to name objects returned from methods

... maybe even from properties? (but should work out of the box if @property is applied as last)

### Deprecated conditions

- `be.present` in favor of `be.present_in_dom`
Expand Down
4 changes: 2 additions & 2 deletions docs/faq/custom-test-id-selectors-howto.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

{% include-markdown 'warn-from-next-release.md' %}

– By using advanced [config.selector_to_by_strategy][selene.core.configuration.Config.selector_to_by_strategy] as simply as:
– By customizing [config.selector_to_by_strategy][selene.core.configuration.Config.selector_to_by_strategy] as simply as:

```python
# tests/conftest.py
Expand Down Expand Up @@ -70,4 +70,4 @@ def test_search():
browser.should(have.title_containing('yashaka/selene'))
```

Se a bigger example of utilizing same technique with [Page Object Model pattern applied to the DataGrid React component](https://github.com/yashaka/selene/blob/master/tests/examples/pom/test_material_ui__react_x_data_grid__mit.py).
See a bigger example of utilizing same technique with [Page Object Model pattern applied to the DataGrid React component](https://github.com/yashaka/selene/blob/master/tests/examples/pom/test_material_ui__react_x_data_grid__mit.py).
6 changes: 5 additions & 1 deletion selene/support/_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ def decorator_factory(wait):
def decorator(for_):
@functools.wraps(for_)
def wrapper(fn):
title = f'{wait.entity}: {fn}'
title = (
wait.entity._name or str(wait.entity)
if hasattr(wait.entity, '_name')
else str(wait.entity)
) + f': {fn}'

def translate(initial: str, item: Tuple[str, str]):
old, new = item
Expand Down
2 changes: 2 additions & 0 deletions selene/support/_pom.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ def __get__(self, instance, owner):
# return getattr(instance, self._name)
# else:
as_context = self._as_context(instance)
setattr(as_context, '_name', self._name)
setattr(instance, self._name, as_context)
return as_context

Expand Down Expand Up @@ -239,6 +240,7 @@ def __set_name__(self, owner, name):

def __get__(self, instance, owner):
as_context = self._as_context(instance)
setattr(as_context, '_name', self._name)
setattr(instance, self._name, as_context)
return as_context

Expand Down
146 changes: 145 additions & 1 deletion tests/examples/pom/test_material_ui__react_x_data_grid__mit.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import re
import logging

import pytest

import selene
from selene import have, be, command, query
from selene import have, be, command, query, support
from selene.support._pom import element, all_
from selene.common.helpers import _HTML_TAGS

Expand Down Expand Up @@ -36,10 +37,12 @@ class DataGridMIT:
def __init__(self, context: selene.Element):
self.context = context

# @all_ # todo: make it or @all_.by or etc. work (maybe @all_.named)
def cells_of_row(self, number, /):
return self.rows[number - 1].all(self._cells_selector)

# todo: support int for column
# @element # todo: make it or @element.by or etc. work (maybe @element.named)
def cell(self, *, row, column_data_field=None, column=None):
if column:
column_data_field = self.column_headers.element_by(
Expand All @@ -50,19 +53,65 @@ def cell(self, *, row, column_data_field=None, column=None):
have.attribute('data-field').value(column_data_field)
)

# @step # todo make it work
def set_cell(self, *, row, column_data_field=None, column=None, to_text):
self.cell(
row=row, column_data_field=column_data_field, column=column
).double_click()
self.editing_cell_input.perform(command.select_all).type(to_text).press_enter()


class StringHandler(logging.Handler):
terminator = '\n'

def __init__(self):
logging.Handler.__init__(self)
self.stream = ''

def emit(self, record):
try:
msg = self.format(record)
# issue 35046: merged two stream.writes into one.
self.stream += msg + self.terminator
except Exception:
self.handleError(record)


log = logging.getLogger(__file__)
log.setLevel(20)
handler = StringHandler()
formatter = logging.Formatter("%(message)s")
handler.setFormatter(formatter)
log.addHandler(handler)


class LogToStringStreamContext:
def __init__(self, title, params):
self.title = title
self.params = params

def __enter__(self):
log.info('%s', self.title)

def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
# log.info('%s: PASSED', self.title)
pass
else:
log.info('\n\n%s\n%s', exc_type, exc_val)


@pytest.fixture(scope='function')
def browser():
selene.browser.driver.refresh()

yield selene.browser.with_(
timeout=2.0,
_wait_decorator=support._logging.wait_with(
context=LogToStringStreamContext, # noqa
# in real life could be:
# context=allure_commons._allure.StepContext
),
selector_to_by_strategy=lambda selector: (
# wrap into default strategy
selene.browser.config.selector_to_by_strategy(
Expand Down Expand Up @@ -146,3 +195,98 @@ def test_material_ui__react_x_data_grid_mit(browser):
characters.cells_of_row(1).should(
have._exact_texts_like({...}, {...}, 'Jon', 'Snow', '14', 'Jon Snow')
)

# - check logging on failed
try:
characters.cells_of_row(1).with_(timeout=0.1).should(
have._exact_texts_like({...}, {...}, 'John', 'Snow', '14', 'John Snow')
)
pytest.fail('should have failed on texts mismatch')
except AssertionError:
# THEN everything is logged:
assert (
'column_headers: should have size 6\n'
'column_headers: should have exact texts like:\n'
' {...}, ID, First name, Last name, Age, Full name\n'
"pagination_rows_displayed: should have exact text '1–5 of 9'\n"
'page_to_right: click\n'
"pagination_rows_displayed: should have exact text '6–9 of 9'\n"
'page_to_left: click\n'
"pagination_rows_displayed: should have exact text '1–5 of 9'\n"
'selected_rows_count: should be not (visible)\n'
"toggle_all_checkbox: should have no (native property 'checked' with value "
"'True')\n"
'toggle_all_checkbox: click\n'
"toggle_all_checkbox: should have native property 'checked' with value "
"'True'\n"
"selected_rows_count: should have exact text '9 rows selected'\n"
'toggle_all_checkbox: click\n'
"toggle_all_checkbox: should have no (native property 'checked' with value "
"'True')\n"
'selected_rows_count: should be not (visible)\n'
'rows: should have size 5\n'
''
'element(\'#DataGridDemo+* .MuiDataGrid-root\').element(\'[role="grid"]\')'
'.element(\'[role="rowgroup"]\').all(\'[role="row"]\')[0].all(\'[role="gridcell"]\'): '
'should have exact texts like:\n'
' {...}, {...}, Jon, Snow, 14, Jon Snow\n'
''
'element(\'#DataGridDemo+* .MuiDataGrid-root\').element(\'[role="grid"]\')'
'.element(\'[role="rowgroup"]\').all(\'[role="row"]\')[0].all(\'[role="gridcell"]\').element_by(has '
"attribute 'data-field' with value 'firstName'): double click\n"
''
'editing_cell_input: send «select all» keys shortcut as ctrl+a or cmd+a for '
'mac\n'
'editing_cell_input: type: John\n'
'editing_cell_input: press keys: ENTER\n'
''
'element(\'#DataGridDemo+* .MuiDataGrid-root\').element(\'[role="grid"]\')'
'.element(\'[role="rowgroup"]\').all(\'[role="row"]\')[0].all(\'[role="gridcell"]\'): '
'should have exact texts like:\n'
' {...}, {...}, John, Snow, 14, John Snow\n'
''
'element(\'#DataGridDemo+* .MuiDataGrid-root\').element(\'[role="grid"]\')'
'.element(\'[role="rowgroup"]\').all(\'[role="row"]\')[0].all(\'[role="gridcell"]\').element_by(has '
"attribute 'data-field' with value 'firstName'): double click\n"
''
'editing_cell_input: send «select all» keys shortcut as ctrl+a or cmd+a for '
'mac\n'
'editing_cell_input: type: Jon\n'
'editing_cell_input: press keys: ENTER\n'
''
'element(\'#DataGridDemo+* .MuiDataGrid-root\').element(\'[role="grid"]\')'
'.element(\'[role="rowgroup"]\').all(\'[role="row"]\')[0].all(\'[role="gridcell"]\'): '
'should have exact texts like:\n'
' {...}, {...}, Jon, Snow, 14, Jon Snow\n'
''
'element(\'#DataGridDemo+* .MuiDataGrid-root\').element(\'[role="grid"]\')'
'.element(\'[role="rowgroup"]\').all(\'[role="row"]\')[0].all(\'[role="gridcell"]\'): '
'should have exact texts like:\n'
' {...}, {...}, John, Snow, 14, John Snow\n'
'\n'
'\n'
"<class 'selene.core.exceptions.TimeoutException'>\n"
'Message: \n'
'\n'
'Timed out after 0.1s, while waiting for:\n'
"browser.element(('css selector', '#DataGridDemo+* "
".MuiDataGrid-root')).element(('css selector', "
'\'[role="grid"]\')).element((\'css selector\', '
'\'[role="rowgroup"]\')).all((\'css selector\', '
'\'[role="row"]\'))[0].all((\'css selector\', \'[role="gridcell"]\')).have '
'exact texts like:\n'
' {...}, {...}, John, Snow, 14, John Snow\n'
'\n'
'Reason: AssertionError: actual visible texts:\n'
' , 1, Jon, Snow, 14, Jon Snow\n'
'\n'
'Pattern used for matching:\n'
' ^[^‚]+‚[^‚]+‚John‚Snow‚14‚John\\ Snow‚$\n'
'Actual text used to match:\n'
' ‹EMTPY_STRING›‚1‚Jon‚Snow‚14‚Jon Snow‚\n'
'Screenshot: '
# 'file:///Users/yashaka/.selene/screenshots/1721780210612/1721780210612.png\n'
# 'PageSource: '
# 'file:///Users/yashaka/.selene/screenshots/1721780210612/1721780210612.html\n'
# '\n'
) in handler.stream

0 comments on commit c74063a

Please sign in to comment.