I created a test using Selenium with Python to use locators from a class.
Test:
def testPage(driver, url):
login(driver, url)
activateProfile(driver, url)
driver.get(my_url)
assert_true(driver, *Common.user_lbl)
assert_true(driver, *Common.pass_lbl)
.
.
Class:
class Common():
locator = namedtuple('Locator', ['elem_type', 'elem_target', 'elem_val'])
user_lbl = locator("By.ID", "username", "username")
pass_lbl = locator("By.ID", "password", "password")
.
.
I want to change the class so that the values 'user_lbl' and 'pass_lbl' can be determined by the value of global variable 'version'. However, that variable is determined after class Common has already been initialized. I want the class file to look more like this:
class Common():
locator = namedtuple('Locator', ['elem_type', 'elem_target', 'elem_val'])
if global.version == 5:
user_lbl = locator("By.ID", "username", "username")
pass_lbl = locator("By.ID", "password", "password")
else:
user_lbl = locator("By.CLASS_NAME", "user", "username")
pass_lbl = locator("By.CLASS_NAME", "pass", "password")
.
.
... and I want those variables to be determined based upon my 'version' variable. In order to pull this off, I need a way to force Class Common to reinitialize the variables. How do I do this?
CodePudding user response:
There is the property
decorator for cases like that :
# n°1
from collections import namedtuple # stdlib
class Common:
locator = namedtuple('Locator', ['elem_type', 'elem_target', 'elem_val'])
@property
def user_lbl(self):
if VERSION == 5:
return self.locator("By.ID", "username", "username")
else:
return self.locator("By.CLASS_NAME", "user", "username")
@property
def pass_lbl(self):
if VERSION == 5:
return self.locator("By.ID", "password", "password")
else:
return self.locator("By.CLASS_NAME", "pass", "password")
VERSION = 4 # default value
print(Common().user_lbl) # Locator(elem_type='By.CLASS_NAME', elem_target='user', elem_val='username')
print(Common().pass_lbl) # Locator(elem_type='By.CLASS_NAME', elem_target='pass', elem_val='password')
VERSION = 5
print(Common().user_lbl) # Locator(elem_type='By.ID', elem_target='username', elem_val='username')
print(Common().pass_lbl) # Locator(elem_type='By.ID', elem_target='password', elem_val='password')
# ^^
It makes possible to call methods that looks like regular fields. But it requires an object, that is a class instance, in order to work. If you prefer not to have objects (and just a class definition that acts like a namespace), you could use the staticmethod
decorator :
# n°2
from collections import namedtuple # stdlib
class Common:
locator = namedtuple('Locator', ['elem_type', 'elem_target', 'elem_val'])
@staticmethod
def user_lbl():
if VERSION == 5:
return Common.locator("By.ID", "username", "username")
else:
return Common.locator("By.CLASS_NAME", "user", "username")
@staticmethod
def pass_lbl():
if VERSION == 5:
return Common.locator("By.ID", "password", "password")
else:
return Common.locator("By.CLASS_NAME", "pass", "password")
VERSION = 4 # default value
print(Common.user_lbl()) # Locator(elem_type='By.CLASS_NAME', elem_target='user', elem_val='username')
print(Common.pass_lbl()) # Locator(elem_type='By.CLASS_NAME', elem_target='pass', elem_val='password')
VERSION = 5
print(Common.user_lbl()) # Locator(elem_type='By.ID', elem_target='username', elem_val='username')
print(Common.pass_lbl()) # Locator(elem_type='By.ID', elem_target='password', elem_val='password')
# ^^
If you really really, really, want to just have to write Common.user_lbl
then you may need @staticmethod
with @property
but it makes things a bit more difficult to maintain.
Another way could be to instantiate an instance named Common
from a class named differently, which declares its property
s :
# n°3
from collections import namedtuple # stdlib
class _Common: # or whatever
locator = namedtuple('Locator', ['elem_type', 'elem_target', 'elem_val'])
@property
def user_lbl(self):
if VERSION == 5:
return self.locator("By.ID", "username", "username")
else:
return self.locator("By.CLASS_NAME", "user", "username")
@property
def pass_lbl(self):
if VERSION == 5:
return self.locator("By.ID", "password", "password")
else:
return self.locator("By.CLASS_NAME", "pass", "password")
Common = _Common()
VERSION = 4 # default value
print(Common.user_lbl) # Locator(elem_type='By.CLASS_NAME', elem_target='user', elem_val='username')
print(Common.pass_lbl) # Locator(elem_type='By.CLASS_NAME', elem_target='pass', elem_val='password')
VERSION = 5
print(Common.user_lbl) # Locator(elem_type='By.ID', elem_target='username', elem_val='username')
print(Common.pass_lbl) # Locator(elem_type='By.ID', elem_target='password', elem_val='password')
This works too, but I recommend against it because it deviates from PEP8 naming conventions (an instance name should be lowercase).
And finally, maybe you will have other configurations to add later, in which case it may be handy to :
# n°4
from collections import namedtuple # stdlib
class Common:
def __init__(self, version: int):
self._version = version
locator = namedtuple('Locator', ['elem_type', 'elem_target', 'elem_val'])
@property
def user_lbl(self):
if self._version == 5:
return self.locator("By.ID", "username", "username")
else:
return self.locator("By.CLASS_NAME", "user", "username")
@property
def pass_lbl(self):
if self._version == 5:
return self.locator("By.ID", "password", "password")
else:
return self.locator("By.CLASS_NAME", "pass", "password")
common = Common(4)
print(common.user_lbl) # Locator(elem_type='By.CLASS_NAME', elem_target='user', elem_val='username')
print(common.pass_lbl) # Locator(elem_type='By.CLASS_NAME', elem_target='pass', elem_val='password')
common = Common(5)
print(common.user_lbl) # Locator(elem_type='By.ID', elem_target='username', elem_val='username')
print(common.pass_lbl) # Locator(elem_type='By.ID', elem_target='password', elem_val='password')
In this case, you do not need a global variable.
My preferred solutions would be n°4 (Class instance with parameter) then n°2 (staticmethod). But make your own choice :)