Skip to content

Commit

Permalink
NOV-239933: set webview to pagelements (#366)
Browse files Browse the repository at this point in the history
* added webview and automatic context selection

* linter fixes

* added test and new logic for iframes

* fix lint

* fix lint

* added tests

* fix lint

* refactored switch to new context

* review fix
  • Loading branch information
hesteban-tuenti authored Nov 23, 2023
1 parent 280c6e3 commit f847786
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 56 deletions.
53 changes: 0 additions & 53 deletions toolium/pageelements/page_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ class PageElement(CommonObject):
or toolium.pageelements.PageElement
or (selenium.webdriver.common.by.By or appium.webdriver.common.mobileby.MobileBy, str)
"""
native_context = "NATIVE_APP"
webview_context_prefix = "WEBVIEW"

def __init__(self, by, value, parent=None, order=None, wait=False, shadowroot=None, webview=False,
webview_context_selection_callback=None, webview_csc_args=None):
Expand Down Expand Up @@ -119,57 +117,6 @@ def _find_web_element(self):
self._web_element = base.find_elements(*self.locator)[self.order] if self.order \
else base.find_element(*self.locator)

def _android_automatic_context_selection(self):
"""Change context selection depending if the element is a webview for android devices"""
# we choose the appPackage webview context, and select the first window returned by mobile: getContexts
if self.webview:
context = None
window_handle = None
if self.webview_context_selection_callback:
context, window_handle = self.webview_context_selection_callback(*self.webview_csc_args)
else:
app_web_context = "{}_{}".format(PageElement.webview_context_prefix,
self.driver.capabilities['appPackage'])
if app_web_context in self.driver.contexts:
context = app_web_context
if self.driver.context != context:
self.driver.switch_to.context(context)
window_handle = self.driver.window_handles[0]
else:
raise KeyError("WEBVIEW context not found")

if context:
if self.driver.context != context:
self.driver.switch_to.context(context)
if self.driver.current_window_handle != window_handle:
self.driver.switch_to.window(window_handle)
else:
raise KeyError("WEBVIEW context not found")
else:
if self.driver.context != PageElement.native_context:
self.driver.switch_to.context(PageElement.native_context)

def _ios_automatic_context_selection(self):
"""Change context selection depending if the element is a webview for ios devices"""
# we choose the last webview context returned by mobile: getContexts for the bundleid
if self.webview:
if self.webview_context_selection_callback:
context_id = self.webview_context_selection_callback(*self.webview_csc_args)
else:
contexts = self.driver.execute_script('mobile: getContexts')
context_id = next(
(item['id'] for item in reversed(contexts) if
'bundleId' in item and item['bundleId'] == self.driver.capabilities['bundleId']),
None)
if context_id:
if self.driver.context != context_id:
self.driver.switch_to.context(context_id)
else:
raise KeyError("WEBVIEW context not found")
else:
if self.driver.context != PageElement.native_context:
self.driver.switch_to.context(PageElement.native_context)

def parent_locator_str(self):
"""Return string with locator tuple for parent element
Expand Down
25 changes: 22 additions & 3 deletions toolium/pageelements/page_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ class PageElements(CommonObject):
"""
page_element_class = PageElement #: class of page elements (PageElement, Button...)

def __init__(self, by, value, parent=None, page_element_class=None, order=None):
def __init__(self, by, value, parent=None, page_element_class=None, order=None, webview=False,
webview_context_selection_callback=None, webview_csc_args=None):
"""Initialize the PageElements object with the given locator components.
If parent is not None, find_elements will be performed over it, instead of
Expand All @@ -53,6 +54,11 @@ def __init__(self, by, value, parent=None, page_element_class=None, order=None):
:param order: index value if the locator returns more than one element
:param page_element_class: class of page elements (PageElement, Button...)
:param shadowroot: CSS SELECTOR of JS element where shadowroot tag appears
:param webview: True if the element is in a mobile webiew
:param webview_context_selection_callback: method provided to select the desired webview context if
automatic_context_selection is enabled. Must return a tuple (context, window_handle) for android, and a context
for ios
:param webview_csc_args: arguments list for webview_context_selection_callback
"""
super(PageElements, self).__init__()
self.locator = (by, value) #: tuple with locator type and locator value
Expand All @@ -61,6 +67,10 @@ def __init__(self, by, value, parent=None, page_element_class=None, order=None):
self.shadowroot = None #: Not implemented for PageElements yet
self.driver_wrapper = DriverWrappersPool.get_default_wrapper() #: driver wrapper instance
# update instance element class or use PageElement class
self.webview = webview
self.webview_context_selection_callback = webview_context_selection_callback #: callback for selection of the
# webview context with automatic_context_selection
self.webview_csc_args = webview_csc_args #: arguments list for the context selection callback method
if page_element_class:
self.page_element_class = page_element_class
self._page_elements = []
Expand Down Expand Up @@ -90,6 +100,12 @@ def web_elements(self):
if self.parent:
self._web_elements = self.utils.get_web_element(self.parent).find_elements(*self.locator)
else:
# check context for mobile webviews
if self.driver_wrapper.config.getboolean_optional('Driver', 'automatic_context_selection'):
if self.driver_wrapper.is_android_test():
self._android_automatic_context_selection()
elif self.driver_wrapper.is_ios_test():
self._ios_automatic_context_selection()
self._web_elements = self.driver.find_elements(*self.locator)
return self._web_elements

Expand All @@ -104,8 +120,11 @@ def page_elements(self) -> List[Any]:
self._page_elements = []
for order, web_element in enumerate(self.web_elements):
# Create multiple PageElement with original locator and order
page_element = self.page_element_class(self.locator[0], self.locator[1], parent=self.parent,
order=order)
page_element =\
self.page_element_class(self.locator[0], self.locator[1], parent=self.parent,
order=order, webview=self.webview,
webview_context_selection_callback=self.webview_context_selection_callback,
webview_csc_args=self.webview_csc_args)
page_element.reset_object(self.driver_wrapper)
page_element._web_element = web_element
self._page_elements.append(page_element)
Expand Down
55 changes: 55 additions & 0 deletions toolium/pageobjects/common_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class CommonObject(object):
:type logger: logging.Logger
:type driver_wrapper: toolium.driver_wrapper.DriverWrapper
"""
native_context = "NATIVE_APP"
webview_context_prefix = "WEBVIEW"

def __init__(self):
"""Initialize common object"""
Expand Down Expand Up @@ -62,3 +64,56 @@ def utils(self):
:returns: utils instance
"""
return self.driver_wrapper.utils

def _switch_to_new_context(self, context):
""" Change to a new context if its different than the current one"""
if self.driver.context != context:
self.driver.switch_to.context(context)

def _android_automatic_context_selection(self):
"""Change context selection depending if the element is a webview for android devices"""
# we choose the appPackage webview context, and select the first window returned by mobile: getContexts
if self.webview or (self.parent and self.parent.webview):
context = None
window_handle = None
if self.webview_context_selection_callback:
context, window_handle = self.webview_context_selection_callback(*self.webview_csc_args)
elif self.parent and self.parent.webview_context_selection_callback:
context, window_handle = self.parent.webview_context_selection_callback(*self.parent.webview_csc_args)
else:
app_web_context = "{}_{}".format(CommonObject.webview_context_prefix,
self.driver.capabilities['appPackage'])
if app_web_context in self.driver.contexts:
context = app_web_context
self._switch_to_new_context(context)
window_handle = self.driver.window_handles[0]
else:
raise KeyError("WEBVIEW context not found")

if context:
self._switch_to_new_context(context)
if self.driver.current_window_handle != window_handle:
self.driver.switch_to.window(window_handle)
else:
raise KeyError("WEBVIEW context not found")
else:
self._switch_to_new_context(CommonObject.native_context)

def _ios_automatic_context_selection(self):
"""Change context selection depending if the element is a webview for ios devices"""
# we choose the last webview context returned by mobile: getContexts for the bundleid
if self.webview:
if self.webview_context_selection_callback:
context_id = self.webview_context_selection_callback(*self.webview_csc_args)
else:
contexts = self.driver.execute_script('mobile: getContexts')
context_id = next(
(item['id'] for item in reversed(contexts) if
'bundleId' in item and item['bundleId'] == self.driver.capabilities['bundleId']),
None)
if context_id:
self._switch_to_new_context(context_id)
else:
raise KeyError("WEBVIEW context not found")
else:
self._switch_to_new_context(CommonObject.native_context)
85 changes: 85 additions & 0 deletions toolium/test/pageelements/test_page_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ def init_page_elements(self):
self.inputs = PageElements(By.XPATH, '//input')
self.links = PageElements(By.XPATH, '//a')
self.inputs_with_parent = PageElements(By.XPATH, '//input', parent=(By.ID, 'parent'))
self.inputs_with_webview = PageElements(By.XPATH, '//input', webview=True)
self.inputs_with_webview_callback = \
PageElements(By.XPATH, '//input', webview_context_selection_callback=lambda a, b: (a, b),
webview_csc_args=['WEBVIEW_fake.other', "CDwindow-0123456789"], webview=True)
self.parent_webview = PageElement(By.XPATH, '//parent', webview=True)
self.inputs_with_webview_parent = PageElements(By.XPATH, '//input', parent=self.parent_webview, webview=True)


@pytest.fixture
Expand Down Expand Up @@ -163,3 +169,82 @@ def test_reset_object(driver_wrapper):
assert len(login_page.links._web_elements) == 1
assert login_page.links._page_elements[0]._web_element is not None
assert page_element_21._web_element is not None


def test_get_page_elements_without_webview(driver_wrapper):
driver_wrapper.driver.find_elements.return_value = child_elements
page_elements = LoginPageObject().inputs.page_elements

# Check webview attribute is set to false by default in child elements
assert not page_elements[0].webview
assert not page_elements[1].webview


def test_get_page_elements_with_webview(driver_wrapper):
driver_wrapper.driver.find_elements.return_value = child_elements
page_elements = LoginPageObject().inputs_with_webview.page_elements

# Check webview attribute is set to true in child element when a Pagelements element
# is created with the webview attribute
assert page_elements[0].webview
assert page_elements[1].webview


def test_get_page_elements_with_context_selection_callback_provided(driver_wrapper):
driver_wrapper.driver.find_elements.return_value = child_elements
page_elements = LoginPageObject().inputs_with_webview_callback.page_elements

# Check context selection callback provided is set correctly to pageelements
assert page_elements[0].webview_context_selection_callback
assert page_elements[0].webview_csc_args
assert page_elements[1].webview_context_selection_callback
assert page_elements[1].webview_csc_args


def test_mobile_automatic_context_selection_switch_to_new_webview_context_in_pagelements_without_parent(driver_wrapper):
driver_wrapper.is_android_test = mock.MagicMock(return_value=True)
driver_wrapper.is_ios_test = mock.MagicMock(return_value=False)
driver_wrapper.config = mock.MagicMock()
driver_wrapper.config.set('Driver', 'automatic_context_selection', 'true')
driver_wrapper.driver.capabilities = {'appPackage': 'test.package.fake'}
driver_wrapper.driver.context = "WEBVIEW_other.fake.context"
driver_wrapper.driver.contexts = ["WEBVIEW_test.package.fake", "WEBVIEW_other.fake.context"]
driver_wrapper.driver.current_window_handle = "0987654321"
driver_wrapper.driver.window_handles = ["1234567890", "0987654321"]
LoginPageObject().inputs_with_webview.page_elements
driver_wrapper.driver.switch_to.context.assert_called_with('WEBVIEW_test.package.fake')
driver_wrapper.driver.switch_to.window.assert_called_once_with('1234567890')


def test_mobile_automatic_context_selection_called_in_pagelements_without_parent(driver_wrapper):
PageElements._android_automatic_context_selection = mock.MagicMock()
PageElement._android_automatic_context_selection = mock.MagicMock()
driver_wrapper.is_android_test = mock.MagicMock(return_value=True)
driver_wrapper.is_ios_test = mock.MagicMock(return_value=False)
driver_wrapper.config = mock.MagicMock()
driver_wrapper.config.set('Driver', 'automatic_context_selection', 'true')

mock_element = mock.MagicMock(spec=WebElement)
mock_element.find_elements.return_value = child_elements
driver_wrapper.driver.find_element.return_value = mock_element

LoginPageObject().inputs_with_webview.page_elements
PageElement._android_automatic_context_selection.assert_not_called
PageElements._android_automatic_context_selection.assert_called_once()


def test_mobile_automatic_context_selection_called_in_pagelements_with_parent(driver_wrapper):
PageElements._android_automatic_context_selection = mock.MagicMock()
PageElement._android_automatic_context_selection = mock.MagicMock()
driver_wrapper.is_android_test = mock.MagicMock(return_value=True)
driver_wrapper.is_ios_test = mock.MagicMock(return_value=False)
driver_wrapper.config = mock.MagicMock()
driver_wrapper.config.set('Driver', 'automatic_context_selection', 'true')

mock_element = mock.MagicMock(spec=WebElement)
mock_element.find_elements.return_value = child_elements
driver_wrapper.driver.find_element.return_value = mock_element

LoginPageObject().inputs_with_webview_parent.page_elements
PageElement._android_automatic_context_selection.assert_called_once()
PageElements._android_automatic_context_selection.assert_not_called

0 comments on commit f847786

Please sign in to comment.