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
1 change: 0 additions & 1 deletion src/SeleniumLibrary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,6 @@ class SeleniumLibrary(DynamicCore):
def __init__(self, timeout=5.0, implicit_wait=0.0,
run_on_failure='Capture Page Screenshot',
screenshot_root_directory=None):

"""SeleniumLibrary can be imported with several optional arguments.

- ``timeout``:
Expand Down
232 changes: 83 additions & 149 deletions src/SeleniumLibrary/keywords/screenshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,174 +14,108 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import errno
import os
import re

from robot.utils import get_link_path

from SeleniumLibrary.base import LibraryComponent, keyword
from SeleniumLibrary.utils import events, is_falsy
from SeleniumLibrary.utils import is_noney


class ScreenshotKeywords(LibraryComponent):

def __init__(self, ctx):
LibraryComponent.__init__(self, ctx)
self._screenshot_index = {}
self._screenshot_path_stack = []
self.screenshot_root_directory = None

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

``path`` argument specifies the absolute path 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.
def set_screenshot_directory(self, path, persist='DEPRECATED'):
"""Sets the directory for captured screenshots.

``path`` argument specifies the absolute path to a directory where
the screenshots should be written to. If the directory does not
exist, it will be created. The directory can also be set when
`importing` the library. If it is not configured anywhere,
screenshots are saved to the same directory where Robot Framework's
log file is written.

``persist`` argument is deprecated and has no effect.

The previous value is returned and can be used to restore
the original value later if needed.

Deprecating ``persist`` and returning the previous value are new
in SeleniumLibrary 3.0.
"""
path = os.path.abspath(path)
self._create_directory(path)
if is_falsy(persist):
self._screenshot_path_stack.append(self.screenshot_root_directory)
# Restore after current scope ends
events.on('scope_end', 'current',
self._restore_screenshot_directory)
self.screenshot_root_directory = path
if is_noney(path):
path = None
else:
path = os.path.abspath(path)
self._create_directory(path)
if persist != 'DEPRECATED':
self.warn("'persist' argument to 'Set Screenshot Directory' "
"keyword is deprecated and has no effect.")
previous = self.ctx.screenshot_root_directory
self.ctx.screenshot_root_directory = path
return previous

@keyword
def capture_page_screenshot(self,
filename='selenium-screenshot-{index}.png'):
"""Takes a screenshot of the current page and embeds it into the log.
"""Takes screenshot of the current page and embeds it into log file.

``filename`` argument specifies the name of the file to write the
screenshot into. If no ``filename`` is given, the screenshot is saved
into file _selenium-screenshot-{index}.png_ under the directory where
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.

Starting from SeleniumLibrary 1.8 if ``filename`` contains _{index}_
characters, it will be automatically replaced with running index.
The running index is unique for each different filename. The absolute
path of the saved screenshot is always returned and it does not depend
does the ``filename`` contain _{index}_. See example 1 and 2 for more
details.

The _{index}_ is replaced with the actual index by using Python's
[https://docs.python.org/2/library/stdtypes.html#str.format|
str.format] method, and it can be formatted using the standard
[https://docs.python.org/2/library/string.html#format-string-syntax|
format string syntax]. The example 3 shows this by setting the width and
the fill character.

If there is a need to write literal _{index}_ or if ``filename``
contains _{_ or _}_ characters, then the braces must be doubled.

If _{index}_ is used, the computed filename will not overwrite
an existing file. The number chosen will be the first number
that results in a unique filename. For example, if the
computed name is screenshot-1.png but screenshot-1.png already
exists, screenshot-2.png will be tried, and so on, until a
unique name is found.

Example 1:
| ${file1} = | Capture Page Screenshot |
| File Should Exist | ${OUTPUTDIR}${/}selenium-screenshot-1.png |
| Should Be Equal | ${file1} | ${OUTPUTDIR}${/}selenium-screenshot-1.png |
| ${file2} = | Capture Page Screenshot |
| File Should Exist | ${OUTPUTDIR}${/}selenium-screenshot-2.png |
| Should Be Equal | ${file2} | ${OUTPUTDIR}${/}selenium-screenshot-2.png |

Example 2:
| ${file1} = | Capture Page Screenshot | ${OTHER_DIR}${/}other-{index}-name.png |
| ${file2} = | Capture Page Screenshot | ${OTHER_DIR}${/}some-other-name-{index}.png |
| ${file3} = | Capture Page Screenshot | ${OTHER_DIR}${/}other-{index}-name.png |
| File Should Exist | ${OTHER_DIR}${/}other-1-name.png |
| Should Be Equal | ${file1} | ${OTHER_DIR}${/}other-1-name.png |
| File Should Exist | ${OTHER_DIR}${/}some-other-name-1.png |
| Should Be Equal | ${file2} | ${OTHER_DIR}${/}some-other-name-1.png |
| File Should Exist | ${OTHER_DIR}${/}other-2-name.png |
| Should Be Equal | ${file3} | ${OTHER_DIR}${/}other-2-name.png |

Example 3:
| Capture Page Screenshot | ${OTHER_DIR}${/}sc-{index:06}.png |
| File Should Exist | ${OTHER_DIR}${/}sc-000001.png |

screenshot into. The directory where screenshots are saved can be
set when `importing` the library or by using the `Set Screenshot
Directory` keyword. If the directory is not configured, screenshots
are saved to the same directory where Robot Framework's log file is
written.

Starting from SeleniumLibrary 1.8, if ``filename`` contains marker
``{index}``, it will be automatically replaced with unique running
index preventing files to be overwritten. Indices start from 1,
and how they are represented can be customized using Python's
[https://docs.python.org/2/library/string.html#formatstrings|
format string syntax].

An absolute path to the created screenshot file is returned.

Examples:
| `Capture Page Screenshot` | |
| `File Should Exist` | ${OUTPUTDIR}/selenium-screenshot-1.png |
| ${path} = | `Capture Page Screenshot` |
| `File Should Exist` | ${OUTPUTDIR}/selenium-screenshot-2.png |
| `File Should Exist` | ${path} |
| `Capture Page Screenshot` | custom_name.png |
| `File Should Exist` | ${OUTPUTDIR}/custom_name.png |
| `Capture Page Screenshot` | custom_with_index_{index}.png |
| `File Should Exist` | ${OUTPUTDIR}/custom_with_index_1.png |
| `Capture Page Screenshot` | formatted_index_{index:03}.png |
| `File Should Exist` | ${OUTPUTDIR}/formatted_index_001.png |
"""
try:
self.browser

