Skip to content

Commit

Permalink
Merge pull request #61 from ElSnoMan/synchronization
Browse files Browse the repository at this point in the history
Should classes
  • Loading branch information
ElSnoMan authored Apr 5, 2020
2 parents a26f603 + 7a55e84 commit 775ecff
Show file tree
Hide file tree
Showing 9 changed files with 816 additions and 141 deletions.
22 changes: 0 additions & 22 deletions examples/script_sample.py

This file was deleted.

85 changes: 50 additions & 35 deletions examples/test_sample.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,54 @@
""" Use @pytest.fixtures when writing UI or end-to-end tests. """
""" Examples will be added to this directory and its files.
However, the best source for info and details is in the
official documentation here:
import pytest
from selenium.webdriver.common.keys import Keys
from pylenium import Pylenium


@pytest.fixture
def py_():
# Code before `yield` is executed Before Each test.
# By default, fixtures are mapped to each test.
# You can change the scope by using:
# `@pytest.fixture(scope=SCOPE)` where SCOPE is 'class', 'module' or 'session'
_py = Pylenium()

# Then `yield` or `return` the instance of Pylenium
yield _py

# Code after `yield` is executed After Each test.
# In this case, once the test is complete, quit the driver.
# This will be executed whether the test passed or failed.
_py.quit()
https://elsnoman.gitbook.io/pylenium
You can also contact the author, @CarlosKidman
on Twitter or LinkedIn.
"""

def test_using_fixture(py_):
""" You pass in the name of the fixture as seen on the line above.
This is the RECOMMENDED option when writing automated tests.
To find more info on PyTest and Fixtures, go to their docs:
https://docs.pytest.org/en/latest/fixture.html
* You can pass in any number of fixtures and fixtures can call other fixtures!
* You can store fixtures locally in test files or in conftest.py global files
"""
py_.visit('https://google.com')
py_.get('[name="q"]').type('puppies', Keys.ENTER)
assert 'puppies' in py_.title
# You can mix Selenium into some Pylenium commands
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as ec


# pass in the `py` fixture into your test function
# this _is_ Pylenium!
def test_pylenium_basics(py):
# Use Cypress-like commands like `.visit()`
py.visit('https://google.com')
# `.get()` uses CSS to locate a single element
py.get('[name="q"]').type('puppies', Keys.ENTER)
# `assert` followed by a boolean expression
assert 'puppies' in py.title


def test_access_selenium(py):
py.visit('https://google.com')
# access the wrapped WebDriver with `py.webdriver`
search_field = py.webdriver.find_element_by_css_selector('[name="q"]')
# access the wrapped WebElement with `Element.webelement`
assert py.get('[name"q"]').webelement.is_enabled()
# you can store elements and objects to be used later since
# we don't rely on Promises or chaining in Python
search_field.send_keys('puppies', Keys.ENTER)
assert 'puppies' in py.title


def test_chaining_commands(py):
py.visit('https://google.com').get('[name="q"]').type('puppies', Keys.ENTER)
assert 'puppies' in py.title


def test_waiting(py):
py.visit('https://google.com')
# wait using expected conditions
# default `.wait()` uses WebDriverWait which returns Selenium's WebElement objects
py.wait().until(ec.visibility_of_element_located((By.CSS_SELECTOR, '[name="q"]'))).send_keys('puppies')
# use_py=True to use a PyleniumWait which returns Pylenium's Element and Elements objects
py.wait(use_py=True).until(lambda _: py.get('[name="q"]')).type(Keys.ENTER)
# wait using lambda function
assert py.wait().until(lambda x: 'puppies' in x.title)
144 changes: 132 additions & 12 deletions pylenium/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import requests
from faker import Faker
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec

from pylenium import webdriver_factory
from pylenium.config import PyleniumConfig
Expand All @@ -14,6 +16,104 @@
from pylenium.wait import PyleniumWait


class PyleniumShould:
def __init__(self, py: 'Pylenium', timeout: int, ignored_exceptions: list = None):
self._py = py
self._wait: PyleniumWait = self._py.wait(timeout=timeout, use_py=True, ignored_exceptions=ignored_exceptions)

def have_title(self, title: str) -> 'Pylenium':
""" An expectation that the title matches the given title.
Args:
title: The title to match.
Returns:
The current instance of Pylenium.
Raises:
`AssertionError` if the condition is not met within the timeout.
"""
self._py.log.step('.should().have_title()', True)
try:
value = self._wait.until(ec.title_is(title))
except TimeoutException:
value = False
if value:
return self._py
else:
self._py.log.failed('.should().have_title()')
raise AssertionError(f'Title was not {title}, but was {self._py.title}')

def contain_title(self, string: str) -> 'Pylenium':
""" An expectation that the title contains the given string.
Args:
string: The case-sensitive string for the title to contain.
Returns:
The current instance of Pylenium.
Raises:
`AssertionError` if the condition is not met within the timeout.
"""
self._py.log.step('.should().contain_title()', True)
try:
value = self._wait.until(ec.title_contains(string))
except TimeoutException:
value = False
if value:
return self._py
else:
self._py.log.failed('.should().contain_title()')
raise AssertionError(f'Title did not contain {string}, but was {self._py.title}')

