How to use python/pytest methods as keywords in robot framework

Hi Mentors

  • How to use python/pytest methods as keywords in robot framework?
  • I have a class:
    Name of File Name: pageObjects/LoginPage.py

from selenium import webdriver
from robot.api.deco import library, keyword, not_keyword

class LoginPage:
    textbox_username_id = "Email"
    textbox_password_id = "Password"
    button_login_xpath = "//button[@class='button-1 login-button']"
    link_logout_link_text = "Logout"

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

    def setUserName(self, username):
        self.driver.find_element("id", self.textbox_username_id).clear()
        self.driver.find_element("id", self.textbox_username_id).send_keys(username)

    @staticmethod
    @keyword("SetPassword")
    def setPassword(self, password):
        self.driver.find_element("id", self.textbox_password_id).clear()
        self.driver.find_element("id", self.textbox_password_id).send_keys(password)

Then in the Robot Framework file: Rf_Tests/RF_NopCommerce_VerifyPageTitle.robot


*** Settings ***
Library         SeleniumLibrary
Resource    ../pageObjects/LoginPage.py  

Getting this exception as tooltip with error-line marking underneath.

#Unresolved library: …/pageObjects/LoginPage.py. Error generating libspec: Library ‘LoginPage’ #expected 1 argument, got 0. Consider using default arguments in the …/pageObjects/LoginPage.py #constructor and calling the “Robot Framework: Clear caches and restart” action or adding #“…/pageObjects/LoginPage.py” to the “robot.libraries.libdoc.needsArgs” setting to pass the typed #arguments when generating the libspec.

# I referred this file : "LoginPage.py" both as Resource and as Library. 
Resource    ../testCases/test_Login.py

#On referring it as Library, it gives error underline.

Library      ../pageObjects/LoginPage.py

*** Test Cases ***
Verify Login Page Title

#For the method setUserName(self, username): which is non-static, I created an object but still it did not work.

    ${loginClassObj}=   Evaluate    LoginPage()
    ${username}=        Evaluate    ${loginClassObj}.setUserName("admin@yourstore.com")

# Another way that I am trying is this:
    Evaluate    LoginPage.setUserName("admin@yourstore.com")
    Evaluate    LoginPage.setPassword("yourstore_admin001")

# Another way that I am trying is this:
# based on the fact that I wrote:  @keyword("SetPassword"), and   @staticmethod in the #LoginPage.py file

    Evaluate    setUserName("admin@yourstore.com")
    Evaluate    SetPassword("yourstore_admin001")

My effort is to use the pytest/python methods as keywords in a robot framework test so that I can write functionality in python and use it in the Robot Framework tests.

Any help will be greatly appreciated.
Sincere Regards
EagerToLearn

Hi @EagerToLearn,

The information you seek is here → Keyword names

In your python file rename the function def setUserName(self, username): to def set_user_name(self, username):

Then in your robot file you can just call it like this

Set User Name    admin@yourstore.com

Dave.

1 Like

Worth pointing out too - LoginPage constructor takes driver instance as argument. And if using SeleniumLibrary, there wont be “driver” instance before Open Browser or Create Webdriver has been succesfully called.

2 Likes

Hi,


from robot.api.deco import keyword


class LoginPage:
    textbox_username_id = "Email"
    textbox_password_id = "Password"
    button_login_xpath = "//button[@class='button-1 login-button']"
    link_logout_link_text = "Logout"

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

    @keyword('Set Username')
    def set_user_name(self, username):
        self.driver.find_element("id", self.textbox_username_id).clear()
        self.driver.find_element("id", self.textbox_username_id).send_keys(username)

    @keyword('Set Password')
    def set_password(self, password):
        self.driver.find_element("id", self.textbox_password_id).clear()
        self.driver.find_element("id", self.textbox_password_id).send_keys(password)

    @keyword('Click Login Button')
    def click_login(self):
        self.driver.find_element("xpath", self.button_login_xpath).click()

    @keyword('Click Logout Button')
    def click_logout(self):
        self.driver.find_element("link_text", self.link_logout_link_text).click()

and Robot Framework file:


*** Settings ***
Library         SeleniumLibrary
Resource    ../pageObjects/LoginPage.py
Resource    ../testCases/test_Login.py
# Still giving error
Library     LoginPage
# Or
Library    ../pageObjects/LoginPage.py

*** Test Cases ***
Verify Login Page Title
# Based on [@keyword decorator](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#keyword-decorator):
    **Set Username    admin@yourstore.com**
# OR
    **Set User Name    admin@yourstore.com**

#When run it gives this error

(.venv) PS D:\LEARNING\MY_LEARNING\ROBOTFRAMEWORK_PYCHARM\ROBOTFRAMEWORK\nopCommerceApp\Rf_Tests> robot .\RF_NopCommerce_VerifyPageTitle.robot
**[ ERROR ] Error in file 'D:\LEARNING\MY_LEARNING\ROBOTFRAMEWORK_PYCHARM\ROBOTFRAMEWORK\nopCommerceApp\Rf_Tests\RF_NopCommerce_VerifyPageTitle.robot' on line 4: Library 'LoginPage' expected 1 argument, got 0.**

