Robot Framework Integrated Development Environment (RIDE)
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 from __future__ import print_function
17 
18 import os
19 import subprocess
20 import sys
21 if sys.platform.startswith('java'):
22  from java.awt import Toolkit, Robot, Rectangle
23  from javax.imageio import ImageIO
24  from java.io import File
25 elif sys.platform == 'cli':
26  import clr
27  clr.AddReference('System.Windows.Forms')
28  clr.AddReference('System.Drawing')
29  from System.Drawing import Bitmap, Graphics, Imaging
30  from System.Windows.Forms import Screen
31 else:
32  try:
33  import wx
34  except ImportError:
35  wx = None
36  try:
37  from gtk import gdk
38  except ImportError:
39  gdk = None
40  try:
41  from PIL import ImageGrab # apparently available only on Windows
42  except ImportError:
43  ImageGrab = None
44 
45 from robotide.lib.robot.api import logger
46 from robotide.lib.robot.libraries.BuiltIn import BuiltIn
47 from robotide.lib.robot.version import get_version
48 from robotide.lib.robot.utils import abspath, get_error_message, get_link_path, py2to3
49 
50 
51 
95 class Screenshot():
96 
97  ROBOT_LIBRARY_SCOPE = 'TEST SUITE'
98  ROBOT_LIBRARY_VERSION = get_version()
99 
100 
120  def __init__(self, screenshot_directory=None, screenshot_module=None):
121  self._given_screenshot_dir_given_screenshot_dir = self._norm_path_norm_path(screenshot_directory)
122  self._screenshot_taker_screenshot_taker = ScreenshotTaker(screenshot_module)
123 
124  def _norm_path(self, path):
125  if not path:
126  return path
127  return os.path.normpath(path.replace('/', os.sep))
128 
129  @property
130  _screenshot_dir = property
131 
132  def _screenshot_dir(self):
133  return self._given_screenshot_dir_given_screenshot_dir or self._log_dir_log_dir_log_dir
134 
135  @property
136  _log_dir = property
137 
138  def _log_dir(self):
139  variables = BuiltIn().get_variables()
140  outdir = variables['${OUTPUTDIR}']
141  log = variables['${LOGFILE}']
142  log = os.path.dirname(log) if log != 'NONE' else '.'
143  return self._norm_path_norm_path(os.path.join(outdir, log))
144 
145 
152  def set_screenshot_directory(self, path):
153  path = self._norm_path_norm_path(path)
154  if not os.path.isdir(path):
155  raise RuntimeError("Directory '%s' does not exist." % path)
156  old = self._screenshot_dir_screenshot_dir_screenshot_dir
157  self._given_screenshot_dir_given_screenshot_dir = path
158  return old
159 
160 
184  def take_screenshot(self, name="screenshot", width="800px"):
185  path = self._save_screenshot_save_screenshot(name)
186  self._embed_screenshot_embed_screenshot(path, width)
187  return path
188 
189 
195  def take_screenshot_without_embedding(self, name="screenshot"):
196  path = self._save_screenshot_save_screenshot(name)
197  self._link_screenshot_link_screenshot(path)
198  return path
199 
200  def _save_screenshot(self, basename, directory=None):
201  path = self._get_screenshot_path_get_screenshot_path(basename, directory)
202  return self._screenshot_to_file_screenshot_to_file(path)
203 
204  def _screenshot_to_file(self, path):
205  path = self._validate_screenshot_path_validate_screenshot_path(path)
206  logger.debug('Using %s module/tool for taking screenshot.'
207  % self._screenshot_taker_screenshot_taker.module)
208  try:
209  self._screenshot_taker_screenshot_taker(path)
210  except:
211  logger.warn('Taking screenshot failed: %s\n'
212  'Make sure tests are run with a physical or virtual '
213  'display.' % get_error_message())
214  return path
215 
216  def _validate_screenshot_path(self, path):
217  path = abspath(self._norm_path_norm_path(path))
218  if not os.path.exists(os.path.dirname(path)):
219  raise RuntimeError("Directory '%s' where to save the screenshot "
220  "does not exist" % os.path.dirname(path))
221  return path
222 
223  def _get_screenshot_path(self, basename, directory):
224  directory = self._norm_path_norm_path(directory) if directory else self._screenshot_dir_screenshot_dir_screenshot_dir
225  if basename.lower().endswith(('.jpg', '.jpeg')):
226  return os.path.join(directory, basename)
227  index = 0
228  while True:
229  index += 1
230  path = os.path.join(directory, "%s_%d.jpg" % (basename, index))
231  if not os.path.exists(path):
232  return path
233 
234  def _embed_screenshot(self, path, width):
235  link = get_link_path(path, self._log_dir_log_dir_log_dir)
236  logger.info('<a href="%s"><img src="%s" width="%s"></a>'
237  % (link, link, width), html=True)
238 
239  def _link_screenshot(self, path):
240  link = get_link_path(path, self._log_dir_log_dir_log_dir)
241  logger.info("Screenshot saved to '<a href=\"%s\">%s</a>'."
242  % (link, path), html=True)
243 
244 
245 @py2to3
247 
248  def __init__(self, module_name=None):
249  self._screenshot_screenshot = self._get_screenshot_taker_get_screenshot_taker(module_name)
250  self.modulemodule = self._screenshot_screenshot.__name__.split('_')[1]
251  self._wx_app_reference_wx_app_reference = None
252 
253  def __call__(self, path):
254  self._screenshot_screenshot(path)
255 
256  def __nonzero__(self):
257  return self.modulemodule != 'no'
258 
259  def test(self, path=None):
260  if not self:
261  print("Cannot take screenshots.")
262  return False
263  print("Using '%s' to take screenshot." % self.modulemodule)
264  if not path:
265  print("Not taking test screenshot.")
266  return True
267  print("Taking test screenshot to '%s'." % path)
268  try:
269  self(path)
270  except:
271  print("Failed: %s" % get_error_message())
272  return False
273  else:
274  print("Success!")
275  return True
276 
277  def _get_screenshot_taker(self, module_name=None):
278  if sys.platform.startswith('java'):
279  return self._java_screenshot_java_screenshot
280  if sys.platform == 'cli':
281  return self._cli_screenshot_cli_screenshot
282  if sys.platform == 'darwin':
283  return self._osx_screenshot_osx_screenshot
284  if module_name:
285  return self._get_named_screenshot_taker_get_named_screenshot_taker(module_name.lower())
286  return self._get_default_screenshot_taker_get_default_screenshot_taker()
287 
289  screenshot_takers = {'wxpython': (wx, self._wx_screenshot_wx_screenshot),
290  'pygtk': (gdk, self._gtk_screenshot_gtk_screenshot),
291  'pil': (ImageGrab, self._pil_screenshot_pil_screenshot),
292  'scrot': (self._scrot_scrot_scrot, self._scrot_screenshot_scrot_screenshot)}
293  if name not in screenshot_takers:
294  raise RuntimeError("Invalid screenshot module or tool '%s'." % name)
295  supported, screenshot_taker = screenshot_takers[name]
296  if not supported:
297  raise RuntimeError("Screenshot module or tool '%s' not installed."
298  % name)
299  return screenshot_taker
300 
302  for module, screenshot_taker in [(wx, self._wx_screenshot_wx_screenshot),
303  (gdk, self._gtk_screenshot_gtk_screenshot),
304  (ImageGrab, self._pil_screenshot_pil_screenshot),
305  (self._scrot_scrot_scrot, self._scrot_screenshot_scrot_screenshot),
306  (True, self._no_screenshot_no_screenshot)]:
307  if module:
308  return screenshot_taker
309 
310  def _java_screenshot(self, path):
311  size = Toolkit.getDefaultToolkit().getScreenSize()
312  rectangle = Rectangle(0, 0, size.width, size.height)
313  image = Robot().createScreenCapture(rectangle)
314  ImageIO.write(image, 'jpg', File(path))
315 
316  def _cli_screenshot(self, path):
317  bmp = Bitmap(Screen.PrimaryScreen.Bounds.Width,
318  Screen.PrimaryScreen.Bounds.Height)
319  graphics = Graphics.FromImage(bmp)
320  try:
321  graphics.CopyFromScreen(0, 0, 0, 0, bmp.Size)
322  finally:
323  graphics.Dispose()
324  bmp.Save(path, Imaging.ImageFormat.Jpeg)
325 
326  def _osx_screenshot(self, path):
327  if self._call_call('screencapture', '-t', 'jpg', path) != 0:
328  raise RuntimeError("Using 'screencapture' failed.")
329 
330  def _call(self, *command):
331  try:
332  return subprocess.call(command, stdout=subprocess.PIPE,
333  stderr=subprocess.STDOUT)
334  except OSError:
335  return -1
336 
337  @property
338  _scrot = property
339 
340  def _scrot(self):
341  return os.sep == '/' and self._call_call('scrot', '--version') == 0
342 
343  def _scrot_screenshot(self, path):
344  if not path.endswith(('.jpg', '.jpeg')):
345  raise RuntimeError("Scrot requires extension to be '.jpg' or "
346  "'.jpeg', got '%s'." % os.path.splitext(path)[1])
347  if self._call_call('scrot', '--silent', path) != 0:
348  raise RuntimeError("Using 'scrot' failed.")
349 
350  def _wx_screenshot(self, path):
351  if not self._wx_app_reference_wx_app_reference:
352  self._wx_app_reference_wx_app_reference = wx.App(False)
353  context = wx.ScreenDC()
354  width, height = context.GetSize()
355  if wx.__version__ >= '4':
356  bitmap = wx.Bitmap(width, height, -1)
357  else:
358  bitmap = wx.EmptyBitmap(width, height, -1)
359  memory = wx.MemoryDC()
360  memory.SelectObject(bitmap)
361  memory.Blit(0, 0, width, height, context, -1, -1)
362  memory.SelectObject(wx.NullBitmap)
363  bitmap.SaveFile(path, wx.BITMAP_TYPE_JPEG)
364 
365  def _gtk_screenshot(self, path):
366  window = gdk.get_default_root_window()
367  if not window:
368  raise RuntimeError('Taking screenshot failed.')
369  width, height = window.get_size()
370  pb = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, width, height)
371  pb = pb.get_from_drawable(window, window.get_colormap(),
372  0, 0, 0, 0, width, height)
373  if not pb:
374  raise RuntimeError('Taking screenshot failed.')
375  pb.save(path, 'jpeg')
376 
377  def _pil_screenshot(self, path):
378  ImageGrab.grab().save(path, 'JPEG')
379 
380  def _no_screenshot(self, path):
381  raise RuntimeError('Taking screenshots is not supported on this platform '
382  'by default. See library documentation for details.')
383 
384 
385 if __name__ == "__main__":
386  if len(sys.argv) not in [2, 3]:
387  sys.exit("Usage: %s <path>|test [wx|pygtk|pil|scrot]"
388  % os.path.basename(sys.argv[0]))
389  path = sys.argv[1] if sys.argv[1] != 'test' else None
390  module = sys.argv[2] if len(sys.argv) > 2 else None
391  ScreenshotTaker(module).test(path)
An always available standard library with often needed keywords.
Definition: BuiltIn.py:3551
Test library for taking screenshots on the machine where tests are run.
Definition: Screenshot.py:95
def set_screenshot_directory(self, path)
Sets the directory where screenshots are saved.
Definition: Screenshot.py:152
def _save_screenshot(self, basename, directory=None)
Definition: Screenshot.py:200
def __init__(self, screenshot_directory=None, screenshot_module=None)
Configure where screenshots are saved.
Definition: Screenshot.py:120
def _get_screenshot_path(self, basename, directory)
Definition: Screenshot.py:223
def take_screenshot(self, name="screenshot", width="800px")
Takes a screenshot in JPEG format and embeds it into the log file.
Definition: Screenshot.py:184
def take_screenshot_without_embedding(self, name="screenshot")
Takes a screenshot and links it from the log file.
Definition: Screenshot.py:195
def get_error_message()
Returns error message of the last occurred exception.
Definition: error.py:41
def abspath(path, case_normalize=False)
Replacement for os.path.abspath with some enhancements and bug fixes.
Definition: robotpath.py:87
def get_link_path(target, base)
Returns a relative path to target from base.
Definition: robotpath.py:100
def get_version(naked=False)
Definition: version.py:24