Skip to content

Commit fde612e

Browse files
authored
Merge pull request #5 from hhslepicka/print-to-pdf
ENH: Print to PDF
2 parents 6f266b3 + a548461 commit fde612e

File tree

4 files changed

+130
-10
lines changed

4 files changed

+130
-10
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
.DS_Store
22

3+
# VSCode IDE files
4+
.vscode
5+
36
# PyCharm IDE files
47
.idea
58

botcity/web/bot.py

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import base64
22
import functools
33
import io
4+
import json
45
import logging
56
import multiprocessing
67
import os
@@ -230,6 +231,31 @@ def set_screen_resolution(self, width=None, height=None):
230231
""", width, height)
231232
self._driver.set_window_size(*window_size)
232233

234+
def _webdriver_command(self, command, params=None, req_type="POST"):
235+
"""
236+
Execute a webdriver command.
237+
238+
Args:
239+
command (str): The command URL after the session part
240+
params (dict): The payload to be serialized and sent to the webdriver. Defaults to None.
241+
req_type (str, optional): The type of request to be made. Defaults to "POST".
242+
243+
Returns:
244+
str: The value of the response
245+
"""
246+
if not params:
247+
params = {}
248+
249+
resource = f"/session/{self.driver.session_id}/{command}"
250+
url = self.driver.command_executor._url + resource
251+
body = json.dumps(params)
252+
response = self.driver.command_executor._request(req_type, url, body)
253+
254+
if not response:
255+
raise Exception(response.get('value'))
256+
257+
return response.get('value')
258+
233259
##########
234260
# Display
235261
##########
@@ -747,7 +773,6 @@ def create_tab(self, url):
747773
self.navigate_to(url)
748774

749775
def create_window(self, url):
750-
751776
try:
752777
# Refactor this when Selenium 4 is released
753778
self.execute_javascript(f"window.open('{url}', '_blank', 'location=0');")
@@ -769,10 +794,47 @@ def close_page(self):
769794
def activate_tab(self, handle):
770795
self._driver.switch_to.window(handle)
771796

797+
def print_pdf(self, path=None, print_options=None):
798+
"""Print the current page as a PDF file.
799+
800+
Args:
801+
path (str, optional): The path for the file to be saved. Defaults to None.
802+
print_options (dict, optional): Print options as defined at. Defaults to None.
803+
804+
Returns:
805+
str: the saved file path
806+
"""
807+
title = self.page_title() or "document"
808+
default_path = os.path.expanduser(os.path.join("~", "Desktop", f"{title}.pdf"))
809+
810+
if self.browser in [Browser.CHROME, Browser.EDGE] and not self.headless:
811+
# Chrome still does not support headless webdriver print
812+
# but Firefox does.
813+
self.execute_javascript("window.print();")
814+
# We need to wait for the file to be available in this case.
815+
self.wait_for_file(default_path)
816+
return default_path
817+
818+
if print_options is None:
819+
print_options = {
820+
'landscape': False,
821+
'displayHeaderFooter': False,
822+
'printBackground': True,
823+
'preferCSSPageSize': True,
824+
'marginTop': 0,
825+
'marginBottom': 0
826+
}
827+
data = self._webdriver_command("print", print_options)
828+
bytes_file = base64.b64decode(data)
829+
if not path:
830+
path = default_path
831+
with open(path, "wb") as f:
832+
f.write(bytes_file)
833+
return path
834+
772835
#######
773836
# Mouse
774837
#######
775-
776838
@only_if_element
777839
def click_on(self, label):
778840
"""
@@ -1491,14 +1553,14 @@ def sleep(self, interval):
14911553
"""
14921554
self.wait(interval)
14931555

1494-
def wait_for_file(self, path, timeout=10000):
1556+
def wait_for_file(self, path, timeout=60000):
14951557
"""
1496-
Invoke the system handler to open the given file.
1558+
Wait for a file to be available on disk.
14971559
14981560
Args:
14991561
path (str): The path for the file to be executed
15001562
timeout (int, optional): Maximum wait time (ms) to search for a hit.
1501-
Defaults to 10000ms (10s).
1563+
Defaults to 60000ms (60s).
15021564
15031565
Returns:
15041566
status (bool): Whether or not the file was available before the timeout

