Skip to content

Return screenshot as base64 string and embed into log #1939

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
8 changes: 5 additions & 3 deletions src/SeleniumLibrary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
WebDriverCache,
WindowKeywords,
)
from SeleniumLibrary.keywords.screenshot import EMBED
from SeleniumLibrary.keywords.screenshot import EMBED, BASE64
from SeleniumLibrary.locators import ElementFinder
from SeleniumLibrary.utils import LibraryListener, is_truthy, _convert_timeout, _convert_delay

Expand Down Expand Up @@ -614,8 +614,8 @@ def __init__(
- ``run_on_failure``:
Default action for the `run-on-failure functionality`.
- ``screenshot_root_directory``:
Path to folder where possible screenshots are created or EMBED.
See `Set Screenshot Directory` keyword for further details about EMBED.
Path to folder where possible screenshots are created or EMBED or BASE64.
See `Set Screenshot Directory` keyword for further details about EMBED and BASE64.
If not given, the directory where the log file is written is used.
- ``plugins``:
Allows extending the SeleniumLibrary with external Python classes.
Expand Down Expand Up @@ -846,6 +846,8 @@ def _resolve_screenshot_root_directory(self):
if is_string(screenshot_root_directory):
if screenshot_root_directory.upper() == EMBED:
self.screenshot_root_directory = EMBED
if screenshot_root_directory.upper() == BASE64:
self.screenshot_root_directory = BASE64

@staticmethod
def _get_translation(language: Union[str, None]) -> Union[Path, None]:
Expand Down
68 changes: 46 additions & 22 deletions src/SeleniumLibrary/keywords/screenshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
DEFAULT_FILENAME_PAGE = "selenium-screenshot-{index}.png"
DEFAULT_FILENAME_ELEMENT = "selenium-element-screenshot-{index}.png"
EMBED = "EMBED"
BASE64 = "BASE64"
EMBEDDED_OPTIONS = [EMBED, BASE64]
DEFAULT_FILENAME_PDF = "selenium-page-{index}.pdf"


Expand Down Expand Up @@ -59,6 +61,8 @@ def set_screenshot_directory(self, path: Union[None, str]) -> str:
path = None
elif path.upper() == EMBED:
path = EMBED
elif path.upper() == BASE64:
path = BASE64
else:
path = os.path.abspath(path)
self._create_directory(path)
Expand All @@ -79,7 +83,14 @@ def capture_page_screenshot(self, filename: str = DEFAULT_FILENAME_PAGE) -> str:

If ``filename`` equals to EMBED (case insensitive), then screenshot
is embedded as Base64 image to the log.html. In this case file is not
created in the filesystem.
created in the filesystem. If ``filename`` equals to BASE64 (case
insensitive), then the base64 string is returned and the screenshot
is embedded to the log. This allows one to reuse the image elsewhere
in the report.

Example:
| ${ss}= | `Capture Page Screenshot` | BASE64 |
| Set Test Message | *HTML*Test Success<p><img src="data:image/png;base64,${ss}" width="256px"> |

Starting from SeleniumLibrary 1.8, if ``filename`` contains marker
``{index}``, it will be automatically replaced with an unique running
Expand All @@ -89,9 +100,10 @@ def capture_page_screenshot(self, filename: str = DEFAULT_FILENAME_PAGE) -> str:
format string syntax].

An absolute path to the created screenshot file is returned or if
``filename`` equals to EMBED, word `EMBED` is returned.
``filename`` equals to EMBED, word `EMBED` is returned. If ``filename``
equals to BASE64, the base64 string containing the screenshot is returned.

Support for EMBED is new in SeleniumLibrary 4.2
Support for BASE64 is new in SeleniumLibrary 6.8

Examples:
| `Capture Page Screenshot` | |
Expand All @@ -111,8 +123,9 @@ def capture_page_screenshot(self, filename: str = DEFAULT_FILENAME_PAGE) -> str:
if not self.drivers.current:
self.info("Cannot capture screenshot because no browser is open.")
return
if self._decide_embedded(filename):
return self._capture_page_screen_to_log()
is_embedded, method = self._decide_embedded(filename)
if is_embedded:
return self._capture_page_screen_to_log(method)
return self._capture_page_screenshot_to_file(filename)

