From d449fa42dcd7eec60afd6cc4a0abaabad3a1500a Mon Sep 17 00:00:00 2001 From: Jari Nurminen Date: Mon, 21 Nov 2016 12:17:14 +0100 Subject: [PATCH 1/8] reusable selenium session prototype --- .../keywords/_browsermanagement.py | 58 ++++++++++++++++++- test/acceptance/create_webdriver.robot | 12 ++++ test/run_tests.py | 4 +- test/unit/keywords/test_browsermanagement.py | 6 ++ 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/Selenium2Library/keywords/_browsermanagement.py b/src/Selenium2Library/keywords/_browsermanagement.py index a4fbc7a65..f4a289820 100644 --- a/src/Selenium2Library/keywords/_browsermanagement.py +++ b/src/Selenium2Library/keywords/_browsermanagement.py @@ -8,6 +8,12 @@ from keywordgroup import KeywordGroup from selenium.common.exceptions import NoSuchWindowException +from selenium.webdriver.remote.errorhandler import ErrorHandler +from selenium.webdriver.remote.remote_connection import RemoteConnection +from selenium.webdriver.remote.switch_to import SwitchTo +from selenium.webdriver.remote.mobile import Mobile +from selenium.webdriver.remote.file_detector import FileDetector, LocalFileDetector + ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) FIREFOX_PROFILE_DIR = os.path.join(ROOT_DIR, 'resources', 'firefoxprofile') BROWSER_NAMES = {'ff': "_make_ff", @@ -27,6 +33,44 @@ 'edge': "_make_edge" } +class _ReusableWebdriver(webdriver.Remote): + def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub', + desired_capabilities=None, browser_profile=None, proxy=None, keep_alive=False, file_detector=None): + + + session_file = open('session.txt', 'r+') + session_id = session_file.readline().replace("\n", "").replace("\r", "") + command_executor = session_file.readline().replace("\n", "").replace("\r", "") + cap = eval(session_file.readline().replace("\n", "").replace("\r", "")) + session_file.close() + + # Here onwards this is identical to the selenium_webdriver.Remote's __init__ + # except that the steps to create session & client are commented out + if desired_capabilities is None: + raise WebDriverException("Desired Capabilities can't be None") + if not isinstance(desired_capabilities, dict): + raise WebDriverException("Desired Capabilities must be a dictionary") + if proxy is not None: + proxy.add_to_capabilities(desired_capabilities) + self.command_executor = command_executor + if type(self.command_executor) is bytes or isinstance(self.command_executor, str): + self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive) + self._is_remote = True + self.error_handler = ErrorHandler() + ## do not start session nor client + # self.start_client() + # self.start_session(desired_capabilities, browser_profile) + ## since we are not creating a new session we need to set attributes + ## that are usually set by the start_session() + self.session_id = session_id + self.capabilities = cap + self.w3c = "specificationLevel" in self.capabilities + + self._switch_to = SwitchTo(self) + self._mobile = Mobile(self) + self.file_detector = file_detector or LocalFileDetector() + + class _BrowserManagementKeywords(KeywordGroup): def __init__(self): @@ -175,8 +219,18 @@ def create_webdriver(self, driver_name, alias=None, kwargs={}, **init_kwargs): except AttributeError: raise RuntimeError("'%s' is not a valid WebDriver name" % driver_name) self._info("Creating an instance of the %s WebDriver" % driver_name) - driver = creation_func(**init_kwargs) - self._debug("Created %s WebDriver instance with session id %s" % (driver_name, driver.session_id)) + if driver_name == 'Remote': + driver = _ReusableWebdriver(desired_capabilities={}) + self._info("Session: '%s' - '%s' " % (driver.session_id, driver.command_executor._url)) + else: + driver = creation_func(**init_kwargs) + self._debug("Created %s WebDriver instance with session id %s" % (driver_name, driver.session_id)) + self._info("Session: '%s' - '%s' " % (driver.session_id, driver.command_executor._url)) + session_file = open('session.txt', 'w+') + session_file.write("%s\n" % driver.session_id) + session_file.write("%s\n" % driver.command_executor._url) + session_file.write("%s\n" % driver.capabilities) + session_file.close() return self._cache.register(driver, alias) def switch_browser(self, index_or_alias): diff --git a/test/acceptance/create_webdriver.robot b/test/acceptance/create_webdriver.robot index faf173d3d..809f946b9 100644 --- a/test/acceptance/create_webdriver.robot +++ b/test/acceptance/create_webdriver.robot @@ -4,6 +4,18 @@ Resource resource.robot Library Collections *** Test Cases *** +Create Reusable Webdriver + [Documentation] LOG 2:1 INFO REGEXP: Creating an instance of the \\w+ WebDriver + ... LOG 2:4 DEBUG REGEXP: Created \\w+ WebDriver instance with session id (\\w|-)+ + [Setup] Set Driver Variables + Create Webdriver ${DRIVER_NAME} kwargs=${KWARGS} + #Sleep 3s + Create Webdriver Remote + Go To ${FRONT PAGE} + Page Should Contain needle + #Sleep 3s + [Teardown] Close Browser + Create Webdriver Creates Functioning WebDriver [Documentation] LOG 2:1 INFO REGEXP: Creating an instance of the \\w+ WebDriver ... LOG 2:4 DEBUG REGEXP: Created \\w+ WebDriver instance with session id (\\w|-)+ diff --git a/test/run_tests.py b/test/run_tests.py index 4de5cf7d7..e760937e2 100755 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -16,7 +16,9 @@ '--escape', 'space:SP', '--report', 'none', '--log', 'none', - #'--suite', 'Acceptance.Keywords.Textfields', + '--suite', 'Acceptance.Create_Webdriver', + #'-t', '*Reusable*', + '-b', 'debug.log', '--loglevel', 'DEBUG', '--pythonpath', '%(pythonpath)s', '--noncritical', 'known_issue_-_%(pyVersion)s', diff --git a/test/unit/keywords/test_browsermanagement.py b/test/unit/keywords/test_browsermanagement.py index 782491730..49ab7eea0 100644 --- a/test/unit/keywords/test_browsermanagement.py +++ b/test/unit/keywords/test_browsermanagement.py @@ -102,12 +102,18 @@ def test_bad_browser_name(self): def test_create_webdriver(self): bm = _BrowserManagementWithLoggingStubs() capt_data = {} + class FakeCmdExec(mock): + _url = 'http://dummy' class FakeWebDriver(mock): + command_executor = FakeCmdExec def __init__(self, some_arg=None): mock.__init__(self) capt_data['some_arg'] = some_arg capt_data['webdriver'] = self + capt_data['session_id'] = 'dummy_ID' + #capt_data['command_executor'] = FakeCmdExecutor() webdriver.FakeWebDriver = FakeWebDriver + #when(webdriver.FakeWebDriver.session_id()).thenReturn('dummy_id') try: index = bm.create_webdriver('FakeWebDriver', 'fake', some_arg=1) self.assertEquals(capt_data['some_arg'], 1) From 207e6397056b6aec59c9517ce59efb8f4cfebfcf Mon Sep 17 00:00:00 2001 From: Jari Nurminen Date: Thu, 24 Nov 2016 15:43:07 +0100 Subject: [PATCH 2/8] new KW's without BrowserCache --- .../keywords/_browsermanagement.py | 108 ++++++++++-------- test/acceptance/create_webdriver.robot | 70 ++++++++++-- test/run_tests.py | 7 +- test/unit/keywords/test_browsermanagement.py | 48 ++++++-- 4 files changed, 162 insertions(+), 71 deletions(-) diff --git a/src/Selenium2Library/keywords/_browsermanagement.py b/src/Selenium2Library/keywords/_browsermanagement.py index f4a289820..db7c97377 100644 --- a/src/Selenium2Library/keywords/_browsermanagement.py +++ b/src/Selenium2Library/keywords/_browsermanagement.py @@ -33,44 +33,24 @@ 'edge': "_make_edge" } -class _ReusableWebdriver(webdriver.Remote): - def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub', - desired_capabilities=None, browser_profile=None, proxy=None, keep_alive=False, file_detector=None): - - - session_file = open('session.txt', 'r+') - session_id = session_file.readline().replace("\n", "").replace("\r", "") - command_executor = session_file.readline().replace("\n", "").replace("\r", "") - cap = eval(session_file.readline().replace("\n", "").replace("\r", "")) - session_file.close() - - # Here onwards this is identical to the selenium_webdriver.Remote's __init__ - # except that the steps to create session & client are commented out - if desired_capabilities is None: - raise WebDriverException("Desired Capabilities can't be None") - if not isinstance(desired_capabilities, dict): - raise WebDriverException("Desired Capabilities must be a dictionary") - if proxy is not None: - proxy.add_to_capabilities(desired_capabilities) - self.command_executor = command_executor - if type(self.command_executor) is bytes or isinstance(self.command_executor, str): - self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive) - self._is_remote = True - self.error_handler = ErrorHandler() - ## do not start session nor client - # self.start_client() - # self.start_session(desired_capabilities, browser_profile) - ## since we are not creating a new session we need to set attributes - ## that are usually set by the start_session() - self.session_id = session_id - self.capabilities = cap +class _ReusableDriver(webdriver.Remote): + def __init__(self, command_executor, sid, keep_alive=False, file_detector=None): + + self.reuse_session_id = sid + super(_ReusableDriver, self).__init__(command_executor, desired_capabilities={}, + browser_profile=None, + proxy=None, keep_alive=keep_alive, + file_detector=file_detector) + + def start_session(self, desired_capabilities, browser_profile=None): + self.session_id = self.reuse_session_id + #print("%s" % self.session_id) + self.command_executor._commands["GetSession"] = ('GET', '/session/$sessionId') + response = self.execute("GetSession") + #print("%s" % response) + self.capabilities = response['value'] self.w3c = "specificationLevel" in self.capabilities - self._switch_to = SwitchTo(self) - self._mobile = Mobile(self) - self.file_detector = file_detector or LocalFileDetector() - - class _BrowserManagementKeywords(KeywordGroup): def __init__(self): @@ -219,18 +199,54 @@ def create_webdriver(self, driver_name, alias=None, kwargs={}, **init_kwargs): except AttributeError: raise RuntimeError("'%s' is not a valid WebDriver name" % driver_name) self._info("Creating an instance of the %s WebDriver" % driver_name) - if driver_name == 'Remote': - driver = _ReusableWebdriver(desired_capabilities={}) - self._info("Session: '%s' - '%s' " % (driver.session_id, driver.command_executor._url)) + driver = creation_func(**init_kwargs) + self._debug("Created %s WebDriver instance with session id %s" % (driver_name, driver.session_id)) + return self._cache.register(driver, alias) + + def save_webdriver(self, file=''): + """saves the current web-driver session. + + """ + self._info("Saving WebDriver session data") + sid = self._current_browser().session_id + curl = self._current_browser().command_executor._url + if file is None: + return (sid, curl) + elif file == "": + inv_dict = {v: k for k, v in self._cache._aliases.items()} + session_name = inv_dict.get(self._cache.current_index, self._cache.current_index) + file = "session_%s.tmp" % (session_name) + self._debug("Storing session (%s-%s) in file '%s'" % (sid, curl, file)) + session_file = open(file, 'w+') + session_file.write("%s\n" % sid) + session_file.write("%s\n" % curl) + session_file.close() + return file + + def restore_webdriver(self, alias=None, file=None, session_id=None, session_url=None, delete_file=True): + """Connects to an already opened web-driver session. + + """ + self._info("Restoring WebDriver session") + if session_id is not None or session_url is not None: + if session_id is None and session_url is None: + raise RuntimeError("both session_id and session_url cannot be None.") + saved_sid = session_id + saved_curl = session_url else: - driver = creation_func(**init_kwargs) - self._debug("Created %s WebDriver instance with session id %s" % (driver_name, driver.session_id)) - self._info("Session: '%s' - '%s' " % (driver.session_id, driver.command_executor._url)) - session_file = open('session.txt', 'w+') - session_file.write("%s\n" % driver.session_id) - session_file.write("%s\n" % driver.command_executor._url) - session_file.write("%s\n" % driver.capabilities) + if file is None: + if alias is None: + raise RuntimeError("both alias and file cannot be None.") + else: + file = "session_%s.tmp" % (alias) + session_file = open(file, 'r+') + saved_sid = session_file.readline().replace("\n", "").replace("\r", "") + saved_curl = session_file.readline().replace("\n", "").replace("\r", "") session_file.close() + self._debug("Reconnecting to '%s' with session id '%s' - alias: '%s' " % (saved_curl, saved_sid, alias)) + driver = _ReusableDriver(saved_curl, saved_sid) + if delete_file and session_id is None and session_url is None: + os.remove(file) return self._cache.register(driver, alias) def switch_browser(self, index_or_alias): diff --git a/test/acceptance/create_webdriver.robot b/test/acceptance/create_webdriver.robot index 809f946b9..c6732223b 100644 --- a/test/acceptance/create_webdriver.robot +++ b/test/acceptance/create_webdriver.robot @@ -4,18 +4,6 @@ Resource resource.robot Library Collections *** Test Cases *** -Create Reusable Webdriver - [Documentation] LOG 2:1 INFO REGEXP: Creating an instance of the \\w+ WebDriver - ... LOG 2:4 DEBUG REGEXP: Created \\w+ WebDriver instance with session id (\\w|-)+ - [Setup] Set Driver Variables - Create Webdriver ${DRIVER_NAME} kwargs=${KWARGS} - #Sleep 3s - Create Webdriver Remote - Go To ${FRONT PAGE} - Page Should Contain needle - #Sleep 3s - [Teardown] Close Browser - Create Webdriver Creates Functioning WebDriver [Documentation] LOG 2:1 INFO REGEXP: Creating an instance of the \\w+ WebDriver ... LOG 2:4 DEBUG REGEXP: Created \\w+ WebDriver instance with session id (\\w|-)+ @@ -41,6 +29,64 @@ Create Webdriver With Bad Keyword Argument Dictionary Run Keyword And Expect Error kwargs must be a dictionary. ... Create Webdriver Firefox kwargs={'spam': 'eggs'} +Create Reusable Webdriver with alias + [Documentation] Reconnect to an existing browser session + [Setup] Set Driver Variables + Create Webdriver ${DRIVER_NAME} alias=dummy kwargs=${KWARGS} + ${file}= Save Webdriver + Restore Webdriver alias=dummy + ${file}= Save Webdriver + Restore Webdriver alias=dummy delete_file=${False} + Restore Webdriver alias=dummy + ${file}= Save Webdriver dummy.tmp + Restore Webdriver alias=dummy file=${file} + ${file}= Save Webdriver ${CURDIR}/dummy.tmp + Restore Webdriver alias=dummy file=${file} + ${file}= Save Webdriver + Restore Webdriver alias=dummy2 file=session_dummy.tmp + ${sid} ${url} = Save Webdriver ${None} + Restore Webdriver alias=dummy session_id=${sid} session_url=${url} + Go To ${FRONT PAGE} + Page Should Contain needle + [Teardown] Close Browser + +Create Reusable Webdriver without alias + [Documentation] Reconnect to an existing browser session + [Setup] Set Driver Variables + ${i1} = Create Webdriver ${DRIVER_NAME} kwargs=${KWARGS} + ${file}= Save Webdriver + ${i2} = Restore Webdriver file=${file} + log to console \n${i1}-${i2} + ${file}= Save Webdriver + Restore Webdriver file=${file} delete_file=${False} + Restore Webdriver file=${file} delete_file=${False} + Restore Webdriver alias=dummy file=${file} + ${file}= Save Webdriver dummy.tmp + Restore Webdriver file=${file} + ${file}= Save Webdriver ${CURDIR}/dummy.tmp + Restore Webdriver file=${file} + ${sid} ${url} = Save Webdriver ${None} + Restore Webdriver session_id=${sid} session_url=${url} + Go To ${FRONT PAGE} + Page Should Contain needle + [Teardown] Close Browser + +Create Reusable Webdriver with Invalid Arguments + [Documentation] Invalid arguments & run-time errors + run keyword and expect error *both alias and file* Restore Webdriver + run keyword and expect error *IOError*No such file* Restore Webdriver alias=foo + run keyword and expect error *IOError*No such file* Restore Webdriver file=foo.tmp + +Create Reusable Webdriver with runtime errors + [Documentation] run-time errors + [Setup] Set Driver Variables + run keyword and expect error *No browser is open* Save Webdriver + run keyword and expect error *URLError*refused* Restore Webdriver session_id=foo session_url=http://127.0.0.1:52470/foo + Create Webdriver ${DRIVER_NAME} kwargs=${KWARGS} + ${sid} ${url} = Save Webdriver ${None} + run keyword and expect error ** Restore Webdriver session_id=bogus session_url=${url} + [Teardown] Close Browser + *** Keywords *** Set Driver Variables [Documentation] Selects proper driver diff --git a/test/run_tests.py b/test/run_tests.py index e760937e2..7432f717a 100755 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -16,9 +16,10 @@ '--escape', 'space:SP', '--report', 'none', '--log', 'none', - '--suite', 'Acceptance.Create_Webdriver', - #'-t', '*Reusable*', - '-b', 'debug.log', + #'--suite', 'Acceptance.Create_Webdriver', + #'-t', '*Webdriver Creates Functioning*', + #'-t', '*Callback Invoked Immediately*', + #'-b', 'debug.log', '--loglevel', 'DEBUG', '--pythonpath', '%(pythonpath)s', '--noncritical', 'known_issue_-_%(pyVersion)s', diff --git a/test/unit/keywords/test_browsermanagement.py b/test/unit/keywords/test_browsermanagement.py index 49ab7eea0..0031f3fd6 100644 --- a/test/unit/keywords/test_browsermanagement.py +++ b/test/unit/keywords/test_browsermanagement.py @@ -1,17 +1,17 @@ import unittest from Selenium2Library.keywords._browsermanagement import _BrowserManagementKeywords +from Selenium2Library import keywords from selenium import webdriver from mockito import * class BrowserManagementTests(unittest.TestCase): - def test_create_firefox_browser(self): test_browsers = ((webdriver.Firefox, "ff"), (webdriver.Firefox, "firEfOx")) for test_browser in test_browsers: self.verify_browser(*test_browser) - + def mock_createProfile(self, profile_directory=None): self.ff_profile_dir = profile_directory return self.old_profile_init(profile_directory) @@ -102,18 +102,12 @@ def test_bad_browser_name(self): def test_create_webdriver(self): bm = _BrowserManagementWithLoggingStubs() capt_data = {} - class FakeCmdExec(mock): - _url = 'http://dummy' class FakeWebDriver(mock): - command_executor = FakeCmdExec def __init__(self, some_arg=None): mock.__init__(self) capt_data['some_arg'] = some_arg capt_data['webdriver'] = self - capt_data['session_id'] = 'dummy_ID' - #capt_data['command_executor'] = FakeCmdExecutor() webdriver.FakeWebDriver = FakeWebDriver - #when(webdriver.FakeWebDriver.session_id()).thenReturn('dummy_id') try: index = bm.create_webdriver('FakeWebDriver', 'fake', some_arg=1) self.assertEquals(capt_data['some_arg'], 1) @@ -127,12 +121,46 @@ def __init__(self, some_arg=None): finally: del webdriver.FakeWebDriver + def test_create_reusable_webdriver(self): + bm = _BrowserManagementWithLoggingStubs() + capt_data = {} + class FakeCmdExecutor(mock): + _url = "http://127.0.0.1:9999/dummy" + class FakeWebDriver(mock): + command_executor = FakeCmdExecutor + session_id = "dummy_sid" + def __init__(self): + mock.__init__(self) + capt_data['webdriver'] = self + webdriver.FakeWebDriver = FakeWebDriver + class FakeReusable(mock): + session_id = None + curl = None + def __init__(self, command_executor, sid, **kwargs): + FakeReusable.session_id = sid + FakeReusable.curl = command_executor + keywords._browsermanagement._ReusableDriver = FakeReusable + try: + index = bm.create_webdriver('FakeWebDriver', 'fake') + (sid, url) = bm.save_webdriver(None) + self.assertEquals(sid, FakeWebDriver.session_id) + self.assertEquals(url, FakeCmdExecutor._url) + #self.assertEquals(sid, bm._current_browser().session_id) + #self.assertEquals(url, bm._current_browser().command_executor._url) + old_index = bm._cache.current_index + bm.restore_webdriver(session_id=sid, session_url=url) + self.assertNotEqual(old_index, bm._cache.current_index) + self.assertEquals(sid, FakeReusable.session_id) + self.assertEquals(url, FakeReusable.curl) + finally: + del webdriver.FakeWebDriver + def verify_browser(self , webdriver_type , browser_name, **kw): #todo try lambda *x: was_called = true bm = _BrowserManagementKeywords() old_init = webdriver_type.__init__ webdriver_type.__init__ = self.mock_init - + try: self.was_called = False bm._make_browser(browser_name, **kw) @@ -141,7 +169,7 @@ def verify_browser(self , webdriver_type , browser_name, **kw): finally: webdriver_type.__init__ = old_init self.assertTrue(self.was_called) - + def mock_init(self, *args, **kw): self.was_called = True From 7aa1caeb6cfeb4da845cf945d785f3ea06a4f904 Mon Sep 17 00:00:00 2001 From: Jari Nurminen Date: Thu, 24 Nov 2016 19:19:49 +0100 Subject: [PATCH 3/8] documentation and BrowserCache --- .../keywords/_browsermanagement.py | 57 ++++++++++++++++++- src/Selenium2Library/utils/browsercache.py | 11 +++- test/run_tests.py | 3 +- test/unit/keywords/test_browsermanagement.py | 17 +++--- 4 files changed, 76 insertions(+), 12 deletions(-) diff --git a/src/Selenium2Library/keywords/_browsermanagement.py b/src/Selenium2Library/keywords/_browsermanagement.py index db7c97377..076d3064d 100644 --- a/src/Selenium2Library/keywords/_browsermanagement.py +++ b/src/Selenium2Library/keywords/_browsermanagement.py @@ -204,8 +204,37 @@ def create_webdriver(self, driver_name, alias=None, kwargs={}, **init_kwargs): return self._cache.register(driver, alias) def save_webdriver(self, file=''): - """saves the current web-driver session. + """Stores the current web-driver session. + This KW makes the WebDriver sessions persistent. I.e. you can store the session(s) and leave the browser + window(s) open after a pybot (test suite) execution. On the next pybot execution you can reconnect to those + already opened browser windows and avoid that slow opening of new browser window(s) step and possibly other + steps (e.g. login) that you might do in your suite/test set-up phase. + NOTE: When re-connecting the session must exists on the server side, in some scenarios the session + might have timed out and the re-connect will fail. + + Saves the session information into file or returns it. + + The file name is taken from the _file_ argument. + This accepts plain file name (e.g. "mysession.txt") and path-to-file (e.g. "/tmp/mysession.txt") format. + The _file_ argument has following special values: + - empty string i.e. ``${EMPTY}`` (default): the session is stored in the current execution directory in a file called + "session_.tmp" (if the connection has an alias) or "session_.tmp" (if the connection does + not have an alias). + - None i.e. ``${None}``: no file is created. Returns a tuplet where the 1st value is the session ID and the 2nd + value is the Webdriver URL. + + Returns the session file-name, unless _file_ argument is None (see above). + + Examples: + | Create Webdriver | Firefox | | | | + | ${file} = | Save Webdriver | | | # saved to "session_1.tmp" | + | Create Webdriver | Firefox | alias=foo | | | + | ${file} = | Save Webdriver | | | # saved to "session_foo.tmp" | + | ${file} = | Save Webdriver | file=dummy.tmp | | # saved to "dummy.tmp" | + | ${sid} | ${url} = | Save Webdriver | file=${None} | # no file created | + + See also `Restore Webdriver` """ self._info("Saving WebDriver session data") sid = self._current_browser().session_id @@ -226,6 +255,32 @@ def save_webdriver(self, file=''): def restore_webdriver(self, alias=None, file=None, session_id=None, session_url=None, delete_file=True): """Connects to an already opened web-driver session. + Restores a Webdriver Session that has been saved using the `Save Webdriver` KW. + + Optional _alias_ is an alias for the browser instance and it can be used + for switching between browsers (just as index can be used). See `Switch + Browser` for more details. + + The session can be restored by: + - using the default session file (e.g. with `alias=foo` we try to read the session info from a file called + "session_foo.tmp") + - using explicit file: file-name is passed in _file_ argument. See `Save Webdriver` KW for _file_ + argument documentation + - using the _session_id_ and _session_url_ arguments + + Optional _delete_file_ can be used to delete (default) the file after the session has been restored. + + Returns the index of this browser instance which can be used later to + switch back to it. Index starts from 1 and is reset back to it when + `Close All Browsers` keyword is used. See `Switch Browser` for + example. + + Examples: + | Restore Webdriver | alias=foo | | # read from "session_foo.tmp" and registered with alias "foo" | + | Restore Webdriver | alias=bar | file=foo.tmp | # read from "foo.tmp" and registered with alias "bar" | + | Restore Webdriver | | file=foo.tmp | # read from "foo.tmp" and registered without alias | + | #do not use file and register without alias | | | | + | Restore Webdriver | session_id=xxx | session_url=http://127.0.0.1:9999/hub | | """ self._info("Restoring WebDriver session") if session_id is not None or session_url is not None: diff --git a/src/Selenium2Library/utils/browsercache.py b/src/Selenium2Library/utils/browsercache.py index 32590f4a6..5cb155229 100644 --- a/src/Selenium2Library/utils/browsercache.py +++ b/src/Selenium2Library/utils/browsercache.py @@ -1,4 +1,5 @@ from robot.utils import ConnectionCache +from robot.api.logger import debug class BrowserCache(ConnectionCache): @@ -16,7 +17,7 @@ def get_open_browsers(self): if browser not in self._closed: open_browsers.append(browser) return open_browsers - + def close(self): if self.current: browser = self.current @@ -30,3 +31,11 @@ def close_all(self): browser.quit() self.empty_cache() return self.current + + def register(self, connection, alias=None): + for browser in self._connections: + if browser.session_id == connection.session_id: # and connection.command_executor._url == browser.command_executor._url: + debug('Browser with session %s already registered. Marking previous one as obsolete' + % connection.session_id) + self._closed.add(browser) + return super(BrowserCache, self).register(connection, alias) diff --git a/test/run_tests.py b/test/run_tests.py index 7432f717a..dc90d3ea2 100755 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -17,8 +17,7 @@ '--report', 'none', '--log', 'none', #'--suite', 'Acceptance.Create_Webdriver', - #'-t', '*Webdriver Creates Functioning*', - #'-t', '*Callback Invoked Immediately*', + #'-t', '*Webdriver*', #'-b', 'debug.log', '--loglevel', 'DEBUG', '--pythonpath', '%(pythonpath)s', diff --git a/test/unit/keywords/test_browsermanagement.py b/test/unit/keywords/test_browsermanagement.py index 0031f3fd6..274b7e89a 100644 --- a/test/unit/keywords/test_browsermanagement.py +++ b/test/unit/keywords/test_browsermanagement.py @@ -102,12 +102,15 @@ def test_bad_browser_name(self): def test_create_webdriver(self): bm = _BrowserManagementWithLoggingStubs() capt_data = {} + class FakeCmdExecutor(mock): + _url = "http://127.0.0.1:9999/dummy" class FakeWebDriver(mock): def __init__(self, some_arg=None): mock.__init__(self) capt_data['some_arg'] = some_arg capt_data['webdriver'] = self webdriver.FakeWebDriver = FakeWebDriver + webdriver.FakeWebDriver.command_executor = FakeCmdExecutor try: index = bm.create_webdriver('FakeWebDriver', 'fake', some_arg=1) self.assertEquals(capt_data['some_arg'], 1) @@ -127,29 +130,27 @@ def test_create_reusable_webdriver(self): class FakeCmdExecutor(mock): _url = "http://127.0.0.1:9999/dummy" class FakeWebDriver(mock): - command_executor = FakeCmdExecutor session_id = "dummy_sid" def __init__(self): mock.__init__(self) capt_data['webdriver'] = self webdriver.FakeWebDriver = FakeWebDriver + webdriver.FakeWebDriver.command_executor = FakeCmdExecutor class FakeReusable(mock): - session_id = None - curl = None def __init__(self, command_executor, sid, **kwargs): FakeReusable.session_id = sid FakeReusable.curl = command_executor keywords._browsermanagement._ReusableDriver = FakeReusable try: - index = bm.create_webdriver('FakeWebDriver', 'fake') + bm.create_webdriver('FakeWebDriver', 'fake') (sid, url) = bm.save_webdriver(None) self.assertEquals(sid, FakeWebDriver.session_id) self.assertEquals(url, FakeCmdExecutor._url) - #self.assertEquals(sid, bm._current_browser().session_id) - #self.assertEquals(url, bm._current_browser().command_executor._url) + self.assertEquals(sid, bm._current_browser().session_id) + self.assertEquals(url, bm._current_browser().command_executor._url) old_index = bm._cache.current_index - bm.restore_webdriver(session_id=sid, session_url=url) - self.assertNotEqual(old_index, bm._cache.current_index) + new_index = bm.restore_webdriver(session_id=sid, session_url=url) + self.assertNotEqual(old_index, new_index) self.assertEquals(sid, FakeReusable.session_id) self.assertEquals(url, FakeReusable.curl) finally: From 2b73925f3ff95c02382468aa0c860e95d7718ec0 Mon Sep 17 00:00:00 2001 From: Jari Nurminen Date: Fri, 25 Nov 2016 23:24:03 +0100 Subject: [PATCH 4/8] webdriver process management --- .../keywords/_browsermanagement.py | 58 +++++++++++++++---- test/acceptance/create_webdriver.robot | 10 ++-- test/resources/testserver/testserver.py | 47 +++++++++++++-- test/run_tests.py | 6 +- test/unit/keywords/test_browsermanagement.py | 2 +- 5 files changed, 97 insertions(+), 26 deletions(-) diff --git a/src/Selenium2Library/keywords/_browsermanagement.py b/src/Selenium2Library/keywords/_browsermanagement.py index 076d3064d..a658f459c 100644 --- a/src/Selenium2Library/keywords/_browsermanagement.py +++ b/src/Selenium2Library/keywords/_browsermanagement.py @@ -1,6 +1,7 @@ -import os +import os, signal import robot from robot.errors import DataError +from robot.api import logger from selenium import webdriver from Selenium2Library import webdrivermonkeypatches from Selenium2Library.utils import BrowserCache @@ -42,12 +43,22 @@ def __init__(self, command_executor, sid, keep_alive=False, file_detector=None): proxy=None, keep_alive=keep_alive, file_detector=file_detector) + def stop_client(self): + if self.pid is not None and self.pid != '': + logger.info("Stopping driver for session '%s' with PID: %s" % (self.session_id, self.pid)) + try: + os.kill(int(self.pid), signal.SIGTERM) + except Exception: + logger.warn("Failed to stop the driver with PID '%s' - please kill the process manually " % (self.pid)) + else: + logger.info("No driver process found for session '%s'" % (self.session_id)) + def start_session(self, desired_capabilities, browser_profile=None): self.session_id = self.reuse_session_id - #print("%s" % self.session_id) - self.command_executor._commands["GetSession"] = ('GET', '/session/$sessionId') - response = self.execute("GetSession") - #print("%s" % response) + logger.info("Reconnecting to session '%s' " % self.session_id) + self.command_executor._commands["GetSessionData"] = ('GET', '/session/$sessionId') + response = self.execute("GetSessionData") + logger.debug("Reconnect OK, session data: '%s' " % response) self.capabilities = response['value'] self.w3c = "specificationLevel" in self.capabilities @@ -237,10 +248,25 @@ def save_webdriver(self, file=''): See also `Restore Webdriver` """ self._info("Saving WebDriver session data") - sid = self._current_browser().session_id - curl = self._current_browser().command_executor._url + driver_service_attr_names = ['service', 'iedriver'] + cb = self._current_browser() + sid = cb.session_id + curl = cb.command_executor._url + # get pid if we are in a restored this session + pid = cb.__dict__.get("pid", None) + if pid is None: + # get driver executable PID + # this part is browser-dependant, currently tested only with: FF, GC & IE + self._debug("'%s'" % self._current_browser().__dict__) + for srvc_name in driver_service_attr_names: + srvc = cb.__dict__.get(srvc_name, None) + if srvc is not None: + pid = srvc.process.pid + break + if pid is None: + self._info("Could not find driver process - we will not attempt to kill the driver process after re-connecting to it with restore_session() ") if file is None: - return (sid, curl) + return (sid, curl, pid) elif file == "": inv_dict = {v: k for k, v in self._cache._aliases.items()} session_name = inv_dict.get(self._cache.current_index, self._cache.current_index) @@ -249,10 +275,12 @@ def save_webdriver(self, file=''): session_file = open(file, 'w+') session_file.write("%s\n" % sid) session_file.write("%s\n" % curl) + if pid is not None: + session_file.write("%s\n" % pid) session_file.close() return file - def restore_webdriver(self, alias=None, file=None, session_id=None, session_url=None, delete_file=True): + def restore_webdriver(self, alias=None, file=None, session_id=None, session_url=None, session_pid=None, delete_file=True): """Connects to an already opened web-driver session. Restores a Webdriver Session that has been saved using the `Save Webdriver` KW. @@ -297,11 +325,17 @@ def restore_webdriver(self, alias=None, file=None, session_id=None, session_url= session_file = open(file, 'r+') saved_sid = session_file.readline().replace("\n", "").replace("\r", "") saved_curl = session_file.readline().replace("\n", "").replace("\r", "") + session_pid = session_file.readline().replace("\n", "").replace("\r", "") session_file.close() self._debug("Reconnecting to '%s' with session id '%s' - alias: '%s' " % (saved_curl, saved_sid, alias)) - driver = _ReusableDriver(saved_curl, saved_sid) - if delete_file and session_id is None and session_url is None: - os.remove(file) + try: + driver = _ReusableDriver(saved_curl, saved_sid) + driver.pid = session_pid + except: + raise + finally: + if delete_file and session_id is None and session_url is None: + os.remove(file) return self._cache.register(driver, alias) def switch_browser(self, index_or_alias): diff --git a/test/acceptance/create_webdriver.robot b/test/acceptance/create_webdriver.robot index c6732223b..16a705086 100644 --- a/test/acceptance/create_webdriver.robot +++ b/test/acceptance/create_webdriver.robot @@ -44,8 +44,8 @@ Create Reusable Webdriver with alias Restore Webdriver alias=dummy file=${file} ${file}= Save Webdriver Restore Webdriver alias=dummy2 file=session_dummy.tmp - ${sid} ${url} = Save Webdriver ${None} - Restore Webdriver alias=dummy session_id=${sid} session_url=${url} + ${sid} ${url} ${pid} = Save Webdriver ${None} + Restore Webdriver alias=dummy session_id=${sid} session_url=${url} session_pid=${pid} Go To ${FRONT PAGE} Page Should Contain needle [Teardown] Close Browser @@ -65,8 +65,8 @@ Create Reusable Webdriver without alias Restore Webdriver file=${file} ${file}= Save Webdriver ${CURDIR}/dummy.tmp Restore Webdriver file=${file} - ${sid} ${url} = Save Webdriver ${None} - Restore Webdriver session_id=${sid} session_url=${url} + ${sid} ${url} ${pid} = Save Webdriver ${None} + Restore Webdriver session_id=${sid} session_url=${url} session_pid=${pid} Go To ${FRONT PAGE} Page Should Contain needle [Teardown] Close Browser @@ -83,7 +83,7 @@ Create Reusable Webdriver with runtime errors run keyword and expect error *No browser is open* Save Webdriver run keyword and expect error *URLError*refused* Restore Webdriver session_id=foo session_url=http://127.0.0.1:52470/foo Create Webdriver ${DRIVER_NAME} kwargs=${KWARGS} - ${sid} ${url} = Save Webdriver ${None} + ${sid} ${url} ${pid} = Save Webdriver ${None} run keyword and expect error ** Restore Webdriver session_id=bogus session_url=${url} [Teardown] Close Browser diff --git a/test/resources/testserver/testserver.py b/test/resources/testserver/testserver.py index c67ca3c4a..bc98b0a87 100644 --- a/test/resources/testserver/testserver.py +++ b/test/resources/testserver/testserver.py @@ -4,17 +4,24 @@ import BaseHTTPServer import httplib import os - +import socket class StoppableHttpRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): """http request handler with QUIT stopping the server""" + def setup(self): + SimpleHTTPServer.SimpleHTTPRequestHandler.setup(self) + self.rfile.close() + self.wfile.close() + self.rfile = HttpEchoer(self.connection._sock, 'rb', self.rbufsize) + self.wfile = HttpEchoer(self.connection._sock, 'wb', self.wbufsize) + def do_QUIT(self): """send 200 OK response, and set server.stop to True""" self.send_response(200) self.end_headers() self.server.stop = True - + def do_POST(self): # We could also process paremeters here using something like below. # length = self.headers['Content-Length'] @@ -63,12 +70,15 @@ def send_head(self): self.send_response(200) self.send_header("Content-type", ctype) fs = os.fstat(f.fileno()) - self.send_header("Content-Length", str(fs[6])) + # chrome hangs if this server runs on windows + # it probably waits for more data -> os.fstat() does not return correct length? + # we are responding with HTTP 1.0 = we do not need content-length header + #self.send_header("Content-Length", str(fs[6])) self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) + #self.send_header("Connection", "close") self.end_headers() return f - class StoppableHttpServer(BaseHTTPServer.HTTPServer): """http server that reacts to self.stop flag""" @@ -78,6 +88,32 @@ def serve_forever(self): while not self.stop: self.handle_request() +class HttpEchoer(socket._fileobject): + # echoes all HTTP traffic to a file if + # the server output is set to a file in run_tests.py + def read(self, size=-1): + data = super(HttpEchoer, self).read(size) + sys.stdout.write("-->: %s" % data) + sys.stdout.flush() + return data + + def readline(self, size=-1): + data = super(HttpEchoer, self).readline(size) + sys.stdout.write("-->: %s" % data) + sys.stdout.flush() + return data + + def readlines(self, sizehint=0): + data = super(HttpEchoer, self).readlines(sizehint) + sys.stdout.write("-->: %s" % data) + sys.stdout.flush() + return data + + def write(self, data): + sys.stdout.write("<--: %s" % data) + sys.stdout.flush() + super(HttpEchoer, self).write(data) + def stop_server(port=7000): """send QUIT request to http server running on localhost:""" conn = httplib.HTTPConnection("localhost:%d" % port) @@ -89,8 +125,7 @@ def start_server(port=7000): os.chdir(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), '..')) server = StoppableHttpServer(('', port), StoppableHttpRequestHandler) server.serve_forever() - - + if __name__ == '__main__': import sys if len(sys.argv) != 2 or sys.argv[1] not in [ 'start', 'stop' ]: diff --git a/test/run_tests.py b/test/run_tests.py index dc90d3ea2..f60163778 100755 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -17,7 +17,8 @@ '--report', 'none', '--log', 'none', #'--suite', 'Acceptance.Create_Webdriver', - #'-t', '*Webdriver*', + #'-t', '*Functioning*', + #'-t', '*Reusable*', #'-b', 'debug.log', '--loglevel', 'DEBUG', '--pythonpath', '%(pythonpath)s', @@ -48,8 +49,9 @@ def acceptance_tests(interpreter, browser, args): def start_http_server(): server_output = TemporaryFile() + #server_output = open("server.log", 'w+', buffering=False) Popen(['python', env.HTTP_SERVER_FILE ,'start'], - stdout=server_output, stderr=server_output) + bufsize=0, stdout=server_output, stderr=server_output) def execute_tests(runner, args): if not os.path.exists(env.RESULTS_DIR): diff --git a/test/unit/keywords/test_browsermanagement.py b/test/unit/keywords/test_browsermanagement.py index 274b7e89a..48d963d1b 100644 --- a/test/unit/keywords/test_browsermanagement.py +++ b/test/unit/keywords/test_browsermanagement.py @@ -143,7 +143,7 @@ def __init__(self, command_executor, sid, **kwargs): keywords._browsermanagement._ReusableDriver = FakeReusable try: bm.create_webdriver('FakeWebDriver', 'fake') - (sid, url) = bm.save_webdriver(None) + (sid, url, pid) = bm.save_webdriver(None) self.assertEquals(sid, FakeWebDriver.session_id) self.assertEquals(url, FakeCmdExecutor._url) self.assertEquals(sid, bm._current_browser().session_id) From ea709a8df2ee60104ca3459847f4ac28dc1cb23f Mon Sep 17 00:00:00 2001 From: Jari Nurminen Date: Tue, 2 May 2017 07:19:08 +0200 Subject: [PATCH 5/8] fixed according to review comments --- .../keywords/browsermanagement.py | 156 +++++++++--------- test/resources/testserver/testserver.py | 13 +- test/run_tests.py | 3 +- test/unit/keywords/test_browsermanagement.py | 71 +++----- 4 files changed, 115 insertions(+), 128 deletions(-) diff --git a/src/Selenium2Library/keywords/browsermanagement.py b/src/Selenium2Library/keywords/browsermanagement.py index b74840022..dacde0aaf 100644 --- a/src/Selenium2Library/keywords/browsermanagement.py +++ b/src/Selenium2Library/keywords/browsermanagement.py @@ -1,10 +1,7 @@ -#Jari -import os, signal -import robot -from robot.api import logger -#Jari - import os.path +import os +import signal + import time import types @@ -12,20 +9,20 @@ from robot.utils import secs_to_timestr, timestr_to_secs from selenium import webdriver -from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +#from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver from selenium.common.exceptions import NoSuchWindowException from Selenium2Library.utils import BrowserCache from Selenium2Library.locators import WindowManager +from Selenium2Library.keywords.logging import LoggingKeywords from .keywordgroup import KeywordGroup - -from selenium.webdriver.remote.errorhandler import ErrorHandler -from selenium.webdriver.remote.remote_connection import RemoteConnection -from selenium.webdriver.remote.switch_to import SwitchTo -from selenium.webdriver.remote.mobile import Mobile -from selenium.webdriver.remote.file_detector import FileDetector, LocalFileDetector +#from selenium.webdriver.remote.errorhandler import ErrorHandler +#from selenium.webdriver.remote.remote_connection import RemoteConnection +#from selenium.webdriver.remote.switch_to import SwitchTo +#from selenium.webdriver.remote.mobile import Mobile +#from selenium.webdriver.remote.file_detector import FileDetector, LocalFileDetector ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) FIREFOX_PROFILE_DIR = os.path.join(ROOT_DIR, 'resources', 'firefoxprofile') @@ -36,17 +33,18 @@ 'googlechrome': "_make_chrome", 'gc': "_make_chrome", 'chrome': "_make_chrome", - 'opera' : "_make_opera", - 'phantomjs' : "_make_phantomjs", - 'htmlunit' : "_make_htmlunit", - 'htmlunitwithjs' : "_make_htmlunitwithjs", + 'opera': "_make_opera", + 'phantomjs': "_make_phantomjs", + 'htmlunit': "_make_htmlunit", + 'htmlunitwithjs': "_make_htmlunitwithjs", 'android': "_make_android", 'iphone': "_make_iphone", 'safari': "_make_safari", 'edge': "_make_edge" - } + } -class _ReusableDriver(webdriver.Remote): + +class _ReusableDriver(webdriver.Remote, LoggingKeywords): def __init__(self, command_executor, sid, keep_alive=False, file_detector=None): self.reuse_session_id = sid @@ -57,26 +55,25 @@ def __init__(self, command_executor, sid, keep_alive=False, file_detector=None): def stop_client(self): if self.pid is not None and self.pid != '': - logger.info("Stopping driver for session '%s' with PID: %s" % (self.session_id, self.pid)) + self._info("Stopping driver for session '%s' with PID: %s" % (self.session_id, self.pid)) try: os.kill(int(self.pid), signal.SIGTERM) - except Exception: - logger.warn("Failed to stop the driver with PID '%s' - please kill the process manually " % (self.pid)) + except EnvironmentError: + self._warn("Failed to stop the driver with PID '%s' - please kill the process manually " % (self.pid)) else: - logger.info("No driver process found for session '%s'" % (self.session_id)) + self._info("No driver process found for session '%s'" % (self.session_id)) def start_session(self, desired_capabilities, browser_profile=None): self.session_id = self.reuse_session_id - logger.info("Reconnecting to session '%s' " % self.session_id) + self._info("Reconnecting to session '%s' " % self.session_id) self.command_executor._commands["GetSessionData"] = ('GET', '/session/$sessionId') response = self.execute("GetSessionData") - logger.debug("Reconnect OK, session data: '%s' " % response) + self._debug("Reconnect OK, session data: '%s' " % response) self.capabilities = response['value'] self.w3c = "specificationLevel" in self.capabilities class BrowserManagementKeywords(KeywordGroup): - def __init__(self): self._cache = BrowserCache() self._window_manager = WindowManager() @@ -105,8 +102,8 @@ def close_browser(self): % self._cache.current.session_id) self._cache.close() - def open_browser(self, url, browser='firefox', alias=None,remote_url=False, - desired_capabilities=None,ff_profile_dir=None): + def open_browser(self, url, browser='firefox', alias=None, remote_url=False, + desired_capabilities=None, ff_profile_dir=None): """Opens a new browser instance to given URL. Returns the index of this browser instance which can be used later to @@ -157,11 +154,11 @@ def open_browser(self, url, browser='firefox', alias=None,remote_url=False, """ if remote_url: self._info("Opening browser '%s' to base url '%s' through remote server at '%s'" - % (browser, url, remote_url)) + % (browser, url, remote_url)) else: self._info("Opening browser '%s' to base url '%s'" % (browser, url)) browser_name = browser - browser = self._make_browser(browser_name,desired_capabilities,ff_profile_dir,remote_url) + browser = self._make_browser(browser_name, desired_capabilities, ff_profile_dir, remote_url) try: browser.get(url) except: @@ -230,6 +227,10 @@ def create_webdriver(self, driver_name, alias=None, kwargs={}, **init_kwargs): def save_webdriver(self, file=''): """Stores the current web-driver session. + NOTE: This KW is not for production use. The `Save Webdriver` and `Restore Webdriver` KW's are there to + speed up the test case development i.e. instead of always opening new browser window you can reconnect to + an already opened Browser window. + This KW makes the WebDriver sessions persistent. I.e. you can store the session(s) and leave the browser window(s) open after a pybot (test suite) execution. On the next pybot execution you can reconnect to those already opened browser windows and avoid that slow opening of new browser window(s) step and possibly other @@ -242,9 +243,9 @@ def save_webdriver(self, file=''): The file name is taken from the _file_ argument. This accepts plain file name (e.g. "mysession.txt") and path-to-file (e.g. "/tmp/mysession.txt") format. The _file_ argument has following special values: - - empty string i.e. ``${EMPTY}`` (default): the session is stored in the current execution directory in a file called - "session_.tmp" (if the connection has an alias) or "session_.tmp" (if the connection does - not have an alias). + - empty string i.e. ``${EMPTY}`` (default): the session is stored in the current execution directory in a + file called "session_.tmp" (if the connection has an alias) or "session_.tmp" + (if the connection does not have an alias). - None i.e. ``${None}``: no file is created. Returns a tuplet where the 1st value is the session ID and the 2nd value is the Webdriver URL. @@ -277,7 +278,9 @@ def save_webdriver(self, file=''): pid = srvc.process.pid break if pid is None: - self._info("Could not find driver process - we will not attempt to kill the driver process after re-connecting to it with restore_session() ") + self._info( + "Could not find driver process - we will not attempt to " + + " kill the driver process after re-connecting to it with restore_session() ") if file is None: return (sid, curl, pid) elif file == "": @@ -293,9 +296,14 @@ def save_webdriver(self, file=''): session_file.close() return file - def restore_webdriver(self, alias=None, file=None, session_id=None, session_url=None, session_pid=None, delete_file=True): + def restore_webdriver(self, alias=None, file=None, session_id=None, session_url=None, session_pid=None, + delete_file=True): """Connects to an already opened web-driver session. + NOTE: This KW is not for production use. The `Save Webdriver` and `Restore Webdriver` KW's are there to + speed up the test case development i.e. instead of always opening new browser window you can reconnect to + an already opened Browser window. + Restores a Webdriver Session that has been saved using the `Save Webdriver` KW. Optional _alias_ is an alias for the browser instance and it can be used @@ -303,7 +311,7 @@ def restore_webdriver(self, alias=None, file=None, session_id=None, session_url= Browser` for more details. The session can be restored by: - - using the default session file (e.g. with `alias=foo` we try to read the session info from a file called + - using the default session file (e.g. with `alias=foo` we try to read the session info from a file called "session_foo.tmp") - using explicit file: file-name is passed in _file_ argument. See `Save Webdriver` KW for _file_ argument documentation @@ -317,11 +325,11 @@ def restore_webdriver(self, alias=None, file=None, session_id=None, session_url= example. Examples: - | Restore Webdriver | alias=foo | | # read from "session_foo.tmp" and registered with alias "foo" | - | Restore Webdriver | alias=bar | file=foo.tmp | # read from "foo.tmp" and registered with alias "bar" | - | Restore Webdriver | | file=foo.tmp | # read from "foo.tmp" and registered without alias | - | #do not use file and register without alias | | | | - | Restore Webdriver | session_id=xxx | session_url=http://127.0.0.1:9999/hub | | + | Restore Webdriver | alias=foo | | #read from "session_foo.tmp" and register with alias "foo" | + | Restore Webdriver | alias=bar | file=foo.tmp | #read from "foo.tmp" and register with alias "bar" | + | Restore Webdriver | | file=foo.tmp | #read from "foo.tmp" and register without alias | + | #do not use file and register without alias | | | | + | Restore Webdriver | session_id=x | session_url=http://127.0.0.1:9999/hub | | """ self._info("Restoring WebDriver session") if session_id is not None or session_url is not None: @@ -379,7 +387,7 @@ def switch_browser(self, index_or_alias): try: self._cache.switch(index_or_alias) self._debug('Switched to browser with Selenium session id %s' - % self._cache.current.session_id) + % self._cache.current.session_id) except (RuntimeError, DataError): # RF 2.6 uses RE, earlier DE raise RuntimeError("No browser with index or alias '%s' found." % index_or_alias) @@ -536,7 +544,7 @@ def get_title(self): def location_should_be(self, url): """Verifies that current URL is exactly `url`.""" actual = self.get_location() - if actual != url: + if actual != url: raise AssertionError("Location should have been '%s' but was '%s'" % (url, actual)) self._info("Current location is '%s'." % url) @@ -576,7 +584,7 @@ def title_should_be(self, title): actual = self.get_title() if actual != title: raise AssertionError("Title should have been '%s' but was '%s'" - % (title, actual)) + % (title, actual)) self._info("Page title is '%s'." % title) # Public, navigation @@ -680,7 +688,6 @@ def set_selenium_implicit_wait(self, seconds): browser.implicitly_wait(self._implicit_wait_in_secs) return old_wait - def set_browser_implicit_wait(self, seconds): """Sets current browser's implicit wait in seconds. @@ -720,71 +727,71 @@ def _make_browser(self, browser_name, desired_capabilities=None, return browser - - def _make_ff(self , remote , desired_capabilites , profile_dir): + def _make_ff(self, remote, desired_capabilites, profile_dir): if not profile_dir: profile_dir = FIREFOX_PROFILE_DIR profile = webdriver.FirefoxProfile(profile_dir) if remote: - browser = self._create_remote_web_driver(webdriver.DesiredCapabilities.FIREFOX , - remote , desired_capabilites , profile) + browser = self._create_remote_web_driver(webdriver.DesiredCapabilities.FIREFOX, + remote, desired_capabilites, profile) else: browser = webdriver.Firefox(firefox_profile=profile) return browser - def _make_ie(self , remote , desired_capabilities , profile_dir): + def _make_ie(self, remote, desired_capabilities, profile_dir): return self._generic_make_browser(webdriver.Ie, - webdriver.DesiredCapabilities.INTERNETEXPLORER, remote, desired_capabilities) + webdriver.DesiredCapabilities.INTERNETEXPLORER, remote, desired_capabilities) - def _make_chrome(self , remote , desired_capabilities , profile_dir): + def _make_chrome(self, remote, desired_capabilities, profile_dir): return self._generic_make_browser(webdriver.Chrome, - webdriver.DesiredCapabilities.CHROME, remote, desired_capabilities) + webdriver.DesiredCapabilities.CHROME, remote, desired_capabilities) - def _make_opera(self , remote , desired_capabilities , profile_dir): + def _make_opera(self, remote, desired_capabilities, profile_dir): return self._generic_make_browser(webdriver.Opera, - webdriver.DesiredCapabilities.OPERA, remote, desired_capabilities) + webdriver.DesiredCapabilities.OPERA, remote, desired_capabilities) - def _make_phantomjs(self , remote , desired_capabilities , profile_dir): + def _make_phantomjs(self, remote, desired_capabilities, profile_dir): return self._generic_make_browser(webdriver.PhantomJS, - webdriver.DesiredCapabilities.PHANTOMJS, remote, desired_capabilities) + webdriver.DesiredCapabilities.PHANTOMJS, remote, desired_capabilities) - def _make_htmlunit(self , remote , desired_capabilities , profile_dir): + def _make_htmlunit(self, remote, desired_capabilities, profile_dir): return self._generic_make_browser(webdriver.Remote, - webdriver.DesiredCapabilities.HTMLUNIT, remote, desired_capabilities) + webdriver.DesiredCapabilities.HTMLUNIT, remote, desired_capabilities) - def _make_htmlunitwithjs(self , remote , desired_capabilities , profile_dir): + def _make_htmlunitwithjs(self, remote, desired_capabilities, profile_dir): return self._generic_make_browser(webdriver.Remote, - webdriver.DesiredCapabilities.HTMLUNITWITHJS, remote, desired_capabilities) + webdriver.DesiredCapabilities.HTMLUNITWITHJS, remote, desired_capabilities) - def _make_android(self , remote , desired_capabilities , profile_dir): + def _make_android(self, remote, desired_capabilities, profile_dir): return self._generic_make_browser(webdriver.Remote, - webdriver.DesiredCapabilities.ANDROID, remote, desired_capabilities) + webdriver.DesiredCapabilities.ANDROID, remote, desired_capabilities) - def _make_iphone(self , remote , desired_capabilities , profile_dir): + def _make_iphone(self, remote, desired_capabilities, profile_dir): return self._generic_make_browser(webdriver.Remote, - webdriver.DesiredCapabilities.IPHONE, remote, desired_capabilities) + webdriver.DesiredCapabilities.IPHONE, remote, desired_capabilities) - def _make_safari(self , remote , desired_capabilities , profile_dir): + def _make_safari(self, remote, desired_capabilities, profile_dir): return self._generic_make_browser(webdriver.Safari, - webdriver.DesiredCapabilities.SAFARI, remote, desired_capabilities) + webdriver.DesiredCapabilities.SAFARI, remote, desired_capabilities) - def _make_edge(self , remote , desired_capabilities , profile_dir): + def _make_edge(self, remote, desired_capabilities, profile_dir): if hasattr(webdriver, 'Edge'): return self._generic_make_browser(webdriver.Edge, - webdriver.DesiredCapabilities.EDGE, remote, desired_capabilities) + webdriver.DesiredCapabilities.EDGE, remote, desired_capabilities) else: - raise ValueError("Edge is not a supported browser with your version of Selenium python library. Please, upgrade to minimum required version 2.47.0.") + raise ValueError( + "Edge is not a supported browser with your version of Selenium python library. Please, upgrade to minimum required version 2.47.0.") - def _generic_make_browser(self, webdriver_type , desired_cap_type, remote_url, desired_caps): + def _generic_make_browser(self, webdriver_type, desired_cap_type, remote_url, desired_caps): '''most of the make browser functions just call this function which creates the appropriate web-driver''' if not remote_url: browser = webdriver_type() else: - browser = self._create_remote_web_driver(desired_cap_type,remote_url , desired_caps) + browser = self._create_remote_web_driver(desired_cap_type, remote_url, desired_caps) return browser - def _create_remote_web_driver(self , capabilities_type , remote_url , desired_capabilities=None , profile=None): + def _create_remote_web_driver(self, capabilities_type, remote_url, desired_capabilities=None, profile=None): '''parses the string based desired_capabilities if neccessary and creates the associated remote web driver''' @@ -796,7 +803,7 @@ def _create_remote_web_driver(self , capabilities_type , remote_url , desired_ca desired_capabilities_object.update(desired_capabilities or {}) return webdriver.Remote(desired_capabilities=desired_capabilities_object, - command_executor=str(remote_url), browser_profile=profile) + command_executor=str(remote_url), browser_profile=profile) def _parse_capabilities_string(self, capabilities_string): '''parses the string based desired_capabilities which should be in the form @@ -823,6 +830,7 @@ def execute(self, driver_command, params=None): if speed > 0: time.sleep(speed) return result + if not hasattr(browser, '_base_execute'): browser._base_execute = browser.execute browser.execute = types.MethodType(execute, browser) diff --git a/test/resources/testserver/testserver.py b/test/resources/testserver/testserver.py index 2839de737..8c321c395 100644 --- a/test/resources/testserver/testserver.py +++ b/test/resources/testserver/testserver.py @@ -19,13 +19,6 @@ class StoppableHttpRequestHandler(SimpleHTTPRequestHandler): """http request handler with QUIT stopping the server""" - def setup(self): - SimpleHTTPServer.SimpleHTTPRequestHandler.setup(self) - self.rfile.close() - self.wfile.close() - self.rfile = HttpEchoer(self.connection._sock, 'rb', self.rbufsize) - self.wfile = HttpEchoer(self.connection._sock, 'wb', self.wbufsize) - def do_QUIT(self): self.send_response(200) self.end_headers() @@ -35,20 +28,24 @@ def do_QUIT(self): def do_POST(self): self.do_GET() + class ThreadingHttpServer(ThreadingMixIn, HTTPServer): pass + def stop_server(port=7000): """send QUIT request to http server running on localhost:""" conn = HTTPConnection("localhost:%d" % port) conn.request("QUIT", "/") conn.getresponse() + def start_server(path, port=7000): os.chdir(path) server = ThreadingHttpServer(('', port), StoppableHttpRequestHandler) server.serve_forever() + if __name__ == '__main__': if len(sys.argv) != 2 or sys.argv[1] not in ['start', 'stop']: print('usage: %s start|stop' % sys.argv[0]) @@ -57,4 +54,4 @@ def start_server(path, port=7000): path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) start_server(path) else: - stop_server() + stop_server() \ No newline at end of file diff --git a/test/run_tests.py b/test/run_tests.py index f5dc79acf..8a80033d8 100755 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -73,11 +73,10 @@ '--log', 'NONE', '--loglevel', 'DEBUG', '--pythonpath', os.pathsep.join([SRC_DIR, TEST_LIBS_DIR]), - #'--suite', 'Acceptance.Create_Webdriver', '--suite', 'Acceptance.Create_Webdriver', #'-t', '*Functioning*', #'-t', '*Reusable*', - #'-b', 'debug.log', + '-b', 'debug.log', ] REBOT_OPTIONS = [ '--outputdir', RESULTS_DIR, diff --git a/test/unit/keywords/test_browsermanagement.py b/test/unit/keywords/test_browsermanagement.py index 24405d69d..5ca3306aa 100644 --- a/test/unit/keywords/test_browsermanagement.py +++ b/test/unit/keywords/test_browsermanagement.py @@ -1,13 +1,9 @@ import unittest -#Jari -## from Selenium2Library.keywords._browsermanagement import _BrowserManagementKeywords -from Selenium2Library import keywords -#Jari from mockito import when, mock, verify, verifyNoMoreInteractions from selenium import webdriver -from Selenium2Library.keywords.browsermanagement import BrowserManagementKeywords +from Selenium2Library.keywords.browsermanagement import BrowserManagementKeywords, _ReusableDriver class BrowserManagementTests(unittest.TestCase): @@ -108,58 +104,45 @@ def test_bad_browser_name(self): def test_create_webdriver(self): bm = _BrowserManagementWithLoggingStubs() - capt_data = {} - class FakeCmdExecutor(mock): - _url = "http://127.0.0.1:9999/dummy" - class FakeWebDriver(mock): - def __init__(self, some_arg=None): - mock.__init__(self) - capt_data['some_arg'] = some_arg - capt_data['webdriver'] = self + FakeWebDriver = mock() + driver = mock() + when(FakeWebDriver).__call__(some_arg=1).thenReturn(driver) + when(FakeWebDriver).__call__(some_arg=2).thenReturn(driver) webdriver.FakeWebDriver = FakeWebDriver - webdriver.FakeWebDriver.command_executor = FakeCmdExecutor try: - index = bm.create_webdriver('FakeWebDriver', 'fake', some_arg=1) - self.assertEquals(capt_data['some_arg'], 1) - self.assertEquals(capt_data['webdriver'], bm._current_browser()) - self.assertEquals(capt_data['webdriver'], bm._cache.get_connection(index)) - self.assertEquals(capt_data['webdriver'], bm._cache.get_connection('fake')) - capt_data.clear() - my_kwargs = {'some_arg':2} - bm.create_webdriver('FakeWebDriver', kwargs=my_kwargs) - self.assertEquals(capt_data['some_arg'], 2) + index = bm.create_webdriver('FakeWebDriver', 'fake1', some_arg=1) + self.assertEqual(bm._current_browser(), driver) + self.assertEquals(bm._cache.get_connection(index), driver) + self.assertEquals(bm._cache.get_connection('fake1'), driver) + my_kwargs = {'some_arg': 2} + bm.create_webdriver('FakeWebDriver', 'fake2', kwargs=my_kwargs) + self.assertEquals(bm._current_browser(), driver) finally: del webdriver.FakeWebDriver def test_create_reusable_webdriver(self): bm = _BrowserManagementWithLoggingStubs() - capt_data = {} - class FakeCmdExecutor(mock): - _url = "http://127.0.0.1:9999/dummy" - class FakeWebDriver(mock): - session_id = "dummy_sid" - def __init__(self): - mock.__init__(self) - capt_data['webdriver'] = self + FakeCmdExecutor = mock() + FakeCmdExecutor._url = "http://127.0.0.1:9999/dummy" + FakeWebDriver = mock() + FakeWebDriver.command_executor = FakeCmdExecutor + FakeWebDriver.session_id = "dummy_id" + when(FakeWebDriver).__call__(some_arg=1).thenReturn(FakeWebDriver) + when(FakeWebDriver).__call__(some_arg=2).thenReturn(FakeWebDriver) + when(_ReusableDriver).execute("GetSessionData").thenReturn({'value': 'dummy'}) webdriver.FakeWebDriver = FakeWebDriver - webdriver.FakeWebDriver.command_executor = FakeCmdExecutor - class FakeReusable(mock): - def __init__(self, command_executor, sid, **kwargs): - FakeReusable.session_id = sid - FakeReusable.curl = command_executor - keywords.browsermanagement._ReusableDriver = FakeReusable try: - bm.create_webdriver('FakeWebDriver', 'fake') + bm.create_webdriver('FakeWebDriver', 'fake', some_arg=1) + self.assertEquals(FakeWebDriver.session_id, bm._current_browser().session_id) + self.assertEquals(FakeCmdExecutor._url, bm._current_browser().command_executor._url) (sid, url, pid) = bm.save_webdriver(None) - self.assertEquals(sid, FakeWebDriver.session_id) - self.assertEquals(url, FakeCmdExecutor._url) - self.assertEquals(sid, bm._current_browser().session_id) - self.assertEquals(url, bm._current_browser().command_executor._url) + self.assertEquals(FakeWebDriver.session_id, sid) + self.assertEquals(FakeCmdExecutor._url, url) old_index = bm._cache.current_index new_index = bm.restore_webdriver(session_id=sid, session_url=url) self.assertNotEqual(old_index, new_index) - self.assertEquals(sid, FakeReusable.session_id) - self.assertEquals(url, FakeReusable.curl) + self.assertEquals(sid, FakeWebDriver.session_id) + self.assertEquals(url, FakeCmdExecutor._url) finally: del webdriver.FakeWebDriver From 1fd33720b6dd996d04dfbbd97cd8f7b9b10eae86 Mon Sep 17 00:00:00 2001 From: Jari Nurminen Date: Tue, 2 May 2017 07:24:43 +0200 Subject: [PATCH 6/8] removed commented imports --- src/Selenium2Library/keywords/browsermanagement.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Selenium2Library/keywords/browsermanagement.py b/src/Selenium2Library/keywords/browsermanagement.py index dacde0aaf..ea0c32351 100644 --- a/src/Selenium2Library/keywords/browsermanagement.py +++ b/src/Selenium2Library/keywords/browsermanagement.py @@ -9,7 +9,6 @@ from robot.utils import secs_to_timestr, timestr_to_secs from selenium import webdriver -#from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver from selenium.common.exceptions import NoSuchWindowException from Selenium2Library.utils import BrowserCache @@ -18,12 +17,6 @@ from .keywordgroup import KeywordGroup -#from selenium.webdriver.remote.errorhandler import ErrorHandler -#from selenium.webdriver.remote.remote_connection import RemoteConnection -#from selenium.webdriver.remote.switch_to import SwitchTo -#from selenium.webdriver.remote.mobile import Mobile -#from selenium.webdriver.remote.file_detector import FileDetector, LocalFileDetector - ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) FIREFOX_PROFILE_DIR = os.path.join(ROOT_DIR, 'resources', 'firefoxprofile') BROWSER_NAMES = {'ff': "_make_ff", From 39f42285f01953264381a54c252caf4ca6f415c4 Mon Sep 17 00:00:00 2001 From: Jari Nurminen Date: Tue, 2 May 2017 07:26:49 +0200 Subject: [PATCH 7/8] one PEP-8 fix --- src/Selenium2Library/keywords/browsermanagement.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Selenium2Library/keywords/browsermanagement.py b/src/Selenium2Library/keywords/browsermanagement.py index ea0c32351..69d4235ea 100644 --- a/src/Selenium2Library/keywords/browsermanagement.py +++ b/src/Selenium2Library/keywords/browsermanagement.py @@ -38,6 +38,7 @@ class _ReusableDriver(webdriver.Remote, LoggingKeywords): + def __init__(self, command_executor, sid, keep_alive=False, file_detector=None): self.reuse_session_id = sid From a4029a033dc27bb5970f1c58dce462feff2e732c Mon Sep 17 00:00:00 2001 From: Jari Nurminen Date: Tue, 2 May 2017 07:38:12 +0200 Subject: [PATCH 8/8] removed reusable session specific config from run_tests --- test/run_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/run_tests.py b/test/run_tests.py index 8a80033d8..352877974 100755 --- a/test/run_tests.py +++ b/test/run_tests.py @@ -73,7 +73,7 @@ '--log', 'NONE', '--loglevel', 'DEBUG', '--pythonpath', os.pathsep.join([SRC_DIR, TEST_LIBS_DIR]), - '--suite', 'Acceptance.Create_Webdriver', + #'--suite', 'Acceptance.Create_Webdriver', #'-t', '*Functioning*', #'-t', '*Reusable*', '-b', 'debug.log',