Issue
I have a BasePage
from selenium import webdriver
from selenium.webdriver import ActionChains, DesiredCapabilities
import pages
"""This class is the parent of all pages"""
"""It contains all the generic methods and utilities for all pages"""
class BasePage:
KV_HUB_URL = "Selenium_Grid_Hub"
KV_PLATFORM = "Platform"
KV_BROWSER = "Browser"
KV_BASE_URL = "Base_Url"
"""If the page is passed with the driver, config, save the info. This should be created from another page object"""
def __init__(self, driver, config):
self.__driver = driver
self.__config = config
#https://www.selenium.dev/selenium/docs/api/py/webdriver/selenium.webdriver.common.desired_capabilities.html
"""If the page is created with the configuration file, set up a new web driver"""
def __init__(self, config):
self.__config = config
capabilities = DesiredCapabilities()
if config[self.KV_BROWSER] == "CHROME":
capabilities = DesiredCapabilities.CHROME.copy()
elif config[self.KV_BROWSER] == "FIREFOX":
capabilities.FIREFOX = DesiredCapabilities.FIREFOX.copy()
capabilities['platform'] = config[self.KV_PLATFORM]
self.__driver = webdriver.Remote(
desired_capabilities=capabilities,
command_executor=config[self.KV_HUB_URL])
def close(self):
self.__driver.close()
def gotoHomePage(self):
self.__driver.get(self.__config[self.KV_BASE_URL])
return pages.home_page.HomePage(self, self.__driver, self.__config)
I also have a HomePage
from selenium.webdriver.common.by import By
import pages
class HomePage(pages.base_page.BasePage):
SEARCH_BAR = (By.XPATH, "//form[@role='search']")
SEARCH_INPUT = (By.XPATH, "//input[@name='q']")
def is_search_bar_dislayed(self):
return False
To not run into circular import, I only performed "import pages" However, when I run, in BasePage's gotoHomePage, the line
return pages.home_page.HomePage(self, self.__driver, self.__config)
is returning
AttributeError: module 'pages' has no attribute 'home_page'
Can you tell me what it means and how to resolve?
Solution
The main problem that needs to be addressed is your class inheritance design. But first, let's solve what you need for the current code. Assuming this is your file structure:
.
└── pages
├── base_page.py
└── home_page.py
Solution 1:
Add pages/__init__.py with the following contents:
import pages.base_page
import pages.home_page
Solution 2:
Change your base_page.py to import pages.home_page
explicitly and not just pages
:
class BasePage:
...
def gotoHomePage(self):
import pages.home_page
...
return pages.home_page.HomePage(self.__driver, self.__config)
Solution 3:
Change your base_page.py to import pages.home_page.HomePage
:
class BasePage:
...
def gotoHomePage(self):
from pages.home_page import HomePage
...
return HomePage(self.__driver, self.__config)
All of the above would have a successful output:
>>> from pages.base_page import BasePage
>>> BasePage("some", "thing").gotoHomePage()
<pages.home_page.HomePage object at 0x7f7bb8dbcd30>
>>>
>>> import pages.base_page
>>> pages.base_page.BasePage("some", "thing").gotoHomePage()
<pages.home_page.HomePage object at 0x7f7bb8cc7bb0>
As I said earlier, the main issue is class inheritance "is-a" relationship. HomePage
is-a BasePage
, in a way that if you create a new instance of HomePage
, you basically created a BasePage
. The BasePage.gotoHomePage()
is wrong for this reason because it seems inappropriate to create a new page that could already be the same type as yourself (in case the current instance is a HomePage
), it is like telling "Hey gotoHomePage
please create a new me".
You might want to redesign this approach. Here is a solution with the use of the Factory Design Pattern.
page_factory.py
from abc import ABC, abstractmethod
from pages.base_page import BasePage
from pages.home_page import HomePage
class PageFactory(ABC):
@abstractmethod
def create_page(self, *args, **kwargs) -> BasePage:
raise NotImplementedError()
class HomePageCreator(PageFactory):
def create_page(self, driver, config) -> BasePage:
return HomePage(driver, config)
Or if you want a simplified version:
page_factory.py
from pages.home_page import HomePage
class SimplifiedPageFactory(ABC):
def create_home_page(self, driver, config) -> HomePage:
return HomePage(driver, config)
Output:
>>> from pages.page_factory import HomePageCreator, SimplifiedPageFactory
>>>
>>> HomePageCreator().create_page("something", "anything")
<pages.home_page.HomePage object at 0x7fbfbc118ca0>
>>>
>>> SimplifiedPageFactory().create_home_page("something", "anything")
<pages.home_page.HomePage object at 0x7fbfbc1b5040>
Then, the order of dependency of the import statements would strictly be child -> parent
so in your case would be home_page.py -> base_page.py
, thus no more circular dependency issues. Now, you can always perform from pages.base_page import BasePage
in all your child classes such as home_page.py
. This is also aligned with your plans for base_page.py where you stated
"This class is the parent of all pages"
Which means it is supposed to be a standalone module, without dependencies to the other modules thus avoiding circular dependencies.
Answered By - Niel Godfrey Ponciano
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.