Automating PDF’s and Windows Objects with Python and Webdriver

At my current gig I needed a way to check the print styling wasn’t broken across a range of pages and browsers. It was an obvious candidate for automation and, since I hadn’t had much of an opportunity to build my Python skills – decided to write the script in Python.

I envisaged the script as being relatively straightforward. Using Webdriver I would insruct the browser to go to the pages we wanted to check, execute the print function and then check the output. Of course, we didn’t want all of those prints to actually end up in the printer. So the first step was to identify a solution that would enable us to print to PDF. Although CutePDF Writer allows you to print to PDF by default, it doesn’t allow you to just save the pdf file. So instead I ended up using NovaPDF, which allows you to setup a custom profile and save the pdf straight to a predefined directory.

Having done that, I was able to implement the following code, which sets-up the Firefox Webdriver instance with a “always_print_silent” profile. This means that when the print function is activated, it won’t open any kind of dialogue. It will just print to whatever the default printer driver has been set to.

The script imports all of the URL’s we want to check via a CSV file. Once the browser is open, it navigates to all of the URL’s in the file, calls the javascript window.print() function, and due to the “always_print_silent” profile, saves the resulting output with the help of NovaPDF.

# script relies on having novaPDF ( installed in order to print to PDF
# AND configure the PDF printer to silently save to a pre-defined location

# selenium imports
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
# csv reader import
import csv, time

# need to setup a webdriver profile so that the print dialog screen is skipped
FFprofile = webdriver.FirefoxProfile()
FFprofile.set_preference('print.always_print_silent', True)

# create driver with the profile
driver = webdriver.Firefox(FFprofile)

# open the CSV file
with open('data/barnetPrintURLs.csv') as csvfile:
	urlReader = csv.reader(csvfile, delimiter=',', quotechar='|')

        #loop through the CSV and check all the URL's
	for row in urlReader:
		# execute javascript to print the page

So far so good.

Next up was Chromedriver, and things started to get a bit more complicated – since Chromedriver doesn’t support silent printing. 🙁 This meant that every time the window.print() function was called I ended up with a Windows print dialogue. I can’t interact with the dialogue window inside of Webdriver, so I needed some other solution.

Fortunately, Python provides some tools with which to accomplish this task.

SWAPY, or Simple Windows Automation on Python which provides a Python interface to Windows objects. In much the same way as you might once have been able to identify objects using,  e.g. Quick Test Pro – you can use the SWAPY interface to interact with Windows programs and convert actions into Python code, which can then be implemented in a script by calling the pywinauto library.

In the screenshot below, you can see I’ve selected the Print dialog, selected the &Print function (the Print button) and generated some pywinauto code in the Editor window.


Having utilised SWAPY to identify the dialog and the actions needed to ineract with it, I just needed to incorporate those actions into my Python script.  That”s just a matter of installing the pywinauto library (and sendkeys, and the Microsoft C++ compiler which is only compatible with Python 2.* – see code comments) and adding some additional code to my script to deal with wait conditions etc, below:

# selenium imports
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
# additional Chromedriver specific import
from import Options
# Chromedriver doesn't support silent printing - so we need to interact with Windows using pywinauto
# (which also requires sendkeys 
# and Microsoft Visual C++ Compiler for Python 2.7
import pywinauto, time

# need to setup a webdriver profile so that the print dialog screen is skipped
chrome_options = Options()
# create the pywinauto object
pwa_app = pywinauto.Application() 

# get the url's
with open('data/barnetPrintURLs.txt', 'r') as urls:
	# for each line in the file navigate to the url
	for line in urls:
		# create driver with the profile
		driver = webdriver.Chrome(chrome_options=chrome_options)

		# execute javascript to print the page
		# now use pywinauto to interact with the Windows dialog and click the print button
			a_check = lambda: pywinauto.findwindows.find_windows(title=u'Print', class_name='#32770')[0]
				dialog = pywinauto.timings.WaitUntilPasses(5, 1, a_check)
				window = pwa_app.window_(handle=dialog)
				ctrl = window['&Print']
				# need an explicit wait to allow the print to go through so we can quit the browser instance
				print('Something went wrong')

In much the same way as the Firefox script, this just runs through the URL’s, navigates to the page, and activates the print function. We then have to switch to pywinauto to interact with the Windows print dialog, hit the print button and wait for the dialog to close and the print to actually be actioned, before closing the webdriver instance and starting the next loop.

I also wrote a script to carry out the same functions in IEdriver. It follows much the same format (with a couple of additional implicit waits and checks for IE quirks) so I haven’t bothered pasting it here.

Phew. My simple scripting exercise was a lot more complicated than I originally thought. Thankfully Python provides a lot of flexibility for doing this kind of stuff. I imagine this would also have been achievable using C# using .Net, but doubt very much whether I would be able to do this in Java or Ruby. If somebody has done this in another language, I’d be very interested in hearing about it just so I can learn how you went about it.

- Simon

P.S If you're interested in learning more about performance testing, checkout my Performance Testing 101 course here.

One Comment

Leave a Reply