├── features
├── __init__.py
├── steps
│ ├── __init__.py
│ ├── div_steps.py
│ ├── span_steps.py
│ ├── label_steps.py
│ ├── paragraph_steps.py
│ ├── shared_element_steps.py
│ ├── link_steps.py
│ ├── element_steps.py
│ ├── navigation_steps.py
│ ├── image_steps.py
│ ├── text_field_steps.py
│ ├── text_area_steps.py
│ ├── check_box_steps.py
│ ├── button_steps.py
│ ├── async_steps.py
│ └── select_list_steps.py
├── support
│ ├── __init__.py
│ ├── html
│ │ ├── images
│ │ │ └── circle.png
│ │ ├── success.html
│ │ ├── async_elements.html
│ │ └── static_elements.html
│ ├── async_page.py
│ └── page.py
├── environment.py
└── elements
│ ├── elements.feature
│ ├── span.feature
│ ├── label.feature
│ ├── link_element.feature
│ ├── div.feature
│ ├── paragraph.feature
│ ├── image.feature
│ ├── check_box.feature
│ ├── async.feature
│ ├── button.feature
│ ├── text_field.feature
│ ├── text_area.feature
│ └── select_list.feature
├── src
├── __init__.py
└── page_object
│ ├── expected_conditions
│ ├── __init__.py
│ ├── element_predicate.py
│ └── element_exists.py
│ ├── elements
│ ├── div.py
│ ├── label.py
│ ├── link.py
│ ├── button.py
│ ├── option.py
│ ├── table_cell.py
│ ├── heading.py
│ ├── paragraph.py
│ ├── radio_button.py
│ ├── span.py
│ ├── image.py
│ ├── table.py
│ ├── substitute_web_element.py
│ ├── checkbox.py
│ ├── text_area.py
│ ├── text_field.py
│ ├── table_row.py
│ ├── select_list.py
│ ├── __init__.py
│ └── element.py
│ ├── __init__.py
│ ├── browser.py
│ ├── locator_generator.py
│ ├── page_factory.py
│ ├── browser_factory.py
│ ├── page_object.py
│ ├── locator.py
│ └── accessors.py
├── requirements.pip
├── .travis.yml
├── specs
├── accessors
│ ├── __init__.py
│ ├── table_accessor_test.py
│ ├── plural_accessor_test.py
│ └── div_accessor_test.py
├── page_factory
│ ├── __init__.py
│ ├── on_page_test.py
│ └── visit_page_test.py
└── __init__.py
├── Makefile
├── Dockerfile
├── setup.py
├── README.md
├── .gitignore
└── LICENSE
/features/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/features/steps/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/features/support/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
--------------------------------------------------------------------------------
/features/support/html/images/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OnShift/page_object/HEAD/features/support/html/images/circle.png
--------------------------------------------------------------------------------
/requirements.pip:
--------------------------------------------------------------------------------
1 | mock==2.0.0
2 | selenium==3.4.3
3 | PyHamcrest==1.8.5
4 | behave==1.2.5
5 | pytest==3.0.3
6 | twine==1.8.1
7 | -e .
8 |
--------------------------------------------------------------------------------
/features/environment.py:
--------------------------------------------------------------------------------
1 | from page_object import Browser
2 |
3 |
4 | def after_all(context):
5 | Browser.selenium_browser().close()
6 |
--------------------------------------------------------------------------------
/src/page_object/expected_conditions/__init__.py:
--------------------------------------------------------------------------------
1 | from .element_predicate import element_predicate
2 | from .element_exists import element_exists
3 |
--------------------------------------------------------------------------------
/features/support/html/success.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Success
4 |
5 |
6 | Success
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/page_object/elements/div.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class Div(Element):
5 | """
6 | Element class to represent HTML Div
7 | """
8 |
--------------------------------------------------------------------------------
/src/page_object/elements/label.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class Label(Element):
5 | """
6 | Element class to represent HTML Label
7 | """
8 |
--------------------------------------------------------------------------------
/src/page_object/elements/link.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class Link(Element):
5 | """
6 | Element class to represent HTML Link
7 | """
8 |
--------------------------------------------------------------------------------
/src/page_object/elements/button.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class Button(Element):
5 | """
6 | Element class to represent HTML Button
7 | """
8 |
--------------------------------------------------------------------------------
/src/page_object/elements/option.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class Option(Element):
5 | """
6 | Element class to represent HTML Option
7 | """
8 |
--------------------------------------------------------------------------------
/src/page_object/elements/table_cell.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class TableCell(Element):
5 | """
6 | Element class to represent HTML td
7 | """
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 |
3 | services:
4 | - docker
5 |
6 | before_install:
7 | - docker build -t page_object .
8 |
9 | script:
10 | docker run page_object
11 |
--------------------------------------------------------------------------------
/src/page_object/elements/heading.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class Heading(Element):
5 | """
6 | Element class to represent HTML headings
7 | """
8 |
--------------------------------------------------------------------------------
/src/page_object/elements/paragraph.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class Paragraph(Element):
5 | """
6 | Element class to represent HTML Paragraph
7 | """
8 |
--------------------------------------------------------------------------------
/src/page_object/elements/radio_button.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class RadioButton(Element):
5 | """
6 | Element class to represent HTML Radio Button
7 | """
8 |
--------------------------------------------------------------------------------
/src/page_object/elements/span.py:
--------------------------------------------------------------------------------
1 | from page_object.elements.element import Element
2 |
3 |
4 | class Span(Element):
5 | """
6 | Element class to represent HTML Span
7 | """
8 |
--------------------------------------------------------------------------------
/src/page_object/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from page_object.page_object import PageObject
3 | from page_object.page_factory import on, visit
4 | from page_object.browser import Browser
5 |
--------------------------------------------------------------------------------
/src/page_object/expected_conditions/element_predicate.py:
--------------------------------------------------------------------------------
1 | class element_predicate():
2 |
3 | def __init__(self, element, predicate):
4 | self.element = element
5 | self.predicate = predicate
6 |
7 | def __call__(self, drivers=None):
8 | return getattr(self.element, self.predicate)()
9 |
--------------------------------------------------------------------------------
/features/steps/div_steps.py:
--------------------------------------------------------------------------------
1 | @when(u'I get the text from the div')
2 | def step_impl(context):
3 | context.expected_text = context.page.div_id()
4 |
5 |
6 | @when(u'I search for the div by "{how}"')
7 | def step_impl(context, how):
8 | method = 'div_{0}'.format(how)
9 | context.expected_text = getattr(context.page, method)()
10 |
--------------------------------------------------------------------------------
/features/steps/span_steps.py:
--------------------------------------------------------------------------------
1 | @when(u'I get the text from the span')
2 | def step_impl(context):
3 | context.expected_text = context.page.span_id()
4 |
5 |
6 | @when(u'I search for the span by "{how}"')
7 | def step_impl(context, how):
8 | method = 'span_{0}'.format(how)
9 | context.expected_text = getattr(context.page, method)()
10 |
--------------------------------------------------------------------------------
/features/steps/label_steps.py:
--------------------------------------------------------------------------------
1 | @when(u'I get the text from the label')
2 | def step_impl(context):
3 | context.expected_text = context.page.label_id()
4 |
5 |
6 | @when(u'I search for the label by "{how}"')
7 | def step_impl(context, how):
8 | method = 'label_{0}'.format(how)
9 | context.expected_text = getattr(context.page, method)()
10 |
--------------------------------------------------------------------------------
/specs/accessors/__init__.py:
--------------------------------------------------------------------------------
1 | from page_object import PageObject
2 |
3 |
4 | class FakeTestPage(PageObject):
5 |
6 | def define_elements(self):
7 | self.divs(name='test_plural', identifier={'css': 'test'})
8 | self.div(name='test_div', identifier={'css': 'div'})
9 | self.table(name='test_table', identifier={'css': 'table'})
10 |
--------------------------------------------------------------------------------
/src/page_object/elements/image.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class Image(Element):
5 | """
6 | Element class to represent HTML Image
7 | """
8 |
9 | @property
10 | def width(self):
11 | return self.attribute('width')
12 |
13 | @property
14 | def height(self):
15 | return self.attribute('height')
16 |
--------------------------------------------------------------------------------
/features/steps/paragraph_steps.py:
--------------------------------------------------------------------------------
1 | @when(u'I get the text from the paragraph')
2 | def step_impl(context):
3 | context.expected_text = context.page.paragraph_id()
4 |
5 |
6 | @when(u'I search for the paragraph by "{how}"')
7 | def step_impl(context, how):
8 | method = 'paragraph_{0}'.format(how)
9 | context.expected_text = getattr(context.page, method)()
10 |
--------------------------------------------------------------------------------
/specs/page_factory/__init__.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase
2 | from mock import MagicMock
3 | from page_object import PageObject, Browser
4 |
5 | fake = MagicMock()
6 |
7 |
8 | class FakeTestPage(PageObject):
9 |
10 | def define_elements(self):
11 | self.page_url('www.noop.com')
12 |
13 |
14 | class MagicPage(PageObject):
15 |
16 | def __new__(self, cls):
17 | return fake
18 |
--------------------------------------------------------------------------------
/features/elements/elements.feature:
--------------------------------------------------------------------------------
1 | Feature: Generic Element Behavior
2 |
3 | Background:
4 | Given I am on the static elements page
5 |
6 | Scenario: Elements enabled?
7 | When I check an enabled button
8 | Then it should know it is enabled
9 | And it should know that is it not disabled
10 | When I check a disabled button
11 | Then it should know it is not enabled
12 | And it should know that it is disabled
13 |
--------------------------------------------------------------------------------
/specs/accessors/table_accessor_test.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to, instance_of
2 | from page_object import on
3 | from .. import BaseTestCase
4 | from . import FakeTestPage
5 | from mock import patch
6 |
7 |
8 | class TestTable(BaseTestCase):
9 |
10 | def test_element_method(self):
11 | table = on(FakeTestPage).test_table_element()
12 | self.fake_browser.find_element.assert_called_once_with('css selector', 'table')
13 |
--------------------------------------------------------------------------------
/src/page_object/elements/table.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class Table(Element):
5 | """
6 | Element class to represent HTML Table
7 | """
8 |
9 | def __iter__(self):
10 | return iter(self.row_elements({'css': 'tbody tr'}))
11 |
12 | def find_row(self, predicate=None):
13 | if predicate is None:
14 | raise Exception
15 | return next(row for row in self if predicate(row) is True)
16 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: specs features test clean install build push release
2 |
3 | specs:
4 | py.test -rwx -s specs/
5 |
6 | features:
7 | cd features/ && \
8 | behave
9 |
10 | clean:
11 | rm -rf dist/
12 | rm -rf build/
13 |
14 | install:
15 | pip install -r requirements.pip
16 |
17 | build:
18 | python setup.py sdist bdist_wheel egg_info
19 |
20 | push:
21 | twine upload dist/*
22 |
23 | release: clean install build push
24 |
25 | test: specs features
26 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM selenium/standalone-chrome:3.6.0
2 |
3 | USER root
4 |
5 | RUN apt-get update && \
6 | apt-get install -y python3.5 python3-pip
7 |
8 | WORKDIR app
9 |
10 | COPY src src
11 | COPY features features
12 | COPY specs specs
13 | COPY setup.py setup.py
14 | COPY requirements.pip requirements.pip
15 | COPY Makefile Makefile
16 |
17 | RUN pip3 install -r requirements.pip
18 |
19 | ENV BROWSER=HEADLESS
20 |
21 | CMD ["make", "test"]
22 |
23 |
24 |
--------------------------------------------------------------------------------
/features/steps/shared_element_steps.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to, contains_string
2 |
3 |
4 | @then(u'the text should be "{expected_text}"')
5 | def step_impl(context, expected_text):
6 | assert_that(context.expected_text, equal_to(expected_text))
7 |
8 |
9 | @then(u'I should be on the success page')
10 | def step_impl(context):
11 | assert_that(context.page.html, contains_string('Success'))
12 | assert_that(context.page.title, equal_to('Success'))
13 |
--------------------------------------------------------------------------------
/src/page_object/expected_conditions/element_exists.py:
--------------------------------------------------------------------------------
1 | from selenium.webdriver.remote.webelement import WebElement
2 |
3 |
4 | class element_exists():
5 | """
6 | Expected Condition for waiting for element to exists.
7 | """
8 |
9 | def __init__(self, element):
10 | self.element = element
11 |
12 | def __call__(self, drivers=None):
13 | if isinstance(self.element, WebElement):
14 | return True
15 |
16 | return self.element.exists()
17 |
--------------------------------------------------------------------------------
/features/steps/link_steps.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that
2 |
3 |
4 | @when(u'I select the link labeled "{link_text}"')
5 | def step_impl(context, link_text):
6 | context.page.google_search_id()
7 |
8 |
9 | @when(u'I search for the link by "{how}"')
10 | def step_impl(context, how):
11 | method = 'google_search_{0}_element'.format(how)
12 | context.link = getattr(context.page, method)()
13 |
14 |
15 | @then(u'I should be able to select the link')
16 | def step_impl(context):
17 | context.link.click()
18 |
--------------------------------------------------------------------------------
/features/elements/span.feature:
--------------------------------------------------------------------------------
1 | Feature: Span Element
2 |
3 | Background:
4 | Given I am on the static elements page
5 |
6 | Scenario: Getting the text from a span
7 | When I get the text from the span
8 | Then the text should be "My alert"
9 |
10 | Scenario Outline: Locating spans on the page
11 | When I search for the span by ""
12 | Then the text should be "My alert"
13 | Examples:
14 | | search_by |
15 | | id |
16 | | class |
17 | | tag |
18 | | css |
19 |
--------------------------------------------------------------------------------
/src/page_object/elements/substitute_web_element.py:
--------------------------------------------------------------------------------
1 | class SubstituteWebElement():
2 |
3 | def __init__(self, locator, identifier, element_type):
4 | self.locator = locator
5 | self.identifier = identifier
6 | self.element_type = element_type
7 |
8 | def exists(self):
9 | element = self._attempt_to_locate()
10 | return not isinstance(element.element, SubstituteWebElement)
11 |
12 | def _attempt_to_locate(self):
13 | return self.locator.element_for(self.element_type, self.identifier)
14 |
--------------------------------------------------------------------------------
/features/elements/label.feature:
--------------------------------------------------------------------------------
1 | Feature: Label Element
2 |
3 | Background:
4 | Given I am on the static elements page
5 |
6 | Scenario: Getting the text from a label
7 | When I get the text from the label
8 | Then the text should be "page-object is the best!"
9 |
10 | Scenario Outline: Locating labels on the page
11 | When I search for the label by ""
12 | Then the text should be "page-object is the best!"
13 |
14 | Examples:
15 | | search_by |
16 | | id |
17 | | class |
18 | | css |
19 |
--------------------------------------------------------------------------------
/features/elements/link_element.feature:
--------------------------------------------------------------------------------
1 | Feature: Link Element
2 |
3 | Background:
4 | Given I am on the static elements page
5 |
6 | Scenario: Selecting a link
7 | When I select the link labeled "Google Search"
8 | Then I should be on the success page
9 |
10 | Scenario Outline: Locating links on the Page
11 | When I search for the link by ""
12 | Then I should be able to select the link
13 | Examples:
14 | | search_by |
15 | | id |
16 | | class |
17 | | css |
18 | | tag |
19 |
--------------------------------------------------------------------------------
/features/elements/div.feature:
--------------------------------------------------------------------------------
1 | Feature: Div Element
2 |
3 | Background:
4 | Given I am on the static elements page
5 |
6 | Scenario: Getting the text from a div
7 | When I get the text from the div
8 | Then the text should be "page-object rocks!"
9 |
10 | Scenario Outline: Locating divs on the page
11 | When I search for the div by ""
12 | Then the text should be "page-object rocks!"
13 | Examples:
14 | | search_by |
15 | | id |
16 | | class |
17 | | tag |
18 | | css |
19 |
--------------------------------------------------------------------------------
/src/page_object/elements/checkbox.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class CheckBox(Element):
5 | """
6 | Element class to represent an HTML Checkbox
7 |
8 | Should maybe have a method to set the value to 'checked' or 'unchecked'
9 | """
10 |
11 | def check(self):
12 | if(not self.element.is_selected()):
13 | self.element.click()
14 |
15 | def uncheck(self):
16 | if(self.element.is_selected()):
17 | self.element.click()
18 |
19 | def is_checked(self):
20 | return self.element.is_selected()
21 |
--------------------------------------------------------------------------------
/features/elements/paragraph.feature:
--------------------------------------------------------------------------------
1 | Feature: Paragraph Element
2 |
3 | Background:
4 | Given I am on the static elements page
5 |
6 | Scenario: Getting the text from a paragraph
7 | When I get the text from the paragraph
8 | Then the text should be "Static Elements Page"
9 |
10 | Scenario Outline: Locating divs on the page
11 | When I search for the paragraph by ""
12 | Then the text should be "Static Elements Page"
13 | Examples:
14 | | search_by |
15 | | id |
16 | | class |
17 | | tag |
18 | | css |
19 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(
4 | name="page_object",
5 | version="0.3.4",
6 | description="Python implementation of the PageObject pattern",
7 | author="Onshift Inc.",
8 | author_email="dev@onshift.com",
9 | url="https://github.com/OnShift/page_object",
10 | license="Apache 2.0",
11 | keywords=['page_object', 'selenium', 'webdriver'],
12 | packages=find_packages('src'),
13 | package_dir={'': 'src'},
14 | install_requires=[
15 | 'pyvirtualdisplay==0.2',
16 | 'selenium==3.4.3'
17 | ]
18 | )
19 |
--------------------------------------------------------------------------------
/features/support/async_page.py:
--------------------------------------------------------------------------------
1 | from page_object import PageObject
2 |
3 |
4 | class AsyncPage(PageObject):
5 |
6 | def define_elements(self):
7 | self.button(name='target', identifier={'id': 'target'})
8 | self.button(name='enable', identifier={'id': 'enable'})
9 | self.button(name='disable', identifier={'id': 'disable'})
10 | self.button(name='create', identifier={'id': 'create'})
11 | self.button(name='hide', identifier={'id': 'hide'})
12 | self.button(name='unhide', identifier={'id': 'unhide'})
13 | self.button(name='new', identifier={'id': 'new'})
14 |
--------------------------------------------------------------------------------
/features/elements/image.feature:
--------------------------------------------------------------------------------
1 | Feature: Image Element
2 |
3 | Background:
4 | Given I am on the static elements page
5 |
6 | Scenario: Getting the image element
7 | When I get the image element
8 | Then the image should be "106" pixels wide
9 | And the image should be "106" pixels tall
10 |
11 | Scenario Outline: Locating an image on the page
12 | When I get the image element by ""
13 | Then the image should be "106" pixels wide
14 | And the image should be "106" pixels tall
15 | Examples:
16 | | search_by |
17 | | id |
18 | | class |
19 | | css |
20 |
--------------------------------------------------------------------------------
/src/page_object/elements/text_area.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class TextArea(Element):
5 | """
6 | Element class to represent HTML TextArea
7 | """
8 |
9 | def set_value(self, text):
10 | """
11 | Set value of text area element
12 |
13 | Args:
14 | text: text to set the value of the text area to.
15 | """
16 | self.clear()
17 | self.send_keys(text)
18 |
19 | def get_value(self):
20 | """
21 | Return:
22 | Current value of text field element
23 | """
24 | return self.attribute('value')
25 |
--------------------------------------------------------------------------------
/src/page_object/elements/text_field.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class TextField(Element):
5 | """
6 | Element class to represent HTML TextField
7 | """
8 |
9 | def set_value(self, text):
10 | """
11 | Set value of text field element
12 |
13 | Args:
14 | text: text to set the value of the text field.
15 | """
16 | self.clear()
17 | self.send_keys(text)
18 |
19 | def get_value(self):
20 | """
21 | Return:
22 | Current value of text field element
23 | """
24 | return self.attribute('value')
25 |
--------------------------------------------------------------------------------
/features/elements/check_box.feature:
--------------------------------------------------------------------------------
1 | Feature: Check Box Element
2 |
3 | Background:
4 | Given I am on the static elements page
5 |
6 | Scenario: Selecting a check box
7 | When I select the First check box
8 | Then the First check box should be selected
9 | When I unselect the First check box
10 | Then the First check box should not be selected
11 |
12 | Scenario Outline: Locating check boxes on the page
13 | When I search for the check box by ""
14 | Then I should be able to check the check box
15 | Examples:
16 | | search_by |
17 | | id |
18 | | class |
19 | | css |
20 |
--------------------------------------------------------------------------------
/features/elements/async.feature:
--------------------------------------------------------------------------------
1 | Feature: Async Elements
2 |
3 | Background:
4 | Given I am on the async elements page
5 |
6 | Scenario: Wait for an element to be enabled
7 | Given the button is disabled
8 | When I enable the button
9 | Then I should be able to wait for the button to be enabled
10 |
11 | Scenario: Wait for an element to be present
12 | When I create a new button
13 | Then I should be able to wait for the button to be present
14 |
15 | Scenario: Wait for an element to be visible
16 | Given the button is hidden
17 | When I show the button
18 | Then I should be able to wait for the button to be visible
19 |
--------------------------------------------------------------------------------
/src/page_object/browser.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from page_object.browser_factory import BrowserFactory
3 |
4 |
5 | class Browser():
6 | _factory = None
7 | _browser = None
8 |
9 | @classmethod
10 | def set_browser(cls, browser):
11 | cls._browser = browser
12 |
13 | @classmethod
14 | def selenium_browser(cls):
15 | if cls._browser is None:
16 | cls._browser = cls.factory().selenium_browser()
17 | return cls._browser
18 |
19 | @classmethod
20 | def factory(cls):
21 | if cls._factory is None:
22 | cls._factory = BrowserFactory()
23 | return cls._factory
24 |
--------------------------------------------------------------------------------
/features/steps/element_steps.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to
2 |
3 |
4 | @then(u'it should know it is enabled')
5 | def step_impl(context):
6 | assert_that(context.element.is_enabled(), equal_to(True))
7 |
8 |
9 | @then(u'it should know that is it not disabled')
10 | def step_impl(context):
11 | assert_that(context.element.is_disabled(), equal_to(False))
12 |
13 |
14 | @then(u'it should know it is not enabled')
15 | def step_impl(context):
16 | assert_that(context.element.is_enabled(), equal_to(False))
17 |
18 |
19 | @then(u'it should know that it is disabled')
20 | def step_impl(context):
21 | assert_that(context.element.is_disabled(), equal_to(True))
22 |
--------------------------------------------------------------------------------
/features/elements/button.feature:
--------------------------------------------------------------------------------
1 | Feature: Button Element
2 |
3 | Background:
4 | Given I am on the static elements page
5 |
6 | Scenario: Clicking a button
7 | When I click the button
8 | Then I should be on the success page
9 |
10 | Scenario Outline: Locating buttons on the page
11 | When I search for the button by ""
12 | Then I should be able to click the button
13 | Examples:
14 | | search_by |
15 | | id |
16 | | class |
17 | | css |
18 |
19 | Scenario: Finding a button dynamically
20 | When I find a button while the script is executing
21 | Then I should see that the button exists
22 | And I should be able to click the button
23 |
--------------------------------------------------------------------------------
/features/steps/navigation_steps.py:
--------------------------------------------------------------------------------
1 | from support.page import Page
2 | from support.async_page import AsyncPage
3 | from page_object import Browser
4 | import os
5 |
6 |
7 | @given(u'I am on the static elements page')
8 | def step_impl(context):
9 | path = os.path.abspath("support/html/static_elements.html")
10 | context.page = Page(Browser.selenium_browser())
11 | context.page.navigate_to('file://{0}'.format(path))
12 | context.page.define_elements()
13 |
14 |
15 | @given(u'I am on the async elements page')
16 | def step_impl(context):
17 | path = os.path.abspath("support/html/async_elements.html")
18 | context.page = AsyncPage(Browser.selenium_browser())
19 | context.page.navigate_to('file://{0}'.format(path))
20 | context.page.define_elements()
21 |
--------------------------------------------------------------------------------
/features/steps/image_steps.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to
2 |
3 |
4 | @when(u'I get the image element')
5 | def step_impl(context):
6 | context.element = context.page.image_id_element()
7 |
8 |
9 | @when(u'I get the image element by "{how}"')
10 | def step_impl(context, how):
11 | method = 'image_{0}_element'.format(how)
12 | context.element = getattr(context.page, method)()
13 |
14 |
15 | @then(u'the image should be "{expected_pixels}" pixels wide')
16 | def step_impl(context, expected_pixels):
17 | assert_that(context.element.width, equal_to(expected_pixels))
18 |
19 |
20 | @then(u'the image should be "{expected_pixels}" pixels tall')
21 | def step_impl(context, expected_pixels):
22 | assert_that(context.element.height, equal_to(expected_pixels))
23 |
--------------------------------------------------------------------------------
/specs/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from unittest import TestCase
4 | from mock import MagicMock
5 | from page_object import PageObject, Browser
6 |
7 | fake = MagicMock()
8 |
9 |
10 | class MagicPage(PageObject):
11 |
12 | def __new__(self, cls):
13 | return fake
14 |
15 |
16 | class BaseTestCase(TestCase):
17 | fake_browser = None
18 |
19 | def setup_method(self, method):
20 | self.fake_browser = self.create_mock()
21 | Browser.set_browser(self.fake_browser)
22 |
23 | def create_mock(self, values=None):
24 | mock = MagicMock()
25 | if values is not None:
26 | self.configure_mock(mock, **values)
27 | return mock
28 |
29 | def configure_mock(self, mock, values):
30 | mock.configure_mock(**values)
31 |
--------------------------------------------------------------------------------
/features/elements/text_field.feature:
--------------------------------------------------------------------------------
1 | Feature: TextField Element
2 |
3 | Background:
4 | Given I am on the static elements page
5 |
6 | Scenario: Setting and getting a value from a text field
7 | When I type "abcDEF" into the text field
8 | Then the text field should contain "abcDEF"
9 |
10 | Scenario: Clear text field vlaue
11 | When I type "abcDEF" into the text field
12 | Then the text field should contain "abcDEF"
13 | When I clear the text field
14 | Then the text field should contain " "
15 |
16 | Scenario Outline: Locating text fields on the Page
17 | When I search for the text field by ""
18 | Then I should be able to type "I found it" into the field
19 | Examples:
20 | | search_by |
21 | | id |
22 | | class |
23 | | css |
24 |
--------------------------------------------------------------------------------
/specs/accessors/plural_accessor_test.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to, instance_of
2 | from page_object import on, PageObject
3 | from page_object.locator_generator import LocatorGenerator
4 | from page_object.elements.div import Div
5 | from .. import BaseTestCase
6 | from . import FakeTestPage
7 | from mock import patch
8 |
9 |
10 | class TestPluralAccessors(BaseTestCase):
11 |
12 | @patch('page_object.locator.Locator.divs_for')
13 | def test_define_plural_accessor_method(self, locator):
14 | elements = on(FakeTestPage).test_plural_elements()
15 | locator.assert_called_once_with({'css': 'test'})
16 |
17 | def test_plural_accessor_methods(self):
18 | page = on(FakeTestPage)
19 | for tag in LocatorGenerator.ELEMENTS:
20 | assert_that('{0}s'.format(tag) in dir(page))
21 |
--------------------------------------------------------------------------------
/src/page_object/elements/table_row.py:
--------------------------------------------------------------------------------
1 | from .element import Element
2 |
3 |
4 | class TableRow(Element):
5 | """
6 | Element class to represent HTML tr
7 | """
8 |
9 | def __iter__(self):
10 | """
11 | Return all cells in row as an iterable
12 | """
13 | return iter(self.cell_elements({'css': 'th,td'}))
14 |
15 | def get_cells(self, identifier, operation=None):
16 | """
17 | Method to retrieve row cells
18 |
19 | Args:
20 | identifier: selector for table cells
21 | operation: lambda to perform on each cell element
22 |
23 | Return:
24 | Mapped cells filtered by identifier
25 | """
26 | if operation is None:
27 | def operation(obj): return obj
28 |
29 | return [operation(cell) for cell in self.cell_elements(identifier)]
30 |
--------------------------------------------------------------------------------
/features/elements/text_area.feature:
--------------------------------------------------------------------------------
1 | Feature: Text Area Element
2 |
3 | Background:
4 | Given I am on the static elements page
5 |
6 | Scenario: Setting and getting a value from a text area
7 | When I type "abcdefghijklmnop" into the text area
8 | Then the text area should contain "abcdefghijklmnop"
9 |
10 | Scenario Outline: Locating text area on the Page
11 | When I search for the text area by ""
12 | Then I should be able to type "I found it" into the area
13 | Examples:
14 | | search_by |
15 | | id |
16 | | class |
17 | | css |
18 |
19 | Scenario: Clearing the text area
20 | When I type "abcdefghijklmnop" into the text area
21 | Then the text area should contain "abcdefghijklmnop"
22 | When I clear the text area
23 | Then the text area should contain " "
24 |
--------------------------------------------------------------------------------
/src/page_object/elements/select_list.py:
--------------------------------------------------------------------------------
1 | from selenium.webdriver.support.select import Select
2 | from selenium.webdriver.support.ui import WebDriverWait
3 | from .element import Element
4 | from .option import Option
5 |
6 |
7 | class SelectList(Select, Element):
8 | """
9 | Element class to represent HTML Select List
10 | """
11 |
12 | def __init__(self, element):
13 | Select.__init__(self, element)
14 | Element.__init__(self, element)
15 |
16 | def selected(self):
17 | return Option(self.first_selected_option)
18 |
19 | def text(self):
20 | return self.selected().text()
21 |
22 | def value(self):
23 | return self.selected().attribute('value')
24 |
25 | def select(self, text):
26 | return self.select_by_visible_text(text)
27 |
28 | def options_text(self):
29 | return [option.text for option in self.options]
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PageObject
2 |
3 | [](https://travis-ci.org/OnShift/page_object)
4 |
5 | PageObject implementation in Python
6 |
7 | The project [wiki](https://github.com/OnShift/page_object/wiki)
8 | is the first place to go to learn about how to use PageObject.
9 |
10 | This project was inspired by [Jeff Morgan's](https://github.com/cheezy) ruby implementation of this pattern. [page-object](https://github.com/cheezy/page-object)
11 |
12 | ## Known Issues
13 |
14 | See [https://github.com/OnShift/page_object/issues](https://github.com/OnShift/page_object/issues)
15 |
16 | ## Contribute
17 |
18 | * Fork the project.
19 | * Test drive your feature addition or bug fix.
20 | * Make sure you describe your new feature with a behave scenario.
21 | * Provide documentation where needed.
22 |
23 | ## Copyright
24 |
25 | See LICENSE for details.
26 |
--------------------------------------------------------------------------------
/src/page_object/locator_generator.py:
--------------------------------------------------------------------------------
1 | class LocatorGenerator:
2 | """
3 | Class to generate all locator functions
4 | """
5 |
6 | ELEMENTS = ['select_list',
7 | 'option',
8 | 'image',
9 | 'table',
10 | 'row',
11 | 'cell',
12 | 'div',
13 | 'span',
14 | 'paragraph',
15 | 'label',
16 | 'text_area',
17 | 'text_field',
18 | 'link',
19 | 'heading',
20 | 'button',
21 | 'radio_button',
22 | 'checkbox']
23 |
24 | def generate_locators(self):
25 | for tag in self.ELEMENTS:
26 | setattr(self, "{}_element".format(tag), getattr(self.locator, "{0}_for".format(tag)))
27 | setattr(self, "{}_elements".format(tag), getattr(self.locator, "{0}s_for".format(tag)))
28 |
--------------------------------------------------------------------------------
/specs/page_factory/on_page_test.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to
2 | from page_object import on, PageObject
3 | from .. import BaseTestCase
4 | from . import MagicPage, FakeTestPage
5 |
6 |
7 | class TestOn(BaseTestCase):
8 |
9 | def test_creates_instance(self):
10 | page = on(FakeTestPage)
11 | assert_that(isinstance(page, FakeTestPage), equal_to(True))
12 | assert_that(isinstance(page, PageObject), equal_to(True))
13 |
14 | def test_calls_define_elements(self):
15 | page = on(MagicPage)
16 | page.define_elements.assert_called_with()
17 |
18 | def test_calls_goto(self):
19 | expect_url_params = {}
20 | page = on(MagicPage, visit_page=True, url_params=expect_url_params)
21 | page.goto.assert_called_with(expect_url_params)
22 |
23 | def test_calls_initalize_page(self):
24 | page = on(MagicPage)
25 | page.initialize_page.assert_called_with()
26 |
--------------------------------------------------------------------------------
/features/steps/text_field_steps.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to
2 |
3 |
4 | @when(u'I search for the text field by "{how}"')
5 | def step_impl(context, how):
6 | method = 'text_field_{0}_element'.format(how)
7 | context.element = getattr(context.page, method)()
8 |
9 |
10 | @when(u'I type "{text}" into the text field')
11 | def step_impl(context, text):
12 | context.page.set_text_field_id(text)
13 |
14 |
15 | @when(u'I clear the text field')
16 | def step_impl(context):
17 | context.page.text_field_id_element().clear()
18 |
19 |
20 | @then(u'the text field should contain "{expected_text}"')
21 | def step_impl(context, expected_text):
22 | assert_that(context.page.text_field_id(), equal_to(expected_text.strip()))
23 |
24 |
25 | @then(u'I should be able to type "{text}" into the field')
26 | def step_impl(context, text):
27 | context.element.set_value(text)
28 | assert_that(context.element.get_value(), equal_to(text))
29 |
--------------------------------------------------------------------------------
/features/elements/select_list.feature:
--------------------------------------------------------------------------------
1 | Feature: Select List Element
2 |
3 | Background:
4 | Given I am on the static elements page
5 |
6 | Scenario: Selecting an element on the select list
7 | When I select "Test 2" from the select list
8 | Then the current item should be "Test 2"
9 |
10 | Scenario Outline: Locating select lists on the Page
11 | When I search for the select list by ""
12 | Then I should be able to select "Test 2"
13 | And the text for the selected item should be "Test 2"
14 | And the value for the option should be "option2"
15 | Examples:
16 | | search_by |
17 | | id |
18 | | css |
19 | | class |
20 | | tag |
21 |
22 | Scenario: Iterating through the options in the select list
23 | When I search for the select list by "id"
24 | Then option "1" should contain "Test 1"
25 | And option "2" should contain "Test 2"
26 | And each option should contain "Test"
27 |
--------------------------------------------------------------------------------
/features/steps/text_area_steps.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to
2 |
3 |
4 | @when(u'I type "{text}" into the text area')
5 | def step_impl(context, text):
6 | context.page.set_text_area_id(text)
7 |
8 |
9 | @when(u'I search for the text area by "{how}"')
10 | def step_impl(context, how):
11 | method = 'text_area_{0}_element'.format(how)
12 | context.text_area = getattr(context.page, method)()
13 |
14 |
15 | @when(u'I clear the text area')
16 | def step_impl(context):
17 | context.page.text_area_id_element().clear()
18 |
19 |
20 | @then(u'I should be able to type "{text}" into the area')
21 | def step_impl(context, text):
22 | context.text_area.set_value(text)
23 | assert_that(context.text_area.get_value(), equal_to(text.strip()))
24 |
25 |
26 | @then(u'the text area should contain "{text}"')
27 | def step_impl(context, text):
28 | text_area = context.page.text_area_id_element()
29 | assert_that(text_area.get_value(), equal_to(text.strip()))
30 |
--------------------------------------------------------------------------------
/features/steps/check_box_steps.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to
2 |
3 |
4 | @when(u'I select the First check box')
5 | def step_impl(context):
6 | context.page.check_cb_id()
7 |
8 |
9 | @when(u'I unselect the First check box')
10 | def step_impl(context):
11 | context.page.uncheck_cb_id()
12 |
13 |
14 | @when(u'I search for the check box by "{how}"')
15 | def step_impl(context, how):
16 | method = 'cb_{0}_element'.format(how)
17 | context.element = getattr(context.page, method)()
18 |
19 |
20 | @then(u'the First check box should be selected')
21 | def step_impl(context):
22 | assert_that(context.page.is_cb_id_checked(), equal_to(True))
23 |
24 |
25 | @then(u'the First check box should not be selected')
26 | def step_impl(context):
27 | assert_that(context.page.is_cb_id_checked(), equal_to(False))
28 |
29 |
30 | @then(u'I should be able to check the check box')
31 | def step_impl(context):
32 | context.element.check()
33 | assert_that(context.element.is_checked(), equal_to(True))
34 |
--------------------------------------------------------------------------------
/src/page_object/elements/__init__.py:
--------------------------------------------------------------------------------
1 | from page_object.elements.substitute_web_element import SubstituteWebElement
2 | from page_object.elements.table import Table
3 | from page_object.elements.table_row import TableRow
4 | from page_object.elements.table_cell import TableCell
5 | from page_object.elements.div import Div
6 | from page_object.elements.span import Span
7 | from page_object.elements.paragraph import Paragraph
8 | from page_object.elements.label import Label
9 | from page_object.elements.image import Image
10 | from page_object.elements.select_list import SelectList
11 | from page_object.elements.option import Option
12 | from page_object.elements.text_area import TextArea
13 | from page_object.elements.text_field import TextField
14 | from page_object.elements.link import Link
15 | from page_object.elements.heading import Heading
16 | from page_object.elements.button import Button
17 | from page_object.elements.radio_button import RadioButton
18 | from page_object.elements.checkbox import CheckBox
19 | from page_object.elements.element import Element
20 |
--------------------------------------------------------------------------------
/specs/accessors/div_accessor_test.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to, instance_of
2 | from page_object import on
3 | from .. import BaseTestCase
4 | from . import FakeTestPage
5 | from mock import patch
6 |
7 |
8 | class TestDiv(BaseTestCase):
9 |
10 | @patch('page_object.locator.Locator.div_for')
11 | def test_element_method(self, mock):
12 | self.element = self.create_mock()
13 | self.configure_mock(mock, {'return_value': self.element})
14 |
15 | actual_element = on(FakeTestPage).test_div_element()
16 | assert_that(actual_element, equal_to(self.element))
17 |
18 | @patch('page_object.locator.Locator.div_for')
19 | def test_get_text_method(self, mock):
20 | self.element = self.create_mock()
21 | self.configure_mock(mock, {'return_value': self.element})
22 |
23 | expected_text = 'I am in a div!'
24 | self.configure_mock(self.element.text, {'return_value': expected_text})
25 |
26 | actual_text = on(FakeTestPage).test_div()
27 | assert_that(actual_text, equal_to(expected_text))
28 |
--------------------------------------------------------------------------------
/features/steps/button_steps.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to
2 |
3 |
4 | @when(u'I click the button')
5 | def step_impl(context):
6 | context.page.button_id()
7 |
8 |
9 | @when(u'I search for the button by "{how}"')
10 | def step_impl(context, how):
11 | method = 'button_{0}_element'.format(how)
12 | context.button = getattr(context.page, method)()
13 |
14 |
15 | @when(u'I find a button while the script is executing')
16 | def step_impl(context):
17 | context.button = context.page.button_element({'id': 'button_id'})
18 |
19 |
20 | @when(u'I check an enabled button')
21 | def step_impl(context):
22 | context.element = context.page.button_id_element()
23 |
24 |
25 | @when(u'I check a disabled button')
26 | def step_impl(context):
27 | context.element = context.page.disabled_button_element()
28 |
29 |
30 | @then(u'I should be able to click the button')
31 | def step_impl(context):
32 | context.button.click()
33 |
34 |
35 | @then(u'I should see that the button exists')
36 | def step_impl(context):
37 | assert_that(context.button.exists(), equal_to(True))
38 |
--------------------------------------------------------------------------------
/src/page_object/page_factory.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from page_object.browser import Browser
3 |
4 |
5 | def on(page, visit_page=False, url_params={}):
6 | """
7 | Creates PageObject with current instance of the selenium_browser.
8 |
9 | Args:
10 | page (PageObject): PageObject class name.
11 | visit_page (boolean): Navigate to page.
12 | url_params (dictionary): url parameter object.
13 |
14 | Returns:
15 | New instance of page argument.
16 | """
17 | page_instance = page(Browser.selenium_browser())
18 |
19 | if hasattr(page_instance, 'define_elements'):
20 | page_instance.define_elements()
21 |
22 | if visit_page:
23 | page_instance.goto(url_params)
24 |
25 | if hasattr(page_instance, 'initialize_page'):
26 | page_instance.initialize_page()
27 |
28 | return page_instance
29 |
30 |
31 | def visit(page, url_params={}):
32 | """
33 | Navigate to page url.
34 |
35 | Creates PageObject with current instance of the selenium_browser.
36 |
37 | Args:
38 | page (PageObject): PageObject class name.
39 | url_params (dictionary): url parameter object.
40 |
41 | Returns:
42 | New instance of page argument.
43 | """
44 | return on(page, visit_page=True, url_params=url_params)
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask stuff:
57 | instance/
58 | .webassets-cache
59 |
60 | # Scrapy stuff:
61 | .scrapy
62 |
63 | # Sphinx documentation
64 | docs/_build/
65 |
66 | # PyBuilder
67 | target/
68 |
69 | # IPython Notebook
70 | .ipynb_checkpoints
71 |
72 | # pyenv
73 | .python-version
74 |
75 | # celery beat schedule file
76 | celerybeat-schedule
77 |
78 | # dotenv
79 | .env
80 |
81 | # virtualenv
82 | venv/
83 | ENV/
84 |
85 | # Spyder project settings
86 | .spyderproject
87 |
88 | # Rope project settings
89 | .ropeproject
90 |
91 | /.DS_Store
92 |
--------------------------------------------------------------------------------
/src/page_object/browser_factory.py:
--------------------------------------------------------------------------------
1 | import os
2 | from selenium import webdriver
3 | from selenium.webdriver.chrome.options import Options
4 |
5 |
6 | class BrowserFactory(object):
7 |
8 | chrome_options = Options()
9 | chrome_options.add_argument("--no-sandbox")
10 | chrome_options.add_argument("--disable-setuid-sandbox")
11 | chrome_options.add_argument("--window-size=1920,1080")
12 |
13 | drivers = {
14 | "CHROME": "_chrome_driver",
15 | "HEADLESS": "_headless_driver",
16 | "FIREFOX": "_firefox_driver",
17 | "SAFARI": "_safari_driver",
18 | "REMOTE": "_remote_driver"
19 | }
20 |
21 | def selenium_browser(self):
22 | browser = str(os.getenv("BROWSER", "CHROME"))
23 | return getattr(self, self.drivers[browser])()
24 |
25 | def _headless_driver(self):
26 | self.chrome_options.add_argument("--headless")
27 | return self._chrome_driver()
28 |
29 | def _chrome_driver(self):
30 | return webdriver.Chrome(chrome_options=self.chrome_options)
31 |
32 | def _firefox_driver(self):
33 | return webdriver.Firefox()
34 |
35 | def _safari_driver(self):
36 | return webdriver.Safari()
37 |
38 | def _remote_driver(self):
39 | url = str(os.getenv("SELENIUM_URL", "http://localhost:4444/wd/hub"))
40 | capability = str(os.getenv("CAPABILITY", "EDGE"))
41 | return webdriver.Remote(url, getattr(webdriver.DesiredCapabilities, capability))
42 |
--------------------------------------------------------------------------------
/features/steps/async_steps.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to
2 |
3 |
4 | @given(u'the button is disabled')
5 | def step_impl(context):
6 | context.page.disable()
7 | context.page.wait(3)
8 |
9 |
10 | @given(u'the button is hidden')
11 | def step_impl(context):
12 | context.page.hide()
13 | context.page.wait(3)
14 |
15 |
16 | @when(u'I enable the button')
17 | def step_impl(context):
18 | context.page.enable()
19 |
20 |
21 | @when(u'I create a new button')
22 | def step_impl(context):
23 | context.page.create()
24 |
25 |
26 | @when(u'I show the button')
27 | def step_impl(context):
28 | context.page.unhide()
29 |
30 |
31 | @then(u'I should be able to wait for the button to be enabled')
32 | def step_impl(context):
33 | element = context.page.target_element()
34 | assert_that(element.is_enabled(), equal_to(False))
35 | element.when_enabled()
36 | assert_that(element.is_enabled(), equal_to(True))
37 |
38 |
39 | @then(u'I should be able to wait for the button to be present')
40 | def step_impl(context):
41 | element = context.page.new_element()
42 | assert_that(element.exists(), equal_to(False))
43 | element.when_present()
44 | assert_that(element.exists(), equal_to(True))
45 |
46 |
47 | @then(u'I should be able to wait for the button to be visible')
48 | def step_impl(context):
49 | element = context.page.target_element()
50 | assert_that(element.is_visible(), equal_to(False))
51 | element.when_visible()
52 | assert_that(element.is_visible(), equal_to(True))
53 |
--------------------------------------------------------------------------------
/specs/page_factory/visit_page_test.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to
2 | from page_object import visit, PageObject
3 | from .. import BaseTestCase
4 | from . import MagicPage, FakeTestPage
5 |
6 |
7 | class CompleteUrlTestPage(PageObject):
8 |
9 | def define_elements(self):
10 | self.page_url('{base_url}/test_route/{id}?{filter}')
11 |
12 |
13 | class TestVisit(BaseTestCase):
14 |
15 | def set_base_url(self):
16 | import os
17 | os.environ['PAGE_OBJECT_BASE_URL'] = 'www.pageobject.com'
18 |
19 | def test_creates_instance(self):
20 | page = visit(FakeTestPage)
21 | assert_that(isinstance(page, FakeTestPage), equal_to(True))
22 | assert_that(isinstance(page, PageObject), equal_to(True))
23 |
24 | def test_should_navigate_to_hardcoded_url(self):
25 | visit(FakeTestPage)
26 | self.fake_browser.get.assert_called_with('www.noop.com')
27 |
28 | def test_builds_url_from_base_url(self):
29 | self.set_base_url()
30 | url_params = {'filter': 'start_date:today', 'id': 18}
31 | visit(CompleteUrlTestPage, url_params=url_params)
32 |
33 | expected_url = 'www.pageobject.com/test_route/18?start_date:today'
34 | self.fake_browser.get.assert_called_with(expected_url)
35 |
36 | def test_calls_define_elements(self):
37 | page = visit(MagicPage)
38 | page.define_elements.assert_called_with()
39 |
40 | def test_calls_goto(self):
41 | expect_url_params = {}
42 | page = visit(MagicPage, url_params=expect_url_params)
43 | page.goto.assert_called_with(expect_url_params)
44 |
45 | def test_calls_initalize_page(self):
46 | page = visit(MagicPage)
47 | page.initialize_page.assert_called_with()
48 |
--------------------------------------------------------------------------------
/features/steps/select_list_steps.py:
--------------------------------------------------------------------------------
1 | from hamcrest import assert_that, equal_to
2 |
3 |
4 | @when(u'I select "{option}" from the select list')
5 | def step_impl(context, option):
6 | context.page.set_sel_list_id(option)
7 |
8 |
9 | @when(u'I search for the select list by "{how}"')
10 | def step_impl(context, how):
11 | context.how = how
12 |
13 |
14 | @then(u'the current item should be "{option_text}"')
15 | def step_impl(context, option_text):
16 | assert_that(context.page.get_sel_list_id(), equal_to(option_text))
17 |
18 |
19 | @then(u'I should be able to select "{option_text}"')
20 | def step_impl(context, option_text):
21 | getattr(context.page, 'set_sel_list_{0}'.format(context.how))(option_text)
22 |
23 |
24 | @then(u'the text for the selected item should be "{option_text}"')
25 | def step_impl(context, option_text):
26 | get_method = 'get_sel_list_{0}'.format(context.how)
27 | selected_text = getattr(context.page, get_method)()
28 | assert_that(selected_text, equal_to(option_text))
29 |
30 |
31 | @then(u'the value for the option should be "{option_value}"')
32 | def step_impl(context, option_value):
33 | method = 'sel_list_{0}_element'.format(context.how)
34 | select_list = getattr(context.page, method)()
35 | assert_that(select_list.value(), equal_to(option_value))
36 |
37 |
38 | @then(u'option "{index}" should contain "{option_text}"')
39 | def step_impl(context, index, option_text):
40 | options = context.page.sel_list_id_element().options_text()
41 | assert_that(options[int(index) - 1], equal_to(option_text))
42 |
43 |
44 | @then(u'each option should contain "{text}"')
45 | def step_impl(context, text):
46 | options = context.page.sel_list_id_element().options_text()
47 | assert_that(all(text in option for option in options))
48 |
--------------------------------------------------------------------------------
/features/support/html/async_elements.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Async Page
4 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/page_object/page_object.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | import time
3 | import os
4 | from .accessors import Accessors
5 | from .locator_generator import LocatorGenerator
6 |
7 |
8 | class PageObject(LocatorGenerator, Accessors, object):
9 | """
10 | Class that when included adds functionality to a page object.
11 | This class will add numerous class and instance methods that you use to define and
12 | interact with web pages.
13 | """
14 |
15 | def __init__(self, browser):
16 | """
17 | Args:
18 | browser (selenium.webdriver.browser) : selenium browser instance to use
19 | visit_page (bool) : force browser to navigate to page, defaults to false.
20 | """
21 | super(PageObject, self).__init__()
22 | self._load_locator(browser)
23 | self.generate_locators()
24 | self.browser = browser
25 |
26 | @property
27 | def base_url(self):
28 | return os.environ.get('PAGE_OBJECT_BASE_URL', None)
29 |
30 | @property
31 | def current_url(self):
32 | """
33 | Return:
34 | Returns the URL of the current page.
35 | """
36 | return self.browser.current_url
37 |
38 | @property
39 | def html(self):
40 | """
41 | Return:
42 | Returns the HTML of the current page.
43 | """
44 | return self.browser.page_source
45 |
46 | @property
47 | def title(self):
48 | """
49 | Return:
50 | Returns the title of the current page.
51 | """
52 | return self.browser.title
53 |
54 | def navigate_to(self, url):
55 | """
56 | Navigate to url.
57 |
58 | Args:
59 | url (string): Url to navigate to.
60 | """
61 | self.browser.get(url)
62 |
63 | def wait(self, seconds=5):
64 | """
65 | Method to wait a given amount of seconds.
66 | Args:
67 | seconds (int): Defaults to 5.
68 | """
69 | time.sleep(seconds)
70 |
71 | def _load_locator(self, browser):
72 | from page_object.locator import Locator
73 | self.locator = Locator(browser)
74 |
--------------------------------------------------------------------------------
/features/support/page.py:
--------------------------------------------------------------------------------
1 | from page_object import PageObject
2 |
3 |
4 | class Page(PageObject):
5 |
6 | def define_elements(self):
7 | self.text_field(name='text_field_id', identifier={'id': 'text_field_id'})
8 | self.text_field(name='text_field_class', identifier={'class': 'text_field_class'})
9 | self.text_field(name='text_field_css', identifier={'css': '[data-test-text-field="text_field_css"]'})
10 |
11 | self.text_area(name='text_area_id', identifier={'id': 'text_area_id'})
12 | self.text_area(name='text_area_class', identifier={'class': 'text_area_class'})
13 | self.text_area(name='text_area_css', identifier={'css': '[data-test-text-area="text_area_css"]'})
14 |
15 | self.select_list(name='sel_list_id', identifier={'id': 'sel_list_id'})
16 | self.select_list(name='sel_list_class', identifier={'class': 'sel_list_class'})
17 | self.select_list(name='sel_list_css', identifier={'css': '[data-test-select-list="select_list_css"]'})
18 | self.select_list(name='sel_list_tag', identifier={'tag': 'select'})
19 |
20 | self.checkbox(name='cb_id', identifier={'id': 'cb_id'})
21 | self.checkbox(name='cb_class', identifier={'class': 'cb_class'})
22 | self.checkbox(name='cb_css', identifier={'css': '[data-test-checkbox="checkbox_css"]'})
23 |
24 | self.button(name='button_id', identifier={'id': 'button_id'})
25 | self.button(name='button_class', identifier={'class': 'button_class'})
26 | self.button(name='button_css', identifier={'css': '[data-test-button="button_css"]'})
27 | self.button(name='disabled_button', identifier={'id': 'disabled_button'})
28 |
29 | self.link(name='google_search_id', identifier={'id': 'link_id'})
30 | self.link(name='google_search_class', identifier={'class': 'link_class'})
31 | self.link(name='google_search_css', identifier={'css': '[data-test-link="link_css"]'})
32 | self.link(name='google_search_tag', identifier={'tag': 'a'})
33 |
34 | self.div(name='div_id', identifier={'id': 'div_id'})
35 | self.div(name='div_class', identifier={'class': 'div_class'})
36 | self.div(name='div_css', identifier={'css': '[data-test-div="div_css"]'})
37 | self.div(name='div_tag', identifier={'tag': 'div'})
38 |
39 | self.span(name='span_id', identifier={'id': 'span_id'})
40 | self.span(name='span_class', identifier={'class': 'span_class'})
41 | self.span(name='span_css', identifier={'css': '[data-test-span="span_css"]'})
42 | self.span(name='span_tag', identifier={'tag': 'span'})
43 |
44 | self.paragraph(name='paragraph_id', identifier={'id': 'p_id'})
45 | self.paragraph(name='paragraph_class', identifier={'class': 'p_class'})
46 | self.paragraph(name='paragraph_css', identifier={'css': '[data-test-p="p_css"]'})
47 | self.paragraph(name='paragraph_tag', identifier={'tag': 'p'})
48 |
49 | self.label(name='label_id', identifier={'id': 'label_id'})
50 | self.label(name='label_class', identifier={'class': 'label_class'})
51 | self.label(name='label_css', identifier={'css': '[data-test-label="label_css"]'})
52 |
53 | self.image(name='image_id', identifier={'id': 'image_id'})
54 | self.image(name='image_class', identifier={'class': 'image_class'})
55 | self.image(name='image_css', identifier={'css': '[data-test-image="image_css"]'})
56 |
--------------------------------------------------------------------------------
/src/page_object/elements/element.py:
--------------------------------------------------------------------------------
1 | from selenium.webdriver.support.ui import WebDriverWait
2 | from selenium.webdriver.common.action_chains import ActionChains
3 | from page_object.locator_generator import LocatorGenerator
4 | from page_object.expected_conditions import element_exists, element_predicate
5 | import os
6 |
7 |
8 | class Element(LocatorGenerator, object):
9 | """
10 | Base class to represent HTML Elements
11 | """
12 |
13 | DEFAULT_TIMEOUT = int(os.getenv("DEFAULT_TIMEOUT", 5))
14 |
15 | def __init__(self, element):
16 | """
17 | Arg:
18 | element (selenium.webdriver.webelement)
19 | """
20 | self._load_locator(element)
21 | self.generate_locators()
22 | self.element = element
23 |
24 | def is_enabled(self):
25 | return self.element.is_enabled()
26 |
27 | def is_visible(self):
28 | return self.element.is_displayed()
29 |
30 | def is_disabled(self):
31 | return not self.is_enabled()
32 |
33 | def exists(self):
34 | return element_exists(self.element)()
35 |
36 | def text(self):
37 | """
38 | Getter for text within the element
39 | """
40 | return self.element.text
41 |
42 | def clear(self):
43 | """
44 | Clears the text if its a text entry element.
45 | """
46 | return self.element.clear()
47 |
48 | def click(self):
49 | """
50 | Clicks the element.
51 | """
52 | return self.element.click()
53 |
54 | def hover(self):
55 | """
56 | Hovers over an element.
57 | """
58 | # The react-bootstrap overlay trigger doesn't register the move_to_element unless there's
59 | # another mouse action ahead of time.
60 | ActionChains(self.element.parent).move_by_offset(10, 10).move_to_element(self.element).perform()
61 |
62 | def class_name(self):
63 | """
64 | Gets the given attribute or property of the element.
65 |
66 | Return:
67 | The class for the element
68 | """
69 | return self.attribute('class')
70 |
71 | def attribute(self, attribute):
72 | """
73 | Gets the given attribute or property of the element.
74 |
75 | Args:
76 | attribute: name of attribute (e.g. class)
77 | """
78 | return self.element.get_attribute(attribute)
79 |
80 | def when_present(self, timeout=DEFAULT_TIMEOUT):
81 | """
82 | Waits for element to be present on the current page.
83 |
84 | Args:
85 | timeout (integer): seconds to wait before throwing exception.
86 | """
87 | self.wait_until(element_predicate(self, 'exists'), timeout)
88 |
89 | def when_enabled(self, timeout=DEFAULT_TIMEOUT):
90 | """
91 | Waits for element to be enabled on the current page.
92 |
93 | Args:
94 | timeout (integer): seconds to wait before throwing exception.
95 | """
96 | self.wait_until(element_predicate(self, 'is_enabled'), timeout)
97 |
98 | def when_visible(self, timeout=DEFAULT_TIMEOUT):
99 | """
100 | Waits for element to be visible on the current page.
101 |
102 | Args:
103 | timeout (integer): seconds to wait before throwing exception.
104 | """
105 | self.wait_until(element_predicate(self, 'is_visible'), timeout)
106 |
107 | def wait_until(self, condtion, timeout=DEFAULT_TIMEOUT):
108 | wait = WebDriverWait(self.element, timeout=timeout, poll_frequency=0.1)
109 | wait.until(condtion)
110 |
111 | def send_keys(self, keys):
112 | """
113 | Simulates key entry into the element.
114 | """
115 | return self.element.send_keys(keys)
116 |
117 | def _load_locator(self, element):
118 | from page_object.locator import Locator
119 | self.locator = Locator(element)
120 |
--------------------------------------------------------------------------------
/src/page_object/locator.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from selenium.webdriver.common.by import By
3 | from selenium.common.exceptions import NoSuchElementException
4 | from page_object.elements import *
5 |
6 |
7 | class Locator(object):
8 | """
9 | Class to encapsulate all selenium selector behavior.
10 | """
11 |
12 | def __init__(self, target):
13 | self.target = target
14 |
15 | def select_list_for(self, identifier):
16 | return self.element_for(SelectList, identifier)
17 |
18 | def select_lists_for(self, identifier):
19 | return self.elements_for(SelectList, identifier)
20 |
21 | def option_for(self, identifier):
22 | return self.element_for(Option, identifier)
23 |
24 | def options_for(self, identifier):
25 | return self.elements_for(Option, identifier)
26 |
27 | def button_for(self, identifier):
28 | return self.element_for(Button, identifier)
29 |
30 | def buttons_for(self, identifier):
31 | return self.elements_for(Button, identifier)
32 |
33 | def image_for(self, identifier):
34 | return self.element_for(Image, identifier)
35 |
36 | def images_for(self, identifier):
37 | return self.elements_for(Image, identifier)
38 |
39 | def text_area_for(self, identifier):
40 | return self.element_for(TextArea, identifier)
41 |
42 | def text_areas_for(self, identifier):
43 | return self.elements_for(TextArea, identifier)
44 |
45 | def text_field_for(self, identifier):
46 | return self.element_for(TextField, identifier)
47 |
48 | def text_fields_for(self, identifier):
49 | return self.elements_for(TextField, identifier)
50 |
51 | def div_for(self, identifier):
52 | return self.element_for(Div, identifier)
53 |
54 | def divs_for(self, identifier):
55 | return self.elements_for(Div, identifier)
56 |
57 | def span_for(self, identifier):
58 | return self.element_for(Span, identifier)
59 |
60 | def spans_for(self, identifier):
61 | return self.elements_for(Span, identifier)
62 |
63 | def paragraph_for(self, identifier):
64 | return self.element_for(Paragraph, identifier)
65 |
66 | def paragraphs_for(self, identifier):
67 | return self.elements_for(Paragraph, identifier)
68 |
69 | def label_for(self, identifier):
70 | return self.element_for(Label, identifier)
71 |
72 | def labels_for(self, identifier):
73 | return self.elements_for(Label, identifier)
74 |
75 | def link_for(self, identifier):
76 | return self.element_for(Link, identifier)
77 |
78 | def links_for(self, identifier):
79 | return self.elements_for(Link, identifier)
80 |
81 | def heading_for(self, identifier):
82 | return self.element_for(Heading, identifier)
83 |
84 | def headings_for(self, identifier):
85 | return self.elements_for(Heading, identifier)
86 |
87 | def table_for(self, identifier):
88 | return self.element_for(Table, identifier)
89 |
90 | def tables_for(self, identifier):
91 | return self.elements_for(Table, identifier)
92 |
93 | def cells_for(self, identifier):
94 | return self.elements_for(TableCell, identifier)
95 |
96 | def cell_for(self, identifier):
97 | return self.element_for(TableCell, identifier)
98 |
99 | def rows_for(self, identifier):
100 | return self.elements_for(TableRow, identifier)
101 |
102 | def row_for(self, identifier):
103 | return self.element_for(TableRow, identifier)
104 |
105 | def radio_buttons_for(self, identifier):
106 | return self.elements_for(RadioButton, identifier)
107 |
108 | def radio_button_for(self, identifier):
109 | return self.element_for(RadioButton, identifier)
110 |
111 | def checkbox_for(self, identifier):
112 | return self.element_for(CheckBox, identifier)
113 |
114 | def checkboxs_for(self, identifier):
115 | return self.elements_for(CheckBox, identifier)
116 |
117 | def element_for(self, element_type, identifier):
118 | """
119 | Method to find a element through selenium
120 | Arg:
121 | element_type (page_object.Element): class to instantiate
122 | identifier (dict): how to identify element and what the value is
123 |
124 | Return:
125 | Returns list of element class for type
126 | """
127 | how, what = self._parse_identifier(identifier)
128 | try:
129 | element = self.target.find_element(how, what)
130 | except NoSuchElementException:
131 | return Element(SubstituteWebElement(self, identifier, element_type))
132 |
133 | return element_type(element)
134 |
135 | def elements_for(self, element_type, identifier):
136 | """
137 | Method to find all elements matching the identifier through selenium
138 |
139 | Arg:
140 | element_type (page_object.Element): class to instantiate
141 | identifier (dict): how to identify element and what the value is
142 |
143 | Return:
144 | Returns list of element class for type
145 | """
146 | how, what = self._parse_identifier(identifier)
147 | elements = self.target.find_elements(how, what)
148 | return list(map(lambda element: element_type(element), elements))
149 |
150 | def _parse_identifier(self, identifier):
151 | mapping = {
152 | 'css': By.CSS_SELECTOR,
153 | 'class': By.CLASS_NAME,
154 | 'tag': By.TAG_NAME,
155 | 'id': By.ID
156 | }
157 | key, value = identifier.copy().popitem()
158 | return mapping[key], value
159 |
--------------------------------------------------------------------------------
/features/support/html/static_elements.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Static Elements Page
4 |
5 |
6 | Static Elements Page
7 |
8 | Text Field
9 |
11 |
12 |
14 |
15 | Text Area
16 |
17 |
18 | Select List
19 |
20 | Test 1
21 | Test 2
22 | Test/Test 3
23 |
24 |
25 |
26 | Test 1
27 | Test 2
28 | Test 3
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
47 |
48 |
49 |
50 | Google Search
51 |
52 | Checkbox
53 |
54 |
55 | Radio
56 | Milk
57 | Butter
58 |
59 |
60 | Radio Button Group
61 | Cheddar
62 | Emmental
63 | Muenster
64 |
65 |
66 |
67 | page-object rocks!
68 |
69 |
70 |
71 | My alert
72 |
73 |
74 |
75 | page-object is the best!
76 |
77 |
78 |
79 |
80 |
81 | Table
82 | Header
83 |
84 |
85 |
86 |
87 | Data1
88 | Data2
89 |
90 |
91 | Data3
92 | Data4
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | Col1
101 | Col2
102 |
103 |
104 |
105 |
106 | Data1
107 | Data2
108 |
109 |
110 | Data3
111 | Data4
112 |
113 |
114 | Data5
115 |
116 |
117 |
118 |
119 |
125 |
126 |
127 |
128 |
130 |
131 | File Field
132 |
133 |
134 |
135 | Item One
136 | Item Two
137 | Item Three
138 |
139 |
140 |
141 | Number One
142 | Number Two
143 | Number Three
144 |
145 |
146 | Hello
147 | Hello
148 | Hello
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 | h1's are cool
157 | h2's are cool
158 | h3's are cool
159 | h4's are cool
160 | h5's are cool
161 | h6's are cool
162 | New Window
163 | Another New Window
164 |
165 |
166 |
167 |
168 |
171 |
172 | HTML 5 Article
173 |
174 |
175 |
176 |
177 | The summary
178 | The details
179 |
180 |
181 |
182 |
183 |
184 | This text have some text in bold
185 | This text have some text in italic
186 | This button is a button
187 |
188 |
189 |
190 |
--------------------------------------------------------------------------------
/src/page_object/accessors.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from page_object.locator_generator import LocatorGenerator
3 |
4 | class Accessors(object):
5 | """
6 | Contains the methods that are inserted into your page objects when you inherit from the PageObject class.
7 | These methods will generate accessor methods for specfic elements, to interact with elements.
8 |
9 | Args:
10 | name (string): The name you want to refer to the element as.
11 | identifier (dict): Method to identify the element.
12 |
13 | Example:
14 | self.table(name = "example", { 'css' : "[data-test-*='example']" })
15 |
16 | Will generate:
17 | - self.example_element()
18 | """
19 |
20 | def __init__(self):
21 | for tag in LocatorGenerator.ELEMENTS:
22 | setattr(self, "{0}s".format(tag), self._define_plural_accessor(tag))
23 |
24 | def page_url(self, url):
25 | def navigate_to(url_params):
26 | complete_url = url.format(base_url=self.base_url, **url_params)
27 | self.browser.get(complete_url)
28 |
29 | setattr(self, "goto", navigate_to)
30 |
31 | def table(self, name, identifier):
32 | """
33 | Will generate:
34 | "{name}_element": returns an element
35 | """
36 |
37 | self._standard_methods(name, identifier, 'table_for')
38 |
39 | def image(self, name, identifier):
40 | """
41 | Will generate:
42 | "{name}_element": returns an element
43 | """
44 | self._standard_methods(name, identifier, 'image_for')
45 |
46 | def button(self, name, identifier):
47 | """
48 | Will generate:
49 | "{name}_element": returns an element
50 | "{name}" : clicks the element
51 | """
52 | def click():
53 | return getattr(self, "{0}_element".format(name))().click()
54 |
55 | setattr(self, "{0}".format(name), click)
56 |
57 | self._standard_methods(name, identifier, 'button_for')
58 |
59 | def link(self, name, identifier):
60 | """
61 | Will generate:
62 | "{name}_element": returns an element
63 | "{name} : clicks the element
64 | """
65 |
66 | def click_method():
67 | return getattr(self, "{0}_element".format(name))().click()
68 |
69 | setattr(self, name, click_method)
70 |
71 | self._standard_methods(name, identifier, 'link_for')
72 |
73 | def text_area(self, name, identifier):
74 | """
75 | Will generate:
76 | "{name}_element": returns an element
77 | "set_{name} : sets text of element
78 | """
79 | def set_method(value):
80 | return getattr(self, "{0}_element".format(name))().set_value(value)
81 |
82 | setattr(self, "set_{0}".format(name), set_method)
83 |
84 | self._standard_methods(name, identifier, 'text_area_for')
85 |
86 | def text_field(self, name, identifier):
87 | """
88 | Will generate:
89 | "{name}_element": returns an element
90 | "set_{name} : sets text of element
91 | """
92 | def set_method(value):
93 | return getattr(self, "{0}_element".format(name))().set_value(value)
94 |
95 | def get_method():
96 | return getattr(self, "{0}_element".format(name))().get_value()
97 |
98 | setattr(self, "set_{0}".format(name), set_method)
99 | setattr(self, "{0}".format(name), get_method)
100 |
101 | self._standard_methods(name, identifier, 'text_field_for')
102 |
103 | def div(self, name, identifier):
104 | """
105 | Will generate:
106 | "{name}_element": returns an element
107 | "{name}: : returns text of element
108 | """
109 |
110 | def text():
111 | return getattr(self, "{0}_element".format(name))().text()
112 |
113 | setattr(self, "{0}".format(name), text)
114 |
115 | self._standard_methods(name, identifier, 'div_for')
116 |
117 | def span(self, name, identifier):
118 | """
119 | Will generate:
120 | "{name}_element": returns an element
121 | "{name}: : returns text of element
122 | """
123 | def text():
124 | return getattr(self, "{0}_element".format(name))().text()
125 |
126 | setattr(self, "{0}".format(name), text)
127 |
128 | self._standard_methods(name, identifier, 'span_for')
129 |
130 | def paragraph(self, name, identifier):
131 | """
132 | Will generate:
133 | "{name}_element": returns an element
134 | "{name}: : returns text of element
135 | """
136 | def text():
137 | return getattr(self, "{0}_element".format(name))().text()
138 |
139 | setattr(self, "{0}".format(name), text)
140 |
141 | self._standard_methods(name, identifier, 'paragraph_for')
142 |
143 | def label(self, name, identifier):
144 | """
145 | Will generate:
146 | "{name}_element": returns an element
147 | "{name}: : returns text of element
148 | """
149 | def text():
150 | return getattr(self, "{0}_element".format(name))().text()
151 |
152 | setattr(self, "{0}".format(name), text)
153 |
154 | self._standard_methods(name, identifier, 'label_for')
155 |
156 | def h1(self, name, identifier):
157 | """
158 | Will generate:
159 | "{name}_element": returns an element
160 | "{name}: : returns text of element
161 | """
162 |
163 | def text():
164 | return getattr(self, "{0}_element".format(name))().text()
165 |
166 | setattr(self, "{0}".format(name), text)
167 |
168 | self._standard_methods(name, identifier, 'heading_for')
169 |
170 | def h2(self, name, identifier):
171 | """
172 | Will generate:
173 | "{name}_element": returns an element
174 | "{name}: : returns text of element
175 | """
176 |
177 | def text():
178 | return getattr(self, "{0}_element".format(name))().text()
179 |
180 | setattr(self, "{0}".format(name), text)
181 |
182 | self._standard_methods(name, identifier, 'heading_for')
183 |
184 | def h3(self, name, identifier):
185 | """
186 | Will generate:
187 | "{name}_element": returns an element
188 | "{name}: : returns text of element
189 | """
190 |
191 | def text():
192 | return getattr(self, "{0}_element".format(name))().text()
193 |
194 | setattr(self, "{0}".format(name), text)
195 |
196 | self._standard_methods(name, identifier, 'heading_for')
197 |
198 | def h4(self, name, identifier):
199 | """
200 | Will generate:
201 | "{name}_element": returns an element
202 | "{name}: : returns text of element
203 | """
204 |
205 | def text():
206 | return getattr(self, "{0}_element".format(name))().text()
207 |
208 | setattr(self, "{0}".format(name), text)
209 |
210 | self._standard_methods(name, identifier, 'heading_for')
211 |
212 | def h5(self, name, identifier):
213 | """
214 | Will generate:
215 | "{name}_element": returns an element
216 | "{name}: : returns text of element
217 | """
218 |
219 | def text():
220 | return getattr(self, "{0}_element".format(name))().text()
221 |
222 | setattr(self, "{0}".format(name), text)
223 |
224 | self._standard_methods(name, identifier, 'heading_for')
225 |
226 | def h6(self, name, identifier):
227 | """
228 | Will generate:
229 | "{name}_element": returns an element
230 | "{name}: : returns text of element
231 | """
232 |
233 | def text():
234 | return getattr(self, "{0}_element".format(name))().text()
235 |
236 | setattr(self, "{0}".format(name), text)
237 |
238 | self._standard_methods(name, identifier, 'heading_for')
239 |
240 | def select_list(self, name, identifier):
241 | """
242 | Will generate:
243 | "{name}_element": returns an element
244 | "set_{name}" : sets the option by option text
245 | "{name}_options": returns options text as list of strings
246 | """
247 |
248 | def set_method(text):
249 | return getattr(self, "{0}_element".format(name))().select(text)
250 |
251 | def get_method():
252 | return getattr(self, "{0}_element".format(name))().text()
253 |
254 | def options():
255 | return getattr(self, "{0}_element".format(name))().options_text()
256 |
257 | setattr(self, "set_{0}".format(name), set_method)
258 | setattr(self, "get_{0}".format(name), get_method)
259 | setattr(self, "{0}_options".format(name), options)
260 |
261 | self._standard_methods(name, identifier, 'select_list_for')
262 |
263 | def checkbox(self, name, identifier):
264 | """
265 | Will generate:
266 | "{name}_element": returns an element
267 | "uncheck_{name}": to uncheck a checked checkbox
268 | "check_{name}" : to check an unchecked checkbox
269 | """
270 |
271 | def check():
272 | return getattr(self, "{0}_element".format(name))().check()
273 |
274 | def uncheck():
275 | return getattr(self, "{0}_element".format(name))().uncheck()
276 |
277 | def is_checked():
278 | return getattr(self, "{0}_element".format(name))().is_checked()
279 |
280 | setattr(self, "check_{0}".format(name), check)
281 | setattr(self, "uncheck_{0}".format(name), uncheck)
282 | setattr(self, "is_{0}_checked".format(name), is_checked)
283 |
284 | self._standard_methods(name, identifier, 'checkbox_for')
285 |
286 | def _define_plural_accessor(self, tag):
287 | def plural_accessor(name, identifier):
288 | def elements():
289 | return getattr(self.locator, "{0}s_for".format(tag))(identifier)
290 |
291 | setattr(self, "{0}_elements".format(name), elements)
292 |
293 | return plural_accessor
294 |
295 | def _standard_methods(self, name, identifier, method):
296 | def element():
297 | return getattr(self.locator, method)(identifier)
298 |
299 | setattr(self, "{}_element".format(name), element)
300 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------