How to use ChatGPT to polish up the code quality
Tags: codequality, python, gpt3, selenium
Published: 2022-12-27 03:31:40

First off and foremost, everyone should be aware of the high impact trend that ChatGPT has had these weeks, if you live under a rock, it doesn't matter, you can check everything about ChatGPT [here](https://openai.com/blog/chatgpt/) first, then come back here and continue reading 😁.

In my case, I'm going to use this in my field that is automation and testing using **Selenium**, however, this could be applied in any area, I'll emphasize on the way to ask questions to ChatGPT which is the aim for this post.

One of the ways to improve our code is to refactor it so that it is not redundant, alluding to the [DRY principle](https://deviq.com/principles/dont-repeat-yourself). We can spend time trying new alternatives to follow this principle, however, if so we got some notions about this, either due to our experience and working on reading a lot of code from others just to make the code better, this tasks could tend to be tedious, that's why we use tools to avoid wasting time, in this case would be ChatGPT. 

Here, we have this **chunk of code**:

```python
def find_element(self, signer: str, value: str):
        LOCATOR_METHODS = {
            "XPATH": By.XPATH,
            "CLASS_NAME": By.CLASS_NAME,
            "CSS_SELECTOR": By.CSS_SELECTOR,
            "ID": By.ID,
            "LINK_TEXT": By.LINK_TEXT,
            "NAME": By.NAME,
            "PARTIAL_LINK_TEXT": By.PARTIAL_LINK_TEXT,
            "TAG_NAME": By.TAG_NAME,
        }

        locator_method = LOCATOR_METHODS.get(signer)
        if not locator_method:
            raise ValueError(f"Invalid signer: {signer}")

        return self.driver.find_element(locator_method, value)  
        
def find_elements(self, signer: str, value: str):
        LOCATOR_METHODS = {
            "XPATH": By.XPATH,
            "CLASS_NAME": By.CLASS_NAME,
            "CSS_SELECTOR": By.CSS_SELECTOR,
            "ID": By.ID,
            "LINK_TEXT": By.LINK_TEXT,
            "NAME": By.NAME,
            "PARTIAL_LINK_TEXT": By.PARTIAL_LINK_TEXT,
            "TAG_NAME": By.TAG_NAME,
        }

        locator_method = LOCATOR_METHODS.get(signer)
        if not locator_method:
            raise ValueError(f"Invalid signer: {signer}")

        return self.driver.find_elements(locator_method, value)
```
In Selenium, we have these two functions which allow us to search one element (`find_element`) or more (`find_elements`) in the [DOM](https://developer.mozilla.org/es/docs/Glossary/DOM), and I'm doing this because of a module where I type my own customised methods, so here we can see they are the same with the only difference that is, one is for singular and the other one is for plural. This can be improved using some techniques you're probably guessing now, as well as, we can implement our knowledge to get a better result with ChatGPT together, here we go!

We can start off by letting ChatGPT decide to give us an answer to this, just to see what we can expect from it. We leave a prompt like this: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/laasvqy4kf9thhezx3rd.png)

As you can see, I put the code below (using `shift` + `enter`), this because sometimes when we provide a bad input way, it might give us a something wrong or we never wished.

ChatGPT's response was about suggestions but not an example in code, so we can type this respectively to get an answer based on the provided suggestions, here is:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yujji2ad79or0j84y81x.png)

And finally we got the response we wanted, code! But...

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qzrtpl9wdryy2jrps36v.png)

