Skip to content

Commit

Permalink
Merge pull request #1 from boakley/master
Browse files Browse the repository at this point in the history
Syncing with latest Upstream changes
  • Loading branch information
GLMeece authored Feb 8, 2019
2 parents 89c417e + 7af43ce commit 8364ccb
Show file tree
Hide file tree
Showing 22 changed files with 278 additions and 113 deletions.
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,19 @@ target/

#Ipython Notebook
.ipynb_checkpoints

# robot cruft
output.xml
log.html
report.html
selenium-screenshot*.png
tests/results

# emacs cruft
\#*\#
\.\#*
*.elc
*~

# misc
tmp
26 changes: 14 additions & 12 deletions PageObjectLibrary/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import absolute_import, unicode_literals

from .keywords import PageObjectLibraryKeywords
from .pageobject import PageObject
from .version import __version__


class PageObjectLibrary(PageObjectLibraryKeywords):

"""This project is hosted on github in the repository
Expand All @@ -11,9 +13,9 @@ class PageObjectLibrary(PageObjectLibraryKeywords):
*PageObjectLibrary* is a lightweight library which supports using
the page object pattern with
[http://robotframework.org/Selenium2Library/doc/Selenium2Library.html|Selenium2Library].
This library does not replace Selenium2Library; rather, it
provides a framework around which to use Selenium2Library and the
[http://robotframework.org/SeleniumLibrary/doc/SeleniumLibrary.html|SeleniumLibrary].
This library does not replace SeleniumLibrary; rather, it
provides a framework around which to use SeleniumLibrary and the
lower-level [http://selenium-python.readthedocs.org/|Python
bindings to Selenium]
Expand All @@ -30,20 +32,20 @@ class your keywords have access to the following pre-defined
attributes and methods:
| =Attribute/method= | =Description= |
| ``self.se2lib`` | A reference to the Selenium2Library instance |
| ``self.selib` ` | A reference to the SeleniumLibrary instance |
| ``self.browser`` | A reference to the currently open browser |
| ``self.locator`` | A wrapper around the ``_locators`` dictionary |
| ``self.logger`` | A reference to the ``robot.api.logger`` instance |
| ``self._wait_for_page_refresh()`` | a context manager for doing work that causes a page refresh |
= Using Selenium2Library Keywords =
= Using SeleniumLibrary Keywords =
Within your keywords you have access to the full power of
Selenium2Library. You can use ``self.se2lib`` to access the
SeleniumLibrary. You can use ``self.selib`` to access the
library keywords. The following example shows how to call the
``Capture Page Screenshot`` keyword:
| self.se2lib.capture_page_screenshot()
| self.selib.capture_page_screenshot()
= Using Selenium Methods =
Expand Down Expand Up @@ -114,8 +116,8 @@ class your keywords have access to the following pre-defined
| def login_as_a_normal_user(self):
| username = BuiltIn().get_variable_value("${USERNAME}"}
| password = BuiltIn().get_variable_value("${PASSWORD}"}
| self.se2lib.input_text(self.locator.username, username)
| self.se2lib.input_text(self.locator.password, password)
| self.selib.input_text(self.locator.username, username)
| self.selib.input_text(self.locator.password, password)
|
| with self._wait_for_page_refresh():
| self.click_the_submit_button()
Expand All @@ -126,7 +128,7 @@ class your keywords have access to the following pre-defined
Robot can import it, just like with any other keyword
library. When you use the keyword `Go to page`, the keyword will
automatically load the keyword library and put it at the front of
the Robot Framework library search order (see
the Robot Framework library search order (see
[http://robotframework.org/robotframework/latest/libraries/BuiltIn.html#Set%20Library%20Search%20Order|Set Library Search Order])
In the following example it is assumed there is a second page
Expand All @@ -135,14 +137,14 @@ class your keywords have access to the following pre-defined
| ``*** Settings ***``
| Library PageObjectLibrary
| Library Selenium2Library
| Library SeleniumLibrary
| Suite Setup Open browser http://www.example.com
| Suite Teardown Close all browsers
|
| ``*** Test Cases ***``
| Log in to the application
| Go to page LoginPage
| Log in as a normal user
| Log in as a normal user
| The current page should be DashboardPage
"""
Expand Down
32 changes: 11 additions & 21 deletions PageObjectLibrary/keywords.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
"""PageObjectLibrary
A library to support the creation of page objects using
selenium and Seleniuim2Library.
selenium and SeleniuimLibrary.
Note: The keywords in this file need to work even if there is no
current page object, which is why they are here instead of on the
PageObject model.
"""

