Giter Club home page Giter Club logo

robotframework-pageobjectlibrary's Introduction

PageObjectLibrary

Overview

PageObjectLibrary is a lightweight Robot Framework keyword library that makes it possible to use the Page Object pattern when testing web pages with the keyword based approach of robot framework.

Installing

pip install --upgrade robotframework-pageobjectlibrary

Source Code

The source code is hosted on GitHub at the following url:

Running the Demo

In the GitHub repository is a small demonstration suite that includes a self-contained webserver and web site.

For the demo to run, you must have robotframework 2.9+ and robotframework-seleniumlibrary installed. You must also have cloned the GitHub repository to have access to the demo files.

To run the demo, clone the GitHub repository, cd to the folder that contains this file, and then run the following command: :

robot -d demo/results demo

A Simple Tutorial

For a simple tutorial, see https://github.com/boakley/robotframework-pageobjectlibrary/wiki/Tutorial

How it Works

The Page Object library is quite simple. Page Object classes are implemented as standard robot keyword libraries, and relies on robot frameworks built-in Set library search order keyword.

The core concept is that when you use PageObjectLibrary keywords to go to a page or assert you are on a specific page, the keyword will automatically load the library for that page and put it at the front of the library search order, guaranteeing that the Page Object keywords are available to your test case.

Why Page Objects Makes Writing Tests Easier

The purpose of the Page Object pattern is to encapsulate the knowledge of how a web page is constructed into an object. Your test uses the object as an interface to the application, isolating your test cases from the details of the implementation of a page.

With Page Objects, developers are free to modify web pages as much as they want, and the only thing they need to do to keep existing tests from failing is to update the Page Object class. Because test cases aren’t directly tied to the implementation, they become more stable and more resistant to change as the website matures.

A Typical Test Without Page Objects

With traditional testing using Selenium, a simple login test might look something like the following: (using the pipe-separated format for clarity):

*** Test Cases ***
| Login with valid credentials
| | Go to | ${ROOT}/Login.html
| | Wait for page to contain | id=id_username
| | Input text | id=id_username | ${USERNAME}
| | Input text | id=id_password | ${PASSWORD}
| | Click button | id=id_form_submit
| | Wait for page to contain | Your Dashboard
| | Location should be | ${ROOT}/dashboard.html

Notice how this test is tightly coupled to the implementation of the page. It has to know that the input field has an id of id_username, and the password field has an id of id_password. It also has to know the URL of the page being tested.

Of course, you can put those hard-coded values into variables and import them from a resource file or environment variables, which makes it easier to update tests when locators change. However, there’s still the overhead of additional keywords that are often required to make a test robust, such as waiting for a page to be reloaded. The provided PageObject superclass handles some of those details for you.

The Same Test, Using Page Objects

Using Page Objects, the same test could be written like this:

*** Test Cases ***
| Login with valid credentials
| | Go to page | LoginPage
| | Login as a normal user
| | The current page should be | DashboardPage

Notice how there are no URLs or element locators in the test whatsoever, and that we’ve been able to eliminate some keywords that typically are necessary for selenium to work but which aren’t part of the test logic per se. What we end up with is test case that is nearly indistinguishable from typical acceptance criteria of an agile story.

Writing a Page Object class

Page Objects are simple python classes that inherit from PageObjectLibrary.PageObject. There are only a couple of requirements for the class:

  • The class should define a variable named PAGE_TITLE
  • The class should define a variable named PAGE_URL which is a URI relative to the site root.

