Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ Release Notes

1.7.2 (Unreleased)
----------------
- Added an argument called screenshot_root_directory that can be passed into S2L's
constructor to specify where to store screenshots.
- Added new keyword 'set_screenshot_directory' which can be used to set the output
of screenshots.
[zephraph]

- Added new keyword Input Text Into Prompt
- Modified 'get_alert_message' to accept a parameter 'dismiss' (defaults to true) which can be
used to prevent closing the alert message and instead will just return the alerts text.
Also created new keyword 'dismiss_alert' to dismiss (default) or confirm the alert without
reading the text of the alert.
[KingWarin]
- Added new keyword Input Text Into Prompt

- Added new keyword Input Text Into Prompt
[boakley][ekasteel]

- Fixed issue that caused tests to fail when selenium > 2.26
Expand Down
12 changes: 11 additions & 1 deletion src/Selenium2Library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,13 @@ class Selenium2Library(
ROBOT_LIBRARY_SCOPE = 'GLOBAL'
ROBOT_LIBRARY_VERSION = VERSION

def __init__(self, timeout=5.0, implicit_wait=0.0, run_on_failure='Capture Page Screenshot'):
def __init__(self,
timeout=5.0,
implicit_wait=0.0,
run_on_failure='Capture Page Screenshot',
screenshot_root_directory=None
):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new argument needs to be documented.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it's on the todo list.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pekkaklarck should I use backticks or single quotes? S2L isn't very consistent on its documentation style.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Robot's own libs we have recently switched to use double backticks to get "code style" formatting. This is what we also recommend in Libdoc's docs:
http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#representing-arguments

We used single backticks earlier but nowadays we prefer to reserve it for internal linking. Code style looks better (that's obviously subjective) and avoids accidentally creating a link.

Hmm, should I add a note about this to Robot's newly added contribution guidelines? robotframework/robotframework#1805

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please.


"""Selenium2Library can be imported with optional arguments.

`timeout` is the default timeout used to wait for all waiting actions.
Expand All @@ -156,6 +162,9 @@ def __init__(self, timeout=5.0, implicit_wait=0.0, run_on_failure='Capture Page
`Register Keyword To Run On Failure` keyword for more information about this
functionality.

`screenshot_root_directory` specifies the default root directory that screenshots should be
stored in. If not provided the default directory will be where robotframework places its logfile.

Examples:
| Library `|` Selenium2Library `|` 15 | # Sets default timeout to 15 seconds |
| Library `|` Selenium2Library `|` 0 `|` 5 | # Sets default timeout to 0 seconds and default implicit_wait to 5 seconds |
Expand All @@ -165,6 +174,7 @@ def __init__(self, timeout=5.0, implicit_wait=0.0, run_on_failure='Capture Page
"""
for base in Selenium2Library.__bases__:
base.__init__(self)
self.screenshot_root_directory = screenshot_root_directory
self.set_selenium_timeout(timeout)
self.set_selenium_implicit_wait(implicit_wait)
self.register_keyword_to_run_on_failure(run_on_failure)
Expand Down
3 changes: 2 additions & 1 deletion src/Selenium2Library/keywords/_logging.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import os
import sys
from robot.libraries.BuiltIn import BuiltIn
from robot.api import logger
from keywordgroup import KeywordGroup
from robot.libraries.BuiltIn import BuiltIn

class _LoggingKeywords(KeywordGroup):

Expand All @@ -13,6 +13,7 @@ def _debug(self, message):

def _get_log_dir(self):
variables = BuiltIn().get_variables()

logfile = variables['${LOG FILE}']
if logfile != 'NONE':
return os.path.dirname(logfile)
Expand Down
71 changes: 57 additions & 14 deletions src/Selenium2Library/keywords/_screenshot.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
import os, errno
import robot
import os, errno

from Selenium2Library import utils
from keywordgroup import KeywordGroup
from robot.libraries.BuiltIn import RobotNotRunningError


class _ScreenshotKeywords(KeywordGroup):

def __init__(self):
self._screenshot_index = 0
self._screenshot_path_stack = []

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's likely that this could cause an issue. If the same scope sets two screenshot paths then only the first would be popped off when the scope exited. That could cause an issue where the wrong path is used for other scopes...

EDIT: I've decided to go ahead and use what I've got. Being that this is a private part of the API I can solidify this in the next release.

self.screenshot_root_directory = None

# Public

def set_screenshot_directory(self, path, persist=False):
"""Sets the root output directory for captured screenshots.

``path`` argument specifies the location to where the screenshots should
be written to. If the specified ``path`` does not exist, it will be created.
Setting ``persist`` specifies that the given ``path`` should
be used for the rest of the test execution, otherwise the path will be restored
at the end of the currently executing scope.
"""
self._create_directory(path)
if persist is False:
self._screenshot_path_stack.append(self.screenshot_root_directory)
# Restore after current scope ends
utils.events.on('scope_end', 'current', self._restore_screenshot_directory)

self.screenshot_root_directory = path

def capture_page_screenshot(self, filename=None):
"""Takes a screenshot of the current page and embeds it into the log.

Expand All @@ -18,22 +41,14 @@ def capture_page_screenshot(self, filename=None):
the Robot Framework log file is written into. The `filename` is
also considered relative to the same directory, if it is not
given in absolute format. If an absolute or relative path is given
but the path does not exist it will be created.
but the path does not exist it will be created.

`css` can be used to modify how the screenshot is taken. By default
the bakground color is changed to avoid possible problems with
background leaking when the page layout is somehow broken.
"""
path, link = self._get_screenshot_paths(filename)

target_dir = os.path.dirname(path)
if not os.path.exists(target_dir):
try:
os.makedirs(target_dir)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.isdir(target_dir):
pass
else: raise
self._create_directory(path)

if hasattr(self._current_browser(), 'get_screenshot_as_file'):
if not self._current_browser().get_screenshot_as_file(path):
Expand All @@ -47,14 +62,42 @@ def capture_page_screenshot(self, filename=None):
'<img src="%s" width="800px"></a>' % (link, link))

# Private
def _create_directory(self, path):
target_dir = os.path.dirname(path)
if not os.path.exists(target_dir):
try:
os.makedirs(target_dir)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.isdir(target_dir):
pass
else:
raise

def _get_screenshot_directory(self):

# Use screenshot root directory if set
if self.screenshot_root_directory is not None:
return self.screenshot_root_directory

# Otherwise use RF's log directory
try:
return self._get_log_dir()

# Unless robotframework isn't running, then use working directory
except RobotNotRunningError:
return os.getcwd()

# should only be called by set_screenshot_directory
def _restore_screenshot_directory(self):
self.screenshot_root_directory = self._screenshot_path_stack.pop()

def _get_screenshot_paths(self, filename):
if not filename:
self._screenshot_index += 1
filename = 'selenium-screenshot-%d.png' % self._screenshot_index
else:
filename = filename.replace('/', os.sep)
logdir = self._get_log_dir()
path = os.path.join(logdir, filename)
link = robot.utils.get_link_path(path, logdir)
screenshotDir = self._get_screenshot_directory()
path = os.path.join(screenshotDir, filename)
link = robot.utils.get_link_path(path, screenshotDir)
return path, link
6 changes: 1 addition & 5 deletions src/Selenium2Library/locators/elementfinder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from Selenium2Library import utils
from robot.api import logger
from robot.utils import NormalizedDict
from robot.libraries.BuiltIn import BuiltIn


class ElementFinder(object):
Expand Down Expand Up @@ -45,10 +44,7 @@ def register(self, strategy, persist):

if not persist:
# Unregister after current scope ends
suite = BuiltIn().get_variable_value('${SUITE NAME}')
test = BuiltIn().get_variable_value('${TEST NAME}', '')
scope = suite + '.' + test if test != '' else suite
utils.events.on('scope_end', scope, self.unregister, strategy.name)
utils.events.on('scope_end', 'current', self.unregister, strategy.name)

def unregister(self, strategy_name):
if strategy_name in self._default_strategies:
Expand Down
6 changes: 6 additions & 0 deletions src/Selenium2Library/utils/events/scope_event.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from event import Event
from robot.libraries.BuiltIn import BuiltIn

class ScopeEvent(Event):
def __init__(self, scope, action, *args, **kwargs):
Expand All @@ -7,6 +8,11 @@ def __init__(self, scope, action, *args, **kwargs):
self.action_args = args
self.action_kwargs = kwargs

if scope == 'current':
suite = BuiltIn().get_variable_value('${SUITE NAME}')
test = BuiltIn().get_variable_value('${TEST NAME}', '')
self.scope = suite + '.' + test if test != '' else suite

def trigger(self, *args, **kwargs):
if args[0] == self.scope:
self.action(*self.action_args, **self.action_kwargs)
Expand Down
11 changes: 11 additions & 0 deletions test/acceptance/keywords/screenshots.robot
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,14 @@ Capture page screenshot to non-existing directory
[Setup] Remove Directory ${OUTPUTDIR}/screenshot recursive
Capture Page Screenshot screenshot/test-screenshot.png
File Should Exist ${OUTPUTDIR}/screenshot/test-screenshot.png

Capture page screenshot to custom root directory
[Setup] Remove Directory ${OUTPUTDIR}/screenshot recursive
Set Screenshot Directory ${OUTPUTDIR}/screenshot
Capture Page Screenshot custom-root-screenshot.png
File Should Exist ${OUTPUTDIR}/screenshot/custom-root-screenshot.png

Ensure screenshot captures revert to default root directory
[Setup] Remove Files ${OUTPUTDIR}/default-root-screenshot.png
Capture Page Screenshot default-root-screenshot.png
File Should Exist ${OUTPUTDIR}/default-root-screenshot.png