How to run App Script in Google Documents, Slides, etc. (or: Apps Script via AppleScript)

While there is a nice albeit a bit crude way to add buttons to a Google Sheet, there is unfortunately no such option in Google documents. So how can I automate repetitive tasks that necessitate running Apps Script within a Google Doc (or within Google Sheets, or Slides, or …)?

It was actually not overly complicated even for a complete AppleScript novice who is still on trial by Script Debugger. (I have still a day left in my trial version of Script Debugger and still wondering what is the difference between AppleScript scripts, bundles libraries and apps; but of course 60 years of teaching CS doesn’t hurt…)

The idea is to have a tab in Google Chrome open with the Apps Script of the document and use AppleScript to click either the “Run” or even the “Debug” button. The code below uses MouseTools - thanks @HAMSoft_Hank! - to click at the given location on the screen so the only nuisance was to find out the CSS selectors of the <button>s in Google’s HTML. The 3 lines of JavaScript to find the location of the button where pretty trivial. Then I only needed to adjust it to make it relative to the Chrome window’s position on the screen.

Needless to say it would be pretty easy to adjust the code to Safari’s way to run JavaScript in a document given that the JavaScript part of the code is exactly the same.

Moreover, the script can be easily adapted to clicking on any page element on any web page and for instance insert the text or image from the clipboard there or select a part of the page and copy it into the clipboard (my next task). And so on - I see quite a few interesting possibilities in my projects… (Could I even insert <script> into the page and run it?)

Hope some of you will find it useful…

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

global mouseToolsPath
set mouseToolsPath to quoted form of POSIX path of ((path to home folder as text) & "UnixBins:MouseTools")

runAppsScript()

-- Clicks on 'Debug' button in Google App Script page to debug the currently selected function
on debugAppsScript()
	clickOnAppsScriptElement("button[aria-label=\"Debug the selected function\"]")
end debugAppsScript

-- Clicks on 'Run' button in Google App Script page to execute the currently selected function
on runAppsScript()
	clickOnAppsScriptElement("button[aria-label=\"Run the selected function\"]")
end runAppsScript

-- Clicks on HTML element in Google App Script page identified by 'cssSelector'
on clickOnAppsScriptElement(cssSelector)
	local elementCenterJS, x, y, x1, y1, x2, y2, dy -- to show the values in Script Debugger
	-- JavaScript code that returns center of HTML element identified by 'cssSelector'
	set elementCenterJS to ¬
		"var area = document.querySelector('" & cssSelector & "').getBoundingClientRect();" & return & ¬
		"[Math.round(area.x + area.width / 2), Math.round(area.y + area.height / 2)];"
	set dy to 78 -- height of Chrome's bars (tab bar and URL/address bar)
	tell application "Google Chrome"
		activate
		delay 1 -- this may need to be adjusted; repeat testing whether Chrome is ready?
		set {x1, y1} to execute of front window's active tab javascript elementCenterJS
		set {x2, y2} to front window's bounds
		set {x, y} to {x1 + x2, y1 + y2 + dy}
	end tell
	my niceMoveTo(x, y) -- leave this out if you don't like movies
	my clickAt(x, y)
	delay 5 -- just for the movie to enjoy the effect
end clickOnAppsScriptElement

-- Clicks at point (x,y) on screen
on clickAt(x, y)
	mouseToolsAt(x, y, "leftClick")
end clickAt

-- Double-clicks at point (x,y) on screen
on doubleclickAt(x, y)
	mouseToolsAt(x, y, "doubleLeftClick")
end doubleclickAt

-- Moves cursor graciously from current location to point(x,y) on the screen
on niceMoveTo(x, y)
	local x2, y2, steps
	-- cursor's current location 
	set {x2, y2} to paragraphs of mouseTools("location")
	-- 'steps' as length of the line segment from current to target location
	set steps to round ((x - x2) ^ 2 + (y - y2) ^ 2) ^ 0.5
	-- move nicely (with the same velocity independent of cursor locations)
	mouseToolsAt(x, y, "mouseSteps " & steps)
end niceMoveTo

-- Runs MouseTools with 'x', 'y' point on screen and 'suffix' command
on mouseToolsAt(x, y, suffix)
	mouseTools("x " & (x as text) & " -y " & (y as text) & " -" & suffix)
end mouseToolsAt

-- Runs MouseTools with just 'suffix' command
on mouseTools(suffix)
	return do shell script mouseToolsPath & " -" & suffix
end mouseTools

PS: If you like movies then you may enjoy the recording of the above AppleScript in action.

PSS: I’m sure that you experts will find a lot of improvements - please comment!

PSSS: I am voice typing and when I dictated “debugger” I got “day bugger”! Hmmm, Apple’s AI is on the spot given that it took me more than one day to learn how to use AppleScript - nevertheless they could be a bit less condescending…

PSSSS: I really enjoy Script Debugger, really great job Mark @alldritt!

PSSSSS: Maybe a script that goes through the elements on a page to find the one you clicked and gives you the nicest unique CSS selector so that you don’t need to mess with Google’s messy HTML - Hmmm, how arrogant! (Well, "aria-label"s are nice and, after all, probably fairly common and unique so it can’t be overly difficult in most cases…)

Enter code by putting lines containing three tick marks before and after. I’ve edited your second post above.

You might want to edit your first post to remove the code.

Done! Learned yet another lesson - Mahalo nui loa (= thanks very much in Hawaiian - as in the video in my post) Shane!

Tried to see whether we can dynamically insert and then run JavaScript code in an arbitrary web page. It works in Safari:

tell application "Safari"
	local script_
	set script_ to "var script = document.createElement('script');" & return & ¬
		"script.innerHTML = 'function doAlert(){alert(\"Aloha nui loa from Bay Area\");}'" & return & ¬
		"document.body.appendChild(script);" & return & ¬
		"setTimeout(doAlert, 5000);"
	activate
	delay 5 -- long to make video
	do JavaScript script_ in document 1
	delay 10 -- again long to make video
end tell

see the video.

Explanation: the JavaScript code in “script_” appends a <script> element at the end of <body> that defines the function “doAlert” and then calls doAlert() after 5 seconds.

The equivalent code in Chrome, however, doesn’t work: while the <script> element got inserted, the Console reports “Uncaught ReferenceError: doAlert is not defined”.

12:45 AM, late night script indeed :sleeping: