From 9f1c1371282ec7df08ef41d31e6a9759c3e42116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Mon, 23 Oct 2017 14:26:26 +0300 Subject: [PATCH 1/4] Cleanup selecting windows. Contains code and test changes related to #966. Documentation still missing. --- src/SeleniumLibrary/errors.py | 4 + .../keywords/browsermanagement.py | 7 +- src/SeleniumLibrary/locators/windowmanager.py | 128 ++++++++++-------- test/acceptance/windows.robot | 2 +- test/unit/locators/test_windowmanager.py | 124 ++++++----------- 5 files changed, 126 insertions(+), 139 deletions(-) diff --git a/src/SeleniumLibrary/errors.py b/src/SeleniumLibrary/errors.py index 8217f34ec..3f905852e 100644 --- a/src/SeleniumLibrary/errors.py +++ b/src/SeleniumLibrary/errors.py @@ -21,3 +21,7 @@ class SeleniumLibraryException(Exception): class ElementNotFound(SeleniumLibraryException): pass + + +class WindowNotFound(SeleniumLibraryException): + pass diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index 1050b797d..fd87d5677 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -325,7 +325,7 @@ def set_window_position(self, x, y): self.browser.set_window_position(int(x), int(y)) @keyword - def select_window(self, locator=None): + def select_window(self, locator='main'): """Selects the window matching locator and return previous window handle. locator: any of name, title, url, window handle, excluded handle's list, or special words. @@ -368,7 +368,10 @@ def select_window(self, locator=None): @keyword def list_windows(self): - """Return all current window handles as a list.""" + """Return all current window handles as a list. + + Can be used as a list of windows to exclude with `Select Window`. + """ return self.browser.window_handles @keyword diff --git a/src/SeleniumLibrary/locators/windowmanager.py b/src/SeleniumLibrary/locators/windowmanager.py index b23f2965c..0e9d6cafe 100644 --- a/src/SeleniumLibrary/locators/windowmanager.py +++ b/src/SeleniumLibrary/locators/windowmanager.py @@ -16,25 +16,35 @@ from collections import namedtuple +from robot.api import logger from selenium.common.exceptions import (NoSuchWindowException, WebDriverException) from SeleniumLibrary.base import ContextAware +from SeleniumLibrary.errors import WindowNotFound +from SeleniumLibrary.utils import is_string WindowInfo = namedtuple('WindowInfo', 'handle, id, name, title, url') class WindowManager(ContextAware): + _deprecated_locators = { + None: 'main', + 'null': 'main', + '': 'main', + 'popup': 'new', + 'self': 'current' + } def __init__(self, ctx): + ContextAware.__init__(self, ctx) self._strategies = { 'title': self._select_by_title, 'name': self._select_by_name, 'url': self._select_by_url, - None: self._select_by_default + 'default': self._select_by_default } - ContextAware.__init__(self, ctx) def get_window_infos(self): infos = [] @@ -52,44 +62,71 @@ def get_window_infos(self): return infos def select(self, locator): - if locator is not None: - if isinstance(locator, list): - self._select_by_excludes(self.browser, locator) - return - if locator.lower() == "self" or locator.lower() == "current": - return - if locator.lower() == "new" or locator.lower() == "popup": - self._select_by_last_index(self.browser) - return - (prefix, criteria) = self._parse_locator(locator) - strategy = self._strategies.get(prefix) - if strategy is None: - raise ValueError("Window locator with prefix '" + prefix + "' is not supported") - return strategy(self.browser, criteria) + locator = self._handle_deprecated_locators(locator) + if not is_string(locator): + self._select_by_excludes(self.browser, locator) + elif locator.upper() == 'CURRENT': + pass + elif locator.upper() == 'MAIN': + self._select_main_window(self.browser) + elif locator.upper() == 'NEW': + self._select_by_last_index(self.browser) + else: + strategy, locator = self._parse_locator(locator) + self._strategies[strategy](self.browser, locator) + + def _handle_deprecated_locators(self, locator): + if not (is_string(locator) or locator is None): + return locator + normalized = locator.lower() if is_string(locator) else locator + if normalized in self._deprecated_locators: + new = self._deprecated_locators[normalized] + logger.warn("Using '%s' as window locator is deprecated. " + "Use '%s' instead." % (locator, new)) + return new + return locator - def _select_by_title(self, browser, criteria): + def _parse_locator(self, locator): + index = self._get_locator_separator_index(locator) + if index != -1: + prefix = locator[:index].strip() + if prefix in self._strategies: + return prefix, locator[index+1:].lstrip() + return 'default', locator + + def _get_locator_separator_index(self, locator): + if '=' not in locator: + return locator.find(':') + if ':' not in locator: + return locator.find('=') + return min(locator.find('='), locator.find(':')) + + def _select_by_title(self, browser, title): self._select_matching( browser, - lambda window_info: window_info[3].strip().lower() == criteria.lower(), - "Unable to locate window with title '" + criteria + "'") + lambda window_info: window_info.title == title, + "Unable to locate window with title '%s'." % title + ) - def _select_by_name(self, browser, criteria): + def _select_by_name(self, browser, name): self._select_matching( browser, - lambda window_info: window_info[2].strip().lower() == criteria.lower(), - "Unable to locate window with name '" + criteria + "'") + lambda window_info: window_info.name == name, + "Unable to locate window with name '%s'." % name + ) - def _select_by_url(self, browser, criteria): + def _select_by_url(self, browser, url): self._select_matching( browser, - lambda window_info: window_info[4].strip().lower() == criteria.lower(), - "Unable to locate window with URL '" + criteria + "'") + lambda window_info: window_info.url == url, + "Unable to locate window with URL '%s'." % url + ) + + def _select_main_window(self, browser): + handles = browser.window_handles + browser.switch_to.window(handles[0]) def _select_by_default(self, browser, criteria): - if criteria is None or len(criteria) == 0 or criteria.lower() == "null": - handles = browser.window_handles - browser.switch_to.window(handles[0]) - return try: starting_handle = browser.current_window_handle except NoSuchWindowException: @@ -99,21 +136,18 @@ def _select_by_default(self, browser, criteria): if criteria == handle: return for item in self._get_current_window_info(browser)[2:4]: - if item.strip().lower() == criteria.lower(): + if item == criteria: return if starting_handle: browser.switch_to.window(starting_handle) - raise ValueError("Unable to locate window with handle or name or title or URL '" + criteria + "'") + raise WindowNotFound("No window matching handle, name, title or URL " + "'%s' found." % criteria) def _select_by_last_index(self, browser): handles = browser.window_handles - try: - if handles[-1] == browser.current_window_handle: - raise AssertionError("No new window at last index. Please use '@{ex}= | List Windows' + new window trigger + 'Select Window | ${ex}' to find it.") - except IndexError: - raise AssertionError("No window found") - except NoSuchWindowException: - raise AssertionError("Currently no focus window. where are you making a popup window?") + if handles[-1] == browser.current_window_handle: + raise WindowNotFound('Window with last index is same as ' + 'the current window.') browser.switch_to.window(handles[-1]) def _select_by_excludes(self, browser, excludes): @@ -121,20 +155,8 @@ def _select_by_excludes(self, browser, excludes): if handle not in excludes: browser.switch_to.window(handle) return - raise ValueError("Unable to locate new window") - - def _parse_locator(self, locator): - prefix = None - criteria = locator - if locator is not None and len(locator) > 0: - locator_parts = locator.partition('=') - if len(locator_parts[1]) > 0: - prefix = locator_parts[0].strip().lower() - criteria = locator_parts[2].strip() - if prefix is None or prefix == 'name': - if criteria is None or criteria.lower() == 'main': - criteria = '' - return (prefix, criteria) + raise WindowNotFound('No window not matching excludes %s found.' + % excludes) def _select_matching(self, browser, matcher, error): try: @@ -147,7 +169,7 @@ def _select_matching(self, browser, matcher, error): return if starting_handle: browser.switch_to.window(starting_handle) - raise ValueError(error) + raise WindowNotFound(error) def _get_current_window_info(self, browser): try: diff --git a/test/acceptance/windows.robot b/test/acceptance/windows.robot index 719e5344e..ebe35eb7f 100644 --- a/test/acceptance/windows.robot +++ b/test/acceptance/windows.robot @@ -122,7 +122,7 @@ Select Popup Window By Excluded List Select Window By Special Locator [Tags] Known Issue Internet Explorer Cannot Be Executed in IE - ${start}= Select Window self + ${start}= Select Window current Click Link my popup Wait Until New Window Is Open ${parent}= Select Window new diff --git a/test/unit/locators/test_windowmanager.py b/test/unit/locators/test_windowmanager.py index 52f2f6b27..16e5c09ba 100644 --- a/test/unit/locators/test_windowmanager.py +++ b/test/unit/locators/test_windowmanager.py @@ -1,8 +1,10 @@ import unittest import uuid -from mockito import mock +from mockito import mock, when +from robot.api import logger +from SeleniumLibrary.errors import WindowNotFound from SeleniumLibrary.locators import WindowManager @@ -10,12 +12,12 @@ class WindowManagerTests(unittest.TestCase): def test_select_with_invalid_prefix(self): manager = WindowManagerWithMockBrowser() - with self.assertRaises(ValueError) as err: + with self.assertRaises(WindowNotFound) as context: manager.select("something=test1") - self.assertEqual( - str(err), - "Window locator with prefix 'something' is not supported" - ) + self.assertEqual( + str(context.exception), + "No window matching handle, name, title or URL 'something=test1' found." + ) def test_select_by_title(self): manager = WindowManagerWithMockBrowser( @@ -26,15 +28,6 @@ def test_select_by_title(self): manager.select("title=Title 2") self.assertEqual(manager.browser.current_window.name, 'win2') - def test_select_by_title_sloppy_match(self): - manager = WindowManagerWithMockBrowser( - {'name': 'win1', 'title': "Title 1", 'url': 'http://localhost/page1.html'}, - {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, - {'name': 'win3', 'title': "Title 3", 'url': 'http://localhost/page3.html'} - ) - manager.select("title= tItLe 2 ") - self.assertEqual(manager.browser.current_window.name, 'win2') - def test_select_by_title_with_multiple_matches(self): manager = WindowManagerWithMockBrowser( {'name': 'win1', 'title': "Title 1", 'url': 'http://localhost/page1.html'}, @@ -50,10 +43,10 @@ def test_select_by_title_no_match(self): {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, {'name': 'win3', 'title': "Title 3", 'url': 'http://localhost/page3.html'} ) - with self.assertRaises(ValueError) as err: + with self.assertRaises(WindowNotFound) as context: manager.select("title=Title -1") - self.assertEqual(str(err), - "Unable to locate window with title 'Title -1'") + self.assertEqual(str(context.exception), + "Unable to locate window with title 'Title -1'.") def test_select_by_name(self): manager = WindowManagerWithMockBrowser( @@ -64,33 +57,16 @@ def test_select_by_name(self): manager.select("name=win2") self.assertEqual(manager.browser.current_window.name, 'win2') - def test_select_by_name_sloppy_match(self): - manager = WindowManagerWithMockBrowser( - {'name': 'win1', 'title': "Title 1", 'url': 'http://localhost/page1.html'}, - {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, - {'name': 'win3', 'title': "Title 3", 'url': 'http://localhost/page3.html'} - ) - manager.select("name= win2 ") - self.assertEqual(manager.browser.current_window.name, 'win2') - - def test_select_by_name_with_bad_case(self): - manager = WindowManagerWithMockBrowser( - {'name': 'win1', 'title': "Title 1", 'url': 'http://localhost/page1.html'}, - {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, - {'name': 'win3', 'title': "Title 3", 'url': 'http://localhost/page3.html'} - ) - manager.select("name=Win2") - self.assertEqual(manager.browser.current_window.name, 'win2') - def test_select_by_name_no_match(self): manager = WindowManagerWithMockBrowser( {'name': 'win1', 'title': "Title 1", 'url': 'http://localhost/page1.html'}, {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, {'name': 'win3', 'title': "Title 3", 'url': 'http://localhost/page3.html'} ) - with self.assertRaises(ValueError) as err: + with self.assertRaises(WindowNotFound) as context: manager.select("name=win-1") - self.assertEqual(str(err), "Unable to locate window with name 'win-1'") + self.assertEqual(str(context.exception), + "Unable to locate window with name 'win-1'.") def test_select_by_url(self): manager = WindowManagerWithMockBrowser( @@ -101,15 +77,6 @@ def test_select_by_url(self): manager.select("url=http://localhost/page2.html") self.assertEqual(manager.browser.current_window.name, 'win2') - def test_select_by_url_sloppy_match(self): - manager = WindowManagerWithMockBrowser( - {'name': 'win1', 'title': "Title 1", 'url': 'http://localhost/page1.html'}, - {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, - {'name': 'win3', 'title': "Title 3", 'url': 'http://localhost/page3.html'} - ) - manager.select("url= http://LOCALHOST/page2.html ") - self.assertEqual(manager.browser.current_window.name, 'win2') - def test_select_by_url_with_multiple_matches(self): manager = WindowManagerWithMockBrowser( {'name': 'win1', 'title': "Title 1", 'url': 'http://localhost/page1.html'}, @@ -125,45 +92,27 @@ def test_select_by_url_no_match(self): {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, {'name': 'win3', 'title': "Title 3", 'url': 'http://localhost/page3.html'} ) - with self.assertRaises(ValueError) as err: + with self.assertRaises(WindowNotFound) as context: manager.select("url=http://localhost/page-1.html") - self.assertEqual(str(err), "Unable to locate window with URL " - "'http://localhost/page-1.html'") - - def test_select_with_null_locator(self): - manager = WindowManagerWithMockBrowser( - {'name': 'win1', 'title': "Title 1", 'url': 'http://localhost/page1.html'}, - {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, - {'name': 'win3', 'title': "Title 3", 'url': 'http://localhost/page3.html'} + self.assertEqual( + str(context.exception), + "Unable to locate window with URL 'http://localhost/page-1.html'." ) - manager.select("name=win2") - self.assertEqual(manager.browser.current_window.name, 'win2') - manager.select(None) - self.assertEqual(manager.browser.current_window.name, 'win1') - def test_select_with_null_string_locator(self): + def test_select_with_deprecated_locators_to_select_main_window(self): manager = WindowManagerWithMockBrowser( {'name': 'win1', 'title': "Title 1", 'url': 'http://localhost/page1.html'}, {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, {'name': 'win3', 'title': "Title 3", 'url': 'http://localhost/page3.html'} ) - manager.select("name=win2") - self.assertEqual(manager.browser.current_window.name, 'win2') - manager.select("null") - self.assertEqual(manager.browser.current_window.name, 'win1') + for deprecated in [None, 'null', 'NULL', '']: + manager.select("name=win2") + msg = "Using '%s' as window locator is deprecated. Use 'main' instead." % deprecated + with when(logger).warn(msg).thenReturn(None): + manager.select(deprecated) + self.assertEqual(manager.browser.current_window.name, 'win1') - def test_select_with_empty_locator(self): - manager = WindowManagerWithMockBrowser( - {'name': 'win1', 'title': "Title 1", 'url': 'http://localhost/page1.html'}, - {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, - {'name': 'win3', 'title': "Title 3", 'url': 'http://localhost/page3.html'} - ) - manager.select("name=win2") - self.assertEqual(manager.browser.current_window.name, 'win2') - manager.select("") - self.assertEqual(manager.browser.current_window.name, 'win1') - - def test_select_with_main_constant_locator(self): + def test_select_main_window(self): manager = WindowManagerWithMockBrowser( {'name': 'win1', 'title': "Title 1", 'url': 'http://localhost/page1.html'}, {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, @@ -173,6 +122,8 @@ def test_select_with_main_constant_locator(self): self.assertEqual(manager.browser.current_window.name, 'win2') manager.select("main") self.assertEqual(manager.browser.current_window.name, 'win1') + manager.select("MAIN") + self.assertEqual(manager.browser.current_window.name, 'win1') def test_select_by_default_with_name(self): manager = WindowManagerWithMockBrowser( @@ -198,9 +149,14 @@ def test_select_by_default_no_match(self): {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, {'name': 'win3', 'title': "Title 3", 'url': 'http://localhost/page3.html'} ) - self.assertRaises(ValueError, manager.select, "win-1") + with self.assertRaises(WindowNotFound) as context: + manager.select('foobar') + self.assertEqual( + str(context.exception), + "No window matching handle, name, title or URL 'foobar' found." + ) - def test_select_with_sloppy_prefix(self): + def test_prefix_is_case_sensitive(self): manager = WindowManagerWithMockBrowser( {'name': 'win1', 'title': "Title 1", 'url': 'http://localhost/page1.html'}, {'name': 'win2', 'title': "Title 2", 'url': 'http://localhost/page2.html'}, @@ -208,10 +164,12 @@ def test_select_with_sloppy_prefix(self): ) manager.select("name=win2") self.assertEqual(manager.browser.current_window.name, 'win2') - manager.select("nAmE=win2") - self.assertEqual(manager.browser.current_window.name, 'win2') - manager.select(" name =win2") - self.assertEqual(manager.browser.current_window.name, 'win2') + with self.assertRaises(WindowNotFound) as context: + manager.select("nAmE=win2") + self.assertEqual( + str(context.exception), + "No window matching handle, name, title or URL 'nAmE=win2' found." + ) def test_get_window_infos(self): manager = WindowManagerWithMockBrowser( From c5371efdf4621819e205c044e98adb1e33a232ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Mon, 23 Oct 2017 23:53:39 +0300 Subject: [PATCH 2/4] Get Window Handles to replace List Windows. See #966. --- src/SeleniumLibrary/keywords/browsermanagement.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index fd87d5677..3c07f7082 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -367,13 +367,20 @@ def select_window(self, locator='main'): self._window_manager.select(locator) @keyword - def list_windows(self): + def get_window_handles(self): """Return all current window handles as a list. Can be used as a list of windows to exclude with `Select Window`. + + Prior to SeleniumLibrary 3.0, this keyword was named `List Windows`. """ return self.browser.window_handles + @keyword + def list_windows(self): + """Deprecated. Use `Get Window Handles` instead.""" + return self.get_window_handles() + @keyword def get_location(self): """Returns the current browser URL.""" From 200cddf28ff4f0390a0fe06f9ac54cd740057757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Mon, 23 Oct 2017 23:54:23 +0300 Subject: [PATCH 3/4] Documented cleaned up Select Window. Fixes #966. Documentation cleanup also related to #925. --- .../keywords/browsermanagement.py | 85 +++++++++++++------ 1 file changed, 60 insertions(+), 25 deletions(-) diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index 3c07f7082..1e2c6039e 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -325,39 +325,74 @@ def set_window_position(self, x, y): self.browser.set_window_position(int(x), int(y)) @keyword - def select_window(self, locator='main'): - """Selects the window matching locator and return previous window handle. + def select_window(self, locator='MAIN'): + """Selects browser window matching ``locator``. - locator: any of name, title, url, window handle, excluded handle's list, or special words. - return: either current window handle before selecting, or None if no current window. + If the window is found, all subsequent commands use the selected + window, until this keyword is used again. If the window is not + found, this keyword fails. The previous window handle is returned, + and can be used to return back to it later. - If the window is found, all subsequent commands use that window, until - this keyword is used again. If the window is not found, this keyword fails. + Notice that in this context _window_ means a pop-up window opened + when doing something on an existing window. It is not possible to + select windows opened with `Open Browser`, `Switch Browser` must + be used instead. Notice also that alerts should be handled with + `Handle Alert` or other alert related keywords. - By default, when a locator value is provided, - it is matched against the title of the window and the - javascript name of the window. If multiple windows with - same identifier are found, the first one is selected. + The ``locator`` can be specified using different strategies somewhat + similarly as when `locating elements` on pages. - There are some special locators for searching target window: - string 'main' (default): select the main window; - string 'self': only return current window handle; - string 'new': select the last-indexed window assuming it is the newest opened window - window list: select the first window not in given list (See 'List Windows' to get the list) + - By default the ``locator`` is matched against window handle, name, + title, and URL. Matching is done in that order and the the first + matching window is selected. - It is also possible to specify the approach SeleniumLibrary should take - to find a window by specifying a locator strategy: + - The ``locator`` can specify an explicit strategy by using format + ``strategy:value`` (recommended) or ``strategy=value``. Supported + strategies are ``name``, ``title`` and ``url``, which match windows + using name, title, and URL, respectively. Additionally, ``default`` + can be used to explicitly use the default strategy explained above. - | *Strategy* | *Example* | *Description* | - | title | Select Window `|` title=My Document | Matches by window title | - | name | Select Window `|` name=${name} | Matches by window javascript name | - | url | Select Window `|` url=http://google.com | Matches by window's current URL | + - If the ``locator`` is ``NEW`` (case-insensitive), the latest + opened window is selected. It is an error if this is the same + as the current window. + + - If the ``locator`` is ``MAIN`` (default, case-insensitive), + the main window is selected. + + - If the ``locator`` is ``CURRENT`` (case-insensitive), nothing is + done. This effectively just returns the current window handle. + + - If the ``locator`` is not a string, it is expected to be a list + of window handles _to exclude_. Such a list of excluded windows + can be get from `Get Window Handles` prior to doing an action that + opens a new window. Example: - | Click Link | popup_link | # opens new window | - | Select Window | popupName | - | Title Should Be | Popup Title | - | Select Window | | | # Chooses the main window again | + | `Click Link` | popup1 | | # Open new window | + | `Select Window` | example | | # Select window using default strategy | + | `Title Should Be` | Pop-up 1 | | + | `Click Button` | popup2 | | # Open another window | + | ${handle} = | `Select Window` | NEW | # Select latest opened window | + | `Title Should Be` | Pop-up 2 | | + | `Select Window` | ${handle} | | # Select window using handle | + | `Title Should Be` | Pop-up 1 | | + | `Select Window` | MAIN | | # Select the main window | + | `Title Should Be` | Main | | + | ${excludes} = | `Get Window Handles` | | # Get list of current windows | + | `Click Link` | popup3 | | # Open one more window | + | `Select Window` | ${excludes} | | # Select window using excludes | + | `Title Should Be` | Pop-up 3 | | + + *NOTE:* + + - The ``strategy:value`` syntax is only supported by SeleniumLibrary + 3.0 and newer. + - Earlier versions supported aliases ``None``, ``null`` and the + empty string for selecting the main window, and alias ``self`` + for selecting the current window. These aliases were deprecated + in SeleniumLibrary 3.0. + - Prior to SeleniumLibrary 3.0 matching windows by name, title + and URL was case-insensitive. """ try: return self.browser.current_window_handle From 166d048009b8e3ccfb6bfafe7c81c0196787db65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pekka=20Kl=C3=A4rck?= Date: Tue, 24 Oct 2017 00:16:38 +0300 Subject: [PATCH 4/4] Moved window keywords to own component --- src/SeleniumLibrary/__init__.py | 6 +- src/SeleniumLibrary/keywords/__init__.py | 1 + .../keywords/browsermanagement.py | 206 +--------------- src/SeleniumLibrary/keywords/window.py | 221 ++++++++++++++++++ 4 files changed, 232 insertions(+), 202 deletions(-) create mode 100644 src/SeleniumLibrary/keywords/window.py diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index ddd0acc47..acb7f0347 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -31,7 +31,8 @@ ScreenshotKeywords, SelectElementKeywords, TableElementKeywords, - WaitingKeywords) + WaitingKeywords, + WindowKeywords) from SeleniumLibrary.locators import ElementFinder, TableElementFinder from SeleniumLibrary.utils import (BrowserCache, Deprecated, LibraryListener, timestr_to_secs) @@ -339,7 +340,8 @@ def __init__(self, timeout=5.0, implicit_wait=0.0, ScreenshotKeywords(self), SelectElementKeywords(self), TableElementKeywords(self), - WaitingKeywords(self) + WaitingKeywords(self), + WindowKeywords(self) ] self._browsers = BrowserCache() DynamicCore.__init__(self, libraries) diff --git a/src/SeleniumLibrary/keywords/__init__.py b/src/SeleniumLibrary/keywords/__init__.py index 5776bc2a1..a3236bcf0 100644 --- a/src/SeleniumLibrary/keywords/__init__.py +++ b/src/SeleniumLibrary/keywords/__init__.py @@ -26,3 +26,4 @@ from .selectelement import SelectElementKeywords from .tableelement import TableElementKeywords from .waiting import WaitingKeywords +from .window import WindowKeywords diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index 1e2c6039e..cc7ccba23 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -20,7 +20,6 @@ from robot.utils import NormalizedDict from selenium import webdriver -from selenium.common.exceptions import NoSuchWindowException from SeleniumLibrary.base import keyword, LibraryComponent from SeleniumLibrary.locators import WindowManager @@ -238,195 +237,6 @@ def switch_browser(self, index_or_alias): self.debug('Switched to browser with Selenium session id %s.' % self.browser.session_id) - @keyword - def close_window(self): - """Closes currently opened pop-up window.""" - self.browser.close() - - @keyword - def get_window_identifiers(self): - """Returns and logs id attributes of all known browser windows.""" - ids = [info.id for info in self._window_manager.get_window_infos()] - return self._log_list(ids) - - @keyword - def get_window_names(self): - """Returns and logs names of all known browser windows.""" - names = [info.name for info in self._window_manager.get_window_infos()] - return self._log_list(names) - - @keyword - def get_window_titles(self): - """Returns and logs titles of all known browser windows.""" - titles = [info.title for info in self._window_manager.get_window_infos()] - return self._log_list(titles) - - @keyword - def maximize_browser_window(self): - """Maximizes current browser window.""" - self.browser.maximize_window() - - @keyword - def get_window_size(self): - """Returns current window width and height as integers. - - See also `Set Window Size`. - - Example: - | ${width} | ${height}= | `Get Window Size` | - """ - size = self.browser.get_window_size() - return size['width'], size['height'] - - @keyword - def set_window_size(self, width, height): - """Sets current windows size to given ``width`` and ``height``. - - Values can be given using strings containing numbers or by using - actual numbers. See also `Get Window Size`. - - Browsers have a limit how small they can be set. Trying to set them - smaller will cause the actual size to be bigger than the requested - size. - - Example: - | `Set Window Size` | 800 | 600 | - """ - return self.browser.set_window_size(int(width), int(height)) - - @keyword - def get_window_position(self): - """Returns current window position. - - Position is relative to the top left corner of the screen. Returned - values are integers. See also `Set Window Position`. - - Example: - | ${x} | ${y}= | `Get Window Position` | - """ - position = self.browser.get_window_position() - return position['x'], position['y'] - - @keyword - def set_window_position(self, x, y): - """Sets window position using ``x`` and ``y`` coordinates. - - The position is relative to the top left corner of the screen, - but some browsers exclude possible task bar set by the operating - system from the calculation. The actual position may thus be - different with different browsers. - - Values can be given using strings containing numbers or by using - actual numbers. See also `Get Window Position`. - - Example: - | `Set Window Position` | 100 | 200 | - """ - self.browser.set_window_position(int(x), int(y)) - - @keyword - def select_window(self, locator='MAIN'): - """Selects browser window matching ``locator``. - - If the window is found, all subsequent commands use the selected - window, until this keyword is used again. If the window is not - found, this keyword fails. The previous window handle is returned, - and can be used to return back to it later. - - Notice that in this context _window_ means a pop-up window opened - when doing something on an existing window. It is not possible to - select windows opened with `Open Browser`, `Switch Browser` must - be used instead. Notice also that alerts should be handled with - `Handle Alert` or other alert related keywords. - - The ``locator`` can be specified using different strategies somewhat - similarly as when `locating elements` on pages. - - - By default the ``locator`` is matched against window handle, name, - title, and URL. Matching is done in that order and the the first - matching window is selected. - - - The ``locator`` can specify an explicit strategy by using format - ``strategy:value`` (recommended) or ``strategy=value``. Supported - strategies are ``name``, ``title`` and ``url``, which match windows - using name, title, and URL, respectively. Additionally, ``default`` - can be used to explicitly use the default strategy explained above. - - - If the ``locator`` is ``NEW`` (case-insensitive), the latest - opened window is selected. It is an error if this is the same - as the current window. - - - If the ``locator`` is ``MAIN`` (default, case-insensitive), - the main window is selected. - - - If the ``locator`` is ``CURRENT`` (case-insensitive), nothing is - done. This effectively just returns the current window handle. - - - If the ``locator`` is not a string, it is expected to be a list - of window handles _to exclude_. Such a list of excluded windows - can be get from `Get Window Handles` prior to doing an action that - opens a new window. - - Example: - | `Click Link` | popup1 | | # Open new window | - | `Select Window` | example | | # Select window using default strategy | - | `Title Should Be` | Pop-up 1 | | - | `Click Button` | popup2 | | # Open another window | - | ${handle} = | `Select Window` | NEW | # Select latest opened window | - | `Title Should Be` | Pop-up 2 | | - | `Select Window` | ${handle} | | # Select window using handle | - | `Title Should Be` | Pop-up 1 | | - | `Select Window` | MAIN | | # Select the main window | - | `Title Should Be` | Main | | - | ${excludes} = | `Get Window Handles` | | # Get list of current windows | - | `Click Link` | popup3 | | # Open one more window | - | `Select Window` | ${excludes} | | # Select window using excludes | - | `Title Should Be` | Pop-up 3 | | - - *NOTE:* - - - The ``strategy:value`` syntax is only supported by SeleniumLibrary - 3.0 and newer. - - Earlier versions supported aliases ``None``, ``null`` and the - empty string for selecting the main window, and alias ``self`` - for selecting the current window. These aliases were deprecated - in SeleniumLibrary 3.0. - - Prior to SeleniumLibrary 3.0 matching windows by name, title - and URL was case-insensitive. - """ - try: - return self.browser.current_window_handle - except NoSuchWindowException: - pass - finally: - self._window_manager.select(locator) - - @keyword - def get_window_handles(self): - """Return all current window handles as a list. - - Can be used as a list of windows to exclude with `Select Window`. - - Prior to SeleniumLibrary 3.0, this keyword was named `List Windows`. - """ - return self.browser.window_handles - - @keyword - def list_windows(self): - """Deprecated. Use `Get Window Handles` instead.""" - return self.get_window_handles() - - @keyword - def get_location(self): - """Returns the current browser URL.""" - return self.browser.current_url - - @keyword - def get_locations(self): - """Returns and logs URLs of all known browser windows.""" - urls = [info.url for info in self._window_manager.get_window_infos()] - return self._log_list(urls) - @keyword def get_source(self): """Returns the entire HTML source of the current page or frame.""" @@ -437,6 +247,12 @@ def get_title(self): """Returns the title of current page.""" return self.browser.title + @keyword + def get_location(self): + """Returns the current browser URL.""" + return self.browser.current_url + + @keyword def location_should_be(self, url): """Verifies that current URL is exactly ``url``.""" @@ -741,16 +557,6 @@ def execute(self, driver_command, params=None): browser.execute = types.MethodType(execute, browser) browser._speed = self.ctx.speed - def _log_list(self, items, what='item'): - msg = [ - 'Altogether {} {}.'.format( - len(items), what if len(items) == 1 else '{}s'.format(what)) - ] - for index, item in enumerate(items): - msg.append('{}: {}'.format(index + 1, item)) - self.info('\n'.join(msg)) - return items - @property def _geckodriver_log_config(self): if SELENIUM_VERSION.major == '3': diff --git a/src/SeleniumLibrary/keywords/window.py b/src/SeleniumLibrary/keywords/window.py new file mode 100644 index 000000000..654d3732d --- /dev/null +++ b/src/SeleniumLibrary/keywords/window.py @@ -0,0 +1,221 @@ +# Copyright 2008-2011 Nokia Networks +# Copyright 2011-2016 Ryan Tomac, Ed Manlove and contributors +# Copyright 2016- Robot Framework Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from selenium.common.exceptions import NoSuchWindowException + +from SeleniumLibrary.base import keyword, LibraryComponent +from SeleniumLibrary.locators import WindowManager + + +class WindowKeywords(LibraryComponent): + + def __init__(self, ctx): + LibraryComponent.__init__(self, ctx) + self._window_manager = WindowManager(ctx) + + @keyword + def select_window(self, locator='MAIN'): + """Selects browser window matching ``locator``. + + If the window is found, all subsequent commands use the selected + window, until this keyword is used again. If the window is not + found, this keyword fails. The previous window handle is returned, + and can be used to return back to it later. + + Notice that in this context _window_ means a pop-up window opened + when doing something on an existing window. It is not possible to + select windows opened with `Open Browser`, `Switch Browser` must + be used instead. Notice also that alerts should be handled with + `Handle Alert` or other alert related keywords. + + The ``locator`` can be specified using different strategies somewhat + similarly as when `locating elements` on pages. + + - By default the ``locator`` is matched against window handle, name, + title, and URL. Matching is done in that order and the the first + matching window is selected. + + - The ``locator`` can specify an explicit strategy by using format + ``strategy:value`` (recommended) or ``strategy=value``. Supported + strategies are ``name``, ``title`` and ``url``, which match windows + using name, title, and URL, respectively. Additionally, ``default`` + can be used to explicitly use the default strategy explained above. + + - If the ``locator`` is ``NEW`` (case-insensitive), the latest + opened window is selected. It is an error if this is the same + as the current window. + + - If the ``locator`` is ``MAIN`` (default, case-insensitive), + the main window is selected. + + - If the ``locator`` is ``CURRENT`` (case-insensitive), nothing is + done. This effectively just returns the current window handle. + + - If the ``locator`` is not a string, it is expected to be a list + of window handles _to exclude_. Such a list of excluded windows + can be get from `Get Window Handles` prior to doing an action that + opens a new window. + + Example: + | `Click Link` | popup1 | | # Open new window | + | `Select Window` | example | | # Select window using default strategy | + | `Title Should Be` | Pop-up 1 | | + | `Click Button` | popup2 | | # Open another window | + | ${handle} = | `Select Window` | NEW | # Select latest opened window | + | `Title Should Be` | Pop-up 2 | | + | `Select Window` | ${handle} | | # Select window using handle | + | `Title Should Be` | Pop-up 1 | | + | `Select Window` | MAIN | | # Select the main window | + | `Title Should Be` | Main | | + | ${excludes} = | `Get Window Handles` | | # Get list of current windows | + | `Click Link` | popup3 | | # Open one more window | + | `Select Window` | ${excludes} | | # Select window using excludes | + | `Title Should Be` | Pop-up 3 | | + + *NOTE:* + + - The ``strategy:value`` syntax is only supported by SeleniumLibrary + 3.0 and newer. + - Earlier versions supported aliases ``None``, ``null`` and the + empty string for selecting the main window, and alias ``self`` + for selecting the current window. These aliases were deprecated + in SeleniumLibrary 3.0. + - Prior to SeleniumLibrary 3.0 matching windows by name, title + and URL was case-insensitive. + """ + try: + return self.browser.current_window_handle + except NoSuchWindowException: + pass + finally: + self._window_manager.select(locator) + + @keyword + def close_window(self): + """Closes currently opened pop-up window.""" + self.browser.close() + + @keyword + def get_window_handles(self): + """Return all current window handles as a list. + + Can be used as a list of windows to exclude with `Select Window`. + + Prior to SeleniumLibrary 3.0, this keyword was named `List Windows`. + """ + return self.browser.window_handles + + @keyword + def list_windows(self): + """Deprecated. Use `Get Window Handles` instead.""" + return self.get_window_handles() + + @keyword + def get_window_identifiers(self): + """Returns and logs id attributes of all known browser windows.""" + ids = [info.id for info in self._window_manager.get_window_infos()] + return self._log_list(ids) + + @keyword + def get_window_names(self): + """Returns and logs names of all known browser windows.""" + names = [info.name for info in self._window_manager.get_window_infos()] + return self._log_list(names) + + @keyword + def get_window_titles(self): + """Returns and logs titles of all known browser windows.""" + titles = [info.title for info in self._window_manager.get_window_infos()] + return self._log_list(titles) + + @keyword + def get_locations(self): + """Returns and logs URLs of all known browser windows.""" + urls = [info.url for info in self._window_manager.get_window_infos()] + return self._log_list(urls) + + @keyword + def maximize_browser_window(self): + """Maximizes current browser window.""" + self.browser.maximize_window() + + @keyword + def get_window_size(self): + """Returns current window width and height as integers. + + See also `Set Window Size`. + + Example: + | ${width} | ${height}= | `Get Window Size` | + """ + size = self.browser.get_window_size() + return size['width'], size['height'] + + @keyword + def set_window_size(self, width, height): + """Sets current windows size to given ``width`` and ``height``. + + Values can be given using strings containing numbers or by using + actual numbers. See also `Get Window Size`. + + Browsers have a limit how small they can be set. Trying to set them + smaller will cause the actual size to be bigger than the requested + size. + + Example: + | `Set Window Size` | 800 | 600 | + """ + return self.browser.set_window_size(int(width), int(height)) + + @keyword + def get_window_position(self): + """Returns current window position. + + Position is relative to the top left corner of the screen. Returned + values are integers. See also `Set Window Position`. + + Example: + | ${x} | ${y}= | `Get Window Position` | + """ + position = self.browser.get_window_position() + return position['x'], position['y'] + + @keyword + def set_window_position(self, x, y): + """Sets window position using ``x`` and ``y`` coordinates. + + The position is relative to the top left corner of the screen, + but some browsers exclude possible task bar set by the operating + system from the calculation. The actual position may thus be + different with different browsers. + + Values can be given using strings containing numbers or by using + actual numbers. See also `Get Window Position`. + + Example: + | `Set Window Position` | 100 | 200 | + """ + self.browser.set_window_position(int(x), int(y)) + + def _log_list(self, items, what='item'): + msg = [ + 'Altogether {} {}.'.format( + len(items), what if len(items) == 1 else '{}s'.format(what)) + ] + for index, item in enumerate(items): + msg.append('{}: {}'.format(index + 1, item)) + self.info('\n'.join(msg)) + return items