from __future__ import print_function, absolute_import, unicode_literals
import six

import robot.api
from robot.libraries.BuiltIn import BuiltIn


from .pageobject import PageObject
try:
from urlparse import urlparse
Expand All @@ -24,20 +30,6 @@ def __init__(self):
self.builtin = BuiltIn()
self.logger = robot.api.logger

@property
def se2lib(self):
# this is implemented as a property so that this class
# can be imported outside the context of a running
# suite (ie: by libdoc, robotframework-hub, etc)
try:
se2 = self.builtin.get_library_instance("Selenium2Library")

except RuntimeError:
self.builtin.import_library("Selenium2Library")
se2 = self.builtin.get_library_instance("Selenium2Library")

return se2

def the_current_page_should_be(self, page_name):
"""Fails if the name of the current page is not the given page name
Expand Down Expand Up @@ -69,7 +61,7 @@ def the_current_page_should_be(self, page_name):
# 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)

def go_to_page(self, page_name, page_root = None):
def go_to_page(self, page_name, page_root=None):
"""Go to the url for the given page object.
Unless explicitly provided, the URL root will be based on the
Expand All @@ -90,7 +82,7 @@ def go_to_page(self, page_name, page_root = None):
The effect is the same as if you had called the following three
keywords:
| Selenium2Library.Go To http://www.example.com/login
| SeleniumLibrary.Go To http://www.example.com/login
| Import Library ExampleLoginPage
| Set Library Search Order ExampleLoginPage
Expand All @@ -100,13 +92,12 @@ def go_to_page(self, page_name, page_root = None):

page = self._get_page_object(page_name)

url = page_root if page_root is not None else self.se2lib.get_location()
url = page_root if page_root is not None else page.selib.get_location()
(scheme, netloc, path, parameters, query, fragment) = urlparse(url)
url = "%s://%s%s" % (scheme, netloc, page.PAGE_URL)

with page._wait_for_page_refresh():
# self.logger.console("\ntrying to go to '%s'" % url) # <-- Remove hashmark if you want this message on console
self.se2lib.go_to(url)
page.selib.go_to(url)
# should I be calling this keyword? Should this keyword return
# true/false, or should it throw an exception?
self.the_current_page_should_be(page_name)
Expand All @@ -125,4 +116,3 @@ def _get_page_object(self, page_name):
page = self.builtin.get_library_instance(page_name)

return page

2 changes: 2 additions & 0 deletions PageObjectLibrary/locatormap.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import absolute_import, unicode_literals

import six


class LocatorMap(dict):
"""LocatorMap - a dict-like object that supports dot notation
Expand Down
39 changes: 25 additions & 14 deletions PageObjectLibrary/pageobject.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
from __future__ import absolute_import, unicode_literals

from abc import ABCMeta
from contextlib import contextmanager
import warnings

import robot.api
from robot.libraries.BuiltIn import BuiltIn
from contextlib import contextmanager
from selenium.webdriver.support.ui import WebDriverWait

from selenium.webdriver.support.expected_conditions import staleness_of
from abc import ABCMeta
from selenium.webdriver.support.ui import WebDriverWait

import six

from .locatormap import LocatorMap


class PageObject(six.with_metaclass(ABCMeta, object)):
"""Base class for page objects
Classes that inherit from this class need to define the
following class variables:
PAGE_TITLE the title of the page; used by the default
PAGE_TITLE the title of the page; used by the default
implementation of _is_current_page
PAGE_URL this should be the URL of the page, minus
the hostname and port (eg: /loginpage.html)
Expand All @@ -25,18 +31,18 @@ class PageObject(six.with_metaclass(ABCMeta, object)):
provided by this class. It compares the current page title to the
class variable PAGE_TITLE. A class can override this method if the
page title is not unique or is indeterminate.
Classes that inherit from this class have access to the
following properties:
* se2lib a reference to an instance of Selenium2Library
* selib a reference to an instance of SeleniumLibrary
* browser a reference to the current webdriver instance
* logger a reference to robot.api.logger
* locator a wrapper around the page object's ``_locators`` dictionary
* locator a wrapper around the page object's ``_locators`` dictionary
This class implements the following context managers:
* _wait_for_page_refresh
* _wait_for_page_refresh
This context manager is designed to be used in page objects when a
keyword should wait to return until the html element has been
Expand All @@ -50,17 +56,23 @@ class variable PAGE_TITLE. A class can override this method if the
def __init__(self):
self.logger = robot.api.logger
self.locator = LocatorMap(getattr(self, "_locators", {}))
self.builtin = BuiltIn()

