Skip to content

Commit 8cdbd45

Browse files
committed
Merge branch 'master' into webdriver-element
(Resolved) Conflicts: src/Selenium2Library/__init__.py
2 parents a2522b0 + 7664b8d commit 8cdbd45

38 files changed

+614
-366
lines changed

.travis.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ before_script:
1414
- "export DISPLAY=:99.0"
1515
- "sh -e /etc/init.d/xvfb start"
1616
script:
17-
- "python test/run_tests.py python $BROWSER --noncritical ignore_${BROWSER}_error"
17+
- "firefox --version"
18+
- "python test/run_tests.py python $BROWSER --noncritical known_issue_-_travisci"
1819
env:
1920
matrix:
2021
- BROWSER=firefox
2122
#- BROWSER=ie # will be added when we have completed SauceLabs CI
22-
#- BROWSER=safari # will be added after #247 merged
23+
#- BROWSER=safari # will be added after #247 merged

CHANGES.rst

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,42 @@ Release Notes
33

44
1.7 (unreleased)
55
----------------
6+
- Fixed ‘NoSuchWindowException' issue. Running keyword 'Select Window' after 'Close Window'
7+
will trigger this issue if locator has prefix 'name=','title=' or 'url='. Also fixed same
8+
issue for keywords 'Get Window Ids', 'Get Window Titles' and 'Get Window Names'.
9+
[divfor]
10+
11+
- Corrected error message in new keyword 'Wait Until Element Is Not
12+
Visible' to reflect element being visible instead of not visible.
13+
[joepurdy]
14+
15+
- Stop using private browsing with default Firefox profile.
16+
[ombre42]
17+
18+
- Added new keyword 'Wait Until Element Is Not Visible'.
19+
[deiga]
20+
21+
- Added new keyword 'Element Should Not Contain'.
22+
[molsky]
23+
24+
- Added new keyword 'Wait Until Page Does Not Contain Element'.
25+
[molsky]
26+
27+
- Added new locator strategy, scLocator, for finding SmartClient and SmartGWT elements.
28+
[IlfirinPL]
29+
30+
- Edited acceptance test scripts to automatically make known issues for the currently
31+
known browser and python version noncritical. Also added a noncritical case to the
32+
travis config for situations where testing is failing on travis for an unknown reason.
33+
- 'Capture Screenshot' now attempts to create its containing directory if the directory
34+
specified in the filename does not exist.
35+
- 'Choose File' now fails if the file doesn't exist
36+
- Added new keywords 'Add Location Strategy' and 'Remove Location Strategy'
37+
[zephraph]
38+
39+
- Added 'Get Window Position' and 'Set Window Position' keywords matching the
40+
Selenium functionality.
41+
[ktarasz]
642

743
1.6
844
---
@@ -15,7 +51,7 @@ Release Notes
1551

1652
- Fixed issue where the browser failed to properly register if 'Open Browser'
1753
did not complete.
18-
[Mika Batsman][elizaleong][emanlove]
54+
[Mika Batsman][elizaleong][emanlove]
1955

2056
- Added support for negative indices for rows and columns in table-related
2157
keywords.
@@ -25,7 +61,7 @@ Release Notes
2561
prefix 'partial link'.
2662
[lina1]
2763

28-
- Added new keyword 'Clear Element Text' for clearing the text of text entry
64+
- Added new keyword 'Clear Element Text' for clearing the text of text entry
2965
elements.
3066
[emanlove]
3167

@@ -85,7 +121,7 @@ continuous integration builds to go green by fixing internal tests.
85121
- Raise exception in selecting non-existing item in list. Error handling varies
86122
between single-select and multi-select lists. See keyword documentation for
87123
more information.
88-
[adwu73][emanlove]
124+
[adwu73][emanlove]
89125

90126
- Added 'Get Window Size' and 'Set Window Size' keywords matching the
91127
Selenium functionality.
@@ -98,11 +134,11 @@ continuous integration builds to go green by fixing internal tests.
98134

