diff --git a/atest/resources/atest_resource.robot b/atest/resources/atest_resource.robot index ed84a5873b2..e39d29dc8aa 100644 --- a/atest/resources/atest_resource.robot +++ b/atest/resources/atest_resource.robot @@ -33,6 +33,7 @@ ${RUNNER DEFAULTS} ... --ConsoleMarkers OFF ... --PYTHONPATH "${CURDIR}${/}..${/}testresources${/}testlibs" ... --PYTHONPATH "${CURDIR}${/}..${/}testresources${/}listeners" +${SCREENSHOT DIR} %{TEMPDIR}${/}robot_atest_screenshots *** Keywords *** Run Tests diff --git a/atest/robot/standard_libraries/screenshot/take_screenshot.robot b/atest/robot/standard_libraries/screenshot/take_screenshot.robot index d1a97b97649..bf437da63fe 100644 --- a/atest/robot/standard_libraries/screenshot/take_screenshot.robot +++ b/atest/robot/standard_libraries/screenshot/take_screenshot.robot @@ -15,6 +15,14 @@ Basename May Be Defined ${tc}= Check Test Case ${TESTNAME} Check Embedding In Log ${tc.kws[0].kws[0].msgs[1]} foo_1.jpg +Basename May Be Defined With Screenshot Format Of PNG + ${tc} = Check Test Case ${TESTNAME} + Check Embedding In Log ${tc.kws[0].kws[0].msgs[1]} foo_1.png + +Basename May Be Defined With Screenshot Format Of TIFF + ${tc} = Check Test Case ${TESTNAME} + Check Embedding In Log ${tc.kws[0].kws[0].msgs[1]} foo_1.tiff + Basename With Extension Turns Off Index Generation ${tc}= Check Test Case ${TESTNAME} Check Embedding In Log ${tc.kws[0].kws[0].msgs[1]} xxx.jpg @@ -38,9 +46,17 @@ Without Embedding *** Keywords *** Check Embedding In Log [Arguments] ${message} ${path} ${width}=800px - Check Log Message ${message} HTML + ${rel_dir} = Get Screenshot Dir As Relative Path ${path} + Check Log Message ${message} HTML Check Linking In Log [Arguments] ${message} ${file} - ${path} = Normalize Path ${OUTDIR}/${file} - Check Log Message ${message} Screenshot saved to '${path}'. HTML + ${path} = Set Variable ${SCREENSHOT DIR}/${file} + ${rel_dir} = Get Screenshot Dir As Relative Path ${file} + Check Log Message ${message} Screenshot saved to '${path}'. HTML + +Get Screenshot Dir As Relative Path + [Arguments] ${path} + ${parent_dir} = Evaluate "${SCREENSHOT DIR}".split('/')[-1] + ${ret_val} = Set Variable ../${parent_dir}/${path} + RETURN ${ret_val} \ No newline at end of file diff --git a/atest/testdata/standard_libraries/screenshot/screenshot_resource.robot b/atest/testdata/standard_libraries/screenshot/screenshot_resource.robot index e1f3f1761e9..9a22a6f554b 100644 --- a/atest/testdata/standard_libraries/screenshot/screenshot_resource.robot +++ b/atest/testdata/standard_libraries/screenshot/screenshot_resource.robot @@ -17,6 +17,7 @@ Save Start Time Set Test Variable \${START TIME} Screenshots Should Exist - [Arguments] ${directory} @{files} - @{actual files}= List Directory ${directory} *.jp*g + [Arguments] ${directory} @{files} ${format}=jpg + ${file_ext_re} = Set Variable If "${format.lower()}" == "jpg" *.jp*g *.${format} + @{actual files}= List Directory ${directory} ${file_ext_re} Lists Should Be Equal ${actual files} ${files} diff --git a/atest/testdata/standard_libraries/screenshot/take_screenshot.robot b/atest/testdata/standard_libraries/screenshot/take_screenshot.robot index 470acbf5ff2..0ecaf9457e1 100644 --- a/atest/testdata/standard_libraries/screenshot/take_screenshot.robot +++ b/atest/testdata/standard_libraries/screenshot/take_screenshot.robot @@ -1,17 +1,22 @@ *** Settings *** -Suite Setup Remove Files ${OUTPUTDIR}/*.jp*g -Test Setup Save Start Time -Test Teardown Remove Files ${OUTPUTDIR}/*.jp*g +Library OperatingSystem +Suite Setup Remove Files ${OUTPUTDIR}/*.jp*g ${OUTPUTDIR}/*.png ${OUTPUTDIR}/*.tiff ${OUTPUTDIR}/*.bmp +Test Setup Take Screenshot Test Setup +Test Teardown Take Screenshot Test Teardown Resource screenshot_resource.robot *** Variables *** ${FIRST_SCREENSHOT} screenshot_1.jpg ${SECOND_SCREENSHOT} screenshot_2.jpg +${SCREENSHOT DIR} = %{TEMPDIR}${/}robot_atest_screenshots +${BASENAME} = ${SCREENSHOT DIR}${/}screenshot +${FIRST_SCREENSHOT} = ${BASENAME}_1.jpg *** Test Cases *** Screenshot Is Embedded in Log File ${path}= Take Screenshot and Verify ${FIRST_SCREENSHOT} - Should Be Equal ${path} ${OUTPUTDIR}${/}${FIRST_SCREENSHOT} + ${path_normalized} = Normalize Path ${SCREENSHOT DIR} + Should Be Equal ${path} ${SCREENSHOT DIR}${/}${FIRST_SCREENSHOT} Each Screenshot Gets Separate Index Take Screenshot and Verify ${FIRST_SCREENSHOT} @@ -19,31 +24,75 @@ Each Screenshot Gets Separate Index Basename May Be Defined Repeat Keyword 2 Take Screenshot foo - Screenshots Should Exist ${OUTPUTDIR} foo_1.jpg foo_2.jpg + Screenshots Should Exist ${SCREENSHOT DIR} foo_1.jpg foo_2.jpg + +Basename May Be Defined With Screenshot Format Of PNG + Repeat Keyword 2 Take Screenshot foo img_format=png + Screenshots Should Exist ${SCREENSHOT DIR} foo_1.png foo_2.png format=png + +Basename May Be Defined With Screenshot Format Of TIFF + Repeat Keyword 2 Take Screenshot foo img_format=tiff + Screenshots Should Exist ${SCREENSHOT DIR} foo_1.tiff foo_2.tiff format=tiff Basename With Extension Turns Off Index Generation - Repeat Keyword 3 Take Screenshot xxx.jpg - Repeat Keyword 2 Take Screenshot yyy.jpeg - Screenshots Should Exist ${OUTPUTDIR} xxx.jpg yyy.jpeg + Repeat Keyword 3 Take Screenshot xxx.jpg img_format=jpg + Repeat Keyword 2 Take Screenshot yyy.jpeg img_format=jpg + Screenshots Should Exist ${SCREENSHOT DIR} xxx.jpg yyy.jpeg format=jpg + Repeat Keyword 3 Take Screenshot xxx.png img_format=png + Screenshots Should Exist ${SCREENSHOT DIR} xxx.png format=png + Repeat Keyword 3 Take Screenshot xxx.bmp img_format=bmp + Screenshots Should Exist ${SCREENSHOT DIR} xxx.bmp format=bmp Name as `pathlib.Path` Take Screenshot ${{pathlib.Path('name.jpg')}} - Screenshots Should Exist ${OUTPUTDIR} name.jpg + Screenshots Should Exist ${SCREENSHOT DIR} name.jpg + +Name as `pathlib.Path` - Format PNG + Take Screenshot ${{pathlib.Path('name.png')}} img_format=png + Screenshots Should Exist ${SCREENSHOT DIR} name.png format=png + +Name as `pathlib.Path` - Format TIFF + Take Screenshot ${{pathlib.Path('name.tiff')}} img_format=tiff + Screenshots Should Exist ${SCREENSHOT DIR} name.tiff format=tiff Screenshot Width Can Be Given Take Screenshot width=300px - Screenshots Should Exist ${OUTPUTDIR} ${FIRST_SCREENSHOT} + Screenshots Should Exist ${SCREENSHOT DIR} ${FIRST_SCREENSHOT} + +Screenshot Width Can Be Given For PNG + Take Screenshot foo.png width=300px img_format=png + Screenshots Should Exist ${SCREENSHOT DIR} foo.png format=png + +Screenshot Width Can Be Given For BMP + Take Screenshot foo.bmp width=300px img_format=bmp + Screenshots Should Exist ${SCREENSHOT DIR} foo.bmp format=bmp Basename With Non-existing Directories Fails - [Documentation] FAIL Directory '${OUTPUTDIR}${/}non-existing' where to save the screenshot does not exist - Take Screenshot ${OUTPUTDIR}${/}non-existing${/}foo + Run Keyword And Expect Error Directory '/non-existing' where to save the screenshot does not exist + ... Take Screenshot ${/}non-existing${/}foo Without Embedding Take Screenshot Without Embedding no_embed.jpeg +Take Screenshot Without Embedding For PNG + Take Screenshot Without Embedding no_embed.png img_format=png + +Take Screenshot Without Embedding for TIFF + Take Screenshot Without Embedding no_embed.tiff img_format=tiff + *** Keywords *** +Take Screenshot Test Setup + Save Start Time + Create Directory ${SCREENSHOT DIR} + Set Screenshot Directory ${SCREENSHOT DIR} + +Take Screenshot Test Teardown + Remove Directory ${SCREENSHOT DIR} recursive=${True} + Remove Files ${SCREENSHOT DIR}/*.jp*g ${SCREENSHOT DIR}/*.png + ... ${SCREENSHOT DIR}/*.tiff ${SCREENSHOT DIR}/*.bmp + Take Screenshot And Verify [Arguments] @{expected files} ${path}= Take Screenshot - Screenshots Should Exist ${OUTPUTDIR} @{expected files} + Screenshots Should Exist ${SCREENSHOT DIR} @{expected files} RETURN ${path} diff --git a/src/robot/libraries/Screenshot.py b/src/robot/libraries/Screenshot.py old mode 100644 new mode 100755 index 92e25daa9c7..4ecf505f2ae --- a/src/robot/libraries/Screenshot.py +++ b/src/robot/libraries/Screenshot.py @@ -57,8 +57,10 @@ class Screenshot: one found will be used. - wxPython :: http://wxpython.org :: Generic Python GUI toolkit. + - supports: png, jpeg, gif, pcx, pnm, tiff, tga, ico, cur - PyGTK :: http://pygtk.org :: This module is available by default on most Linux distributions. + - supports: - Pillow :: http://python-pillow.github.io :: Only works on Windows. Also the original PIL package is supported. - Scrot :: http://en.wikipedia.org/wiki/Scrot :: Not used on Windows. @@ -85,8 +87,26 @@ class Screenshot: ROBOT_LIBRARY_SCOPE = 'TEST SUITE' ROBOT_LIBRARY_VERSION = get_version() - - def __init__(self, screenshot_directory=None, screenshot_module=None): + IMAGE_FORMATS = { + "jpg": {"extensions": ("jpg", "jpeg",), + "wx_bmp_type": "BITMAP_TYPE_JPEG", + "gtk_type": "jpeg", + "pil_type": "JPEG"}, + "png": {"extensions": ("png",), + "wx_bmp_type": "BITMAP_TYPE_PNG", + "gtk_type": "png", + "pil_type": "PNG"}, + "tiff": {"extensions": ("tiff", "tif",), + "wx_bmp_type": "BITMAP_TYPE_TIFF", + "gtk_type": None, + "pil_type": "TIFF"}, + "bmp": {"extensions": ("bmp",), + "wx_bmp_type": "BITMAP_TYPE_BMP", + "gtk_type": "bmp", + "pil_type": "BMP"} + } + + def __init__(self, screenshot_directory=None, screenshot_module=None, image_format="jpg"): """Configure where screenshots are saved. If ``screenshot_directory`` is not given, screenshots are saved into @@ -104,8 +124,9 @@ def __init__(self, screenshot_directory=None, screenshot_module=None): | Library | Screenshot | ${TEMPDIR} | | Library | Screenshot | screenshot_module=PyGTK | """ + self._out_img_format = image_format self._given_screenshot_dir = self._norm_path(screenshot_directory) - self._screenshot_taker = ScreenshotTaker(screenshot_module) + self._screenshot_taker = ScreenshotTaker(screenshot_module, self._out_img_format) def _norm_path(self, path): if not path: @@ -136,6 +157,7 @@ def set_screenshot_directory(self, path): The directory can also be set in `importing`. """ + print(f"path passed in is: {path}") path = self._norm_path(path) if not os.path.isdir(path): raise RuntimeError("Directory '%s' does not exist." % path) @@ -143,8 +165,9 @@ def set_screenshot_directory(self, path): self._given_screenshot_dir = path return old - def take_screenshot(self, name="screenshot", width="800px"): - """Takes a screenshot in JPEG format and embeds it into the log file. + def take_screenshot(self, name="screenshot", width="800px", img_format="jpg"): + """Takes a screenshot in the specified format, jpg by default, and embeds + it into the log file Name of the file where the screenshot is stored is derived from the given ``name``. If the ``name`` ends with extension ``.jpg`` or @@ -158,6 +181,8 @@ def take_screenshot(self, name="screenshot", width="800px"): ``width`` specifies the size of the screenshot in the log file. + ``img_format`` specifies the image format for the screenshot, default is "jpg" + Examples: (LOGDIR is determined automatically by the library) | Take Screenshot | | | # LOGDIR/screenshot_1.jpg (index automatically incremented) | | Take Screenshot | mypic | | # LOGDIR/mypic_1.jpg (index automatically incremented) | @@ -165,20 +190,24 @@ def take_screenshot(self, name="screenshot", width="800px"): | Take Screenshot | pic.jpg | | # LOGDIR/pic.jpg (always uses this file) | | Take Screenshot | images/login.jpg | 80% | # Specify both name and width. | | Take Screenshot | width=550px | | # Specify only width. | + | Take Screenshot | img_format=png | | # Specify only image format, png in this case | + | Take Screenshot | png_image | img_format=tiff | # Specify name and image format, "tiff" in this case | The path where the screenshot is saved is returned. """ + self._out_img_format = img_format path = self._save_screenshot(name) self._embed_screenshot(path, width) return path - def take_screenshot_without_embedding(self, name="screenshot"): + def take_screenshot_without_embedding(self, name="screenshot", img_format="jpg"): """Takes a screenshot and links it from the log file. This keyword is otherwise identical to `Take Screenshot` but the saved screenshot is not embedded into the log file. The screenshot is linked so it is nevertheless easily available. """ + self._out_img_format = img_format path = self._save_screenshot(name) self._link_screenshot(path) return path @@ -208,12 +237,18 @@ def _validate_screenshot_path(self, path): return path def _get_screenshot_path(self, basename): - if basename.lower().endswith(('.jpg', '.jpeg')): + try: + # retrieve a tuple of the common file extension variations + file_extensions = Screenshot.IMAGE_FORMATS[self._out_img_format]["extensions"] + except KeyError as ke: + logger.error(f"KeyError in _get_screenshot_path(): {ke}") + if any([basename.lower().endswith(x) for x in file_extensions]): return os.path.join(self._screenshot_dir, basename) index = 0 while True: index += 1 - path = os.path.join(self._screenshot_dir, "%s_%d.jpg" % (basename, index)) + # use the image format key for the file extension + path = os.path.join(self._screenshot_dir, "%s_%d.%s" % (basename, index, self._out_img_format)) if not os.path.exists(path): return path @@ -230,7 +265,8 @@ def _link_screenshot(self, path): class ScreenshotTaker: - def __init__(self, module_name=None): + def __init__(self, module_name=None, image_format='jpg'): + self._image_format = image_format self._screenshot = self._get_screenshot_taker(module_name) self.module = self._screenshot.__name__.split('_')[1] self._wx_app_reference = None @@ -289,7 +325,7 @@ def _get_default_screenshot_taker(self): return screenshot_taker def _osx_screenshot(self, path): - if self._call('screencapture', '-t', 'jpg', path) != 0: + if self._call('screencapture', '-t', self._image_format, path) != 0: raise RuntimeError("Using 'screencapture' failed.") def _call(self, *command): @@ -304,9 +340,13 @@ def _scrot(self): return os.sep == '/' and self._call('scrot', '--version') == 0 def _scrot_screenshot(self, path): - if not path.endswith(('.jpg', '.jpeg')): - raise RuntimeError("Scrot requires extension to be '.jpg' or " - "'.jpeg', got '%s'." % os.path.splitext(path)[1]) + try: + file_extensions = Screenshot.IMAGE_FORMATS[self._image_format]["extensions"] + except KeyError as ke: + logger.error(f"KeyError in _scrot_screenshot(): {ke}") + if any([path.endswith(x) for x in file_extensions]): + raise RuntimeError("Scrot requires extension to be like %s, " + "but got '%s'." % (str(file_extensions), os.path.splitext(path)[1])) if os.path.exists(path): os.remove(path) if self._call('scrot', '--silent', path) != 0: @@ -325,7 +365,7 @@ def _wx_screenshot(self, path): memory.SelectObject(bitmap) memory.Blit(0, 0, width, height, context, -1, -1) memory.SelectObject(wx.NullBitmap) - bitmap.SaveFile(path, wx.BITMAP_TYPE_JPEG) + bitmap.SaveFile(path, Screenshot.IMAGE_FORMATS[self._image_format]["wx_bmp_type"]) def _gtk_screenshot(self, path): window = gdk.get_default_root_window() @@ -337,10 +377,10 @@ def _gtk_screenshot(self, path): 0, 0, 0, 0, width, height) if not pb: raise RuntimeError('Taking screenshot failed.') - pb.save(path, 'jpeg') + pb.save(path, Screenshot.IMAGE_FORMATS[self._image_format]["gtk_type"]) def _pil_screenshot(self, path): - ImageGrab.grab().save(path, 'JPEG') + ImageGrab.grab().save(path, Screenshot.IMAGE_FORMATS[self._image_format]["pil_type"]) def _no_screenshot(self, path): raise RuntimeError('Taking screenshots is not supported on this platform '