From 1091b4df2ebea5f44bac1919d2a021959eccac14 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Fri, 28 Oct 2016 10:05:01 -0700 Subject: [PATCH 01/32] Changes to allow any remote URL to be passed in for grid, not just credentials to Sauce Labs. --- robotpageobjects/page.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index fb0e51b..8b7b88f 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -137,8 +137,10 @@ def __init__(self): for base in Page.__bases__: base.__init__(self) + self.browser = self._option_handler.get("browser") or "phantomjs" self.service_args = self._parse_service_args(self._option_handler.get("service_args", "")) + self.remote_url = self._option_handler.get("remote_url") self._sauce_options = [ "sauce_username", @@ -153,6 +155,12 @@ def __init__(self): self._attempt_sauce = self._validate_sauce_options() + self._Capabilities = getattr(webdriver.DesiredCapabilities, self.browser.upper()) + for cap in self._Capabilities: + new_cap = self._option_handler.get(cap) + if new_cap is not None: + self._Capabilities[cap] = new_cap + # There's only a session ID when using a remote webdriver (Sauce, for example) self.session_id = None @@ -560,6 +568,8 @@ class MyPageObject(PageObject): :type delete_cookies: Boolean :returns: _BaseActions instance """ + caps = None + remote_url = False resolved_url = self._resolve_url(*args) if self._attempt_sauce: remote_url = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub" % (self.sauce_username, self.sauce_apikey) @@ -572,6 +582,10 @@ class MyPageObject(PageObject): if self.sauce_screenresolution: caps["screenResolution"] = self.sauce_screenresolution + if self.remote_url is not None: + remote_url = self.remote_url + caps = self._Capabilities + try: self.open_browser(resolved_url, self.browser, remote_url=remote_url, desired_capabilities=caps) except (urllib2.HTTPError, WebDriverException, ValueError), e: From 0fb3b2ce108798888b57c615adf4ac20a02b1329 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Mon, 28 Nov 2016 13:19:30 -0800 Subject: [PATCH 02/32] Notes and a tiny bit of code to support self starting on use of this library. --- QUICKSTART.md | 26 ++++++++++++++++++++++++++ common.robot | 29 +++++++++++++++++++++++++++++ open_capture.robot | 11 +++++++++++ requirements.txt | 7 ++----- 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 QUICKSTART.md create mode 100644 common.robot create mode 100644 open_capture.robot diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..5f4b3c1 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,26 @@ +# Robot Framework Page Objects - Quick Start + + +## Installation and Setup +Clone this repository to a Linux system, open a terminal in the cloned directory +Verify python is available +Using pip, install from the requirement.txt file in this directory with: +`$ pip install -r requirements.txt` +Find and download [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads), make sure it is in your path: +`$ which chromedriver` +`/home/aaronpa/chromedriver/chromedriver` +## Running an example +`$ pybot -v baseurl:http://www.conversica.com open_capture.robot` +## Video of Setup and Running +[Screencast video demo](http://screencast.com/t/jhf74SbtYv5) - Note: browser activity not picked up by screen capture, but can be seen live instead of white canvas areas seen in this screen capture. + +### What the example does +Starting with the open_capture.robot file, a resource file is read, common.robot. In that file some external library references are defined, as well as the browser type, and browser width and height. The pybot python robot test runner provides for these variables to be overridden via command line - the width of the test run can be changed for each run. + +Once the test starts, the browser is opened to the value found in the `baseurl` variable. Selenium will wait for the page to load entirely before proceeding. + +The `Startup` and `Shutdown` keywords are defined in common.robot, but not executed. They are called via the Suite Setup and Suite Teardown settings at the stop of open_capture.robot. + +After the suite has started and the browser is open and on the intended target, the test logic can begin. In this example test, all links are scraped off of the page using a CSS locator expression. They are then logged from a FOR loop so that the href target for each link is written out. This example test has no assertions and is intended to simply demonstrate the supporting software. + + \ No newline at end of file diff --git a/common.robot b/common.robot new file mode 100644 index 0000000..6381cd3 --- /dev/null +++ b/common.robot @@ -0,0 +1,29 @@ +*** Settings *** +Library Selenium2Library +Library robotpageobjects.Page +Library uuid + +*** Variables *** +${width} 1024 +${height} 768 +${browser} chrome + +*** Keywords *** +Save Selenium Screenshot + [documentation] Make sure there is a unique name to prevent overwriting + ${screenshot_index}= Get Variable Value ${screenshot_index} ${0} + Set Global Variable ${screenshot_index} ${screenshot_index.__add__(1)} + ${time}= Evaluate str(time.time()) time + Capture Page Screenshot selenium-screenshot-${time}-${screenshot_index}.png + +Startup + [documentation] Initial work to be completed in suite startup + Register Keyword To Run On Failure Save Selenium Screenshot + Open ${baseurl} + Set Window Size ${width} ${height} + +Shutdown + [documentation] Final record keeping, cleanup + Capture Page Screenshot end.png + Close + diff --git a/open_capture.robot b/open_capture.robot new file mode 100644 index 0000000..f9f8545 --- /dev/null +++ b/open_capture.robot @@ -0,0 +1,11 @@ +*** Settings *** +Resource common.robot +Suite Setup Startup +Suite Teardown Shutdown +Force Tags quickstart + +*** Test Cases *** +Sample Test + ${links}= Get Web Elements css=a + :FOR ${some link} IN @{links} + \ Log ${some link.get_attribute('href')} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index fb94389..f6df82f 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,2 @@ -decorator -mock==1.0.1 -requests==2.1.0 -robotframework-selenium2library==1.7.2 -uritemplate==0.6 +robotframework-selenium2library +robotframework-pageobjects From bd2af3b6ff818e88e85c9856938203e020573d5b Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Mon, 28 Nov 2016 13:25:58 -0800 Subject: [PATCH 03/32] Formatting, typos --- QUICKSTART.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/QUICKSTART.md b/QUICKSTART.md index 5f4b3c1..4332c98 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -3,14 +3,19 @@ ## Installation and Setup Clone this repository to a Linux system, open a terminal in the cloned directory -Verify python is available + +Verify python is available. + Using pip, install from the requirement.txt file in this directory with: `$ pip install -r requirements.txt` + Find and download [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads), make sure it is in your path: `$ which chromedriver` `/home/aaronpa/chromedriver/chromedriver` + ## Running an example `$ pybot -v baseurl:http://www.conversica.com open_capture.robot` + ## Video of Setup and Running [Screencast video demo](http://screencast.com/t/jhf74SbtYv5) - Note: browser activity not picked up by screen capture, but can be seen live instead of white canvas areas seen in this screen capture. From cba9d64bb5716ee141105ee6513c400aeb85dc1b Mon Sep 17 00:00:00 2001 From: conversica-aaronpa Date: Mon, 28 Nov 2016 13:27:41 -0800 Subject: [PATCH 04/32] More typo --- QUICKSTART.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QUICKSTART.md b/QUICKSTART.md index 4332c98..c40d97e 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -6,7 +6,7 @@ Clone this repository to a Linux system, open a terminal in the cloned directory Verify python is available. -Using pip, install from the requirement.txt file in this directory with: +Using pip, install from the requirements.txt file in this directory with: `$ pip install -r requirements.txt` Find and download [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads), make sure it is in your path: @@ -28,4 +28,4 @@ The `Startup` and `Shutdown` keywords are defined in common.robot, but not execu After the suite has started and the browser is open and on the intended target, the test logic can begin. In this example test, all links are scraped off of the page using a CSS locator expression. They are then logged from a FOR loop so that the href target for each link is written out. This example test has no assertions and is intended to simply demonstrate the supporting software. - \ No newline at end of file + From b3d769877480004744adf84b6e4fc4cfbb0ada51 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Tue, 29 Nov 2016 09:31:38 -0800 Subject: [PATCH 05/32] Now remote_url parameter now does not require sauce parameters, unless it contains "saucelabs.com", then they are required. Allows remote_url to be used alone for local grid testing. --- robotpageobjects/page.py | 50 ++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 8b7b88f..90c74bc 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -128,6 +128,8 @@ class Page(_BaseActions, _SelectorsManager, _ComponentsManager): """ __metaclass__ = _PageMeta ROBOT_LIBRARY_SCOPE = 'TEST SUITE' + _attempt_sauce = False + _attempt_remote = False def __init__(self): """ @@ -142,18 +144,21 @@ def __init__(self): self.service_args = self._parse_service_args(self._option_handler.get("service_args", "")) self.remote_url = self._option_handler.get("remote_url") - self._sauce_options = [ - "sauce_username", - "sauce_apikey", - "sauce_platform", - "sauce_browserversion", - "sauce_device_orientation", - "sauce_screenresolution", - ] - for sauce_opt in self._sauce_options: - setattr(self, sauce_opt, self._option_handler.get(sauce_opt)) - - self._attempt_sauce = self._validate_sauce_options() + if self.remote_url.find('saucelabs.com') > -1: + self._sauce_options = [ + "sauce_username", + "sauce_apikey", + "sauce_platform", + "sauce_browserversion", + "sauce_device_orientation", + "sauce_screenresolution", + ] + for sauce_opt in self._sauce_options: + setattr(self, sauce_opt, self._option_handler.get(sauce_opt)) + + self._attempt_sauce = self._validate_sauce_options() + else: + self._attempt_remote = True self._Capabilities = getattr(webdriver.DesiredCapabilities, self.browser.upper()) for cap in self._Capabilities: @@ -571,16 +576,17 @@ class MyPageObject(PageObject): caps = None remote_url = False resolved_url = self._resolve_url(*args) - if self._attempt_sauce: - remote_url = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub" % (self.sauce_username, self.sauce_apikey) - caps = getattr(webdriver.DesiredCapabilities, self.browser.upper()) - caps["platform"] = self.sauce_platform - if self.sauce_browserversion: - caps["version"] = self.sauce_browserversion - if self.sauce_device_orientation: - caps["device_orientation"] = self.sauce_device_orientation - if self.sauce_screenresolution: - caps["screenResolution"] = self.sauce_screenresolution + if self._attempt_sauce | self._attempt_remote: + if self._attempt_sauce: + self.remote_url = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub" % (self.sauce_username, self.sauce_apikey) + caps = getattr(webdriver.DesiredCapabilities, self.browser.upper()) + caps["platform"] = self.sauce_platform + if self.sauce_browserversion: + caps["version"] = self.sauce_browserversion + if self.sauce_device_orientation: + caps["device_orientation"] = self.sauce_device_orientation + if self.sauce_screenresolution: + caps["screenResolution"] = self.sauce_screenresolution if self.remote_url is not None: remote_url = self.remote_url From 4cf678086e2eb28493e9038f6fc814ac9a4df098 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Mon, 24 Apr 2017 11:13:32 -0700 Subject: [PATCH 06/32] Addition of some default settings via DesiredCapabilities for Chrome, formatting changes. --- robotpageobjects/page.py | 51 +++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 90c74bc..806f5c6 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -19,22 +19,23 @@ """ from __future__ import print_function + import inspect import re import urllib2 import decorator +import uritemplate from Selenium2Library import Selenium2Library from selenium import webdriver from selenium.common.exceptions import WebDriverException -import uritemplate +from selenium.webdriver.chrome.options import Options as ChromeOptions -from .base import _ComponentsManagerMeta, not_keyword, robot_alias, _BaseActions, _Keywords, Override, _SelectorsManager, _ComponentsManager from . import exceptions +from .base import _ComponentsManagerMeta, not_keyword, _BaseActions, _Keywords, _SelectorsManager, _ComponentsManager from .context import Context from .sig import get_method_sig - # determine if libdoc is running to avoid generating docs for automatically generated aliases ld = 'libdoc' in_ld = any([ld in str(x) for x in inspect.stack()]) @@ -139,26 +140,26 @@ def __init__(self): for base in Page.__bases__: base.__init__(self) - self.browser = self._option_handler.get("browser") or "phantomjs" self.service_args = self._parse_service_args(self._option_handler.get("service_args", "")) self.remote_url = self._option_handler.get("remote_url") - if self.remote_url.find('saucelabs.com') > -1: - self._sauce_options = [ - "sauce_username", - "sauce_apikey", - "sauce_platform", - "sauce_browserversion", - "sauce_device_orientation", - "sauce_screenresolution", - ] - for sauce_opt in self._sauce_options: - setattr(self, sauce_opt, self._option_handler.get(sauce_opt)) - - self._attempt_sauce = self._validate_sauce_options() - else: - self._attempt_remote = True + if self.remote_url != None: + if self.remote_url.find('saucelabs.com') > -1: + self._sauce_options = [ + "sauce_username", + "sauce_apikey", + "sauce_platform", + "sauce_browserversion", + "sauce_device_orientation", + "sauce_screenresolution", + ] + for sauce_opt in self._sauce_options: + setattr(self, sauce_opt, self._option_handler.get(sauce_opt)) + + self._attempt_sauce = self._validate_sauce_options() + else: + self._attempt_remote = True self._Capabilities = getattr(webdriver.DesiredCapabilities, self.browser.upper()) for cap in self._Capabilities: @@ -166,6 +167,17 @@ def __init__(self): if new_cap is not None: self._Capabilities[cap] = new_cap + if self.browser == "chrome": + opts = ChromeOptions() + opts.add_argument("--disable-infobars") + opts.add_argument("--disable-popups") + opts.add_argument("--disable-save-password-bubble") + opts.add_argument("--disable-extensions") + opts.add_experimental_option('prefs', {'credentials_enable_service': False, + 'profile': {'password_manager_enabled': False}}) + opts.add_experimental_option("excludeSwitches", ["enable-automation"]) + self._Capabilities.update(opts.to_capabilities()) + # There's only a session ID when using a remote webdriver (Sauce, for example) self.session_id = None @@ -573,6 +585,7 @@ class MyPageObject(PageObject): :type delete_cookies: Boolean :returns: _BaseActions instance """ + caps = None remote_url = False resolved_url = self._resolve_url(*args) From 4d1f54184a3ff548e08d4f473952a523bd3f4b94 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Fri, 28 Apr 2017 21:31:45 -0700 Subject: [PATCH 07/32] Better way to increase node count is via docker-compose command line options --- robotpageobjects/page.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 806f5c6..21608e0 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -608,17 +608,20 @@ class MyPageObject(PageObject): try: self.open_browser(resolved_url, self.browser, remote_url=remote_url, desired_capabilities=caps) except (urllib2.HTTPError, WebDriverException, ValueError), e: - raise exceptions.SauceConnectionError("Unable to run Sauce job.\n%s\n" - "Sauce variables were:\n" - "sauce_platform: %s\n" - "sauce_browserversion: %s\n" - "sauce_device_orientation: %s\n" - "sauce_screenresolution: %s" - - % (str(e), self.sauce_platform, - self.sauce_browserversion, self.sauce_device_orientation, - self.sauce_screenresolution) - ) + if self._attempt_sauce: + raise exceptions.SauceConnectionError("Unable to run Sauce job.\n%s\n" + "Sauce variables were:\n" + "sauce_platform: %s\n" + "sauce_browserversion: %s\n" + "sauce_device_orientation: %s\n" + "sauce_screenresolution: %s" + + % (str(e), self.sauce_platform, + self.sauce_browserversion, self.sauce_device_orientation, + self.sauce_screenresolution) + ) + else: + raise e self.session_id = self.get_current_browser().session_id self.log("session ID: %s" % self.session_id) From 51efefb09d8444d22ce809fed7daaba5bfd579ef Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Wed, 31 May 2017 15:44:27 -0700 Subject: [PATCH 08/32] More data in Sauce console to make it easier to match runs --- robotpageobjects/page.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 21608e0..74e046f 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -143,6 +143,7 @@ def __init__(self): self.browser = self._option_handler.get("browser") or "phantomjs" self.service_args = self._parse_service_args(self._option_handler.get("service_args", "")) self.remote_url = self._option_handler.get("remote_url") + self.rest_url = None if self.remote_url != None: if self.remote_url.find('saucelabs.com') > -1: @@ -600,6 +601,7 @@ class MyPageObject(PageObject): caps["device_orientation"] = self.sauce_device_orientation if self.sauce_screenresolution: caps["screenResolution"] = self.sauce_screenresolution + caps["name"] = self._option_handler.get('suite_name') if self.remote_url is not None: remote_url = self.remote_url @@ -607,6 +609,11 @@ class MyPageObject(PageObject): try: self.open_browser(resolved_url, self.browser, remote_url=remote_url, desired_capabilities=caps) + if self._attempt_sauce: + # username, apikey = self.get_sauce_creds() + self.rest_url = "https://%s:%s@saucelabs.com/rest/v1/%s/jobs/%s" \ + % (self.sauce_username, self.sauce_apikey, self.sauce_username, self.driver.session_id) + except (urllib2.HTTPError, WebDriverException, ValueError), e: if self._attempt_sauce: raise exceptions.SauceConnectionError("Unable to run Sauce job.\n%s\n" @@ -638,5 +645,23 @@ def close(self): Wrapper for Selenium2Library's close_browser. :returns: None """ + self._report_sauce_status(self._option_handler.get('suite_name'), 'PASS', 'page.py,close', self.rest_url) self.close_browser() return self + + def _report_sauce_status(self, name, status, tags=[], rest_url=None): + # Parse username and access_key from the remote_url + import requests + import json + payload = {'name': name, + 'passed': status == 'PASS', + 'tags': tags} + + response = requests.put(rest_url, data=json.dumps(payload)) + assert response.status_code == 200, response.text + + # Log video url from the response + video_url = json.loads(response.text).get('video_url') + if video_url: + self.log('video.flv'.format(video_url)) + From e5e9dcdb0e8f4245b06466a01d07132d6d6ddf43 Mon Sep 17 00:00:00 2001 From: conversica-aaronpa Date: Wed, 31 May 2017 16:32:19 -0700 Subject: [PATCH 09/32] Revert "Sauce title status" --- robotpageobjects/page.py | 50 +++++++++------------------------------- 1 file changed, 11 insertions(+), 39 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 74e046f..806f5c6 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -143,7 +143,6 @@ def __init__(self): self.browser = self._option_handler.get("browser") or "phantomjs" self.service_args = self._parse_service_args(self._option_handler.get("service_args", "")) self.remote_url = self._option_handler.get("remote_url") - self.rest_url = None if self.remote_url != None: if self.remote_url.find('saucelabs.com') > -1: @@ -601,7 +600,6 @@ class MyPageObject(PageObject): caps["device_orientation"] = self.sauce_device_orientation if self.sauce_screenresolution: caps["screenResolution"] = self.sauce_screenresolution - caps["name"] = self._option_handler.get('suite_name') if self.remote_url is not None: remote_url = self.remote_url @@ -609,26 +607,18 @@ class MyPageObject(PageObject): try: self.open_browser(resolved_url, self.browser, remote_url=remote_url, desired_capabilities=caps) - if self._attempt_sauce: - # username, apikey = self.get_sauce_creds() - self.rest_url = "https://%s:%s@saucelabs.com/rest/v1/%s/jobs/%s" \ - % (self.sauce_username, self.sauce_apikey, self.sauce_username, self.driver.session_id) - except (urllib2.HTTPError, WebDriverException, ValueError), e: - if self._attempt_sauce: - raise exceptions.SauceConnectionError("Unable to run Sauce job.\n%s\n" - "Sauce variables were:\n" - "sauce_platform: %s\n" - "sauce_browserversion: %s\n" - "sauce_device_orientation: %s\n" - "sauce_screenresolution: %s" - - % (str(e), self.sauce_platform, - self.sauce_browserversion, self.sauce_device_orientation, - self.sauce_screenresolution) - ) - else: - raise e + raise exceptions.SauceConnectionError("Unable to run Sauce job.\n%s\n" + "Sauce variables were:\n" + "sauce_platform: %s\n" + "sauce_browserversion: %s\n" + "sauce_device_orientation: %s\n" + "sauce_screenresolution: %s" + + % (str(e), self.sauce_platform, + self.sauce_browserversion, self.sauce_device_orientation, + self.sauce_screenresolution) + ) self.session_id = self.get_current_browser().session_id self.log("session ID: %s" % self.session_id) @@ -645,23 +635,5 @@ def close(self): Wrapper for Selenium2Library's close_browser. :returns: None """ - self._report_sauce_status(self._option_handler.get('suite_name'), 'PASS', 'page.py,close', self.rest_url) self.close_browser() return self - - def _report_sauce_status(self, name, status, tags=[], rest_url=None): - # Parse username and access_key from the remote_url - import requests - import json - payload = {'name': name, - 'passed': status == 'PASS', - 'tags': tags} - - response = requests.put(rest_url, data=json.dumps(payload)) - assert response.status_code == 200, response.text - - # Log video url from the response - video_url = json.loads(response.text).get('video_url') - if video_url: - self.log('video.flv'.format(video_url)) - From 845e7e942d33b433df0287b0ca923be2f655966c Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Fri, 2 Jun 2017 11:42:38 -0700 Subject: [PATCH 10/32] Changes to get correct suite status into Sauce console --- robotpageobjects/page.py | 54 ++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 806f5c6..dea59a0 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -21,10 +21,12 @@ from __future__ import print_function import inspect +import json import re import urllib2 import decorator +import requests import uritemplate from Selenium2Library import Selenium2Library from selenium import webdriver @@ -143,6 +145,7 @@ def __init__(self): self.browser = self._option_handler.get("browser") or "phantomjs" self.service_args = self._parse_service_args(self._option_handler.get("service_args", "")) self.remote_url = self._option_handler.get("remote_url") + self.rest_url = None if self.remote_url != None: if self.remote_url.find('saucelabs.com') > -1: @@ -600,6 +603,7 @@ class MyPageObject(PageObject): caps["device_orientation"] = self.sauce_device_orientation if self.sauce_screenresolution: caps["screenResolution"] = self.sauce_screenresolution + caps["name"] = self._option_handler.get('suite_name') if self.remote_url is not None: remote_url = self.remote_url @@ -607,18 +611,26 @@ class MyPageObject(PageObject): try: self.open_browser(resolved_url, self.browser, remote_url=remote_url, desired_capabilities=caps) + if self._attempt_sauce: + # username, apikey = self.get_sauce_creds() + self.rest_url = "https://%s:%s@saucelabs.com/rest/v1/%s/jobs/%s" \ + % (self.sauce_username, self.sauce_apikey, self.sauce_username, self.driver.session_id) + except (urllib2.HTTPError, WebDriverException, ValueError), e: - raise exceptions.SauceConnectionError("Unable to run Sauce job.\n%s\n" - "Sauce variables were:\n" - "sauce_platform: %s\n" - "sauce_browserversion: %s\n" - "sauce_device_orientation: %s\n" - "sauce_screenresolution: %s" - - % (str(e), self.sauce_platform, - self.sauce_browserversion, self.sauce_device_orientation, - self.sauce_screenresolution) - ) + if self._attempt_sauce: + raise exceptions.SauceConnectionError("Unable to run Sauce job.\n%s\n" + "Sauce variables were:\n" + "sauce_platform: %s\n" + "sauce_browserversion: %s\n" + "sauce_device_orientation: %s\n" + "sauce_screenresolution: %s" + + % (str(e), self.sauce_platform, + self.sauce_browserversion, self.sauce_device_orientation, + self.sauce_screenresolution) + ) + else: + raise e self.session_id = self.get_current_browser().session_id self.log("session ID: %s" % self.session_id) @@ -635,5 +647,25 @@ def close(self): Wrapper for Selenium2Library's close_browser. :returns: None """ + if self._attempt_sauce: + self._report_sauce_status(self._option_handler.get('suite_name'), + self._option_handler.get('suite status'), + ['test-tag', 'page.py', 'close'], + self.rest_url) self.close_browser() return self + + def _report_sauce_status(self, name, status, tags=[], rest_url=None): + # Parse username and access_key from the remote_url + payload = {'name': name, + 'passed': status == 'PASS', + 'tags': tags} + + response = requests.put(rest_url, data=json.dumps(payload)) + assert response.status_code == 200, response.text + + # Log video url from the response + video_url = json.loads(response.text).get('video_url') + if video_url: + self.log('video.flv'.format(video_url)) + From 160ab2bbb91a5ee5131e91a5566aeba441e90ab1 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Tue, 13 Jun 2017 13:57:24 -0700 Subject: [PATCH 11/32] Removing much of reliance on implicit wait, some code will now require explicit waits --- robotpageobjects/base.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/robotpageobjects/base.py b/robotpageobjects/base.py index ba4b11b..04cb154 100755 --- a/robotpageobjects/base.py +++ b/robotpageobjects/base.py @@ -540,7 +540,7 @@ class _BaseActions(_S2LWrapper): _abstracted_logger = abstractedlogger.Logger() def __init__(self, *args, **kwargs): - """ + self.class__ = """ Initializes the options used by the actions defined in this class. """ #_ComponentsManager.__init__(self, *args, **kwargs) @@ -553,8 +553,6 @@ def __init__(self, *args, **kwargs): self.set_selenium_speed(self.selenium_speed) siw_opt = self._option_handler.get("selenium_implicit_wait") self.selenium_implicit_wait = siw_opt if siw_opt is not None else 10 - self.set_selenium_implicit_wait(self.selenium_implicit_wait) - self.set_selenium_timeout(self.selenium_implicit_wait) self.baseurl = self._option_handler.get("baseurl") @@ -710,16 +708,10 @@ def _element_find(self, locator, *args, **kwargs): if isinstance(locator, WebElement): return locator - our_wait = self.selenium_implicit_wait if kwargs.get("wait") is None else kwargs["wait"] - # If wait is set, don't pass it along to the super classe's implementation, since it has none. if "wait" in kwargs: del kwargs["wait"] - - self.driver.implicitly_wait(our_wait) - - if locator in self.selectors: locator = self.resolve_selector(locator) @@ -732,8 +724,6 @@ def _element_find(self, locator, *args, **kwargs): "\"%s\" is not a valid locator. If this is a selector name, make sure it is spelled correctly." % locator) else: raise - finally: - self.driver.implicitly_wait(self.selenium_implicit_wait) @not_keyword def find_element(self, locator, required=True, wait=None, **kwargs): From c0cde1ba59004a8bc8dbba5a6a9234f61fd1dc36 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Fri, 23 Jun 2017 09:13:01 -0700 Subject: [PATCH 12/32] Adding basic support for Applitools Eyes --- robotpageobjects/page.py | 46 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index dea59a0..b1f51be 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -24,11 +24,14 @@ import json import re import urllib2 +from uuid import uuid4 import decorator import requests import uritemplate from Selenium2Library import Selenium2Library +from applitools.eyes import Eyes +from applitools.eyes import StitchMode from selenium import webdriver from selenium.common.exceptions import WebDriverException from selenium.webdriver.chrome.options import Options as ChromeOptions @@ -133,6 +136,8 @@ class Page(_BaseActions, _SelectorsManager, _ComponentsManager): ROBOT_LIBRARY_SCOPE = 'TEST SUITE' _attempt_sauce = False _attempt_remote = False + _attempt_eyes = False + eyes = Eyes() def __init__(self): """ @@ -145,6 +150,10 @@ def __init__(self): self.browser = self._option_handler.get("browser") or "phantomjs" self.service_args = self._parse_service_args(self._option_handler.get("service_args", "")) self.remote_url = self._option_handler.get("remote_url") + self.eyes_apikey = self._option_handler.get("eyes_apikey") + self.eyes_name = self._option_handler.get("eyes_name") + self.suite_name = self._option_handler.get('suite_name') + self.rest_url = None if self.remote_url != None: @@ -164,6 +173,9 @@ def __init__(self): else: self._attempt_remote = True + if self.eyes_apikey != None: + self._attempt_eyes = True + self._Capabilities = getattr(webdriver.DesiredCapabilities, self.browser.upper()) for cap in self._Capabilities: new_cap = self._option_handler.get(cap) @@ -178,7 +190,6 @@ def __init__(self): opts.add_argument("--disable-extensions") opts.add_experimental_option('prefs', {'credentials_enable_service': False, 'profile': {'password_manager_enabled': False}}) - opts.add_experimental_option("excludeSwitches", ["enable-automation"]) self._Capabilities.update(opts.to_capabilities()) # There's only a session ID when using a remote webdriver (Sauce, for example) @@ -532,6 +543,20 @@ def go_to(self, *args): super(_BaseActions, self).go_to(resolved_url) return self + def set_window_size(self, width, height, *args): + """ + Wrapper to make set_window_size method support applitools eyes resize. + """ + resolved_url = self._resolve_url(*args) + super(_BaseActions, self).set_window_size(width, height, *args) + if self._attempt_eyes: + self.eyes.set_viewport_size( + super(_BaseActions, self).driver + ,viewport_size={'width': int(width), 'height': int(height)}) + else: + super(_BaseActions, self).set_window_size(width, height, *args) + return self + def _generic_make_browser(self, webdriver_type, desired_cap_type, remote_url, desired_caps): """Override Selenium2Library's _generic_make_browser to allow for extra params to driver constructor.""" @@ -592,6 +617,7 @@ class MyPageObject(PageObject): caps = None remote_url = False resolved_url = self._resolve_url(*args) + if self._attempt_sauce | self._attempt_remote: if self._attempt_sauce: self.remote_url = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub" % (self.sauce_username, self.sauce_apikey) @@ -616,6 +642,16 @@ class MyPageObject(PageObject): self.rest_url = "https://%s:%s@saucelabs.com/rest/v1/%s/jobs/%s" \ % (self.sauce_username, self.sauce_apikey, self.sauce_username, self.driver.session_id) + if self._attempt_eyes: + self.eyes.api_key = self.eyes_apikey + self.eyes.force_full_page_screenshot = True + self.eyes.stitch_mode = StitchMode.CSS + if self.eyes_name == None: self.eyes_name = self.suite_name + # size = self.driver.get_window_size() + self.eyes.open(driver=self.driver, app_name='Robot Page - spike', + test_name=self.eyes_name,) + # viewport_size={'width': size['width'], 'height': size['height']}) + except (urllib2.HTTPError, WebDriverException, ValueError), e: if self._attempt_sauce: raise exceptions.SauceConnectionError("Unable to run Sauce job.\n%s\n" @@ -636,7 +672,7 @@ class MyPageObject(PageObject): self.log("session ID: %s" % self.session_id) else: - self.open_browser(resolved_url, self.browser) + self.open_browser(resolved_url, self.browser, desired_capabilities=caps) self.log("PO_BROWSER: %s" % (str(self.get_current_browser())), is_console=False) @@ -652,7 +688,13 @@ def close(self): self._option_handler.get('suite status'), ['test-tag', 'page.py', 'close'], self.rest_url) + + if self._attempt_eyes: + self._attempt_eyes = False + self.eyes.close() + self.close_browser() + return self def _report_sauce_status(self, name, status, tags=[], rest_url=None): From 4153a7cb394cb67df60b2b1870dc32cc78eaace9 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Tue, 27 Jun 2017 16:27:36 -0700 Subject: [PATCH 13/32] Changes to work better with Applitools Eyes --- robotpageobjects/page.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index b1f51be..455c886 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -31,6 +31,7 @@ import uritemplate from Selenium2Library import Selenium2Library from applitools.eyes import Eyes +from applitools.eyes import BatchInfo from applitools.eyes import StitchMode from selenium import webdriver from selenium.common.exceptions import WebDriverException @@ -151,7 +152,7 @@ def __init__(self): self.service_args = self._parse_service_args(self._option_handler.get("service_args", "")) self.remote_url = self._option_handler.get("remote_url") self.eyes_apikey = self._option_handler.get("eyes_apikey") - self.eyes_name = self._option_handler.get("eyes_name") + self.eyes_batch = self._option_handler.get("eyes_batch") self.suite_name = self._option_handler.get('suite_name') self.rest_url = None @@ -646,11 +647,8 @@ class MyPageObject(PageObject): self.eyes.api_key = self.eyes_apikey self.eyes.force_full_page_screenshot = True self.eyes.stitch_mode = StitchMode.CSS - if self.eyes_name == None: self.eyes_name = self.suite_name - # size = self.driver.get_window_size() - self.eyes.open(driver=self.driver, app_name='Robot Page - spike', - test_name=self.eyes_name,) - # viewport_size={'width': size['width'], 'height': size['height']}) + if self.eyes_batch == None: self.eyes_batch = self.suite_name + self.eyes.batch = BatchInfo(self.eyes_batch) except (urllib2.HTTPError, WebDriverException, ValueError), e: if self._attempt_sauce: @@ -678,6 +676,16 @@ class MyPageObject(PageObject): return self + def eyes_open(self,test_name): + if self._attempt_eyes: + self.eyes.open(driver=self.driver, app_name='Robot Page - spike', test_name=test_name,) + return self + + def eyes_close(self): + if self._attempt_eyes: + self.eyes.close() + return self + def close(self): """ Wrapper for Selenium2Library's close_browser. From bda9dd87e3ab70868f485b56f6df18104c07d0dc Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Thu, 29 Jun 2017 11:17:15 -0700 Subject: [PATCH 14/32] More logging, corrected error on calling eyes.close --- robotpageobjects/page.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 455c886..64e8093 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -678,11 +678,13 @@ class MyPageObject(PageObject): def eyes_open(self,test_name): if self._attempt_eyes: + self.log("eyes.open test_name={}".format(test_name)) self.eyes.open(driver=self.driver, app_name='Robot Page - spike', test_name=test_name,) return self def eyes_close(self): if self._attempt_eyes: + self.log("eyes.close") self.eyes.close() return self @@ -699,7 +701,7 @@ def close(self): if self._attempt_eyes: self._attempt_eyes = False - self.eyes.close() + self.eyes_close() self.close_browser() From b2acc836f08b3d46387c5c2e1cd4deaec38696f6 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Mon, 3 Jul 2017 14:11:36 -0700 Subject: [PATCH 15/32] Changes to warn on screen capture mismatch but let test continue --- robotpageobjects/page.py | 60 ++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 64e8093..2d9849f 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -33,6 +33,7 @@ from applitools.eyes import Eyes from applitools.eyes import BatchInfo from applitools.eyes import StitchMode +from applitools.errors import TestFailedError from selenium import webdriver from selenium.common.exceptions import WebDriverException from selenium.webdriver.chrome.options import Options as ChromeOptions @@ -176,6 +177,12 @@ def __init__(self): if self.eyes_apikey != None: self._attempt_eyes = True + self.eyes.api_key = self.eyes_apikey + self.eyes.force_full_page_screenshot = True + self.eyes.stitch_mode = StitchMode.CSS + if self.eyes_batch == None: self.eyes_batch = self.suite_name + if self.eyes.batch == None: + self.eyes.batch = BatchInfo(self.eyes_batch) self._Capabilities = getattr(webdriver.DesiredCapabilities, self.browser.upper()) for cap in self._Capabilities: @@ -218,7 +225,7 @@ def _titleize(str): :param str: camel case string :return: title case string """ - return re.sub('([a-z0-9])([A-Z])', r'\1 \2', re.sub(r"(.)([A-Z][a-z]+)", r'\1 \2', str)) + return re.sub('([a-z0-9])([A-Z])', r'\1 \2', re.sub(r"(.)([A-Z][a-z]+)", r'\1 \2', str)) @staticmethod @not_keyword @@ -238,7 +245,7 @@ def get_keyword_names(self): # Return all method names on the class to expose keywords to Robot Framework keywords = [] - #members = inspect.getmembers(self, inspect.ismethod) + # members = inspect.getmembers(self, inspect.ismethod) # Look through our methods and identify which ones are Selenium2Library's @@ -279,11 +286,11 @@ def get_keyword_names(self): return keywords def _attempt_screenshot(self): - try: - self.capture_page_screenshot() - except Exception, e: - if e.message.find("No browser is open") != -1: - pass + try: + self.capture_page_screenshot() + except Exception, e: + if e.message.find("No browser is open") != -1: + pass @not_keyword def run_keyword(self, alias, args, kwargs): @@ -360,7 +367,8 @@ def get_keyword_documentation(self, kwname): kw = getattr(self, kwname, None) alias = '' if kwname in _Keywords._aliases: - alias = '*Alias: %s*\n\n' % _Keywords.get_robot_aliases(kwname, self._underscore(self.name))[0].replace('_', ' ').title() + alias = '*Alias: %s*\n\n' % _Keywords.get_robot_aliases(kwname, self._underscore(self.name))[0].replace('_', + ' ').title() docstring = kw.__doc__ if kw.__doc__ else '' docstring = re.sub(r'(wrapper)', r'*\1*', docstring, flags=re.I) return alias + docstring @@ -524,8 +532,9 @@ def _resolve_url(self, *args): return uritemplate.expand(self.baseurl + self.uri, uri_vars) else: if uri_type == 'template': - raise exceptions.UriResolutionError('%s has uri template %s , but no arguments were given to resolve it' % - (pageobj_name, self.uri)) + raise exceptions.UriResolutionError( + '%s has uri template %s , but no arguments were given to resolve it' % + (pageobj_name, self.uri)) # the user wants to open the default uri return self.baseurl + self.uri @@ -553,7 +562,7 @@ def set_window_size(self, width, height, *args): if self._attempt_eyes: self.eyes.set_viewport_size( super(_BaseActions, self).driver - ,viewport_size={'width': int(width), 'height': int(height)}) + , viewport_size={'width': int(width), 'height': int(height)}) else: super(_BaseActions, self).set_window_size(width, height, *args) return self @@ -621,7 +630,8 @@ class MyPageObject(PageObject): if self._attempt_sauce | self._attempt_remote: if self._attempt_sauce: - self.remote_url = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub" % (self.sauce_username, self.sauce_apikey) + self.remote_url = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub" % ( + self.sauce_username, self.sauce_apikey) caps = getattr(webdriver.DesiredCapabilities, self.browser.upper()) caps["platform"] = self.sauce_platform if self.sauce_browserversion: @@ -641,14 +651,8 @@ class MyPageObject(PageObject): if self._attempt_sauce: # username, apikey = self.get_sauce_creds() self.rest_url = "https://%s:%s@saucelabs.com/rest/v1/%s/jobs/%s" \ - % (self.sauce_username, self.sauce_apikey, self.sauce_username, self.driver.session_id) - - if self._attempt_eyes: - self.eyes.api_key = self.eyes_apikey - self.eyes.force_full_page_screenshot = True - self.eyes.stitch_mode = StitchMode.CSS - if self.eyes_batch == None: self.eyes_batch = self.suite_name - self.eyes.batch = BatchInfo(self.eyes_batch) + % ( + self.sauce_username, self.sauce_apikey, self.sauce_username, self.driver.session_id) except (urllib2.HTTPError, WebDriverException, ValueError), e: if self._attempt_sauce: @@ -660,9 +664,9 @@ class MyPageObject(PageObject): "sauce_screenresolution: %s" % (str(e), self.sauce_platform, - self.sauce_browserversion, self.sauce_device_orientation, - self.sauce_screenresolution) - ) + self.sauce_browserversion, self.sauce_device_orientation, + self.sauce_screenresolution) + ) else: raise e @@ -676,16 +680,19 @@ class MyPageObject(PageObject): return self - def eyes_open(self,test_name): + def eyes_open(self, test_name): if self._attempt_eyes: self.log("eyes.open test_name={}".format(test_name)) - self.eyes.open(driver=self.driver, app_name='Robot Page - spike', test_name=test_name,) + self.eyes.open(driver=self.driver, app_name='Robot Page - spike', test_name=test_name, ) return self def eyes_close(self): if self._attempt_eyes: self.log("eyes.close") - self.eyes.close() + try: + self.eyes.close() + except TestFailedError as e: + self.log("Applitools Eyes error detected: {}".format(e.message), level="WARNING") return self def close(self): @@ -720,4 +727,3 @@ def _report_sauce_status(self, name, status, tags=[], rest_url=None): video_url = json.loads(response.text).get('video_url') if video_url: self.log('video.flv'.format(video_url)) - From a63f544422eeb0929d645db9b50c83bbe441c481 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Wed, 26 Jul 2017 15:34:36 -0700 Subject: [PATCH 16/32] Changes to get Sauce Labs test status recording working --- robotpageobjects/page.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 2d9849f..116e370 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -148,7 +148,6 @@ def __init__(self): """ for base in Page.__bases__: base.__init__(self) - self.browser = self._option_handler.get("browser") or "phantomjs" self.service_args = self._parse_service_args(self._option_handler.get("service_args", "")) self.remote_url = self._option_handler.get("remote_url") @@ -156,8 +155,6 @@ def __init__(self): self.eyes_batch = self._option_handler.get("eyes_batch") self.suite_name = self._option_handler.get('suite_name') - self.rest_url = None - if self.remote_url != None: if self.remote_url.find('saucelabs.com') > -1: self._sauce_options = [ @@ -701,6 +698,12 @@ def close(self): :returns: None """ if self._attempt_sauce: + try: + self.rest_url + except AttributeError: + self.rest_url = "https://%s:%s@saucelabs.com/rest/v1/%s/jobs/%s" \ + % ( + self.sauce_username, self.sauce_apikey, self.sauce_username, self.driver.session_id) self._report_sauce_status(self._option_handler.get('suite_name'), self._option_handler.get('suite status'), ['test-tag', 'page.py', 'close'], From 0d07298e3d85453ae23d3beb6ab2f43d7cab7ee4 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Fri, 4 Aug 2017 08:08:34 -0700 Subject: [PATCH 17/32] Additional Desired Capabilities for Sauce Connect VPN --- robotpageobjects/page.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 116e370..a571f3a 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -164,6 +164,8 @@ def __init__(self): "sauce_browserversion", "sauce_device_orientation", "sauce_screenresolution", + "sauce_tunnel_id", + "sauce_parent_tunnel", ] for sauce_opt in self._sauce_options: setattr(self, sauce_opt, self._option_handler.get(sauce_opt)) @@ -637,6 +639,10 @@ class MyPageObject(PageObject): caps["device_orientation"] = self.sauce_device_orientation if self.sauce_screenresolution: caps["screenResolution"] = self.sauce_screenresolution + if self.sauce_tunnel_id: + caps["tunnelIdentifier"] = self.sauce_tunnel_id + if self.sauce_parent_tunnel: + caps["parentTunnel"] = self.sauce_parent_tunnel caps["name"] = self._option_handler.get('suite_name') if self.remote_url is not None: From 21ce2cd84b151c9d1ed5c42227ac3d063daee79c Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Mon, 14 Aug 2017 13:49:48 -0700 Subject: [PATCH 18/32] Adding batch_id for Eyes part of interface to consolidate like name batches --- robotpageobjects/page.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index a571f3a..4432ec0 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -153,6 +153,7 @@ def __init__(self): self.remote_url = self._option_handler.get("remote_url") self.eyes_apikey = self._option_handler.get("eyes_apikey") self.eyes_batch = self._option_handler.get("eyes_batch") + self.eyes_id = self._option_handler.get("eyes_id") self.suite_name = self._option_handler.get('suite_name') if self.remote_url != None: @@ -180,8 +181,10 @@ def __init__(self): self.eyes.force_full_page_screenshot = True self.eyes.stitch_mode = StitchMode.CSS if self.eyes_batch == None: self.eyes_batch = self.suite_name + if self.eyes_id == None: self.eyes_id = uuid4().__str__() if self.eyes.batch == None: self.eyes.batch = BatchInfo(self.eyes_batch) + self.eyes.batch.id_ = self.eyes_id self._Capabilities = getattr(webdriver.DesiredCapabilities, self.browser.upper()) for cap in self._Capabilities: From bd2af2d27f4767dce082a86b82cd12b1d8a24c63 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Fri, 8 Sep 2017 20:48:24 -0700 Subject: [PATCH 19/32] For IE, make sure we're using later Selenium --- robotpageobjects/page.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 4432ec0..0f5e3f0 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -202,6 +202,14 @@ def __init__(self): 'profile': {'password_manager_enabled': False}}) self._Capabilities.update(opts.to_capabilities()) + if self.browser == "internetexplorer": + self._Capabilities.update( + { + "seleniumVersion":"3.5.3", + "requireWindowFocus": True, + } + ) + # There's only a session ID when using a remote webdriver (Sauce, for example) self.session_id = None From 03098a376a2afd4a3711887e74c0f4991e83200a Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Mon, 11 Sep 2017 09:16:32 -0700 Subject: [PATCH 20/32] Prevent use of force full page screenshot and CSS stitchmode for IE on Sauce Labs --- robotpageobjects/page.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 0f5e3f0..9163f8b 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -178,8 +178,9 @@ def __init__(self): if self.eyes_apikey != None: self._attempt_eyes = True self.eyes.api_key = self.eyes_apikey - self.eyes.force_full_page_screenshot = True - self.eyes.stitch_mode = StitchMode.CSS + if not(self._attempt_sauce and self.browser == "internetexplorer"): + self.eyes.force_full_page_screenshot = True + self.eyes.stitch_mode = StitchMode.CSS if self.eyes_batch == None: self.eyes_batch = self.suite_name if self.eyes_id == None: self.eyes_id = uuid4().__str__() if self.eyes.batch == None: From 02640163fa9b005ccd305128aa747bc5095672a3 Mon Sep 17 00:00:00 2001 From: conversica-aaronpa Date: Wed, 13 Sep 2017 19:09:21 -0700 Subject: [PATCH 21/32] Update page.py Need to fix the IE driver version for now --- robotpageobjects/page.py | 1 + 1 file changed, 1 insertion(+) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 9163f8b..9abde19 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -207,6 +207,7 @@ def __init__(self): self._Capabilities.update( { "seleniumVersion":"3.5.3", + "iedriverVersion": "3.4.0", "requireWindowFocus": True, } ) From a4a48b931ff875cb4f375d4e70978c67a3540cac Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Thu, 5 Oct 2017 14:52:10 -0700 Subject: [PATCH 22/32] Adding some duration tracking and changing match level to see how it works --- robotpageobjects/page.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 9abde19..e7fe35e 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -24,16 +24,17 @@ import json import re import urllib2 +import time from uuid import uuid4 import decorator import requests import uritemplate from Selenium2Library import Selenium2Library -from applitools.eyes import Eyes +from applitools.errors import TestFailedError from applitools.eyes import BatchInfo +from applitools.eyes import Eyes from applitools.eyes import StitchMode -from applitools.errors import TestFailedError from selenium import webdriver from selenium.common.exceptions import WebDriverException from selenium.webdriver.chrome.options import Options as ChromeOptions @@ -140,6 +141,9 @@ class Page(_BaseActions, _SelectorsManager, _ComponentsManager): _attempt_remote = False _attempt_eyes = False eyes = Eyes() + _total_eyes_elapsed = 0 + _min_eyes_elapsed = 999999 + _max_eyes_elapsed = 0 def __init__(self): """ @@ -187,6 +191,8 @@ def __init__(self): self.eyes.batch = BatchInfo(self.eyes_batch) self.eyes.batch.id_ = self.eyes_id + self.eyes.match_level = MatchLevel.LAYOUT2 + self._Capabilities = getattr(webdriver.DesiredCapabilities, self.browser.upper()) for cap in self._Capabilities: new_cap = self._option_handler.get(cap) @@ -706,7 +712,11 @@ def eyes_close(self): if self._attempt_eyes: self.log("eyes.close") try: + start = time.time() self.eyes.close() + done = time.time() + elapsed = done - start + self.log(" duration: {}".format(elapsed)) except TestFailedError as e: self.log("Applitools Eyes error detected: {}".format(e.message), level="WARNING") return self From 9bb1e0d6eb21982e616eff1b0ada3116a613808d Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Thu, 5 Oct 2017 15:49:32 -0700 Subject: [PATCH 23/32] Changing match level and logging calculated durations --- robotpageobjects/page.py | 1 + 1 file changed, 1 insertion(+) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index e7fe35e..290d55d 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -35,6 +35,7 @@ from applitools.eyes import BatchInfo from applitools.eyes import Eyes from applitools.eyes import StitchMode +from applitools.eyes import MatchLevel from selenium import webdriver from selenium.common.exceptions import WebDriverException from selenium.webdriver.chrome.options import Options as ChromeOptions From 91681996f5514f3fa3bdd7fffd71cae6d0fc62b5 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Thu, 19 Oct 2017 13:58:01 -0700 Subject: [PATCH 24/32] Go back to strict matching for now --- robotpageobjects/page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 290d55d..b0f9246 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -192,7 +192,7 @@ def __init__(self): self.eyes.batch = BatchInfo(self.eyes_batch) self.eyes.batch.id_ = self.eyes_id - self.eyes.match_level = MatchLevel.LAYOUT2 + self.eyes.match_level = MatchLevel.STRICT self._Capabilities = getattr(webdriver.DesiredCapabilities, self.browser.upper()) for cap in self._Capabilities: From 2c2fd6b9d40529ed398a48110a4bd01a0b4cca15 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Tue, 31 Oct 2017 11:20:21 -0700 Subject: [PATCH 25/32] Trying layout2 as default page matching --- robotpageobjects/page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index b0f9246..290d55d 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -192,7 +192,7 @@ def __init__(self): self.eyes.batch = BatchInfo(self.eyes_batch) self.eyes.batch.id_ = self.eyes_id - self.eyes.match_level = MatchLevel.STRICT + self.eyes.match_level = MatchLevel.LAYOUT2 self._Capabilities = getattr(webdriver.DesiredCapabilities, self.browser.upper()) for cap in self._Capabilities: From 78c34a56001d106b6168bf8f3c86810115408ae6 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Wed, 1 Nov 2017 11:18:49 -0700 Subject: [PATCH 26/32] Try a different level of match for visual tests. TODO: parameterize this. --- robotpageobjects/page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 290d55d..c4f1e0c 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -192,7 +192,7 @@ def __init__(self): self.eyes.batch = BatchInfo(self.eyes_batch) self.eyes.batch.id_ = self.eyes_id - self.eyes.match_level = MatchLevel.LAYOUT2 + self.eyes.match_level = MatchLevel.CONTENT self._Capabilities = getattr(webdriver.DesiredCapabilities, self.browser.upper()) for cap in self._Capabilities: From e549e08aab7fd469f5dea670b461a526fcda8c3e Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Wed, 13 Dec 2017 09:10:41 -0800 Subject: [PATCH 27/32] Trying LAYOUT2 again --- robotpageobjects/page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index c4f1e0c..290d55d 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -192,7 +192,7 @@ def __init__(self): self.eyes.batch = BatchInfo(self.eyes_batch) self.eyes.batch.id_ = self.eyes_id - self.eyes.match_level = MatchLevel.CONTENT + self.eyes.match_level = MatchLevel.LAYOUT2 self._Capabilities = getattr(webdriver.DesiredCapabilities, self.browser.upper()) for cap in self._Capabilities: From 634a95fa999354fff238592a8ba647a1cf1e3b78 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Fri, 2 Mar 2018 09:41:53 -0800 Subject: [PATCH 28/32] Add optional match level parameter to Eyes Open keyword --- robotpageobjects/abstractedlogger.py | 2 +- robotpageobjects/page.py | 31 ++++++++++++++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/robotpageobjects/abstractedlogger.py b/robotpageobjects/abstractedlogger.py index 0cac87a..7b02980 100755 --- a/robotpageobjects/abstractedlogger.py +++ b/robotpageobjects/abstractedlogger.py @@ -23,7 +23,7 @@ def __init__(self): # Stream handler is attached from log() since # that must be decided at run-time, but here we might as well - # do the setup to keep log() clean. + # do the client_setup to keep log() clean. self.stream_handler = logging.StreamHandler(sys.stdout) self.stream_handler.setFormatter(self.formatter) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 290d55d..2e386f6 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -49,6 +49,7 @@ ld = 'libdoc' in_ld = any([ld in str(x) for x in inspect.stack()]) + class _PageMeta(_ComponentsManagerMeta): """Meta class that allows decorating of all page object methods with must_return decorator. This ensures that all page object @@ -183,7 +184,7 @@ def __init__(self): if self.eyes_apikey != None: self._attempt_eyes = True self.eyes.api_key = self.eyes_apikey - if not(self._attempt_sauce and self.browser == "internetexplorer"): + if not (self._attempt_sauce and self.browser == "internetexplorer"): self.eyes.force_full_page_screenshot = True self.eyes.stitch_mode = StitchMode.CSS if self.eyes_batch == None: self.eyes_batch = self.suite_name @@ -192,8 +193,6 @@ def __init__(self): self.eyes.batch = BatchInfo(self.eyes_batch) self.eyes.batch.id_ = self.eyes_id - self.eyes.match_level = MatchLevel.LAYOUT2 - self._Capabilities = getattr(webdriver.DesiredCapabilities, self.browser.upper()) for cap in self._Capabilities: new_cap = self._option_handler.get(cap) @@ -213,7 +212,7 @@ def __init__(self): if self.browser == "internetexplorer": self._Capabilities.update( { - "seleniumVersion":"3.5.3", + "seleniumVersion": "3.5.3", "iedriverVersion": "3.4.0", "requireWindowFocus": True, } @@ -266,7 +265,6 @@ def get_keyword_names(self): keywords = [] # members = inspect.getmembers(self, inspect.ismethod) - # Look through our methods and identify which ones are Selenium2Library's # (by checking it and its base classes). for name in dir(self): @@ -650,7 +648,7 @@ class MyPageObject(PageObject): if self._attempt_sauce | self._attempt_remote: if self._attempt_sauce: self.remote_url = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub" % ( - self.sauce_username, self.sauce_apikey) + self.sauce_username, self.sauce_apikey) caps = getattr(webdriver.DesiredCapabilities, self.browser.upper()) caps["platform"] = self.sauce_platform if self.sauce_browserversion: @@ -675,7 +673,8 @@ class MyPageObject(PageObject): # username, apikey = self.get_sauce_creds() self.rest_url = "https://%s:%s@saucelabs.com/rest/v1/%s/jobs/%s" \ % ( - self.sauce_username, self.sauce_apikey, self.sauce_username, self.driver.session_id) + self.sauce_username, self.sauce_apikey, self.sauce_username, + self.driver.session_id) except (urllib2.HTTPError, WebDriverException, ValueError), e: if self._attempt_sauce: @@ -703,9 +702,23 @@ class MyPageObject(PageObject): return self - def eyes_open(self, test_name): + def eyes_open(self, test_name, eyes_match_level=None): if self._attempt_eyes: - self.log("eyes.open test_name={}".format(test_name)) + if eyes_match_level == None: + self.eyes.match_level = MatchLevel.LAYOUT + elif eyes_match_level.lower == 'layout': + self.eyes.match_level = MatchLevel.LAYOUT + elif eyes_match_level.lower == 'content': + self.eyes.match_level = MatchLevel.CONTENT + elif eyes_match_level.lower == 'exact': + self.eyes.match_level = MatchLevel.EXACT + elif eyes_match_level.lower == 'strict': + self.eyes.match_level = MatchLevel.STRICT + else: + self.eyes.match_level = MatchLevel.STRICT + self.log( + "eyes.open test_name={}, batch={}, id={}, match={}".format(test_name, self.eyes_batch, self.eyes_id, + self.eyes.match_level)) self.eyes.open(driver=self.driver, app_name='Robot Page - spike', test_name=test_name, ) return self From 2ce40445cf590f4945992b9b7f39ede805104e11 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Tue, 1 May 2018 09:53:37 -0700 Subject: [PATCH 29/32] Pin newer versions of IE driver and Selenium for IE --- robotpageobjects/page.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 2e386f6..3bdb5fc 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -212,8 +212,8 @@ def __init__(self): if self.browser == "internetexplorer": self._Capabilities.update( { - "seleniumVersion": "3.5.3", - "iedriverVersion": "3.4.0", + "seleniumVersion": "3.11.0", + "iedriverVersion": "3.11.1", "requireWindowFocus": True, } ) From 9db2f6e15a547794af77e6f9a899039804b68278 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Tue, 1 May 2018 10:24:08 -0700 Subject: [PATCH 30/32] 3.11 versions not working so well, trying with unspecified versions --- robotpageobjects/page.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 3bdb5fc..88d2473 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -212,8 +212,6 @@ def __init__(self): if self.browser == "internetexplorer": self._Capabilities.update( { - "seleniumVersion": "3.11.0", - "iedriverVersion": "3.11.1", "requireWindowFocus": True, } ) From 5c0f4f65ae40e68c3ce1a2f864920a3abb253253 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Wed, 8 Aug 2018 15:10:57 -0700 Subject: [PATCH 31/32] Adding a keyword to report any javascript errors on the page, throw optional --- robotpageobjects/page.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 88d2473..7f76863 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -733,6 +733,14 @@ def eyes_close(self): self.log("Applitools Eyes error detected: {}".format(e.message), level="WARNING") return self + def check_javascript_error(self, throw=False): + browser_errors = self.driver.get_log("browser") + for some_error in browser_errors: + self.log("Browser error detected: {}".format(some_error),level="ERROR") + if throw == True or throw.lower() == 'true': + assert len(browser_errors)==0,"Non-zero Javascript error count" + return self + def close(self): """ Wrapper for Selenium2Library's close_browser. From b057ae393da04e7fb6857844cfb9830f9a2bb429 Mon Sep 17 00:00:00 2001 From: Aaron Pailthorp Date: Thu, 9 Aug 2018 09:06:30 -0700 Subject: [PATCH 32/32] Make this for Chrome only --- robotpageobjects/page.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/robotpageobjects/page.py b/robotpageobjects/page.py index 7f76863..e0c3891 100755 --- a/robotpageobjects/page.py +++ b/robotpageobjects/page.py @@ -734,11 +734,12 @@ def eyes_close(self): return self def check_javascript_error(self, throw=False): - browser_errors = self.driver.get_log("browser") - for some_error in browser_errors: - self.log("Browser error detected: {}".format(some_error),level="ERROR") - if throw == True or throw.lower() == 'true': - assert len(browser_errors)==0,"Non-zero Javascript error count" + if self.browser == 'chrome': + browser_errors = self.driver.get_log("browser") + for some_error in browser_errors: + self.log("Browser error detected: {}".format(some_error),level="ERROR") + if throw == True or throw.lower() == 'true': + assert len(browser_errors)==0,"Non-zero Javascript error count" return self def close(self):