One of the big things I script is the FedEx website. Originally I had done this via Applescript. (See this hint I’d written years ago) I’ve chosen to do it in Python with Appscript since it is part of a much larger invoice workflow.
I have some macros that extract the text of an email my e-commerce site generates. The macro then calls a Python script which generates a nicely formatted invoice in Excel, creates a FedEx shipping slip, and then creates a nice HTML email with the invoice along with a link to the tracking for the order at FedEx. So pretty much in a couple pages of Python I wrote a very nice system which you typically pay thousands of dollars for if you were to do it commercially. Best of all it’s very flexible and easy to change. Because I wrote it in Python it was very easy to leverage the various libraries for templating, SMPT mail and so forth that would be quite difficult to do in Applescript.
What I want to do is briefly explain how I script the FedEx website in order to generate a packing slip. Since I wrote that first post for Mac OSX Hints I’ve received a surprising number of emails asking how to do this. Since FedEx changed their website around that old hint no longer works. So hopefully by blogging about it people with questions will find this explanation via Google.
Why Script FedEx.com?
If you do a lot of shipping with FedEx, you know how annoying it is to cut and paste all the address information into Safari. There are expensive programs that will extract information, but most don’t work with OS X or require Filemaker.
Because of some bugs by the programmers of FedEx.com there are a few annoyances. That is this will not be completely automated. If you figure a way around these annoyances please let me know in the comments.
The Script
I’m going to assume you have all the data to fill in the forms prepared. I have a little script that scans through an email that generates a dictionary of all the variables I need. You can generate that data yourself anyway you care to. (Say from a FileMaker or Bento database) My dictionary is called order_data. I also have an additional argument called shipping that contains the type of shipping I want FedEx to use. (Ground, Next Day, 2-Day air, etc.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | def check_Safari_done(): # wait until first failure is finished while True: try: s = app(u'/System/Library/CoreServices/System Events.app').application_processes[u'Safari'].windows[1].groups[1].static_texts[1].name.get(resulttype=k.unicode_text) break except: time.sleep(1) continue # wait until second failure - then Safari is done while True: try: s = app(u'/System/Library/CoreServices/System Events.app').application_processes[u'Safari'].windows[1].groups[1].static_texts[1].name.get(resulttype=k.unicode_text) if s[0:7] == "Loading": continue if s[0:10] == "Contacting": continue break except: time.sleep(1) def fill_safari( order_data, shipping ): Safari = app(u'/Applications/Safari.app') Safari.activate() ## make a new window and go to the FedEx site. (Note: you have to have logged in already) Safari.make(new=k.document) Safari.documents[0].URL.set(u'https://www.fedex.com/shipping/shipAction.do?method=doInitialize&urlparams=us') ## get the document name we just created -- we use that to make sure we operate on the right windo time.sleep(6) FEdoc = Safari.documents[0].get() time.sleep(4) if order_data["telephone"] == "": order_data["telephone"] = "801-655-1996" if order_data["company"] =="": order_data["company"] == "---" Safari.do_JavaScript("document.forms['shipActionForm']['toData.companyName'].value = '" + order_data["company"] + "'", in_=FEdoc) Safari.do_JavaScript("document.forms['shipActionForm']['toData.contactName'].value = '" + order_data["first"] + ' ' + order_data["last"] + "'" , in_=FEdoc) Safari.do_JavaScript("document.forms['shipActionForm']['toData.addressLine1'].value = '" + order_data["address"] + "'", in_=FEdoc) Safari.do_JavaScript("document.forms['shipActionForm']['toData.city'].value = '" + order_data["city"] + "'", in_=FEdoc) Safari.do_JavaScript("document.forms['shipActionForm']['toData.stateProvinceCode'].value = '" + order_data["state"] + "'", in_=FEdoc) # Safari.do_JavaScript("document.forms['shipActionForm']['toData.stateProvinceCode'].value = \"AL\"", in_=FEdoc) if shipping == 'FedEx 2-Day': Safari.do_JavaScript("document.forms['shipActionForm']['psdData.serviceType'].value = 'FedEx 2-Day'", in_=FEdoc) if shipping == 'FedEx Ground': Safari.do_JavaScript("document.forms['shipActionForm']['psdData.serviceType'].value = 'FedEx Home Delivery'", in_=FEdoc) if shipping == 'FedEx Express': Safari.do_JavaScript("document.forms['shipActionForm']['psdData.serviceType'].value = 'FedEx Express Saver'", in_=FEdoc) Safari.do_JavaScript("document.forms['shipActionForm']['toData.zipPostalCode'].value = '" + order_data["zip"] + "'", in_=FEdoc) Safari.do_JavaScript("document.forms['shipActionForm']['toData.phoneNumber'].value = '" + order_data["telephone"] + "'", in_=FEdoc) Safari.do_JavaScript("document.forms['shipActionForm']['toData.residential'].checked = true", in_=FEdoc) Safari.do_JavaScript("document.forms['shipActionForm']['psdData.packageType'].value = 'Your Packaging'", in_=FEdoc) Safari.do_JavaScript("document.forms['shipActionForm']['psd.mps.row.weight.0'].value = '2'", in_=FEdoc) Safari.do_JavaScript("document.forms['shipActionForm']['psd.mps.row.dimensions.0'].value = '1006956'", in_=FEdoc) Safari.do_JavaScript("document.forms['shipActionForm']['psd.mps.row.declaredValue.0'].value = '0'", in_=FEdoc) # Safari.do_JavaScript("recipientAddressChanged()", in_=FEdoc) return |
The Explanation
The key parts of the script is to generate a Safari scripting object. We create a new document with the shipping web page of FedEx. (Note, we’re assuming you are already logged in — if you aren’t FedEx will prompt you for your password keeping this script from working. You could easily add in logic to deal with this.) We then inject JavaScript with the do_JavaScript command of Safari. All we need to do is find the DOM path to each of the fields we need and put in the values we wish to enter.
You’ll notice I’ve put in two pauses. I put them in because FedEx’s website sometimes is very slow. (You can imagine how many users they must get at any moment) The pauses are just there to ensure that the page is fully loaded before I try and enter data. Feel free to adjust the values.
All I do is for each DOM object create a JavaScript command to set it’s value. (This is done by Object.value = myvalue) I have to be a little careful since I’m creating a string I send to Safari.
The in_=FEdoc might seem a bit confusing. I set FEdoc earlier to be the frontmost page in Safari. (This means you probably shouldn’t go changing Safari pages while the script is running) So the do_JavaScript command injects the text as JavaScript which is run in our FEdoc window.
The only other confusing part is the psdData.serviceType object. That’s where FedEx determines the shipping type. You’ll probably want to change that to match how you pass in your shipping.
The final bit is for custom package types. You can probably delete that without trouble. I have some standard sized boxes I’ve entered in. 1006956 is a number I found by listing the source code of FedEx’s page. It corresponds to a custom box size I entered in. I also set the value of the package to 0. The psd.mps.row.weight.0 is the weight of the box. You can enter that in as whatever you wish. Most of my boxes are 2 pounds so I set that automatically and then adjust it manually before generating the packing slip.
[Edit: I've modified the script so it waits until Safari has the page ready for entering the field information. I have a post with a detailed explanation of this approach to check if Safari is done loading a page. I deleted the part of the post that discussed this issue.]
Related posts:
.jpg)
#1 by Clark on 2009/06/15 - 11:09 am
Wow. I tried my script today and FedEx changed things. So my caveat no longer applies. Scripting the State now works. Since I’d just put up this blog post I suspect someone at FedEx must have read this post. If so thanks guys.
#2 by clark on 2010/02/05 - 11:14 pm
Note the above is out of date due to some changes to Safari and FedEx.com. I’ve put up a permanent page for the latest version of this script.