diff --git a/README.md b/README.md index d75308e..6768e5c 100755 --- a/README.md +++ b/README.md @@ -778,6 +778,19 @@ which follows the example of Robot assertions and makes it obvious that the meth Page object assertion methods shouldn't change the state of the page (eg. clicking links, navigating back etc.) and minimal computation, looping etc. State change and computation should be done in page object action/helper methods. In your test, you should get the page to the state where you want it to be using other page object methods, and call the assert method. +## Sauce Labs Cloud Testing Service Integration + +robotframework-pageobjects integrates seamlessly with +[Sauce Labs](http://saucelabs.com/), a cloud service allowing you to run Selenium-based +jobs on a [multitude of browsers and platforms](https://docs.saucelabs.com/reference/platforms-configurator/#/). +Simply set at least the `sauce_apikey`, `sauce_username`, `sauce_platform` and the `browser` +built-in IFT options. See the Built-in options section [above](#built-in-options-for-page) for options +related to running tests in Sauce. + +Your page objects will automatically tag your Robot Sauce jobs with their +associated test names and +test status. + ## Logging Reporting & Debugging ### Robot diff --git a/requirements.txt b/requirements.txt index 0944fb5..c3116db 100755 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ decorator mock==1.0.1 requests==2.1.0 robotframework-selenium2library==1.6.0 +python-saucerest uritemplate==0.6 diff --git a/robotpageobjects/base.py b/robotpageobjects/base.py index 1b2c4fc..a7ef01c 100755 --- a/robotpageobjects/base.py +++ b/robotpageobjects/base.py @@ -16,6 +16,7 @@ from . import exceptions from .context import Context from .optionhandler import OptionHandler +import saucelabs.saucerest as saucerest class _Keywords(object): @@ -523,6 +524,69 @@ class _BaseActions(_S2LWrapper): """ _abstracted_logger = abstractedlogger.Logger() + ROBOT_LISTENER_API_VERSION = 2 + + # For keeping track if we've tagged a sauce + # job. + _session_to_test_hash = {} + + @property + def _sauce_job_registered(self): + return self.session_id in self._session_to_test_hash + + def _register_sauce_job(self): + self._session_to_test_hash[self.session_id] = self._current_test + + def _start_test(self, name, attrs): + self._current_test = name + + def _end_test(self, name, attrs): + self.log("Tag sauce job, %s with %s" %(self.session_id, attrs["status"])) + passed = attrs["status"] == "PASS" + self._saucerest.update_job(self.session_id, dict(passed=passed)) + + def _end_keyword(self, name, attrs): + """ Called after every keyword is called in Robot test. + We need to get the session ID here, and initialize sauce tagging + here because the browser must + first be open to get a session ID, and thus be able to tag a + sauce job. We can only be assured of this once we've called + a keyword that's ultimately called SE2Lib's Open Browser + keyword. + """ + session_id = None + + # Wait until we have a session ID + try: + session_id = self.session_id + + except AttributeError: + return + + # If we haven't tagged this job then, + # initialize a sauce rest object and tag. + if not self._sauce_job_registered: + self.log("Tag sauce job %s with %s" %(session_id, self._current_test)) + + # Have we initialized a saucerest object already? + try: + self._saucerest + except AttributeError: + self._saucerest = saucerest.SauceRest( + username=self.sauce_username, + password=self.sauce_apikey + ) + + # We should have a saucerest object by now + # so go ahead and tag the job with the name of + # the Robot test. + self._saucerest.update_job( + session_id, + dict(name=self._current_test) + ) + + # Sets the flag whether we've already tagged this job + self._register_sauce_job() def __init__(self, *args, **kwargs): """ @@ -532,6 +596,10 @@ def __init__(self, *args, **kwargs): #_SelectorsManager.__init__(self, *args, **kwargs) super(_BaseActions, self).__init__(*args, **kwargs) + # Make this library a listener so we can + # centralize robot listener hooks, esp. for + # sauce. + self.ROBOT_LIBRARY_LISTENER = self self._option_handler = OptionHandler() self._is_robot = Context.in_robot() self.selenium_speed = self._option_handler.get("selenium_speed") or 0 diff --git a/tests/README.md b/tests/README.md index 4d9a8a9..47a70fe 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,6 +6,7 @@ scenarios directory. In general each functional test is executed both in the Rob unittest context. Functional tests are found in functional.py. They run actual browsers. Unittests are found in unit .py and test simple inputs and outputs of critical page object methods and helpers. +- Install nose with `$ pip install nose`, then run tests: $ nosetests -vs tests/test_unit.py tests/test_functional.py`. - The `scenarios` directory contains the unittests and robot tests which the page object framework tests call in a subprocess. - The `site` directory contains the sample site under test