Robot Framework
Screenshot.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import os
17 import subprocess
18 import sys
19 
20 try:
21  import wx
22 except ImportError:
23  wx = None
24 try:
25  from gtk import gdk
26 except ImportError:
27  gdk = None
28 try:
29  from PIL import ImageGrab # apparently available only on Windows
30 except ImportError:
31  ImageGrab = None
32 
33 from robot.api import logger
34 from robot.libraries.BuiltIn import BuiltIn
35 from robot.version import get_version
36 from robot.utils import abspath, get_error_message, get_link_path
37 
38 
39 
84 class Screenshot:
85 
86  ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
87  ROBOT_LIBRARY_VERSION = get_version()
88 
89 
106  def __init__(self, screenshot_directory=None, screenshot_module=None):
107  self._given_screenshot_dir_given_screenshot_dir = self._norm_path_norm_path(screenshot_directory)
108  self._screenshot_taker_screenshot_taker = ScreenshotTaker(screenshot_module)
109 
110  def _norm_path(self, path):
111  if not path:
112  return path
113  elif isinstance(path, os.PathLike):
114  path = str(path)
115  else:
116  path = path.replace('/', os.sep)
117  return os.path.normpath(path)
118 
119  @property
120  _screenshot_dir = property
121 
122  def _screenshot_dir(self):
123  return self._given_screenshot_dir_given_screenshot_dir or self._log_dir_log_dir_log_dir
124 
125  @property
126  _log_dir = property
127 
128  def _log_dir(self):
129  variables = BuiltIn().get_variables()
130  outdir = variables['${OUTPUTDIR}']
131  log = variables['${LOGFILE}']
132  log = os.path.dirname(log) if log != 'NONE' else '.'
133  return self._norm_path_norm_path(os.path.join(outdir, log))
134 
135 
142  def set_screenshot_directory(self, path):
143  path = self._norm_path_norm_path(path)
144  if not os.path.isdir(path):
145  raise RuntimeError("Directory '%s' does not exist." % path)
146  old = self._screenshot_dir_screenshot_dir_screenshot_dir
147  self._given_screenshot_dir_given_screenshot_dir = path
148  return old
149 
150 
174  def take_screenshot(self, name="screenshot", width="800px"):
175  path = self._save_screenshot_save_screenshot(name)
176  self._embed_screenshot_embed_screenshot(path, width)
177  return path
178 
179 
185  def take_screenshot_without_embedding(self, name="screenshot"):
186  path = self._save_screenshot_save_screenshot(name)
187  self._link_screenshot_link_screenshot(path)
188  return path
189 
190  def _save_screenshot(self, name):
191  name = str(name) if isinstance(name, os.PathLike) else name.replace('/', os.sep)
192  path = self._get_screenshot_path_get_screenshot_path(name)
193  return self._screenshot_to_file_screenshot_to_file(path)
194 
195  def _screenshot_to_file(self, path):
196  path = self._validate_screenshot_path_validate_screenshot_path(path)
197  logger.debug('Using %s module/tool for taking screenshot.'
198  % self._screenshot_taker_screenshot_taker.module)
199  try:
200  self._screenshot_taker_screenshot_taker(path)
201  except:
202  logger.warn('Taking screenshot failed: %s\n'
203  'Make sure tests are run with a physical or virtual '
204  'display.' % get_error_message())
205  return path
206 
207  def _validate_screenshot_path(self, path):
208  path = abspath(self._norm_path_norm_path(path))
209  if not os.path.exists(os.path.dirname(path)):
210  raise RuntimeError("Directory '%s' where to save the screenshot "
211  "does not exist" % os.path.dirname(path))
212  return path
213 
214  def _get_screenshot_path(self, basename):
215  if basename.lower().endswith(('.jpg', '.jpeg')):
216  return os.path.join(self._screenshot_dir_screenshot_dir_screenshot_dir, basename)
217  index = 0
218  while True:
219  index += 1
220  path = os.path.join(self._screenshot_dir_screenshot_dir_screenshot_dir, "%s_%d.jpg" % (basename, index))
221  if not os.path.exists(path):
222  return path
223 
224  def _embed_screenshot(self, path, width):
225  link = get_link_path(path, self._log_dir_log_dir_log_dir)
226  logger.info('<a href="%s"><img src="%s" width="%s"></a>'
227  % (link, link, width), html=True)
228 
229  def _link_screenshot(self, path):
230  link = get_link_path(path, self._log_dir_log_dir_log_dir)
231  logger.info("Screenshot saved to '<a href=\"%s\">%s</a>'."
232  % (link, path), html=True)
233 
234 
236 
237  def __init__(self, module_name=None):
238  self._screenshot_screenshot = self._get_screenshot_taker_get_screenshot_taker(module_name)
239  self.modulemodule = self._screenshot_screenshot.__name__.split('_')[1]
240  self._wx_app_reference_wx_app_reference = None
241 
242  def __call__(self, path):
243  self._screenshot_screenshot(path)
244 
245  def __bool__(self):
246  return self.modulemodule != 'no'
247 
248  def test(self, path=None):
249  if not self:
250  print("Cannot take screenshots.")
251  return False
252  print("Using '%s' to take screenshot." % self.modulemodule)
253  if not path:
254  print("Not taking test screenshot.")
255  return True
256  print("Taking test screenshot to '%s'." % path)
257  try:
258  self(path)
259  except:
260  print("Failed: %s" % get_error_message())
261  return False
262  else:
263  print("Success!")
264  return True
265 
266  def _get_screenshot_taker(self, module_name=None):
267  if sys.platform == 'darwin':
268  return self._osx_screenshot_osx_screenshot
269  if module_name:
270  return self._get_named_screenshot_taker_get_named_screenshot_taker(module_name.lower())
271  return self._get_default_screenshot_taker_get_default_screenshot_taker()
272 
274  screenshot_takers = {'wxpython': (wx, self._wx_screenshot_wx_screenshot),
275  'pygtk': (gdk, self._gtk_screenshot_gtk_screenshot),
276  'pil': (ImageGrab, self._pil_screenshot_pil_screenshot),
277  'scrot': (self._scrot_scrot_scrot, self._scrot_screenshot_scrot_screenshot)}
278  if name not in screenshot_takers:
279  raise RuntimeError("Invalid screenshot module or tool '%s'." % name)
280  supported, screenshot_taker = screenshot_takers[name]
281  if not supported:
282  raise RuntimeError("Screenshot module or tool '%s' not installed."
283  % name)
284  return screenshot_taker
285 
287  for module, screenshot_taker in [(wx, self._wx_screenshot_wx_screenshot),
288  (gdk, self._gtk_screenshot_gtk_screenshot),
289  (ImageGrab, self._pil_screenshot_pil_screenshot),
290  (self._scrot_scrot_scrot, self._scrot_screenshot_scrot_screenshot),
291  (True, self._no_screenshot_no_screenshot)]:
292  if module:
293  return screenshot_taker
294 
295  def _osx_screenshot(self, path):
296  if self._call_call('screencapture', '-t', 'jpg', path) != 0:
297  raise RuntimeError("Using 'screencapture' failed.")
298 
299  def _call(self, *command):
300  try:
301  return subprocess.call(command, stdout=subprocess.PIPE,
302  stderr=subprocess.STDOUT)
303  except OSError:
304  return -1
305 
306  @property
307  _scrot = property
308 
309  def _scrot(self):
310  return os.sep == '/' and self._call_call('scrot', '--version') == 0
311 
312  def _scrot_screenshot(self, path):
313  if not path.endswith(('.jpg', '.jpeg')):
314  raise RuntimeError("Scrot requires extension to be '.jpg' or "
315  "'.jpeg', got '%s'." % os.path.splitext(path)[1])
316  if os.path.exists(path):
317  os.remove(path)
318  if self._call_call('scrot', '--silent', path) != 0:
319  raise RuntimeError("Using 'scrot' failed.")
320 
321  def _wx_screenshot(self, path):
322  if not self._wx_app_reference_wx_app_reference:
323  self._wx_app_reference_wx_app_reference = wx.App(False)
324  context = wx.ScreenDC()
325  width, height = context.GetSize()
326  if wx.__version__ >= '4':
327  bitmap = wx.Bitmap(width, height, -1)
328  else:
329  bitmap = wx.EmptyBitmap(width, height, -1)
330  memory = wx.MemoryDC()
331  memory.SelectObject(bitmap)
332  memory.Blit(0, 0, width, height, context, -1, -1)
333  memory.SelectObject(wx.NullBitmap)
334  bitmap.SaveFile(path, wx.BITMAP_TYPE_JPEG)
335 
336  def _gtk_screenshot(self, path):
337  window = gdk.get_default_root_window()
338  if not window:
339  raise RuntimeError('Taking screenshot failed.')
340  width, height = window.get_size()
341  pb = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, width, height)
342  pb = pb.get_from_drawable(window, window.get_colormap(),
343  0, 0, 0, 0, width, height)
344  if not pb:
345  raise RuntimeError('Taking screenshot failed.')
346  pb.save(path, 'jpeg')
347 
348  def _pil_screenshot(self, path):
349  ImageGrab.grab().save(path, 'JPEG')
350 
351  def _no_screenshot(self, path):
352  raise RuntimeError('Taking screenshots is not supported on this platform '
353  'by default. See library documentation for details.')
354 
355 
356 if __name__ == "__main__":
357  if len(sys.argv) not in [2, 3]:
358  sys.exit("Usage: %s <path>|test [wxpython|pygtk|pil|scrot]"
359  % os.path.basename(sys.argv[0]))
360  path = sys.argv[1] if sys.argv[1] != 'test' else None
361  module = sys.argv[2] if len(sys.argv) > 2 else None
362  ScreenshotTaker(module).test(path)
An always available standard library with often needed keywords.
Definition: BuiltIn.py:3944
def _get_screenshot_taker(self, module_name=None)
Definition: Screenshot.py:266
def __init__(self, module_name=None)
Definition: Screenshot.py:237
Library for taking screenshots on the machine where tests are executed.
Definition: Screenshot.py:84
def __init__(self, screenshot_directory=None, screenshot_module=None)
Configure where screenshots are saved.
Definition: Screenshot.py:106
def _embed_screenshot(self, path, width)
Definition: Screenshot.py:224
def _get_screenshot_path(self, basename)
Definition: Screenshot.py:214
def take_screenshot_without_embedding(self, name="screenshot")
Takes a screenshot and links it from the log file.
Definition: Screenshot.py:185
def take_screenshot(self, name="screenshot", width="800px")
Takes a screenshot in JPEG format and embeds it into the log file.
Definition: Screenshot.py:174
def set_screenshot_directory(self, path)
Sets the directory where screenshots are saved.
Definition: Screenshot.py:142
def get_error_message()
Returns error message of the last occurred exception.
Definition: error.py:34
def abspath(path, case_normalize=False)
Replacement for os.path.abspath with some enhancements and bug fixes.
Definition: robotpath.py:65
def get_link_path(target, base)
Returns a relative path to target from base.
Definition: robotpath.py:78
def get_version(naked=False)
Definition: version.py:24