Yeah, the result is too long to put it as a screenshot here. So, here is completely: 
```python
class ElementFinder:
    LOCATOR_METHODS = {
        "XPATH": By.XPATH,
        "CLASS_NAME": By.CLASS_NAME,
        "CSS_SELECTOR": By.CSS_SELECTOR,
        "ID": By.ID,
        "LINK_TEXT": By.LINK_TEXT,
        "NAME": By.NAME,
        "PARTIAL_LINK_TEXT": By.PARTIAL_LINK_TEXT,
        "TAG_NAME": By.TAG_NAME,
    }

    def __init__(self, driver):
        self.driver = driver

    def find_element(self, signer: str, value: str) -> WebElement:
        """Find a single web element on the page.

        Args:
            signer: The type of locator to use (e.g. "XPATH", "ID", etc.).
            value: The value of the locator (e.g. the XPath expression or ID).

        Returns:
            The first web element matching the given locator.

        Raises:
            ValueError: If the signer is invalid.
            NoSuchElementException: If the element is not found.
        """
        locator_method = self.LOCATOR_METHODS.get(signer)
        if not locator_method:
            raise ValueError(f"Invalid signer: {signer}")

        return self.driver.find_element(locator_method, value)  
        
    def find_elements(self, signer: str, value: str) -> List[WebElement]:
        """Find multiple web elements on the page.

        Args:
            signer: The type of locator to use (e.g. "XPATH", "ID", etc.).
            value: The value of the locator (e.g. the XPath expression or ID).

        Returns:
            A list of web elements matching the given locator.

        Raises:
            ValueError: If the signer is invalid.
        """
        locator_method = self.LOCATOR_METHODS.get(signer)
        if not locator_method:
            raise ValueError(f"Invalid signer: {signer}")

        return self.driver.find_elements(locator_method, value)
```
Although the code that ChatGPT is giving us is very good and could be perfectly usable, in my case it is not convenient for me in the context that I need it.

We can ask you to do the same, but ignore **point 5** provided in the previous suggestions, because I don’t need it.

> Point 5: _If the code is intended to be used with multiple web drivers, you may want to consider abstracting the driver-specific code into a separate class or function, so that the main code can be easily adapted to work with different drivers._

Also, instead of using a class, do it in an auxiliary function or using decorators, with these ideas that I already had in mind at the beginning of writing this post, we can improve the answer, let's try to see what it gives us.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wmiejxbzdfivpkoqdoue.png)

Again, the code is too long, so here is:
```python
LOCATOR_METHODS = {
    "XPATH": By.XPATH,
    "CLASS_NAME": By.CLASS_NAME,
    "CSS_SELECTOR": By.CSS_SELECTOR,
    "ID": By.ID,
    "LINK_TEXT": By.LINK_TEXT,
    "NAME": By.NAME,
    "PARTIAL_LINK_TEXT": By.PARTIAL_LINK_TEXT,
    "TAG_NAME": By.TAG_NAME,
}

def find_element(driver: WebDriver, signer: str, value: str) -> WebElement:
    """Find a single web element on the page.

    Args:
        driver: The web driver to use.
        signer: The type of locator to use (e.g. "XPATH", "ID", etc.).
        value: The value of the locator (e.g. the XPath expression or ID).

    Returns:
        The first web element matching the given locator.

    Raises:
        ValueError: If the signer is invalid.
        NoSuchElementException: If the element is not found.
    """
    locator_method = LOCATOR_METHODS.get(signer)
    if not locator_method:
        raise ValueError(f"Invalid signer: {signer}")

    return driver.find_element(locator_method, value)  

def find_elements(driver: WebDriver, signer: str, value: str) -> List[WebElement]:
    """Find multiple web elements on the page.

    Args:
        driver: The web driver to use.
        signer: The type of locator to use (e.g. "XPATH", "ID", etc.).
        value: The value of the locator (e.g. the XPath expression or ID).

    Returns:
        A list of web elements matching the given locator.

    Raises:
        ValueError: If the signer is invalid.
    """
    locator_method = LOCATOR_METHODS.get(signer)
    if not locator_method:
        raise ValueError(f"Invalid signer: {signer}")

    return driver.find_elements(locator_method, value)
```

However, this code would still be inconvenient, and this is because ChatGPT follows the thread of the conversation and follows what you type previously. For him to give us a totally different answer, and in which he does so to the ideas we have in mind, is simply to create a new chat.

Now let's try with this prompt:
> _Now, make me a function that doesn't make me repeat the same function twice to just change the `find_elements` to `find_element`. Here the code:_ **The code is the same above at the beginning of the reading**
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n9jepmwwo72silqz4w0m.png)


### Without further ado, the result here:

> _Here's a modified version of the code that combines the `find_element` and `find_elements` functions into a single function that takes an additional parameter `return_multiple`:_
> ```python
> def find_elements(self, signer: str, value: str, return_multiple=False):
>     LOCATOR_METHODS = {
>         "XPATH": By.XPATH,
>         "CLASS_NAME": By.CLASS_NAME,
>         "CSS_SELECTOR": By.CSS_SELECTOR,
>         "ID": By.ID,
>         "LINK_TEXT": By.LINK_TEXT,
>         "NAME": By.NAME,
>         "PARTIAL_LINK_TEXT": By.PARTIAL_LINK_TEXT,
>         "TAG_NAME": By.TAG_NAME,
>     }
>     locator_method = LOCATOR_METHODS.get(signer)
>     if not locator_method:
>         raise ValueError(f"Invalid signer: {signer}")
>     if return_multiple:
>         return self.driver.find_elements(locator_method, value)
>     else:
>         return self.driver.find_element(locator_method, value)
> ```
> To use the function to find a single element, you can call it like this:
> ```python
> element = find_elements(self, "XPATH", "//div[@class='my-class']", return_multiple=False)
> ```
> _To use the function to find multiple elements, you can call it like this:_
> ```python
> elements = find_elements(self, "XPATH", "//div[@class='my-class']", return_multiple=True)
> ```
> ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1gfn4phcdqvcnoyx966u.png)


This code is more convenient, it is shorter, but because it needs to add the docstrings, we are going to ask for it, as well as to use a decorator function. Here the input: 
> _Now, make a version with decorator functions and add docstrings to it._
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6wdxzi2qt47stceda3fq.png)

The result is too long, again 😐
> ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nao6igfx9o5qujzy1iz6.png)

Anyways, here is: 
```python
def element_finder(return_multiple=False):
    """Decorator function for finding elements.
    
    Parameters:
        return_multiple (bool): Whether to return a single element or a list of elements.
            If True, returns a list of elements. If False, returns a single element.
    
    Returns:
        element or elements: The element(s) found by the decorated function.
    """

    def decorator(func):

        def wrapper(self, signer: str, value: str):
            LOCATOR_METHODS = {
                "XPATH": By.XPATH,
                "CLASS_NAME": By.CLASS_NAME,
                "CSS_SELECTOR": By.CSS_SELECTOR,
                "ID": By.ID,
                "LINK_TEXT": By.LINK_TEXT,
                "NAME": By.NAME,
                "PARTIAL_LINK_TEXT": By.PARTIAL_LINK_TEXT,
                "TAG_NAME": By.TAG_NAME,
            }

            locator_method = LOCATOR_METHODS.get(signer)
            if not locator_method:
                raise ValueError(f"Invalid signer: {signer}")

            if return_multiple:
                return self.driver.find_elements(locator_method, value)
            else:
                return self.driver.find_element(locator_method, value)

        return wrapper

    return decorator


@element_finder(return_multiple=False)
def find_element(self, signer: str, value: str):
    """Find a single element on the page.
    
    Parameters:
        signer (str): The method to use for finding the element.
            Can be one of the following: "XPATH", "CLASS_NAME", "CSS_SELECTOR", "ID",
            "LINK_TEXT", "NAME", "PARTIAL_LINK_TEXT", "TAG_NAME".
        value (str): The value to use with the specified locator method.
    
    Returns:
        element: The element found on the page.
    """
    pass


@element_finder(return_multiple=True)
def find_elements(self, signer: str, value: str):
    """Find multiple elements on the page.
    
    Parameters:
        signer (str): The method to use for finding the element.
            Can be one of the following: "XPATH", "CLASS_NAME", "CSS_SELECTOR", "ID",
            "LINK_TEXT", "NAME", "PARTIAL_LINK_TEXT", "TAG_NAME".
        value (str): The value to use with the specified locator method.
    
    Returns:
        elements: The elements found on the page.
    """
    pass

element = find_element(self, "XPATH", "//div[@class='my-class']")
elements = find_elements(self, "XPATH", "//div[@class='my-class']")

```

In conclusion, we can see that in these ways we can polish up our code with the help of ChatGPT, we can keep in mind these certain things:

- If we want that ChatGPT generates totally different answers, we can generate a new chat and give an input with brief changes.

- It gives us very good implementations by itself, by not giving it so much context as general answers, if we put more specific things in it, it improves, thanks to the fact that we put together what we could have implemented manually ourselves, but ChatGPT interprets it well, and we already value the result at the end.

**Thanks for stopping by, I hope you find these ways to use ChatGPT useful, let your imagination run wild :D**