# N.B. se2lib, browser use @property so that a
# N.B. selib, browser use @property so that a
# subclass can be instantiated outside of the context of a running
# test (eg: by libdoc, robotframework-hub, etc)
@property
def se2lib(self):
return BuiltIn().get_library_instance("Selenium2Library")
warnings.warn("se2lib is deprecated. Use selib intead.", warnings.DeprecationWarning)
return self.selib

@property
def selib(self):
return self.builtin.get_library_instance("SeleniumLibrary")

@property
def browser(self):
return self.se2lib._current_browser()
return self.selib._current_browser()

def __str__(self):
return self.__class__.__name__
Expand All @@ -85,7 +97,7 @@ def _wait_for_page_refresh(self, timeout=10):
staleness_of(old_page),
message="Old page did not go stale within %ss" % timeout
)
self.se2lib.wait_for_condition("return (document.readyState == 'complete')", timeout=10)
self.selib.wait_for_condition("return (document.readyState == 'complete')", timeout=10)

def _is_current_page(self):
"""Determine if this page object represents the current page.
Expand All @@ -100,7 +112,7 @@ def _is_current_page(self):
"""

actual_title = self.se2lib.get_title()
actual_title = self.selib.get_title()
expected_title = self.PAGE_TITLE

if actual_title.lower() == expected_title.lower():
Expand All @@ -110,4 +122,3 @@ def _is_current_page(self):
self.logger.info(" actual title: '%s'" % actual_title)
raise Exception("expected title to be '%s' but it was '%s'" % (expected_title, actual_title))
return False

3 changes: 1 addition & 2 deletions PageObjectLibrary/version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
__version__ = "1.0.0-beta.2"

__version__ = "1.0.0b3"
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The source code is hosted on github at the following url:

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 Selenium2Library installed. You must also have cloned the github repository to have access to the demo files.
For the demo to run you must have robotframework 2.9+ and 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: :

Expand Down Expand Up @@ -86,8 +86,8 @@ Page objects are simple python classes that inherit from `PageObjectLibrary.Page

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

- `self.se2lib` - a reference to an instance of Selenium2Library. With this you can call any of the Selenium2Library keywords via their python method names (eg: self.se2lib.input\_text)
- `self.browser` - a reference to the webdriver object created when a browser was opened by Selenium2Library. With this you can bypass Selenium2Library and directly call all of the functions provided by the core selenium library.
- `self.se2lib` - a reference to an instance of SeleniumLibrary. With this you can call any of the SeleniumLibrary keywords via their python method names (eg: self.se2lib.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
Expand Down
19 changes: 19 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
To run a short demo, run the following command from the same directory
as this file:

$ robot demo

To run acceptance tests (which includes running the demo), run the following
command in same directory as this file:

$ robot -A tests/config.args tests


By default the tests will run with chrome, but you can change to any
browser supported by your system by setting the variable BROWSER
from the command line.

Example:

robot --variable browser:firefox demo

5 changes: 5 additions & 0 deletions demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This folder contains example tests and page objects, along
with a small web app to be tested.

For more information on the web app see demo/webapp/README.md

2 changes: 1 addition & 1 deletion demo/resources/HomePage.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from PageObjectLibrary import PageObject


class HomePage(PageObject):
"""Keywords for the Home page of the demo app
Expand All @@ -16,4 +17,3 @@ class HomePage(PageObject):
# (eg: self.locator.username, etc)
_locators = {
}

10 changes: 6 additions & 4 deletions demo/resources/LoginPage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from PageObjectLibrary import PageObject

from robot.libraries.BuiltIn import BuiltIn


class LoginPage(PageObject):
PAGE_TITLE = "Login - PageObjectLibrary Demo"
PAGE_URL = "/login.html"
Expand All @@ -22,13 +24,13 @@ def login_as_a_normal_user(self):

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

def enter_password(self,password):
def enter_password(self, password):
"""Enter the given string into the password field"""
self.se2lib.input_text(self.locator.password, password)
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.se2lib.click_button(self.locator.submit_button)
self.selib.click_button(self.locator.submit_button)
Loading

0 comments on commit 8364ccb

Please sign in to comment.