botcity/web/browsers/chrome.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import atexit
23
import os
34
import tempfile
@@ -10,7 +11,6 @@
1011

1112
def default_options(headless=False, download_folder_path=None, user_data_dir=None):
1213
chrome_options = ChromeOptions()
13-
chrome_options.add_argument("--disable-extensions")
1414
chrome_options.add_argument("--remote-debugging-port=0")
1515
chrome_options.add_argument("--no-first-run")
1616
chrome_options.add_argument("--no-default-browser-check")
@@ -48,8 +48,33 @@ def default_options(headless=False, download_folder_path=None, user_data_dir=Non
4848
if not download_folder_path:
4949
download_folder_path = os.path.join(os.path.expanduser("~"), "Desktop")
5050

51+
app_state = {
52+
'recentDestinations': [{
53+
'id': 'Save as PDF',
54+
'origin': 'local'
55+
}],
56+
'selectedDestinationId': 'Save as PDF',
57+
'version': 2
58+
}
59+
5160
# Set the Downloads default folder
52-
prefs = {"download.default_directory": download_folder_path}
61+
prefs = {
62+
"printing.print_preview_sticky_settings.appState": json.dumps(app_state),
63+
"download.default_directory": download_folder_path,
64+
"savefile.default_directory": download_folder_path,
65+
"printing.default_destination_selection_rules": {
66+
"kind": "local",
67+
"namePattern": "Save as PDF",
68+
},
69+
"safebrowsing.enabled": True
70+
}
71+
5372
chrome_options.add_experimental_option("prefs", prefs)
73+
chrome_options.add_argument(
74+
"user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
75+
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
76+
)
77+
78+
chrome_options.add_argument("--kiosk-printing")
5479

5580
return chrome_options

botcity/web/browsers/edge.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import atexit
2+
import json
23
import os
34
import tempfile
45

@@ -9,8 +10,7 @@
910

1011
def default_options(headless=False, download_folder_path=None, user_data_dir=None):
1112
edge_options = EdgeOptions()
12-
edge_options.use_chrome = True
13-
edge_options.add_argument("--disable-extensions")
13+
edge_options.use_chromium = True
1414
edge_options.add_argument("--remote-debugging-port=0")
1515
edge_options.add_argument("--no-first-run")
1616
edge_options.add_argument("--no-default-browser-check")
@@ -48,8 +48,38 @@ def default_options(headless=False, download_folder_path=None, user_data_dir=Non
4848
if not download_folder_path:
4949
download_folder_path = os.path.join(os.path.expanduser("~"), "Desktop")
5050

51+
app_state = {
52+
"recentDestinations": [{
53+
"id": "Save as PDF",
54+
"origin": "local",
55+
"account": ""
56+
}],
57+
"selectedDestinationId": "Save as PDF",
58+
"version": 2,
59+
"isHeaderFooterEnabled": False,
60+
"marginsType": 2,
61+
"isCssBackgroundEnabled": True
62+
}
63+
5164
# Set the Downloads default folder
52-
prefs = {"download.default_directory": download_folder_path}
65+
prefs = {
66+
"printing.print_preview_sticky_settings.appState": json.dumps(app_state),
67+
"download.default_directory": download_folder_path,
68+
"savefile.default_directory": download_folder_path,
69+
"printing.default_destination_selection_rules": {
70+
"kind": "local",
71+
"namePattern": "Save as PDF",
72+
},
73+
"safebrowsing.enabled": True
74+
}
75+
5376
edge_options.add_experimental_option("prefs", prefs)
5477

78+
edge_options.add_argument(
79+
"user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
80+
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
81+
)
82+
83+
edge_options.add_argument("--kiosk-printing")
84+
5585
return edge_options

0 commit comments

Comments
 (0)