Skip to content

Commit 2673f21

Browse files
authored
Add Base64 encoded screenshot and additional screenshot creation from XPATH element (#194)
* Update .gitignore, add VSCode directory and .venv * Add base64 logging function to robotlog * Change screenshot module behaviour to accomodate base64 image conversion - Add xpath to ValueContainer - Add additional actions for element and Base64 format - add automation to init - Add capture function for base64 using MemoryStream - Add optional xpath to both capture functions * Change screenshot keywords to use optional base64 and allow capturing by XPATh - Adjust 'take screenshot' keyword with optional base64 boolean - Add keyword 'take screenshot from element' for capturing element with xpath * Fix multiple build errors - Fix initialization error due to missing automation parameter - fix line too long - Fix build errors, add ignore for ImageFormat - Remove ScreenshotType - Remove unused Optional import * Add screenshot mode, Adjust parameter to retrieved element, execute capture based on mode * Add AutomationInterfaceContainer, Combine capture into single keyword with optional XPATH, Add set mode keyword * Remove unused imports * Change example text * Add simple testcases for new screenshot keyword behaviour * Fix init to use container for screenshot keywords, remove container from Screenshot module * Fix ScreenshotMode not defined (missing self) * add getter for screenshot mode, Fix linting errors/warnings, adjust docstrings * Update changelog * Fix missing default value for element in _capture_base64 * Improve screenshot testcases for base64 mode and xpath * Fix argument name typo * Tiny fix of take_screenshot docstring to explain identifier * Fix AttributeError due to improper key access * Add truthy checks in base64 screenshot tests before checking length * Remove redundant None, explicit msg argument for get_element * Add return statement to _capture function * Fix check for None (wrong keyword used) * Fix element assigned to wrong argument for screenshot action * Remove redundant screenshot logging in keyword * Fix filename for screenshot in test * Fix Screenshot.robot (reorder test cases, deactivate Base64) * Update Screenshot.robot (Minor format adjustment) * Add getter keyword for screenshot log mode * Add teardown mechanism with default state for all screenshot cases * Add missing PID for reset to properly close applications * Update .gitignore for custom keen.bat file in development * Fix pylint errors to get them good grades * Fix missing trailing blank line * Fix via robotidy
1 parent f002d62 commit 2673f21

File tree

7 files changed

+200
-23
lines changed

7 files changed

+200
-23
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# Custom keen.bat for development
2+
keen.dev.bat
3+
4+
# Python environment
5+
.venv
6+
7+
# VSCode IDE files
8+
.vscode
9+
tmp/
10+
111
## Ignore Visual Studio temporary files, build results, and
212
## files generated by popular Visual Studio add-ons.
313

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
77

88
## [Unreleased][]
99

10+
### Added
11+
- [#192](https://github.com/GDATASoftwareAG/robotframework-flaui/issues/192) New Screenshot Logging behaviour using Base64 encoded images
12+
- [#193](https://github.com/GDATASoftwareAG/robotframework-flaui/issues/193) Changed Screenshot keyword behaviour (Optional XPATH for element-based screenshot)
13+
1014
## [Release][3.3.0] [3.3.0][3.2.0-3.3.0] - 2024-08-03
1115

1216
### Added

atests/Screenshot.robot

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Library String
88
Library OperatingSystem
99
Library StringFormat
1010
Library FlaUILibrary uia=${UIA} screenshot_on_failure=True
11+
Resource util/Common.resource
1112
Resource util/Error.resource
1213
Resource util/XPath.resource
1314
@@ -24,7 +25,7 @@ Take No Screenshot If Module Is Disabled
2425
Take Screenshots On Failure False
2526
Run Keyword And Expect Error ${EXP_ERR_MSG} Click ${XPATH_NOT_EXISTS}
2627
File Should Not Exist ${OUTPUT DIR}/${SCREENSHOT_FOLDER}/${FILENAME}
27-
Take Screenshots On Failure True
28+
[Teardown] Reset Screenshot Environment To Default
2829
2930
Take Screenshot If XPath Not Found Multiple Times Default Folder
3031
FOR ${_} IN RANGE 1 3
@@ -33,6 +34,7 @@ Take Screenshot If XPath Not Found Multiple Times Default Folder
3334
Run Keyword And Expect Error ${EXP_ERR_MSG} Click ${XPATH_NOT_EXISTS}
3435
Wait Until Created ${OUTPUT DIR}/${FILENAME} 1s
3536
END
37+
[Teardown] Reset Screenshot Environment To Default
3638
3739
Take Screenshot If XPath Not Found Multiple Times By Specific Folder
3840
Set Screenshot Directory ${SCREENSHOT_FOLDER}
@@ -42,7 +44,7 @@ Take Screenshot If XPath Not Found Multiple Times By Specific Folder
4244
Run Keyword And Expect Error ${EXP_ERR_MSG} Click ${XPATH_NOT_EXISTS}
4345
Wait Until Created ${OUTPUT DIR}/${SCREENSHOT_FOLDER}/${FILENAME} 1s
4446
END
45-
Set Screenshot Directory
47+
[Teardown] Reset Screenshot Environment To Default
4648
4749
Take Manual Screenshot By Keyword
4850
Set Screenshot Directory ${SCREENSHOT_FOLDER}
@@ -54,16 +56,15 @@ Take Manual Screenshot By Keyword
5456
File Should Not Exist ${OUTPUT DIR}/${SCREENSHOT_FOLDER}/${FILENAME}
5557
Take Screenshot
5658
File Should Exist ${OUTPUT DIR}/${SCREENSHOT_FOLDER}/${FILENAME}
57-
Set Screenshot Directory
58-
Take Screenshots On Failure True
59+
[Teardown] Reset Screenshot Environment To Default
5960
6061
Test Case 1234: Something to Test
6162
Set Screenshot Directory ${SCREENSHOT_FOLDER}
6263
${FILENAME} Get Expected Filename Test Case 1234 Something to Test
6364
File Should Not Exist ${OUTPUT DIR}/${SCREENSHOT_FOLDER}/${FILENAME}
6465
Take Screenshot
6566
File Should Exist ${OUTPUT DIR}/${SCREENSHOT_FOLDER}/${FILENAME}
66-
Set Screenshot Directory
67+
[Teardown] Reset Screenshot Environment To Default
6768
6869
No Screenshots Should Created For No Library Keywords
6970
Set Screenshot Directory ${SCREENSHOT_FOLDER}
@@ -72,7 +73,33 @@ No Screenshots Should Created For No Library Keywords
7273
Run Keyword And Ignore Error Fail You Should Not Pass
7374
Run Keyword And Ignore Error Wait Until Keyword Succeeds 5x 10ms Fail You Should Not Pass
7475
File Should Not Exist ${OUTPUT DIR}/${SCREENSHOT_FOLDER}/${FILENAME}
75-
Set Screenshot Directory
76+
[Teardown] Reset Screenshot Environment To Default
77+
78+
Take Screenshot Of Window
79+
[Setup] Start Application
80+
${PID} Attach Application By Name ${TEST_APP}
81+
Set Screenshot Directory ${SCREENSHOT_FOLDER}
82+
${FILENAME} Get Expected Filename ${TEST_NAME}
83+
File Should Not Exist ${OUTPUT DIR}/${SCREENSHOT_FOLDER}/${FILENAME}
84+
Take Screenshot ${MAIN_WINDOW}
85+
File Should Exist ${OUTPUT DIR}/${SCREENSHOT_FOLDER}/${FILENAME}
86+
[Teardown] Reset Screenshot Environment To Default ${PID}
87+
88+
Take Screenshot As Base64
89+
Set Screenshot Log Mode Base64
90+
${base64} Take Screenshot
91+
Should Not Be Equal ${base64} ${None} Returned base64 image is 'None'
92+
Should Not Be Empty ${base64} Returned base64 image is empty
93+
[Teardown] Reset Screenshot Environment To Default
94+
95+
Take Screenshot Of Window As Base64
96+
[Setup] Start Application
97+
${PID} Attach Application By Name ${TEST_APP}
98+
Set Screenshot Log Mode Base64
99+
${base64} Take Screenshot ${MAIN_WINDOW}
100+
Should Not Be Equal ${base64} ${None} Returned base64 image is 'None'
101+
Should Not Be Empty ${base64} Returned base64 image is empty
102+
[Teardown] Reset Screenshot Environment To Default ${PID}
76103
77104
78105
*** Keywords ***
@@ -91,3 +118,11 @@ Get Expected Filename
91118
${FILENAME} Catenate SEPARATOR=. ${FILENAME} jpg
92119
93120
RETURN ${FILENAME}
121+
122+
Reset Screenshot Environment To Default
123+
[Documentation] Reset screenshot environment to default and initial settings.
124+
[Arguments] ${pid}=${None}
125+
Set Screenshot Log Mode File
126+
Take Screenshots On Failure True
127+
Set Screenshot Directory
128+
Run Keyword And Ignore Error Stop Application ${pid}

src/FlaUILibrary/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def __init__(self, uia='UIA3', screenshot_on_failure='True', screenshot_dir=None
138138
FlaUILibrary.KeywordModules.GRID: GridKeywords(self.container),
139139
FlaUILibrary.KeywordModules.MOUSE: MouseKeywords(self.container),
140140
FlaUILibrary.KeywordModules.KEYBOARD: KeyboardKeywords(self.container),
141-
FlaUILibrary.KeywordModules.SCREENSHOT: ScreenshotKeywords(self.screenshots),
141+
FlaUILibrary.KeywordModules.SCREENSHOT: ScreenshotKeywords(self.screenshots, self.container),
142142
FlaUILibrary.KeywordModules.TEXTBOX: TextBoxKeywords(self.container),
143143
FlaUILibrary.KeywordModules.WINDOW: WindowKeywords(self.container),
144144
FlaUILibrary.KeywordModules.RADIOBUTTON: RadioButtonKeywords(self.container),

src/FlaUILibrary/flaui/module/screenshot.py

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@
22
import platform
33
import time
44
from enum import Enum
5+
from typing import Any, Optional
56
from FlaUI.Core.Capturing import Capture # pylint: disable=import-error
67
from System import Exception as CSharpException # pylint: disable=import-error
8+
from System import Convert as CSharpConvert # pylint: disable=import-error
9+
from System.IO import MemoryStream # pylint: disable=import-error
10+
from System.Drawing.Imaging import ImageFormat # pylint: disable=import-error
711
from FlaUILibrary.flaui.exception import FlaUiError
812
from FlaUILibrary.flaui.interface import (ModuleInterface, ValueContainer)
913
from FlaUILibrary.robotframework import robotlog
1014

1115

16+
1217
# pylint: disable=too-many-instance-attributes
1318
class Screenshot(ModuleInterface):
1419
"""
@@ -21,12 +26,21 @@ class Container(ValueContainer):
2126
Value container from screenshot module.
2227
"""
2328
keywords: list
29+
element: Optional[Any]
2430

2531
class Action(Enum):
2632
"""
2733
Supported actions for execute action implementation.
2834
"""
2935
CAPTURE = "CAPTURE"
36+
CAPTURE_ELEMENT = "CAPTURE_ELEMENT"
37+
38+
class ScreenshotMode(Enum):
39+
"""
40+
Supported modes for screenshots.
41+
"""
42+
FILE = "File"
43+
BASE64 = "Base64"
3044

3145
def __init__(self, directory, is_enabled):
3246
"""
@@ -40,6 +54,7 @@ def __init__(self, directory, is_enabled):
4054
self._name = ""
4155
self._hostname = self._clean_invalid_windows_syntax(platform.node().lower())
4256
self._filename = "test_{}_{}_{}_{}.jpg"
57+
self._mode = self.ScreenshotMode.FILE
4358

4459
def set_name(self, name):
4560
"""
@@ -51,8 +66,28 @@ def set_name(self, name):
5166
"""
5267
self._name = self._clean_invalid_windows_syntax(name.replace(" ", "_").lower())
5368

69+
def set_mode(self, mode: str):
70+
"""
71+
Set screenshot logging mode. Available modes: File, Base64
72+
73+
Args:
74+
mode (str): Screenshot mode to set.
75+
"""
76+
if mode.upper() == 'FILE':
77+
self._mode = self.ScreenshotMode.FILE
78+
elif mode.upper() == 'BASE64':
79+
self._mode = self.ScreenshotMode.BASE64
80+
else:
81+
FlaUiError.raise_fla_ui_error(FlaUiError.ActionNotSupported)
82+
83+
def get_mode(self):
84+
"""
85+
Return the configured screenshot logging mode.
86+
"""
87+
return self._mode
88+
5489
@staticmethod
55-
def create_value_container(keywords=None):
90+
def create_value_container(keywords=None, element=None):
5691
"""
5792
Helper to create container object.
5893
@@ -62,17 +97,29 @@ def create_value_container(keywords=None):
6297
if keywords is None:
6398
keywords = []
6499

65-
return Screenshot.Container(keywords=keywords)
100+
return Screenshot.Container(keywords=keywords, element=element)
66101

67102
def execute_action(self, action: Action, values: ValueContainer):
68103
# pylint: disable=unnecessary-lambda
69104
switcher = {
70-
self.Action.CAPTURE: lambda: self._capture()
105+
self.Action.CAPTURE: lambda: self._capture(),
106+
self.Action.CAPTURE_ELEMENT: lambda: self._capture(element=values['element'])
71107
}
72108

73109
return switcher.get(action, lambda: FlaUiError.raise_fla_ui_error(FlaUiError.ActionNotSupported))()
74110

75-
def _capture(self):
111+
def _capture(self, element=None):
112+
"""
113+
Capture image depending on mode
114+
"""
115+
if self._mode == self.ScreenshotMode.FILE:
116+
return self._capture_file(element)
117+
if self._mode == self.ScreenshotMode.BASE64:
118+
return self._capture_base64(element)
119+
return FlaUiError.raise_fla_ui_error("Invalid screenshot mode selected. Available modes: "
120+
+ '\n'.join([str(mode) for mode in self.ScreenshotMode]))
121+
122+
def _capture_file(self, element=None):
76123
"""
77124
Capture image from desktop.
78125
"""
@@ -90,7 +137,10 @@ def _capture(self):
90137
os.makedirs(directory)
91138

92139
try:
93-
image = Capture.Screen()
140+
if element:
141+
image = Capture.Element(element)
142+
else:
143+
image = Capture.Screen()
94144
image.ToFile(filepath)
95145

96146
# Log screenshot from temp or persist mode
@@ -107,6 +157,33 @@ def _capture(self):
107157

108158
return filepath
109159

160+
def _capture_base64(self, element=None):
161+
image = None
162+
163+
try:
164+
if element:
165+
image = Capture.Element(element)
166+
else:
167+
image = Capture.Screen()
168+
stream = MemoryStream()
169+
image.Bitmap.Save(stream, ImageFormat.Png)
170+
base64 = CSharpConvert.ToBase64String(stream.GetBuffer())
171+
stream.Close()
172+
173+
# Log screenshot from temp or persist mode
174+
robotlog.log_screenshot_base64(base64)
175+
176+
except CSharpException:
177+
robotlog.log("Error to save as base64 encoded string: " + element)
178+
179+
finally:
180+
self.img_counter += 1
181+
if image is not None:
182+
# C# --> class CaptureImage : IDisposable
183+
image.Dispose()
184+
185+
return base64
186+
110187
def _get_path(self):
111188
"""
112189
Get directory path for logging.

src/FlaUILibrary/keywords/screenshot.py

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,74 @@
11
from robot.utils import is_truthy
22
from robotlibcore import keyword
33
from FlaUILibrary.flaui.module.screenshot import Screenshot
4-
from FlaUILibrary.robotframework import robotlog
5-
4+
from FlaUILibrary.flaui.util.automationinterfacecontainer import AutomationInterfaceContainer
65

76
class ScreenshotKeywords:
87
"""
98
Interface implementation from Robotframework usage for screenshot keywords.
109
"""
1110

12-
def __init__(self, screenshots: Screenshot):
11+
def __init__(self, screenshots: Screenshot, container: AutomationInterfaceContainer):
1312
"""Creates screenshot keywords module to handle image capturing.
1413
15-
``screenshots`` Screenshots module for image capturing.
14+
``screenshots`` Screenshots module for image capturing
15+
``container`` User automation container to handle element interaction
1616
"""
1717
self._screenshots = screenshots
18+
self._container = container
1819

1920
@keyword
20-
def take_screenshot(self):
21-
""" Takes a screenshot of the whole desktop. Returns path to the screenshot if screenshot was created.
21+
def get_screenshot_log_mode(self):
22+
"""Returns the current logging mode of the screenshot module. Default is 'File'.
23+
24+
Example:
25+
| ${log_mode} = | Get Screenshot Log Mode |
26+
"""
27+
return self._screenshots.get_mode()
28+
29+
@keyword
30+
def set_screenshot_log_mode(self, log_mode: str):
31+
"""Sets the logging mode of the screenshot module. Default is 'File'.
32+
Mode 'File' logs screenshots as files in the screenshot directory.
33+
Mode 'Base64' logs screenshots as base64 encoded strings embedded in the test report.
34+
35+
Arguments:
36+
| Argument | Type | Description |
37+
| log_mode | string | File | Base64 |
38+
39+
Example:
40+
| Set Screenshot Log Mode Base64 |
41+
"""
42+
self._screenshots.set_mode(log_mode)
43+
44+
@keyword
45+
def take_screenshot(self, identifier=None, msg=None):
46+
""" Takes a screenshot of the whole desktop or the element, from the optionally provided identifier.
47+
Returns screenshot depending on log mode.
48+
Screenshot mode File -> returns filepath
49+
Screenshot mode Base64 -> returns encoded base64 string of image
50+
51+
Arguments:
52+
| Argument | Type | Description |
53+
| identifier | string | XPath identifier from element |
54+
| msg | string | Custom error message |
2255
2356
Example:
2457
| Take Screenshot |
58+
| Take Screenshot <XPATH> |
59+
| Take Screenshot <XPATH> "Your custom error message" |
2560
"""
26-
filepath = self._screenshots.execute_action(Screenshot.Action.CAPTURE,
61+
image_var = None
62+
if identifier:
63+
module = self._container.create_or_get_module()
64+
element = module.get_element(identifier, msg=msg)
65+
image_var = self._screenshots.execute_action(Screenshot.Action.CAPTURE_ELEMENT,
66+
Screenshot.create_value_container(element=element))
67+
else:
68+
image_var = self._screenshots.execute_action(Screenshot.Action.CAPTURE,
2769
Screenshot.create_value_container())
2870

29-
if filepath:
30-
robotlog.log_screenshot(filepath)
31-
32-
return filepath
71+
return image_var
3372

3473
@keyword
3574
def take_screenshots_on_failure(self, enabled):

src/FlaUILibrary/robotframework/robotlog.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,15 @@ def log_screenshot(filepath: str):
3737
),
3838
html=True,
3939
)
40+
41+
def log_screenshot_base64(image: str):
42+
"""
43+
Append testing log by a screenshot in base64 format.
44+
45+
``image`` Image as string in base64 encoding.
46+
"""
47+
logger.info(
48+
'</td></tr><tr><td colspan="3">' +
49+
f'<img src="data:image/png;base64,{image}" width="800px"/>',
50+
html=True
51+
)

0 commit comments

Comments
 (0)