By inheriting from PageObjectLibrary.PageObject, methods have access to the following special object attributes:

  • self.selib - a reference to an instance of SeleniumLibrary. With this you can call any of the SeleniumLibrary keywords via their python method names (eg: self.selib.input_text)
  • self.browser - a reference to the webdriver object created when a browser was opened by SeleniumLibrary. With this you can bypass SeleniumLibrary and directly call all of the functions provided by the core selenium library.
  • self.locator - a wrapper around the _locators dictionary of the page. This dictionary can contain all of the locators used by the Page Object keywords. self.locators adds the ability to access the locators with dot notation rather than the slightly more verbose dictionary syntax (eg: self.locator.username vs self._locators["username"].

An example Page Object

A Page Object representing a login page might look like this:

from PageObjectLibrary import PageObject

class LoginPage(PageObject):
    PAGE_TITLE = "Login - PageObjectLibrary Demo"
    PAGE_URL = "/login.html"

    _locators = {
        "username": "id=id_username",
        "password": "id=id_password",
        "submit_button": "id=id_submit",
    }

    def enter_username(self, username):
        """Enter the given string into the username field"""
        self.selib.input_text(self.locator.username, username)

    def enter_password(self,password):
        """Enter the given string into the password field"""
        self.selib.input_text(self.locator.password, password)

    def click_the_submit_button(self):
        """Click the submit button, and wait for the page to reload"""
        with self._wait_for_page_refresh():
            self.selib.click_button(self.locator.submit_button)

robotframework-pageobjectlibrary's People

Contributors

boakley avatar glmeece avatar yahman72 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

robotframework-pageobjectlibrary's Issues

Test Setup broken

When using your page object library, if you try to use the keyword
Test Setup my_step

(my_step is in this case a placeholder. It could be any keyword like for example Open Test Browser, or Setup Config )

the my_step is never run. Suit Setup, Suit Teardown and Test Teardown runs ok. Only Test setup is not run.

Installation issue with Python 3.6

There is an issue with installation the package against Python 3.6.4

Downloading https://files.pythonhosted.org/packages/88/21/fa58016bae1a0bbe693ed745d188ef3b01c75ad3b575c9b7cd540522da07/robotframework-pageobjectlibrary-0.1.7.tar.gz
Complete output from command python setup.py egg_info:
Traceback (most recent call last):
File "", line 1, in
File "C:\Users\avasilev\AppData\Local\Temp\pip-install-p80l_dxl\robotframework-pageobjectlibrary\setup.py", line 5, in

execfile('PageObjectLibrary/version.py')
NameError: name 'execfile' is not defined

How to do when lots of page objects are in different directories?

Hi,
My product is very complex, it has more than 100 pages, so I don't want to put all the page objects files in the same level's directory. I try to organize the page objects by function as below.
The folder structure:
demo
|-- resources
| |-- function1_folder
| | |-----func1_subfolder1
| | | |------page1
| | | |------page2
| | |-----func1_subfolder2
| | | |------page3
| | | |------page4
| | |-----page5
| |-- function2_folder
| | |-----page6
| | | ----page7
|-- results
|-- tests
|-- test1.robot
|-- test2.robot

I can execute my test cases when all the pages are in the same directory, but when I organize the pages as above, "The current page should be" keyword can't find the module name "page1" , "page2" and so on.
Can you tell me how to use this library to test the pages as show above, thank you very much.

Dynamic elements in locators?

I am testing an application where I have to select different items in a navigation bar. So in my locators I have:

"ShoesCategory": "//a[contains(text(),'Shoes')]",
"CoatsCategory": "//a[contains(text(),'Coats')]",

and then I have 2 separate methods like this to click

def click_shoes_category(self):
    self.se2lib.click_element(self.locator.ShoesCategory)

def click_coats_category(self):
   self.se2lib.click_element(self.locator.CoatsCategory)

I have quite a number of different categories to select and verify, is there a way to DRY this up so that I can pass a variable into the locator? So something like this:

"category": "//a[contains(text(),'[%s]')] % category"

And then have a method like this:

 def click_category(self, category):
     self.se2lib.click_element(self.locator.category)

Any help would be great, thanks!

Webdriver didn't get website with document.readyState == 'complete'

For some reason some website with document.readyState == 'complete' just get in this condition, and selenium is getting lost.
I tested the same pages with ruby and everything works pretty fine.

@contextmanager
def _wait_for_page_refresh(self, timeout=10):
yield
WebDriverWait(self.browser, timeout).until(
staleness_of(old_page),
message="Old page did not go stale within %ss" % timeout
)
self.selib.wait_for_condition("return (document.readyState == 'complete')", timeout=10)

Init method of user class execute few times

Init method of user class execute few times.
For example

my page object class:

class ViewFrontageAboutPage(PageObject):
	def __init__(self):
		robot.api.logger.console("ViewFrontageAboutPage __init__")

robot file
`*** Settings ***
Library Selenium2Library
Library PageObjectLibrary

*** Test Cases ***
Qwe
Go to page Pages.FrontageAboutPage.ViewFrontageAboutPage

Result:
Qwe
ViewFrontageAboutPage init
ViewFrontageAboutPage init

Problem with setting library search order

Hi Brayan,
I'm having problems with error "AttributeError: 'tuple' object has no attribute 'split'"
Issue occurs when I use a few times "Go To Page" keyword e.g:

Valid Credentials Should Log User In
    Go to page    ThanksArticlesListPage
    Go to page    LoginPage
    Submit Valid Credentials
    The current page should be    ThanksArticlesListPage
    Go to page    LoginPage
    Submit Valid Credentials
    User Name Should Be Visible On The Navigation Panel

I'm using Mixin class in my page objects like in one of your tutorials.

Anyway, I think I've found solution, but I'm not permitted to push into your repo. So I'm pasting fix here:
robotframework-pageobjectlibrary/PageObjectLibrary/keywords.py

    def the_current_page_should_be(self, page_name):
        """Fails if the name of the current page is not the given page name

        ``page_name`` is the name you would use to import the page.

        This keyword will import the given page object, put it at the
        front of the Robot library search order, then call the method
        ``_is_current_page`` on the library. The default
        implementation of this method will compare the page title to
        the ``PAGE_TITLE`` attribute of the page object, but this
        implementation can be overridden by each page object.

        """

        page = self._get_page_object(page_name)

        # This causes robot to automatically resolve keyword
        # conflicts by looking in the current page first.
        if page._is_current_page():
            # only way to get the current order is to set a
            # new order. Once done, if there actually was an
            # old order, preserve the old but make sure our
            # page is at the front of the list
            old_order = self.builtin.set_library_search_order()
            new_order = (str(page),) + old_order
            self.builtin.set_library_search_order(*new_order)
            return

        # If we get here, we're not on the page we think we're on
        raise Exception("Expected page to be %s but it was not" % page_name)

Could this package be used with other test-framework

Thank you for the package.

I would like to request please if we could have the package be used independent of robotframework. Currently I understood that se2lib instance is based on

BuiltIn().get_library_instance("Selenium2Library")

If the instance se2lib, could be created universally, (without using the method of BuiltIn that gets an existing-running instance of Selenium2Library ,) then the package could also be used with other test-framework also. I would like to request if we could have the enhancement and if its practical

p.s The other dependency on robot api wont be affecting if used with other test-framework. Only this specific dependency affects

wait_for_page_refresh, "timeout" parameter not taken into account by the wait_for_condition function

Hello,
I use the wait_for_condition function , and I pass a different parameter than the default one. It is taken into account by the WebDriverWait function, but not the wait_for_condition one.

Capture

So for example, if I pass "50' parameter like: with self._wait_for_page_refresh(50):
It will raise an error if the page didn't go stale within 50 secs. But it will wait only 10 secs and not 50 secs for document to be ready.

2

Would it be possible to modify the library so the parameter I send to wait_for_page_refresh is taken into account by the both condition ?

Thank you !

First Feedback

This is some sort of braindump just to start some discussion i.e. I can open issues and/or PR's based on the outcome of the discussion...

I've been now trying the Library for a couple of days and I'm pretty happy about it. I especially like:

  • The simplicity of it, one can indeed get something working very quickly
  • The idea of having each Page as it's own RF Library
  • The contextmanager handling the page-loading stuff

I chose rather complex Use Case to see what is possible and what is not: "Password Reset" i.e.

  1. go to website "X", enter Username and click "Forgot My Password"
  2. Open another Browser and login to gmail and open the password reset Email from website "X"
  3. login with the new password

This use case has a couple tricky things:

  • manage multiple browser sessions (one for the "X" and the other for "Gmail") within one test case
  • gmail login has two "subpages" i.e. login prompt and the passwd prompt
  • gmail login also involves a lots of redirections (gmail.com --> accounts.google.com --> gmail.com)

The Braindump in no particular order:

  • support for "sub pages" (e.g. gmail login: 1) login name page 2) password page) would be nice, i.e. now the contextmanager waits for the old page to go stale AND the new to finish loading, this fails in the gmail case because the login-->passwd transition does not reload the page. Without contextmanager entering passwd fails sometimes because the page is not displayed
  • because of the gmail login redirections going to gmail.com lands on the accounts.google.com --> I have to use the 'page_root' with 'go to page' KW, IMHO it would be better if this information is not exposed to the Test Case developer --> additional ROOT_URL variable in the page object would be good (if not set, user se2.getLocation())
  • put load timeout behind a variable (override in pageObject itself, and maybe also as RF variable)
  • support for multiple browser sessions would be good
  • IMHO 'the current page should be' at the end of the 'go_to_page' is not a good idea (additional 'verify=True/False' argument would be nice)
  • support for "page transitions" would be great i.e. how to move to the next page without calling 'go_to_page' i.e. after gmail login I'm on the Inbox page, but my Inbox page-object is not loaded automatically. I need to explicitly import the Library or call "Current Page Should Be" that loads the page-object
  • The Current Page Should Be should be Current Page Should Be RF KWs in general do not use "The" e.g. The BuiltIn has Variable Should Exist KW, not The Variable Should Exist
  • Tutorial could include also one more step after the login (to demonstrate the above page transitions)
  • current page check with the title could be made more flexible (one shouldn't be forced to override the default behaviour that often, e.g. the title is "gmail" on the pages = I needed to override it for each pageobject), e.g. check the title with regular expression or even better: someting xpath based that support verification of any element on the page.

Page objects with extended Selenium2Library

You use following to share instance of selenium2library: BuiltIn().get_library_instance("Selenium2Library")

Now in my case, I extend Selenium2Library to MySelenium2Library class in python.. with some additional functions in MySelenium2Library class.
Is it possible to share the instance of MySelenium2Library?

Any clue how we can achieve page objects with custom: MySelenium2Library. Thanks a lot.

Argument check probably wrong

PageObjectLibrary/locatormap.py has,

16 | if " " in k in k or "." in k:
17 | raise Exception("Keys cannot have spaces or periods in them")

I wonder if the duplication
16 | ... " " in k in k ...
is necessary and correct?

Failed when keyword conflicts in two files

Hi,
I follow the guide "https://github.com/boakley/robotframework-pageobjectlibrary/wiki/Tutorial" to learn how to use this library.
When keyword doesn't conflict, the case passed.
But after adding conflict keyword in two files as following, the case failed, can you help to check where is wrong in my test, thank you very much.
In LoginPage.py:

    def new_keyword(self):
        self.logger.info("In LoginPage")

In SecureAreaPage:

   def new_keyword(self):
        self.logger.info("In SecureAreaPage")

And change the 'Valid Login' Case as following

    [Setup]    Go To Page    LoginPage
    Enter Username    tomsmith
    Enter password    SuperSecretPassword!
    new_keyword
    Click the login button
    The current page should be    SecureAreaPage
    new_keyword
    sleep    5

This time, the case failed, here is the error information

PS D:\demo> robot --outputdir results --pythonpath resources tests
==============================================================================
Tests
==============================================================================
Tests.Login
==============================================================================
Valid Login :: Verify that we can successfully  log in to the inte...
trying to go to 'https://the-internet.herokuapp.com/login'
[ ERROR ] Unexpected error: AttributeError: 'tuple' object has no attribute 'split'
Traceback (most recent call last):
  File "c:\python27\lib\site-packages\robot\utils\application.py", line 83, in _execute
    rc = self.main(arguments, **options)
  File "c:\python27\lib\site-packages\robot\run.py", line 445, in main
    result = suite.run(settings)
  File "c:\python27\lib\site-packages\robot\running\model.py", line 248, in run
    self.visit(runner)
  File "c:\python27\lib\site-packages\robot\model\testsuite.py", line 161, in visit
    visitor.visit_suite(self)
  File "c:\python27\lib\site-packages\robot\model\visitor.py", line 86, in visit_suite
    suite.suites.visit(self)
  File "c:\python27\lib\site-packages\robot\model\itemlist.py", line 76, in visit
    item.visit(visitor)
  File "c:\python27\lib\site-packages\robot\model\testsuite.py", line 161, in visit
    visitor.visit_suite(self)
  File "c:\python27\lib\site-packages\robot\model\visitor.py", line 87, in visit_suite
    suite.tests.visit(self)
  File "c:\python27\lib\site-packages\robot\model\itemlist.py", line 76, in visit
    item.visit(visitor)
  File "c:\python27\lib\site-packages\robot\model\testcase.py", line 74, in visit
    visitor.visit_test(self)
  File "c:\python27\lib\site-packages\robot\running\runner.py", line 135, in visit_test
    test.template).run_steps(test.keywords.normal)
  File "c:\python27\lib\site-packages\robot\running\steprunner.py", line 36, in run_steps
    self.run_step(step)
  File "c:\python27\lib\site-packages\robot\running\steprunner.py", line 54, in run_step
    runner = context.get_runner(name or step.name)
  File "c:\python27\lib\site-packages\robot\running\context.py", line 183, in get_runner
    return self.namespace.get_runner(name)
  File "c:\python27\lib\site-packages\robot\running\namespace.py", line 224, in get_runner
    return self._kw_store.get_runner(name)
  File "c:\python27\lib\site-packages\robot\running\namespace.py", line 265, in get_runner
    runner = self._get_runner(name)
  File "c:\python27\lib\site-packages\robot\running\namespace.py", line 288, in _get_runner
    runner = self._get_implicit_runner(name)
  File "c:\python27\lib\site-packages\robot\running\namespace.py", line 306, in _get_implicit_runner
    runner = self._get_runner_from_libraries(name)
  File "c:\python27\lib\site-packages\robot\running\namespace.py", line 331, in _get_runner_from_libraries
    found = self._get_runner_based_on_search_order(found)
  File "c:\python27\lib\site-packages\robot\running\namespace.py", line 341, in _get_runner_based_on_search_order
    if eq(libname, runner.libname):
  File "c:\python27\lib\site-packages\robot\utils\match.py", line 26, in eq
    str1 = normalize(str1, ignore, caseless, spaceless)
  File "c:\python27\lib\site-packages\robot\utils\normalizing.py", line 34, in normalize
    string = empty.join(string.split())
Valid Login :: Verify that we can successfully  log in to the inte... PS D:\demo>
PS D:\demo>
PS D:\demo>

User defined inheritance using Page Object Library

I want to implement my own inheritance using page object library. Want to implement base class inheriting PageObject class and child class will inherit base class.
e.g. Fruit is my base class which inherits PageObject class and Apple is child class should inherit Fruit class. Tried to implement using PageObject but system throws an error.

Localization Support

One more nice feature would be localization i.e. test web site in different languages.

This would mean multi-language support for:

  • the locators
  • the PAGE_TITLE variable

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.