I am also concerned about two errors:

  • How to successfully refer to a python file and its methods as a Library
  • My python file’s constructor take an argument as a driver which is get using the pytest fixture:
  • Do I need to instantiate an instance of my LoginPage class in Robot framework and then refer its methods using the object? Any link how to do that?

@pytest.fixture()
def setup():
driver = webdriver.Firefox()
print("Firefox is launched ")
return driver


==============================================================================
RF NopCommerce VerifyPageTitle                                                
==============================================================================
Verify Login Page Title                                               | FAIL |
**No keyword with name 'Set Username' found.**
------------------------------------------------------------------------------
RF NopCommerce VerifyPageTitle                                        | FAIL |
1 test, 0 passed, 1 failed

Help is greatly appreciated
Thank you,
EagerToLearn

This is exactly what @rasjani was telling you, LoginPage is expecting an argument, ie:

Library     LoginPage     ${driver}

or

Library    ../pageObjects/LoginPage.py     ${driver}

The problem is at this point you haven’t opened the browser yet so the driver doesn’t exit

Dave.

So what could be the way to accomplish this: to open a web browser before or within the Settings


*** Variables ***
${driver}= “C:/Users/munee/.wdm/drivers/geckodriver/win64/v0.34.0/geckodriver.exe”

*** Settings ***
Library SeleniumLibrary

Library …//pageObjects/LoginPage.py ${driver}
Resource SeleniumDrivers.robot
Suite Setup Update Firefox Webdriver

*** Test Cases ***
Verify Login Page Title
${driverpath}= Update Firefox Webdriver
Create Webdriver Firefox executable_path=${driverpath}
Open Browser https://demo.nopcommerce.com/ Firefox executable_path=${driverpath}
#Set Username admin@yourstore.com
#Set User Name admin@yourstore.com


This is the error that is coming.
TypeError: WebDriver.init() got an unexpected keyword argument ‘executable_path’

While searching: I came to this link:

He says that :


Note that `executable_path` was removed.

If you want to pass in an `executable_path`, you'll have to use the `service` arg now.

