Post

Locating elements.

Locating elements.

Locating Elements

In the last post we discussed about loading locators dynamically. The next portion is to locate the elements when the user types container.page_name.element_name. In order to locate the elements, we will be adopting an approach using the __getattr__(...) of Python.

getattr

Before we go ahead with the implementation of the __getattr__ of Python, we need to understand the working of this magic function.

Yes, I know these are called magic “methods”. However, to me, one who comes from C and C++, these are just functions. There are no methods for me.

In Python, we have two magic functions which might be of interest when we are retrieving attributes(some call it properties as well) of an object. One of the functions is the __getattr__ and the other is the __getattribute__ magic function. When we create an instance of a class, or as we call an object, Python basically calls getattr(instance, attribute_name). Internally when the attribute value is being retrieved using getattr(...) as well, internally the __getattr__ is called if the attribute is not present. __getattribute__ magic function is always called, irrespective of whether the attribute is present or not present.

The loading of the elements are done in the following manner:

1
2
3
4
5
6
...
for section in parser.sections():
  ...
  for option, value in parser.items(section):
    setattr(tmp, option, value)
...

Since __getattr__ is called when the attribute is not present in the instance, we will be using the same in order to locate the element. The following could be considered as a simple implementation.

1
2
3
4
class Page:
  ...
  def __getattr__(self, attribute_name: str):
    ...

Implementation of getting the attribute into a Selenium Web Element

In this function, we will be adding the capacity to resolve the element based on the format of the INI file. In the INI file, the locators would be present as follows:

1
2
3
4
5
6
7
8
...
[Login]
username = CSS | input[data-test="username"] | false
password = CSS | input[data-test="password"] | false
login_button = CSS | button[data-test="login"] | false

[Profile]
...

In order to locate the element, the user would be writing something like Login.username.type("john_doe"). We will concentrate on the Login.username... portion only, we will get back to the ...type("john_doe") in the next post. In order to locate the element and return the same using the ., the definition of the __getattr__(...) to check the value of the option and then return a Selenium Web Element.

The implementation would be changed to something as follows:

1
2
3
4
5
6
7
def __getattr__(self, name):
  value = self.__dict__.get(name)
  if not value:
    raise AttributeError("Invalid attribute name specified")

  strategy, locator, is_multiple = (i.strip() for i in value.split("|"))
  ...

The is_multiple could be converted into boolean using a simple logic as follows:

1
2
3
4
5
...
from ast import literal_eval
...
is_multiple = literal_eval(is_multiple.capitalize())
...

Once this is done, the next step is to locate the element using By.<strategy> resolution. The By.<strategy> resolution could be done in multiple ways, one could achieve this in multiple ways. The easiest one could think of is to create a dictionary and then refer to the dictionary for the value of the strategy. For example,

1
2
3
4
5
strategy = {
  "CSS": By.CSS_SELECTOR,
  "XPATH": By.XPATH,
  ...
}

Now one could query the strategy from the strategy map and retrieve the actual intended By.<strategy>.

There is still a small portion remaining to be done which when added to __getattr__ shall complete the implementation and element location.

Finally locating the elements

Now that we have already parsed the value of the option stored, we will be going ahead to locate the element.

1
2
3
4
5
6
def __getattr__(self, attribute_name: str):
  ...
  if is_multiple:
    return self._get_elements(strategy, locator)
  else:
    return self._get_element(strategy, locator)

As we can see, the self._get_elements(...) will be locating and returning a list of Web Elements and self._get_element(...) will be locating and returning a single Selenium Web Element.

Nut shell

In short, we can write a simple dynamic Page Factory without having to resort to using an external library, armed with just the basics of Python and Selenium module of Python. There is still the part where we might want to write custom utility functions for the Page class instances that are generated at runtime. This is a topic for the next post.

Till then, hang tight.

This post is licensed under CC BY 4.0 by the author.