except RuntimeError:
self.info("Couldn't capture page screenshot because no browser is opened")

else:
path, link = self._get_screenshot_paths(filename)
self._create_directory(path)
if hasattr(self.browser, 'get_screenshot_as_file'):
if not self.browser.get_screenshot_as_file(path):
raise RuntimeError('Failed to save screenshot ' + link)
else:
if not self.browser.save_screenshot(path):
raise RuntimeError('Failed to save screenshot ' + link)
# Image is shown on its own row and thus prev row is closed on purpose
msg = (
'</td></tr><tr><td colspan="3"><a href="{}">'
'<img src="{}" width="800px"></a>'.format(link, link)
)
self.info(msg, html=True)
return path
if not self.browsers.current:
self.info('Cannot capture screenshot because no browser is open.')
return
path = self._get_screenshot_path(filename)
self._create_directory(path)
if not self.browser.save_screenshot(path):
raise RuntimeError("Failed to save screenshot '{}'.".format(path))
# Image is shown on its own row and thus previous row is closed on
# purpose. Depending on Robot's log structure is a bit risky.
self.info('</td></tr><tr><td colspan="3">'
'<a href="{src}"><img src="{src}" width="800px"></a>'
.format(src=get_link_path(path, self.log_dir)), html=True)
return path

def _get_screenshot_path(self, filename):
directory = self.ctx.screenshot_root_directory or self.log_dir
filename = filename.replace('/', os.sep)
index = 0
while True:
index += 1
formatted = filename.format(index=index)
path = os.path.join(directory, formatted)
# filename didn't contain {index} or unique path was found
if formatted == filename or not os.path.exists(path):
return path

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
return self.log_dir

# 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_template):
screenshotdir = self._get_screenshot_directory()

filename = filename_template.format(
index=self._get_screenshot_index(filename_template))

# try to match {index} but not {{index}} (plus handle
# other variants like {index!r})
if re.search(r'(?<!{){index(![rs])?(:.*?)?}(?!})', filename_template):
# make sure the computed filename doesn't exist. We only
# do this if the template had the {index} formatting
# sequence (or one of it's variations)
while os.path.exists(os.path.join(screenshotdir, filename)):
filename = filename_template.format(
index=self._get_screenshot_index(filename_template))

filename = filename.replace('/', os.sep)
path = os.path.join(screenshotdir, filename)
link = get_link_path(path, self.log_dir)
return path, link

def _get_screenshot_index(self, filename):
if filename not in self._screenshot_index:
self._screenshot_index[filename] = 0
self._screenshot_index[filename] += 1
return self._screenshot_index[filename]
os.makedirs(target_dir)
Loading