3. Getting Started¶
3.1. Sample Usage¶
The following code is an example of a controller for Google’s search engine:
from pyscc import Controller, Component, component_element, \
component_elements, component_group
class Home(Component):
_ = 'body > app' # optional root selector to be applied to all component items
@component_element
def search_bar(self):
# we only need to return the selector
# pyscc will automatically determine whether it's xpath or css
return 'input#lst-ib'
@component_element
def search_button(self):
# the element wrapper allows us to format our selectors
# ref.search_button.fmt(value='Google Search')
return '//input[@type="submit"][@value="{value}"]'
class Results(Component):
@component_elements
def results(self):
return 'div.g'
class Google(Controller):
def __init__(self, browser, base_url):
super(Product, self).__init__(browser, base_url, {
'home': Home,
'results': Results
})
def search(self, query, redirect=False):
# navigate to base url
self.navigate('/')
# create reference to our home component
home = self.components.home
# wait 5 seconds for search bar to be visible
# write query in the search bar
home.search_bar\
.wait_visible(5, error=True)\
.send_input(query)
# target the search button with the text "Google Search"
# click on the search button
home.search_button\
.fmt('Google Search')\
.click()
def number_of_results():
return self.components.results.results.count()
The sample above can be utilized like so:
from selenium import webdriver
google = Google(webdriver.Chrome(), 'https://google.com')
google.search('py-component-controller')
# ensure at least one result is available within 5 seconds
assert google.components.results.results\
.wait_for(5, length=1)
# terminate our controller's webdriver
google.exit()
3.2. Example Explained¶
As seen in the example above, components can be defined relatively quickly without any hassle. Simply import Component from pyscc and define your class. When specifying your component properties, the following decorators can be used to construct Element and Elements wrappers.
- @component_element: Expects a single css or xpath selector, will return an Element object when referenced.
- @component_elements: Expects a single css or xpath selector, will return an Elements object when referenced.
- @component_group: Expects a dictionary of element name and selector pairs, will return a resource with attributes relevant to your provided pairs returning Element objects when referenced.
Using the intended design pattern, Component instances should never be instantiated outside of the scope of the controller. When the controller is intantiated, it will take the provided component name pairs and automatically instantiate them in a components attribute.
3.3. Writing Tests¶
The pyscc framework works very well for creating scrapers and other automation tools, but it was designed with end to end testing in mind. Controllers were also designed to allow developers to easily export their work into client packages for larger suites. The following is an example as to how one may structure tests:
from project import Google
from selenium import webdriver
from unittest import TestCase
class GoogleBaseTest(TestCase):
def setUp(self):
self.google = Google(webdriver.Chrome(), 'https://google.com')
def tearDown(self):
self.google.exit()
...
class TestGoogleHome(GoogleBaseTest):
def test_search(self):
self.google.search('py-component-controller')
# ensure at least one result is available within 5 seconds
self.assertNotNone(self.google.components.results.results\
.wait_for(5, length=1))
def test_search_autocomplete(self):
home = self.google.components.home
home.search_bar\
.wait_visible(5, error='Google search bar was not visible')\
.click()\
.send_input("python")
# ensure autocomplete popup appears
self.assertEqual(home.get_attribute('aria-haspopup'))
As can be seen in the example above, our product logic is actually loosely coupled with the test. Our controller allows us to define shorthand functionality ie; search, but we can still directly access each individual component and their respective elements. The controller and component have also been designed to work on any webdriver across any platform (excluding mobile forks) using polyfills, so you can write your code once and provision it to run in any environment you please!