diff --git a/atest/acceptance/keywords/print_page.robot b/atest/acceptance/keywords/print_page.robot new file mode 100644 index 000000000..a78cd6623 --- /dev/null +++ b/atest/acceptance/keywords/print_page.robot @@ -0,0 +1,25 @@ +*** Settings *** +Documentation Suite description +Suite Setup Go To Page "non_ascii.html" +Resource ../resource.robot + +Test Setup Remove Files ${OUTPUTDIR}/selenium-page-*.pdf + +*** Test Cases *** +Print Page As PDF Without Print Options + Print Page As PDF + +Verify Index Increments With Multiple Prints + [Setup] Remove Files ${OUTPUTDIR}/selenium-page-*.pdf + ${file_1} = Print Page As PDF background=${True} scale=${2} + Should Be Equal ${file_1} ${OUTPUTDIR}${/}selenium-page-1.pdf + ${file_2} = Print Page As PDF orientation=landscape + Should Be Equal ${file_2} ${OUTPUTDIR}${/}selenium-page-2.pdf + Go To https://robotframework.org/foundation/ + ${file_3} = Print Page As PDF shrink_to_fit=${True} page_height=${35.56} page_width=${21.59} + Should Be Equal ${file_3} ${OUTPUTDIR}${/}selenium-page-3.pdf + +Print With Full Options + Print Page As PDF page_ranges=['1'] background=${False} shrink_to_fit=${False} orientation=portrait + ... margin_top=${0.5} margin_left=${1.5} margin_bottom=${0.5} margin_right=${1.5} + ... page_height=${35.56} page_width=${21.59} diff --git a/src/SeleniumLibrary/keywords/screenshot.py b/src/SeleniumLibrary/keywords/screenshot.py index db84c2861..8cd8dc299 100644 --- a/src/SeleniumLibrary/keywords/screenshot.py +++ b/src/SeleniumLibrary/keywords/screenshot.py @@ -14,10 +14,12 @@ # See the License for the specific language governing permissions and # limitations under the License. import os -from typing import Union +from typing import Optional, Union +from base64 import b64decode from robot.utils import get_link_path from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.common.print_page_options import PrintOptions, Orientation from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.utils.path_formatter import _format_path @@ -25,6 +27,7 @@ DEFAULT_FILENAME_PAGE = "selenium-screenshot-{index}.png" DEFAULT_FILENAME_ELEMENT = "selenium-element-screenshot-{index}.png" EMBED = "EMBED" +DEFAULT_FILENAME_PDF = "selenium-page-{index}.pdf" class ScreenshotKeywords(LibraryComponent): @@ -235,3 +238,100 @@ def _embed_to_log_as_file(self, path, width): f'', html=True, ) + + @keyword + def print_page_as_pdf(self, + filename: str = DEFAULT_FILENAME_PDF, + background: Optional[bool] = None, + margin_bottom: Optional[float] = None, + margin_left: Optional[float] = None, + margin_right: Optional[float] = None, + margin_top: Optional[float] = None, + orientation: Optional[Orientation] = None, + page_height: Optional[float] = None, + page_ranges: Optional[list] = None, + page_width: Optional[float] = None, + scale: Optional[float] = None, + shrink_to_fit: Optional[bool] = None, + # path_to_file=None, + ): + """ Print the current page as a PDF + + ``page_ranges`` defaults to `['-']` or "all" pages. ``page_ranges`` takes a list of + strings indicating the ranges. + + The page size defaults to 21.59 for ``page_width`` and 27.94 for ``page_height``. + This is the equivalent size of US-Letter. The assumed units on these parameters + is centimeters. + + The default margin for top, left, bottom, right is `1`. The assumed units on + these parameters is centimeters. + + The default ``orientation`` is `portrait`. ``orientation`` can be either `portrait` + or `landscape`. + + The default ``scale`` is `1`. ``scale`` must be greater than or equal to `0.1` and + less than or equal to `2`. + + ``background`` and ``scale_to_fit`` can be either `${True}` or `${False}`.. + + If all print options are None then a pdf will fail to print silently. + """ + + if page_ranges is None: + page_ranges = ['-'] + + print_options = PrintOptions() + if background is not None: + print_options.background = background + if margin_bottom is not None: + print_options.margin_bottom = margin_bottom + if margin_left is not None: + print_options.margin_left = margin_left + if margin_right is not None: + print_options.margin_right = margin_right + if margin_top is not None: + print_options.margin_top = margin_top + if orientation is not None: + print_options.orientation = orientation + if page_height is not None: + print_options.page_height = page_height + if page_ranges is not None: + print_options.page_ranges = page_ranges + if page_width is not None: + print_options.page_width = page_width + if scale is not None: + print_options.scale = scale + if shrink_to_fit is not None: + print_options.shrink_to_fit = shrink_to_fit + + if not self.drivers.current: + self.info("Cannot print page to pdf because no browser is open.") + return + return self._print_page_as_pdf_to_file(filename, print_options) + + def _print_page_as_pdf_to_file(self, filename, options): + path = self._get_pdf_path(filename) + self._create_directory(path) + pdfdata = self.driver.print_page(options) + if not pdfdata: + raise RuntimeError(f"Failed to print page.") + self._save_pdf_to_file(pdfdata, path) + return path + + def _save_pdf_to_file(self, pdfbase64, path): + pdfdata = b64decode(pdfbase64) + with open(path, mode='wb') as pdf: + pdf.write(pdfdata) + + def _get_pdf_path(self, filename): + directory = self.log_dir + filename = filename.replace("/", os.sep) + index = 0 + while True: + index += 1 + formatted = _format_path(filename, index) + path = os.path.join(directory, formatted) + # filename didn't contain {index} or unique path was found + if formatted == filename or not os.path.exists(path): + return path diff --git a/utest/test/api/test_plugins.py b/utest/test/api/test_plugins.py index 2a07a1156..c8241d8ba 100644 --- a/utest/test/api/test_plugins.py +++ b/utest/test/api/test_plugins.py @@ -22,7 +22,7 @@ def setUpClass(cls): def test_no_libraries(self): for item in [None, "None", ""]: sl = SeleniumLibrary(plugins=item) - self.assertEqual(len(sl.get_keyword_names()), 181) + self.assertEqual(len(sl.get_keyword_names()), 182) def test_parse_library(self): plugin = "path.to.MyLibrary"