def have_url(self, url: str) -> 'Pylenium':
""" An expectation that the URL matches the given url.
Args:
url: The url to match.
Returns:
The current instance of Pylenium.
Raises:
`AssertionError` if the condition is not met within the timeout.
"""
self._py.log.step('.should().have_url()', True)
try:
value = self._wait.until(ec.url_to_be(url))
except TimeoutException:
value = False
if value:
return self._py
else:
self._py.log.failed('.should().contain_title()')
raise AssertionError(f'URL was not {url}, but was {self._py.url}')

def contain_url(self, string: str) -> 'Pylenium':
""" An expectation that the URL contains the given string.
Args:
string: The case-sensitive string for the url to contain.
Returns:
The current instance of Pylenium.
Raises:
`AssertionError` if the condition is not met within the timeout.
"""
self._py.log.step('.should().contain_url()', True)
try:
value = self._wait.until(ec.url_contains(string))
except TimeoutException:
value = False
if value:
return self._py
else:
self._py.log.failed('.should().contain_url()', True)
raise AssertionError(f'URL did not contain {string}, but was {self._py.url}')


class Pylenium:
""" The Pylenium API.
Expand Down Expand Up @@ -122,11 +222,12 @@ def contains(self, text: str, timeout: int = 0) -> Element:
The first element that is found, even if multiple elements match the query.
"""
self.log.step(f'py.contains() - Find the element with text: ``{text}``')
locator = (By.XPATH, f'//*[contains(text(), "{text}")]')
element = self.wait(timeout).until(
lambda _: self._webdriver.find_element(By.XPATH, f'//*[contains(text(), "{text}")]'),
lambda x: x.find_element(*locator),
f'Could not find element with the text ``{text}``'
)
return Element(self, element)
return Element(self, element, locator)

def get(self, css: str, timeout: int = 0) -> Element:
""" Get the DOM element that matches the `css` selector.
Expand All @@ -139,11 +240,12 @@ def get(self, css: str, timeout: int = 0) -> Element:
The first element that is found, even if multiple elements match the query.
"""
self.log.step(f'py.get() - Find the element with css: ``{css}``')
by = By.CSS_SELECTOR
element = self.wait(timeout).until(
lambda _: self._webdriver.find_element(By.CSS_SELECTOR, css),
lambda x: x.find_element(by, css),
f'Could not find element with the CSS ``{css}``'
)
return Element(self, element)
return Element(self, element, locator=(by, css))

def find(self, css: str, at_least_one=True, timeout: int = 0) -> Elements:
""" Finds all DOM elements that match the `css` selector.
Expand All @@ -156,16 +258,17 @@ def find(self, css: str, at_least_one=True, timeout: int = 0) -> Elements:
Returns:
A list of the found elements.
"""
by = By.CSS_SELECTOR
if at_least_one:
self.log.step(f'py.find() - Find at least one element with css: ``{css}``')
elements = self.wait(timeout).until(
lambda _: self.webdriver.find_elements(By.CSS_SELECTOR, css),
lambda x: x.find_elements(by, css),
f'Could not find any elements with the CSS ``{css}``'
)
else:
self.log.action(f'py.find() - Find elements with css (no wait): ``{css}``')
elements = self.webdriver.find_elements(By.CSS_SELECTOR, css)
return Elements(self, elements)
self.log.step(f'py.find() - Find elements with css (no wait): ``{css}``')
elements = self.webdriver.find_elements(by, css)
return Elements(self, elements, locator=(by, css))

def xpath(self, xpath: str, at_least_one=True, timeout: int = 0) -> Union[Element, Elements]:
""" Finds all DOM elements that match the `xpath` selector.
Expand All @@ -178,22 +281,39 @@ def xpath(self, xpath: str, at_least_one=True, timeout: int = 0) -> Union[Elemen
Returns:
A list of the found elements. If only one is found, return that as Element.
"""
by = By.XPATH
if at_least_one:
self.log.step(f'py.xpath() - Find at least one element with xpath: ``{xpath}``')
elements = self.wait(timeout).until(
lambda _: self.webdriver.find_elements(By.XPATH, xpath),
lambda x: x.find_elements(by, xpath),
f'Could not find any elements with the CSS ``{xpath}``'
)
else:
self.log.step(f'py.xpath() - Find elements with xpath (no wait): ``{xpath}``')
elements = self.webdriver.find_elements(By.CSS_SELECTOR, xpath)
elements = self.webdriver.find_elements(by, xpath)

if len(elements) == 1:
self.log.info('Only 1 element matched your xpath')
return Element(self, elements[0])
return Element(self, elements[0], locator=(by, xpath))

self.log.info(f'{len(elements)} elements matched your xpath')
return Elements(self, elements)
return Elements(self, elements, locator=(by, xpath))

# EXPECTATIONS #
################

def should(self, timeout: int = 0, ignored_exceptions: list = None) -> PyleniumShould:
""" A collection of expectations for this driver.
Examples:
py.should().contain_title('QA at the Point')
py.should().have_url('https://qap.dev')
"""
if timeout:
wait_time = timeout
else:
wait_time = self.config.driver.wait_time
return PyleniumShould(self, wait_time, ignored_exceptions)

# UTILITIES #
#############
Expand Down
Loading

0 comments on commit 775ecff

Please sign in to comment.