Repository: Telefonica/toolium

Issue:
Elements not found in nested groups
When you have several nested groups the parent parameter is not correctly filled and sometimes the elements of a group cannot be found and "NoSuchElementException" is given.

Defined groups example:
class ThirdGroup(Group):
  def init_page_elements(self):
     self.element3 = Button(By.XPATH, './/button[@class="three"]')

class SecondGroup(Group):
  def init_page_elements(self):
     self.element2 = ThirdGroup(By.XPATH, './/button[@class="two"]')
     self.test_element = Button(By.XPATH, './/button[@class="two"]//button[@class="three"]')

class FirstGroup(Group):
  def init_page_elements(self):
     self.element1 = SecondGroup(By.XPATH, './/button[@class="first"]')

class ThisPageObject(PageObject):
  def init_page_elements(self):
     self.my_first_group` = FirstGroup(By.XPATH, '//div')

Elements accessing example:
visible1 = self.my_first_group.element1.element2.element3.web_element.is_displayed()
visible2 = self.my_first_group.element1.test_element.web_element.is_displayed()

That first line (visible1) gives a NoSuchElementException, but the last line (visible2) finds the element and returns the correct result. The element accessed in both cases has the same XPATH, but in the first case there is one more nested group than in the second one.


Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 355d22e..8ecab67 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,13 @@
 Toolium Changelog
 =================
 
+v1.2.4
+------
+
+*Release date: 2017-03-17*
+
+- Fix NoSuchElementException error finding elements in nested groups
+
 v1.2.3
 ------
 
diff --git a/VERSION b/VERSION
index 0495c4a..e8ea05d 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-1.2.3
+1.2.4
diff --git a/toolium/pageelements/group_page_element.py b/toolium/pageelements/group_page_element.py
index ae5c376..8f0c0d2 100644
--- a/toolium/pageelements/group_page_element.py
+++ b/toolium/pageelements/group_page_element.py
@@ -50,11 +50,12 @@ class Group(PageObject, PageElement):
 
         :param driver_wrapper: driver wrapper instance
         """
+        from toolium.pageelements.page_elements import PageElements
         if driver_wrapper:
             self.driver_wrapper = driver_wrapper
         self._web_element = None
         for element in self._get_page_elements():
             element.reset_object(driver_wrapper)
-            if not isinstance(element, PageObject):
+            if isinstance(element, (PageElement, PageElements)):
                 # If element is not a page object, update element parent
                 element.parent = self
diff --git a/toolium/pageobjects/page_object.py b/toolium/pageobjects/page_object.py
index d367d0c..84d4a4f 100644
--- a/toolium/pageobjects/page_object.py
+++ b/toolium/pageobjects/page_object.py
@@ -60,10 +60,8 @@ class PageObject(CommonObject):
 
         :returns: list of page elements and page objects
         """
-        from toolium.pageelements.page_element import PageElement
-        from toolium.pageelements.page_elements import PageElements
         page_elements = []
         for attribute, value in list(self.__dict__.items()) + list(self.__class__.__dict__.items()):
-            if attribute != 'parent' and isinstance(value, (PageElement, PageElements, PageObject)):
+            if attribute != 'parent' and isinstance(value, CommonObject):
                 page_elements.append(value)
         return page_elements


Test patch:
diff --git a/toolium/test/pageelements/test_derived_page_element.py b/toolium/test/pageelements/test_derived_page_element.py
index 57ef942..5701f3e 100644
--- a/toolium/test/pageelements/test_derived_page_element.py
+++ b/toolium/test/pageelements/test_derived_page_element.py
@@ -150,18 +150,18 @@ def test_group_reset_object(driver_wrapper):
     # Check that web elements are empty
     assert login_page.menu._web_element is None
     assert login_page.menu.logo._web_element is None
-    assert login_page.menu.logo.parent._web_element is None
+    assert login_page.menu.logo.parent == login_page.menu
 
     login_page.menu.logo.web_element
 
     # Check that web elements are filled
     assert login_page.menu._web_element is not None
     assert login_page.menu.logo._web_element is not None
-    assert login_page.menu.logo.parent._web_element is not None
+    assert login_page.menu.logo.parent == login_page.menu
 
     login_page.menu.reset_object()
 
     # Check that web elements are empty
     assert login_page.menu._web_element is None
     assert login_page.menu.logo._web_element is None
-    assert login_page.menu.logo.parent._web_element is None
+    assert login_page.menu.logo.parent == login_page.menu
diff --git a/toolium/test/pageelements/test_page_elements_groups.py b/toolium/test/pageelements/test_page_elements_groups.py
index 347d67a..bcea69c 100644
--- a/toolium/test/pageelements/test_page_elements_groups.py
+++ b/toolium/test/pageelements/test_page_elements_groups.py
@@ -108,6 +108,15 @@ def test_reset_object_page_elements_groups(driver_wrapper):
     assert column_21.input._web_element is not None
     assert column_21.link._web_element is not None
     assert column_21.input_with_parent._web_element is not None
+    # Check that the group elements have the group as parent
+    assert column_11.parent == row_1
+    assert column_21.parent == row_2
+    assert column_11.input.parent == column_11
+    assert column_11.link.parent == column_11
+    assert column_11.input_with_parent.parent == column_11
+    assert column_21.input.parent == column_21
+    assert column_21.link.parent == column_21
+    assert column_21.input_with_parent.parent == column_21
 
     table_page.reset_object()
 
@@ -128,3 +137,12 @@ def test_reset_object_page_elements_groups(driver_wrapper):
     assert column_21.input._web_element is None
     assert column_21.link._web_element is None
     assert column_21.input_with_parent._web_element is None
+    # Check that the group elements have the group as parent
+    assert column_11.parent == row_1
+    assert column_21.parent == row_2
+    assert column_11.input.parent == column_11
+    assert column_11.link.parent == column_11
+    assert column_11.input_with_parent.parent == column_11
+    assert column_21.input.parent == column_21
+    assert column_21.link.parent == column_21
+    assert column_21.input_with_parent.parent == column_21
diff --git a/toolium/test/pageelements/test_page_nested_groups.py b/toolium/test/pageelements/test_page_nested_groups.py
new file mode 100644
index 0000000..4a4b0cf
--- /dev/null
+++ b/toolium/test/pageelements/test_page_nested_groups.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+u"""
+Copyright 2017 Telefónica Investigación y Desarrollo, S.A.U.
+This file is part of Toolium.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import mock
+import pytest
+from selenium.webdriver.common.by import By
+from selenium.webdriver.remote.webelement import WebElement
+
+from toolium.driver_wrapper import DriverWrapper
+from toolium.driver_wrappers_pool import DriverWrappersPool
+from toolium.pageelements import PageElements, Group, InputText, Link
+from toolium.pageobjects.page_object import PageObject
+
+
+class InnerGroup(Group):
+    def init_page_elements(self):
+        self.input = InputText(By.XPATH, './/input')
+        self.input_with_parent = InputText(By.XPATH, './/input', (By.XPATH, './/parent'))
+
+
+class OuterGroup(Group):
+    def init_page_elements(self):
+        self.inner = InnerGroup(By.XPATH, './/div')
+
+
+class NestedPageObject(PageObject):
+    def init_page_elements(self):
+        self.outer = OuterGroup(By.XPATH, '//div')
+
+
+@pytest.fixture
+def driver_wrapper():
+    # Reset wrappers pool values
+    DriverWrappersPool._empty_pool()
+    DriverWrapper.config_properties_filenames = None
+
+    # Create a new wrapper
+    driver_wrapper = DriverWrappersPool.get_default_wrapper()
+    driver_wrapper.driver = mock.MagicMock()
+
+    return driver_wrapper
+
+
+def test_reset_object_nested_groups(driver_wrapper):
+    # Mock Driver.save_web_element = True
+    driver_wrapper.config = mock.MagicMock()
+    driver_wrapper.config.getboolean_optional.return_value = True
+    # Create mock element
+    mock_element = mock.MagicMock(spec=WebElement)
+    driver_wrapper.driver.find_element.return_value = mock_element
+
+    nested_page = NestedPageObject()
+
+    # Check that web elements are empty
+    assert nested_page.outer._web_element is None
+    assert nested_page.outer.inner._web_element is None
+    assert nested_page.outer.inner.input._web_element is None
+    assert nested_page.outer.inner.input_with_parent._web_element is None
+    assert nested_page.outer.inner.parent == nested_page.outer
+    assert nested_page.outer.inner.input.parent == nested_page.outer.inner
+    assert nested_page.outer.inner.input_with_parent.parent == nested_page.outer.inner
+
+    nested_page.outer.inner.input.web_element
+    nested_page.outer.inner.input_with_parent.web_element
+
+    # Check that web elements are filled
+    assert nested_page.outer._web_element is not None
+    assert nested_page.outer.inner._web_element is not None
+    assert nested_page.outer.inner.input._web_element is not None
+    assert nested_page.outer.inner.input_with_parent._web_element is not None
+    assert nested_page.outer.inner.parent == nested_page.outer
+    assert nested_page.outer.inner.input.parent == nested_page.outer.inner
+    assert nested_page.outer.inner.input_with_parent.parent == nested_page.outer.inner
+
+    nested_page.outer.reset_object()
+
+    # Check that web elements are empty
+    assert nested_page.outer._web_element is None
+    assert nested_page.outer.inner._web_element is None
+    assert nested_page.outer.inner.input._web_element is None
+    assert nested_page.outer.inner.input_with_parent._web_element is None
+    assert nested_page.outer.inner.parent == nested_page.outer
+    assert nested_page.outer.inner.input.parent == nested_page.outer.inner
+    assert nested_page.outer.inner.input_with_parent.parent == nested_page.outer.inner