99135
- Beautified README.rst.
100136
[j1z0][emanlove]
101-
137+
102138
- Changed press key test to use Line Feed (\10) instead of
103139
Carriage Return (\13).
104140
[emanlove]
105-
141+
106142
- Added new keyword 'Click Element At Coordinates'.
107143
[aaltat][pierreroth64][ombre42][emanlove]
108144

@@ -153,7 +189,7 @@ continuous integration builds to go green by fixing internal tests.
153189
[emanlove]
154190

155191
- Use Selenium's Select class within Selenium2Library's "Select *" keywords.
156-
Optimization of certain "Select *" keywords to increase performance.
192+
Optimization of certain "Select *" keywords to increase performance.
157193
[emanlove] [schminitz]
158194
159195
- Replace maximize current browser window from JS to webdriver.

doc/Selenium2Library.html

Lines changed: 0 additions & 281 deletions
This file was deleted.

src/Selenium2Library/__init__.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import os
22
from keywords import *
3-
4-
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
5-
execfile(os.path.join(THIS_DIR, 'version.py'))
3+
from version import VERSION
4+
from utils import LibraryListener
65

76
__version__ = VERSION
87

98
class Selenium2Library(
10-
_LoggingKeywords,
11-
_RunOnFailureKeywords,
12-
_BrowserManagementKeywords,
13-
_ElementKeywords,
9+
_LoggingKeywords,
10+
_RunOnFailureKeywords,
11+
_BrowserManagementKeywords,
12+
_ElementKeywords,
1413
_TableElementKeywords,
1514
_FormElementKeywords,
1615
_SelectElementKeywords,
@@ -31,7 +30,7 @@ class Selenium2Library(
3130
= Before running tests =
3231
3332
Prior to running test cases using Selenium2Library, Selenium2Library must be
34-
imported into your Robot test suite (see `importing` section), and the
33+
imported into your Robot test suite (see `importing` section), and the
3534
`Open Browser` keyword must be used to open a browser to the desired location.
3635
3736
**--- Note important change starting with Version 1.7.0 release ---**
@@ -100,7 +99,26 @@ class Selenium2Library(
10099
| css | Table Should Contain `|` css=table.my_class `|` text | Matches by @id or @name attribute |
101100
| xpath | Table Should Contain `|` xpath=//table/[@name="my_table"] `|` text | Matches by @id or @name attribute |
102101
103-
*Timeouts*
102+
= Custom Locators =
103+
104+
If more complex lookups are required than what is provided through the default locators, custom lookup strategies can
105+
be created. Using custom locators is a two part process. First, create a keyword that returns the WebElement
106+
that should be acted on.
107+
108+
| Custom Locator Strategy | [Arguments] | ${browser} | ${criteria} | ${tag} | ${constraints} |
109+
| | ${retVal}= | Execute Javascript | return window.document.getElementById('${criteria}'); |
110+
| | [Return] | ${retVal} |
111+
112+
This keyword is a reimplementation of the basic functionality of the `id` locator where `${browser}` is a reference
113+
to the WebDriver instance and `${criteria}` is the text of the locator (i.e. everything that comes after the = sign).
114+
To use this locator it must first be registered with `Add Location Strategy`.
115+
116+
Add Location Strategy custom Custom Locator Strategy
117+
118+
The first argument of `Add Location Strategy` specifies the name of the lookup strategy (which must be unique). After
119+
registration of the lookup strategy, the usage is the same as other locators. See `Add Location Strategy` for more details.
120+
121+
= Timeouts =
104122
105123
There are several `Wait ...` keywords that take timeout as an
106124
argument. All of these timeout arguments are optional. The timeout
@@ -150,3 +168,4 @@ def __init__(self, timeout=5.0, implicit_wait=0.0, run_on_failure='Capture Page
150168
self.set_selenium_timeout(timeout)
151169
self.set_selenium_implicit_wait(implicit_wait)
152170
self.register_keyword_to_run_on_failure(run_on_failure)
171+
self.ROBOT_LIBRARY_LISTENER = LibraryListener()

src/Selenium2Library/keywords/_browsermanagement.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,26 @@ def set_window_size(self, width, height):
258258
"""
259259
return self._current_browser().set_window_size(width, height)
260260

261+
def get_window_position(self):
262+
"""Returns current window position as `x` then `y`.
263+
264+
Example:
265+
| ${x} | ${y}= | Get Window Position |
266+
"""
267+
position = self._current_browser().get_window_position()
268+
return position['x'], position['y']
269+
270+
def set_window_position(self, x, y):
271+
"""Sets the position `x` and `y` of the current window to the specified values.
272+
273+
Example:
274+
| Set Window Size | ${1000} | ${0} |
275+
| ${x} | ${y}= | Get Window Position |
276+
| Should Be Equal | ${x} | ${1000} |
277+
| Should Be Equal | ${y} | ${0} |
278+
"""
279+
return self._current_browser().set_window_position(x, y)
280+
261281
def select_frame(self, locator):
262282
"""Sets frame identified by `locator` as current frame.
263283

src/Selenium2Library/keywords/_element.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from selenium.webdriver.remote.webelement import WebElement
44
from Selenium2Library import utils
55
from Selenium2Library.locators import ElementFinder
6+
from Selenium2Library.locators import CustomLocator
67
from keywordgroup import KeywordGroup
78

89
try:
@@ -72,6 +73,23 @@ def element_should_contain(self, locator, expected, message=''):
7273
"its text was '%s'." % (locator, expected, actual)
7374
raise AssertionError(message)
7475

76+
def element_should_not_contain(self, locator, expected, message=''):
77+
"""Verifies element identified by `locator` does not contain text `expected`.
78+
79+
`message` can be used to override the default error message.
80+
81+
Key attributes for arbitrary elements are `id` and `name`. See
82+
`Element Should Contain` for more details.
83+
"""
84+
self._info("Verifying element '%s' does not contain text '%s'."
85+
% (locator, expected))
86+
actual = self._get_text(locator)
87+
if expected in actual:
88+
if not message:
89+
message = "Element '%s' should not contain text '%s' but " \
90+
"it did." % (locator, expected)
91+
raise AssertionError(message)
92+
7593
def frame_should_contain(self, locator, text, loglevel='INFO'):
7694
"""Verifies frame identified by `locator` contains `text`.
7795
@@ -602,6 +620,38 @@ def xpath_should_match_x_times(self, xpath, expected_xpath_count, message='', lo
602620
self._info("Current page contains %s elements matching '%s'."
603621
% (actual_xpath_count, xpath))
604622

623+
# Public, custom
624+
def add_location_strategy(self, strategy_name, strategy_keyword, persist=False):
625+
"""Adds a custom location strategy based on a user keyword. Location strategies are
626+
automatically removed after leaving the current scope by default. Setting `persist`
627+
to any non-empty string will cause the location strategy to stay registered throughout
628+
the life of the test.
629+
630+
Trying to add a custom location strategy with the same name as one that already exists will
631+
cause the keyword to fail.
632+
633+
Custom locator keyword example:
634+
| Custom Locator Strategy | [Arguments] | ${browser} | ${criteria} | ${tag} | ${constraints} |
635+
| | ${retVal}= | Execute Javascript | return window.document.getElementById('${criteria}'); |
636+
| | [Return] | ${retVal} |
637+
638+
Usage example:
639+
| Add Location Strategy | custom | Custom Locator Strategy |
640+
| Page Should Contain Element | custom=my_id |
641+
642+
See `Remove Location Strategy` for details about removing a custom location strategy.
643+
"""
644+
strategy = CustomLocator(strategy_name, strategy_keyword)
645+
self._element_finder.register(strategy, persist)
646+
647+
def remove_location_strategy(self, strategy_name):
648+
"""Removes a previously added custom location strategy.
649+
Will fail if a default strategy is specified.
650+
651+
See `Add Location Strategy` for details about adding a custom location strategy.
652+
"""
653+
self._element_finder.unregister(strategy_name)
654+
605655
# Private
606656

607657
def _element_find(self, locator, first_only, required, tag=None):
@@ -733,4 +783,3 @@ def _page_should_not_contain_element(self, locator, tag, message, loglevel):
733783
raise AssertionError(message)
734784
self._info("Current page does not contain %s '%s'."
735785
% (element_name, locator))
736-

src/Selenium2Library/keywords/_formelement.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,17 +165,17 @@ def select_radio_button(self, group_name, value):
165165
# Public, text fields
166166

167167
def choose_file(self, locator, file_path):
168-
"""Inputs the `file_path` into file input field found by `identifier`.
168+
"""Inputs the `file_path` into file input field found by `locator`.
169169
170170
This keyword is most often used to input files into upload forms.
171-
The file specified with `file_path` must be available on the same host
171+
The file specified with `file_path` must be available on the same host
172172
where the Selenium Server is running.
173173
174174
Example:
175175
| Choose File | my_upload_field | /home/user/files/trades.csv |
176176
"""
177177
if not os.path.isfile(file_path):
178-
self._info("File '%s' does not exist on the local file system"
178+
raise AssertionError("File '%s' does not exist on the local file system"
179179
% file_path)
180180
self._element_find(locator, True, True).send_keys(file_path)
181181

@@ -271,7 +271,7 @@ def textarea_should_contain(self, locator, expected, message=''):
271271
else:
272272
raise ValueError("Element locator '" + locator + "' did not match any elements.")
273273
self._info("Text area '%s' contains text '%s'." % (locator, expected))
274-
274+
275275
def textarea_value_should_be(self, locator, expected, message=''):
276276
"""Verifies the value in text area identified by `locator` is exactly `expected`.
277277
@@ -290,7 +290,7 @@ def textarea_value_should_be(self, locator, expected, message=''):
290290
else:
291291
raise ValueError("Element locator '" + locator + "' did not match any elements.")
292292
self._info("Content of text area '%s' is '%s'." % (locator, expected))
293-
293+
294294
# Public, buttons
295295

296296
def click_button(self, locator):

src/Selenium2Library/keywords/_screenshot.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import os
1+
import os, errno
22
import robot
33
from keywordgroup import KeywordGroup
4+
from robot.api import logger
45

56
class _ScreenshotKeywords(KeywordGroup):
67

@@ -17,18 +18,30 @@ def capture_page_screenshot(self, filename=None):
1718
`selenium-screenshot-<counter>.png` under the directory where
1819
the Robot Framework log file is written into. The `filename` is
1920
also considered relative to the same directory, if it is not
20-
given in absolute format.
21+
given in absolute format. If an absolute or relative path is given
22+
but the path does not exist it will be created.
2123
2224
`css` can be used to modify how the screenshot is taken. By default
2325
the bakground color is changed to avoid possible problems with
2426
background leaking when the page layout is somehow broken.
2527
"""
2628
path, link = self._get_screenshot_paths(filename)
2729

30+
target_dir = os.path.dirname(path)
31+
if not os.path.exists(target_dir):
32+
try:
33+
os.makedirs(target_dir)
34+
except OSError as exc:
35+
if exc.errno == errno.EEXIST and os.path.isdir(target_dir):
36+
pass
37+
else: raise
38+
2839
if hasattr(self._current_browser(), 'get_screenshot_as_file'):
29-
self._current_browser().get_screenshot_as_file(path)
40+
if not self._current_browser().get_screenshot_as_file(path):
41+
raise RuntimeError('Failed to save screenshot ' + filename)
3042
else:
31-
self._current_browser().save_screenshot(path)
43+
if not self._current_browser().save_screenshot(path):
44+
raise RuntimeError('Failed to save screenshot ' + filename)
3245

3346
# Image is shown on its own row and thus prev row is closed on purpose
3447
self._html('</td></tr><tr><td colspan="3"><a href="%s">'

0 commit comments

Comments
 (0)