def _capture_page_screenshot_to_file(self, filename):
Expand All @@ -123,9 +136,11 @@ def _capture_page_screenshot_to_file(self, filename):
self._embed_to_log_as_file(path, 800)
return path

def _capture_page_screen_to_log(self):
def _capture_page_screen_to_log(self, return_val):
screenshot_as_base64 = self.driver.get_screenshot_as_base64()
self._embed_to_log_as_base64(screenshot_as_base64, 800)
base64_str = self._embed_to_log_as_base64(screenshot_as_base64, 800)
if return_val == BASE64:
return base64_str
return EMBED

@keyword
Expand All @@ -140,27 +155,34 @@ def capture_element_screenshot(
See the `Locating elements` section for details about the locator
syntax.

An absolute path to the created element screenshot is returned.
An absolute path to the created element screenshot is returned. If the ``filename``
equals to BASE64 (case insensitive), then the base64 string is returned in addition
to the screenshot embedded to the log. See ``Capture Page Screenshot`` for more
information.

Support for capturing the screenshot from an element has limited support
among browser vendors. Please check the browser vendor driver documentation
does the browser support capturing a screenshot from an element.

New in SeleniumLibrary 3.3. Support for EMBED is new in SeleniumLibrary 4.2.
Support for BASE64 is new in SeleniumLibrary 6.8.

Examples:
| `Capture Element Screenshot` | id:image_id | |
| `Capture Element Screenshot` | id:image_id | ${OUTPUTDIR}/id_image_id-1.png |
| `Capture Element Screenshot` | id:image_id | EMBED |
| ${ess}= | `Capture Element Screenshot` | id:image_id | BASE64 |

"""
if not self.drivers.current:
self.info(
"Cannot capture screenshot from element because no browser is open."
)
return
element = self.find_element(locator, required=True)
if self._decide_embedded(filename):
return self._capture_element_screen_to_log(element)
is_embedded, method = self._decide_embedded(filename)
if is_embedded:
return self._capture_element_screen_to_log(element, method)
return self._capture_element_screenshot_to_file(element, filename)

def _capture_element_screenshot_to_file(self, element, filename):
Expand All @@ -171,8 +193,10 @@ def _capture_element_screenshot_to_file(self, element, filename):
self._embed_to_log_as_file(path, 400)
return path

def _capture_element_screen_to_log(self, element):
self._embed_to_log_as_base64(element.screenshot_as_base64, 400)
def _capture_element_screen_to_log(self, element, return_val):
base64_str = self._embed_to_log_as_base64(element.screenshot_as_base64, 400)
if return_val == BASE64:
return base64_str
return EMBED

@property
Expand All @@ -184,20 +208,20 @@ def _screenshot_root_directory(self, value):
self.ctx.screenshot_root_directory = value

def _decide_embedded(self, filename):
filename = filename.lower()
filename = filename.upper()
if (
filename == DEFAULT_FILENAME_PAGE
and self._screenshot_root_directory == EMBED
filename == DEFAULT_FILENAME_PAGE.upper()
and self._screenshot_root_directory in EMBEDDED_OPTIONS
):
return True
return True, self._screenshot_root_directory
if (
filename == DEFAULT_FILENAME_ELEMENT
and self._screenshot_root_directory == EMBED
filename == DEFAULT_FILENAME_ELEMENT.upper()
and self._screenshot_root_directory in EMBEDDED_OPTIONS
):
return True
if filename == EMBED.lower():
return True
return False
return True, self._screenshot_root_directory
if filename in EMBEDDED_OPTIONS:
return True, self._screenshot_root_directory
return False, None

def _get_screenshot_path(self, filename):
if self._screenshot_root_directory != EMBED:
Expand Down
41 changes: 30 additions & 11 deletions utest/test/keywords/test_screen_shot.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
SCREENSHOT_FILE_NAME = "selenium-screenshot-{index}.png"
ELEMENT_FILE_NAME = "selenium-element-screenshot-{index}.png"
EMBED = "EMBED"

BASE64 = "BASE64"

@pytest.fixture(scope="module")
def screen_shot():
Expand All @@ -22,24 +22,34 @@ def teardown_function():


def test_defaults(screen_shot):
assert screen_shot._decide_embedded(SCREENSHOT_FILE_NAME) is False
assert screen_shot._decide_embedded(ELEMENT_FILE_NAME) is False
assert screen_shot._decide_embedded(SCREENSHOT_FILE_NAME) == (False, None)
assert screen_shot._decide_embedded(ELEMENT_FILE_NAME) == (False, None)


def test_screen_shotdir_embeded(screen_shot):
screen_shot.ctx.screenshot_root_directory = EMBED
assert screen_shot._decide_embedded(SCREENSHOT_FILE_NAME) is True
assert screen_shot._decide_embedded(SCREENSHOT_FILE_NAME.upper()) is True
assert screen_shot._decide_embedded(ELEMENT_FILE_NAME) is True
assert screen_shot._decide_embedded(ELEMENT_FILE_NAME.upper()) is True
assert screen_shot._decide_embedded("other.psn") is False
assert screen_shot._decide_embedded(SCREENSHOT_FILE_NAME) == (True, EMBED)
assert screen_shot._decide_embedded(SCREENSHOT_FILE_NAME.upper()) == (True, EMBED)
assert screen_shot._decide_embedded(ELEMENT_FILE_NAME) == (True, EMBED)
assert screen_shot._decide_embedded(ELEMENT_FILE_NAME.upper()) == (True, EMBED)
assert screen_shot._decide_embedded("other.psn") == (False, None)


def test_screen_shotdir_return_base64(screen_shot):
screen_shot.ctx.screenshot_root_directory = BASE64
assert screen_shot._decide_embedded(SCREENSHOT_FILE_NAME) == (True, BASE64)
assert screen_shot._decide_embedded(SCREENSHOT_FILE_NAME.upper()) == (True, BASE64)
assert screen_shot._decide_embedded(ELEMENT_FILE_NAME) == (True, BASE64)
assert screen_shot._decide_embedded(ELEMENT_FILE_NAME.upper()) == (True, BASE64)
assert screen_shot._decide_embedded("other.psn") == (False, None)


def test_file_name_embeded(screen_shot):
assert screen_shot._decide_embedded(EMBED) is True
assert screen_shot._decide_embedded("other.psn") is False
assert screen_shot._decide_embedded("other.psn") == (False, None)
screen_shot.ctx.screenshot_root_directory = EMBED
assert screen_shot._decide_embedded(EMBED) is True
assert screen_shot._decide_embedded(EMBED) == (True, EMBED)
screen_shot.ctx.screenshot_root_directory = BASE64
assert screen_shot._decide_embedded(BASE64) == (True, BASE64)


def test_screenshot_path_embedded(screen_shot):
Expand All @@ -56,6 +66,12 @@ def test_sl_init_embed():
sl = SeleniumLibrary(screenshot_root_directory=EMBED)
assert sl.screenshot_root_directory == EMBED

sl = SeleniumLibrary(screenshot_root_directory="bAsE64")
assert sl.screenshot_root_directory == BASE64

sl = SeleniumLibrary(screenshot_root_directory=BASE64)
assert sl.screenshot_root_directory == BASE64


def test_sl_init_not_embed():
sl = SeleniumLibrary(screenshot_root_directory=None)
Expand All @@ -76,6 +92,9 @@ def test_sl_set_screenshot_directory():
sl.set_screenshot_directory(EMBED)
assert sl.screenshot_root_directory == EMBED

sl.set_screenshot_directory(BASE64)
assert sl.screenshot_root_directory == BASE64

sl.set_screenshot_directory("EEmBedD")
assert "EEmBedD" in sl.screenshot_root_directory
assert len("EEmBedD") < len(sl.screenshot_root_directory)
Expand Down
Loading