```
import os

from selenium import webdriver
from selenium.webdriver.chrome.service import Service

service = Service(
executable_path=r’/usr/bin/chromedriver’,
service_log_path=os.devnull,
)
options = webdriver.ChromeOptions()
options.add_argument(‘–headless’)
options.add_argument(‘–no-sandbox’)
options.add_argument(‘–disable-dev-shm-usage’)
driver = webdriver.Chrome(service=service, options=options)

driver.quit()


Based on this: I modified the init constructor in my LoginPage.py class

def __init__(self, driver):
    self.driver = driver
    service = Service(
        executable_path=r'C:/Users/munee/.wdm/drivers/geckodriver/win64/v0.34.0/',
        service_log_path=os.devnull,
    )
    options = webdriver.FirefoxOptions()
    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-dev-shm-usage')
    driver = webdriver.Firefox(service=service, options=options)
  • but getting this: TypeError: WebDriver.init() got an unexpected keyword argument ‘executable_path’
    So how to do it on the robot framework side? :slight_smile:
    Sincere Regards
    EagerToLearn

Hi @EagerToLearn,

Do you need to configure the driver with __init__?

You could make life a lot easier on yourself by renaming __init__ to something like set_driver and calling

Set Driver     ${driver}

After Open Browser and before Set Username

Also, I’m not an expert on SeleniumLibrary, but I don’t think the value you have for ${driver} in your robot file is what you python code is expecting to receive.

By removing the __init__ with an argument, you can simply import your library with

Library    …//pageObjects/LoginPage.py

Dave.

Based on this: I modified the init constructor in my LoginPage.py class

While that approach “could” work as a code without throwing errors, you will end up having 2 browsers open - one where SeleniumLibrary is in control, and second where your python side of things is in control. Pretty sure this is not what you want.

@damies13 suggested that you could add a separate method to your LoginPage class. Here’s something that might work:

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

def set_driver(driver):
  self.driver = driver      

Now, what happens is that when constructor is called, self.driver is initialized only if you pass something to it. This would allow loading your class as Library to Robot Framework without triggering error that you are calling a constructor without needed arguments. This is important as the class itself is initialized when it gets loaded and at this time, there is no way you have a instance of driver anywhere.

Then, on Robot Framework side – you open the browser/create driver normally but before you use the keywords from the LoginPage library that you now have, you need to get the current driver that SeleniumLibrary has initialized and pass that to the LoginPage. Something like this.

    ${sl}    Get Library  Instance   SeleniumLibrary
    Set Driver    ${sl.driver}

This is only one way to work around the issue you have, im not saying that this is the most optimal but the approach should work if rest of your code is working…

1 Like

Hi,
I am very thankful to your support, @dave and @rasjani.
Based on your last comments I thought to clean the solution and create it from scratch.
Now there are two things.

  • my-first-test.robot

 *** Settings ***

Resource   SeleniumDrivers.robot
Library    SeleniumLibrary
Library    ../lib/CustomLib.py
Resource    SeleniumDrivers.robot
Suite Setup     Update Firefox Webdriver


*** Keywords ***
Navigate To Google
    ${driverpath}= 	    Update Firefox Webdriver
    Create Webdriver    Firefox
	Log    "Driver: "
	Log    ${driverpath}

    Set Driver    ${driverpath}
    Sleep               2s
    **# I created this keyword to open google.com**
    Open Webpage
	Sleep               10s
	Set Input Text      ROBOT
	Hello World         ${driverpath}
	Sleep               2s
	Close Browser

*** Test Cases ***
Launch Google Website
    Navigate To Google

  • CustomLib.py

import random
import string
from selenium import webdriver
from selenium.webdriver.common.by import By

__version__ = '1.0.0'

class CustomLib(object):
    ROBOT_LIBRARY_VERSION = __version__
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
    textbox_search_field = "APjFqb"
    textbox_password_id = "Password"
    button_login_xpath = "//button[@class='button-1 login-button']"
    link_logout_link_text = "Logout"
    driverPath = ""

    # I declare it to be as Firefox
    # driver = webdriver.Firefox()

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

    def set_driver(self, driver):
        print("driver: " + driver)
        self.driver = driver

    def get_driver(self):
        return self.driver

    def open_webpage(self):
        self.driver.get("www.google.com")
    def set_input_text(self, text):
        self.driver.find_element(By.ID, self.textbox_search_field).clear()
        self.driver.find_element(By.ID, self.textbox_search_field).send_keys(text)

    def hello_world(self, driverPath):
        print("Hello World! ", driverPath)

and then the code you have shared:

  • SeleniumDrivers.robot

*** Settings ***
Documentation		These keywords need the webdriver-manager module
... 							pip install webdriver-manager
... 							https://pypi.org/project/webdriver-manager/

*** Keywords ***
Update Chrome Webdriver
	[Documentation]		Update Chrome Web Driver
	${driverpath}= 	Evaluate 	webdriver_manager.chrome.ChromeDriverManager().install() 	modules=webdriver_manager.chrome
	Log         ${driverpath}
	RETURN   	${driverpath}

Update Firefox Webdriver
	[Documentation]		Update Firefox Web Driver
	${driverpath}= 	Evaluate 	webdriver_manager.firefox.GeckoDriverManager().install() 	modules=webdriver_manager.firefox
	Log         ${driverpath}
	RETURN 	    ${driverpath}

  • So the problem now is that I am getting the firefox getting launched but not the url : www.google.com

So how I can launch the web browser with url From Robot Framework using Selenium?
I am day and night working and wish there could be some proper page where these things are written and examples are there. Please tell me if there any good projects that even are doing these two things:

  • Launch a web browser using Selenium and the test is written in Robotframe work, but robot framework is not launching the web browser.

Sincere Regards
EagerToLearn

  1. New selenium ships with selenium-manager and there’s 0 need to download webdriver’s anymore.
  2. Open Browser $your_url_here firefox will open a browser with the given url.
  3. After that, you can use the Get Library Instance to get that driver instance and pass it you your custom library.
2 Likes

Hi @damies13 and @rasjani
Thank you for your guidance I got it working now.
Could you still suggest me what is the best way to learn robot framework, selenium and python, especially robot framework and selenium?

What path I can follow to get myself to the level (to be able to work on robot framework and selenium ) that I can easily do professional development?, of course practice is the key.
Sincere Regards
EagerToLearn

Hi @EagerToLearn,

You are correct practice is key, and asking here on the forums when you don’t understand is a great thing too because you can learn from people like us.

I can’t really give you good resources, because all I did was read the Robot Framework User Guide, I already had a very strong background in test automation and programming, so that was all I needed.

I’ve seen quite a few video’s on YouTube on robot framework, but can’t really say which ones are worth watching but that might be a helpful place to look too.

There are some open source applications that have live demo’s, that could be a good place to practice your test automation skills, just don’t abuse them by running lots of robots or messing up the demo data.But if you already work in a test team the best thing to do is automate the apps and test cases you already have.

Hopefully this is helpful,

Dave.

1 Like

Seems to me it directly relates to my newly developed package.

There is a new package that can start keywords written for robot framework in python from pytest behind. The only thing that is needed is to import and write @execute_pyteest decorator above all decorators from pytest.

The package is already at PyPi and GitHub.

The name is pytest-in-robotframework.

There is also an example with the selenium web-driver and logging page. Where the driver instance is transferred between the Robot Framework context and pytest context. (created under Roboto Framework and used from Pytest in parametrized test).