Open Safari web page, scroll down & highlight text on said page.

I need to make an AppleScript that opens a specific URL and acts as a “fake” anchor link of sorts. During my research I’m often linking to URLs but I also want specific areas of an URL’s web page to show up in Safari and preferably some specific text highlighted as well.

This jumbled mess of a script I’ve put together accounts for the webpage to load up first and works fine, but the only way I got it to work properly every time is with some delays added and other mess I commented within the code below.

set theURL to "https://en.wikipedia.org/wiki/The_Rolling_Stones"
set theTEXT to "Mick and I thought these songs were really puerile"

tell application "Safari" to activate
open location theURL

tell application "Safari"
	activate
	delay 1
	repeat
		if (do JavaScript "document.readyState" in document 1) is "complete" then exit repeat
		delay 1 -- wait a second before checking again
	end repeat
	delay 1
end tell

tell application "System Events"
	tell process "Safari" to keystroke "f" using command down
	-- If I don't hit delete key twice and find dialog previously open it fails on some websites
	keystroke (ASCII character 127)
	keystroke (ASCII character 127)
	keystroke theTEXT
	-- If I don't hit enter key often jumbles first letters of theTEXT if find dialog previously open
	key code 36
	-- to close the Find dialog and leave blue highighted text I'd like to to use this following keystroke
	-- keystroke "." using command down	
	-- unfortunately this makes the highlighted text unhighlight entirely on some longer websites
end tell
-- But if I put it down here like this it works :/
tell application "System Events"
	tell process "Safari" to keystroke "." using command down
end tell

Please show me a better, faster way that’s reliable. This DOES work and it IS reliable on various websites after all my silly tweaks, but it’s slow and I’m sure my script code looks ridiculous.

Model: MacBook Pro
AppleScript: 2.9
Browser: Safari 10.0.1
Operating System: Mac OS X (10.10)

I forgot to say that I wonder if much more javascript could better take care of this task within AppleScript?

In 10.10 activating an application doesn’t put its process at front immediately.

With your original code there was at least 3 seconds before entering the tell application “System Events” block.
After reworking your code 2 seconds are sufficient - at least here.

I saw that you use ASCII character when we are supposed for years to drop them and une character id.
After replacing ASCII character 127 by character id 127, here there is no need to issue it twice.
My understanding is that it must be a short delay between the keycode 36 one and the keystroke “.”
Quitting the process then reenter it introduce this delay.
So I tried to introduce an explicit short delay (0.001) and here it works.

Below is the resulting edited script.

set theURL to "https://en.wikipedia.org/wiki/The_Rolling_Stones"
set theTEXT to "Mick and I thought these songs were really puerile"

tell application "Safari" to activate

open location theURL

tell application "Safari"
	--activate # no need to activate it one more time !
	--delay 1 # DISABLED
	repeat
		delay 1 # NEW LOCATION
		if (do JavaScript "document.readyState" in document 1) is "complete" then exit repeat
		--delay 1 # DISABLED
	end repeat
	--delay 1 # DISABLED
end tell

tell application "System Events"
	tell process "Safari" # SPLITTED
		set frontmost to true # ADDED
		keystroke "f" using command down
		-- If I don't hit delete key twice and find dialog previously open it fails on some websites
		keystroke (character id 127) --(ASCII character 127) # EDITED
		--keystroke (character id 127) --(ASCII character 127) # EDITED and DISABLED
		delay 0.05 # ADDED
		
		# Loop useful with 10.12.2 & 10.12.3
		set theChars to text items of theTEXT
		repeat with aChar in theChars
			keystroke aChar
			delay 2 / 1000
		end repeat
		--keystroke theTEXT
		-- If I don't hit enter key often jumbles first letters of theTEXT if find dialog previously open
		delay 0.2
		--key code 36
		-- to close the Find dialog and leave blue highighted text I'd like to to use this following keystroke
		delay 1 / 1000 # ADDED
		keystroke "." using {command down}
		-- unfortunately this makes the highlighted text unhighlight entirely on some longer websites
	end tell # process
end tell # System Events
-- But if I put it down here like this it works :/
(*
tell application "System Events"
	tell process "Safari" to keystroke "." using {command down}
end tell
*)

I applied a timer using ASObjC instructions which returned :
→ “That took 2,706492006779 seconds”
I removed it because after every execution I was forced to force the Editor to quit. I assume that there is a conflict with javascript but I’m not sure.

With this edited script, the events log was:

tell application "Safari"
	activate
end tell
tell application "Script Editor"
	open location "https://en.wikipedia.org/wiki/The_Rolling_Stones"
	open location "https://en.wikipedia.org/wiki/The_Rolling_Stones"
end tell
tell application "Safari"
	do JavaScript "document.readyState" in document 1
end tell
tell application "System Events"
	set frontmost of process "Safari" to true
	keystroke "f" using command down
	keystroke ""
	keystroke "M"
	keystroke "i"
	keystroke "c"
	keystroke "k"
	keystroke " "
	keystroke "a"
	keystroke "n"
	keystroke "d"
	keystroke " "
	keystroke "I"
	keystroke " "
	keystroke "t"
	keystroke "h"
	keystroke "o"
	keystroke "u"
	keystroke "g"
	keystroke "h"
	keystroke "t"
	keystroke " "
	keystroke "t"
	keystroke "h"
	keystroke "e"
	keystroke "s"
	keystroke "e"
	keystroke " "
	keystroke "s"
	keystroke "o"
	keystroke "n"
	keystroke "g"
	keystroke "s"
	keystroke " "
	keystroke "w"
	keystroke "e"
	keystroke "r"
	keystroke "e"
	keystroke " "
	keystroke "r"
	keystroke "e"
	keystroke "a"
	keystroke "l"
	keystroke "l"
	keystroke "y"
	keystroke " "
	keystroke "p"
	keystroke "u"
	keystroke "e"
	keystroke "r"
	keystroke "i"
	keystroke "l"
	keystroke "e"
	keystroke "." using {command down}
end tell

With the page already open the only difference is that do JavaScript “document.readyState” in document 1 is only executed once.

Yvan KOENIG running Sierra 10.12.2 in French (VALLAURIS, France) lundi 19 décembre 2016 20:51:54

Thank you for your script!

Yours doesn’t work for me, unfortunately. It just scrolls down to the bottom of the page and doesn’t highlight the text.

I’m using macOS Sierra 10.12.1 with Safari 10.0.1 at the moment and it works like this consistently across various website with my current script –

I apologizes, here it highlight the sentence.

https://app.box.com/s/sw9mug8w5m7tdy25dyrqggrjhfeahtsc

Oops the test was made with a slightly different code.

The posted code contained :

keystroke “f” using command down

As you may see, the executed one contained :

keystroke “f” using {command down}

It seems surprising but without the brackets the string isn’t highlighted. In fact it’s from time to time.
With them it is.

It behaves this way if the page isn’t open when I call it.
If it is already open, I get an odd behavior : the script displays the end of the web page (with the timeline).

Bingo.

If I insert a short delay (0.05) just after the delete command, the script behaves well even if the document was already open when I call the script.
I found that after getting the odd behavior described above.
I moved to the top of the document, typed cmd +f and discovered that the contents of the field was a mix of two interleaved copy of theText.
Inserting the delay gave the app sufficient time to really clear the field.
I tried to reduce the delay but with 0.01 the odd behavior stroke from time to time to I choose to use 0.05.

With 10.12.2, it would be useful to replace
keystroke theTEXT by :

set theChars to text items of theTEXT
		repeat with aChar in theChars
			keystroke aChar
			delay 2 / 1000
		end repeat
		--keystroke theTEXT

The edited script is available in message #3

With this version of the OS (10.12.3 behaves the same, some typed chars are replaced by uppercase - which isn’t a problem as the search is not case sensitive - but some worse changes are done. On a french system,the opening pare ( may be replaced by 5, closing paren may be replaced by ° and in such case the search fails.

This behavior was already reported as report #
Re: Keystroke and 10.12.2
Problem confirmed by a friend running also 10.12.2.
It strike also in 10.12.13.

Reported as :
Report # 29683268, Under 10.12.2 (and 10.12.3) AppleScript keystroke command fails.
It would be fine if some of you file a report too.
More reports means : the problem is more important so it must be treated faster.

Yvan KOENIG running Sierra 10.12.2 in French (VALLAURIS, France) vendredi 23 décembre 2016 10:04:05

Thank you again for your script. I tried your edited script and it still doesn’t work for me. It scrolls down, but doesn’t scroll to the correct area of the webpage, nor highlight any text. Weird.

I really don’t understand.

One more time, I double clicked the button [Open this Scriplet in your Editor] from message #3 (the one at top of the script, not the one at top of the events log).
The script opened in Script Debugger.
I clicked the button Execute and I got the sentence highlighted.

I made an other attempt with these two strings on entry and it behaved flawlessly.

set theURL to "https://fr.wikipedia.org/wiki/Le_Corbusier"
set theTEXT to "La modestie du commandeur influença probablement le choix définitif"

Same results after a copy / paste from Script Debugger to Apple’s Script Editor.

I made a last test with Shane STANLEY’s ASObjC Explorer
This time it’s only at the 3rd attempt that I got the sentence highlighted and it work only if I don’t activate the log events feature. I guess that it’s due to the use of javascript.

Yvan KOENIG running Sierra 10.12.2 in French (VALLAURIS, France) mardi 27 décembre 2016 19:55:28

I noticed you said you used Script Debugger to launch the script instead of Apple’s Script Editor. I used Script Debugger and it worked. For some reason, your script doesn’t work if it’s launched with Apple’s default Script Editor. My script works with either app for some reason. Bizarre.

I wrote also that it works with Apple’s Script Editor.
The only one with which I had problems is ASObjC Explorer but it’s not the kind of oddity which you describe. When the log events feature is active, the editor issue :
[format]Unable to compile running script (2)
fin de ligne, etc. prévu(s) mais identificateur trouvé(s). (-2741)[/format]

Which machine are you running ?
Maybe it’s a more recent than mine with a faster processor.
Mine is an iMac 21.5" mid 2011. Processor 2.8 GHz Intel Core 7

During my tests, the delay 0.05 after the instruction keystroke (character id 127) is critical. If I replace it by delay 0.02 the string isn’t highlighted.

Yvan KOENIG running Sierra 10.12.2 in French (VALLAURIS, France) mardi 27 décembre 2016 20:56:02

I just noticed a difference. My script highlights the text in blue without greying out the rest of the text - Your version highlights the text in blue, but keeps everything else greyed out. Not a big deal, but I prefer the way mine works where it’s not greyed out. Must have something to do with this part being different.

tell process "Safari" to keystroke "." using command down

MacBook Pro early 2011 w/i7 2.3 GHz and 16GB RAM.

Changing the delay to from 0.05 to 1 now makes it highlight text with Script Editor.

Your now works with the delay set to 1.0, but I find that my script is surprisingly a little bit faster overall.

Rats, one thing I find is that if I quit Safari, clear the History and Cache, both scripts tend to fail to select the text.

Replacing delay 0.05 by delay 1 is a bit exaggerated.
There is no need for such a delay to clear the field containing the string to search.

I am unable to guess what is giving the odd behavior described on your machine.

I cleared the cache and history and the script did its duty flawlessly.
On my machine, my version is fastest than yours but as you never responded when I asked for details about yours I am unable to try to find an explanation. I may just guess that yours was delivered in march 2015 or later as it was running 10.10 when you opened this thread.

According to Script Debugger, today the entire job was achieved in 4.11 seconds.
In Apple’s Script Editor “That took 2,983440041542 seconds.”

Yvan KOENIG running Sierra 10.12.2 in French (VALLAURIS, France) mercredi 28 décembre 2016 11:24:37

Is this any help? I’m not a JavaScript expert, I’m afraid.

set theURL to "https://en.wikipedia.org/wiki/The_Rolling_Stones"
set theTEXT to "Mick and I thought these songs were really puerile"

tell application "Safari"
	activate
	open location theURL
	delay 1
	repeat until (do JavaScript "document.readyState" in document 1) is "complete"
		delay 1
	end repeat
	-- This works from the current cursor position, so only really useful with a fresh document.
	-- theTEXT might need to be doctored, depending on the characters it contained.
	do JavaScript "window.find(\"" & theTEXT & "\")" in document 1 --> boolean indicating found or otherwise.
end tell

Hello Nigel
Your version is the fastest but after running it I am puzzled.
With it, the events log is :

tell application "Safari"
	activate
	open location "https://en.wikipedia.org/wiki/The_Rolling_Stones"
	do JavaScript "document.readyState" in document 1
		--> "complete"
	do JavaScript "window.find(\"Mick and I thought these songs were really puerile\")" in document 1
		--> true
end tell

With my code slightly modified to match your first instructions :

(*
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
set startDate to current application's NSDate's |date|()
*)
set theURL to "https://en.wikipedia.org/wiki/The_Rolling_Stones"
set theTEXT to "Mick and I thought these songs were really puerile"
(*
set theURL to "https://fr.wikipedia.org/wiki/Le_Corbusier"
set theTEXT to "La modestie du commandeur influença probablement le choix définitif"
*)
tell application "Safari"
	activate
	--end tell # ACTIVE in original code
	
	open location theURL
	
	--tell application "Safari" # ACTIVE in original code
	delay 1
	repeat until (do JavaScript "document.readyState" in document 1) is "complete"
		delay 1
	end repeat
end tell

tell application "System Events" to tell process "Safari"
	set frontmost to true # ADDED
	delay 0.2 # REQUIRED when there is a single tell application "Safari", no need with the originals two tell instructions
	keystroke "f" using command down
	tell window 1
		-- class of UI elements --> {splitter group, button, button, button, group, button, toolbar}
		-- class of UI elements of splitter group 1 --> {splitter, tab}
		# The tab element is not "recognized"
		-- class of UI elements of UI element 2 of splitter group 1 --> {group, group}
		-- class of UI elements of group 2 of UI element 2 of splitter group 1 --> {button, group, text field, static text}
		set theTarget to text field 1 of group 2 of UI element 2 of splitter group 1
		-- value of theTarget
		(*
			repeat
				delay 1 / 1000
				if value of theTarget = "" then exit repeat
			end repeat
			*)
		set value of theTarget to theTEXT
	end tell
	keystroke "." using {command down}
	
end tell
(*
set timeDiff to startDate's timeIntervalSinceNow()
display dialog "That took " & (-timeDiff as real) & " seconds."
*)

The events log is :

tell application "Safari"
	activate
	open location "https://en.wikipedia.org/wiki/The_Rolling_Stones"
	do JavaScript "document.readyState" in document 1
		--> "interactive"
	do JavaScript "document.readyState" in document 1
		--> "complete"
end tell
tell application "System Events"
	set frontmost of process "Safari" to true
	keystroke "f" using command down
	get text field 1 of group 2 of UI element 2 of splitter group 1 of window 1 of process "Safari"
		--> text field 1 of group 2 of tab group 1 of splitter group 1 of window "The Rolling Stones - Wikipedia" of application process "Safari"
	set value of text field 1 of group 2 of tab group 1 of splitter group 1 of window "The Rolling Stones - Wikipedia" of application process "Safari" to "Mick and I thought these songs were really puerile"
	keystroke "." using {command down}
end tell

I really don’t understand why in repeated tests, your version execute only once the instruction
do JavaScript “document.readyState” in document 1
while mine executes it twice.

I scrap my head but find no explanation.

Worse, the code posted above behaves flawlessly from Apple’s Script Editor but fails from Script Debugger with the message :
[Format]
get text field 1 of group 2 of UI element 2 of splitter group 1 of window 1 of process “Safari”
index is out of range (errorAEIllegalindex:-1719)
[/Format]

Which is what I get from Apple’s Editor if I remove the “REQUIRED” delay 0.2

Don’t tell me that it’s a side effect of Xmas, I drunk absolutely no spirit for months :wink:

Yvan KOENIG running Sierra 10.12.2 in French (VALLAURIS, France) mercredi 28 décembre 2016 14:34:23

Hi Yvan.

I think it’s just due to transient conditions. It depends on how long it takes Safari to fetch and render the page, given conditions on the Internet and whatever the computer’s doing in the background at the time. The number of executions is different every time for me, with both scripts.

I find I have to put extra delays in your test script:

set theURL to "https://en.wikipedia.org/wiki/The_Rolling_Stones"
set theTEXT to "Mick and I thought these songs were really puerile"
(*
set theURL to "https://fr.wikipedia.org/wiki/Le_Corbusier"
set theTEXT to "La modestie du commandeur influença probablement le choix définitif"
*)
tell application "Safari"
	activate
	--end tell # ACTIVE in original code
	
	open location theURL
	
	--tell application "Safari" # ACTIVE in original code
	delay 1
	repeat until (do JavaScript "document.readyState" in document 1) is "complete"
		delay 1
	end repeat
end tell

tell application "System Events" to tell process "Safari"
	set frontmost to true # ADDED
	delay 0.2 # REQUIRED when there is a single tell application "Safari", no need with the originals two tell instructions
	keystroke "f" using command down
	tell window 1
		-- Wait until the "Find" text field appears.
		repeat until (text field 1 of group 2 of UI element 2 of splitter group 1 exists)
			delay 0.2
		end repeat
		set theTarget to text field 1 of group 2 of UI element 2 of splitter group 1
		set value of theTarget to theTEXT
	end tell
	delay 0.5 -- Wait until the found text is selected and comes into view.
	keystroke "." using {command down}
end tell

Far be it from me to doubt your words! :stuck_out_tongue:

Wow! Thank you Nigel and thank you again Yvan! Nigel, the JavaScript find is great. I’m testing it all out now.

Nigel, I tried your nice & fast script you graciously shared in post #15 above. I see in the comments in the script that it needs a fresh document. Does that mean it doesn’t work well if the page is slow to load? I’ve found that to be the case.

And, to both Yvan & Nigel it looks like the script in post #17 works pretty good overall. I’ve still had it glitch out on occasion when I clear the Safari cache & history and have slow page load, but it’s the most stable overall version I’ve tried yet.

MBP i7 early 2011, macOS 10.12.1

Hi.

I found when trying it out that the JavaScript ‘window.find’ ” in Safari at least ” is a kind of “find next” which doesn’t cycle round to the top of the page. If the search string’s already found and selected, using ‘window.find’ again tries to find the next occurrence of it. If there isn’t another occurrence before the bottom of the page, it doesn’t start again at the top like Mac OS’s ‘find’, but simply returns ‘false’. Similarly, if you click below the text before trying to ‘window.find’ it, it won’t be found (unless you add a parameter for searching backwards). My comment was more precautionary than relevant here, as the ‘open location’ command apparently reloads the page anyway, putting the focus at the top again.

One would hope that the ‘document.readyState’ repeat would take care of the page being slow to load, but I don’t know how reliable it is. So far, on my system, my script has successfully fetched your example page and selected your example text every time ” provided that Safari’s already open when the script’s run. I did have one failure-to-select yesterday when the script had to launch Safari itself.

Here is a script demonstrating that we may easily get rid of the first problem described by Nigel.
Some instructions force the cursor to move so that the search starts from the very beginning of the web page.
Some extraneous instructions move the cursor to an other string or to the very bottom of the web page.
Some others force Safari to quit when the count of pass is a multiple of 5.
I feel that this way every conditions are treated.

set pass to 0
repeat 50 times
	set pass to pass + 1
	log "pass = " & pass
	set theURL to "https://en.wikipedia.org/wiki/The_Rolling_Stones"
	set theTEXT to "Mick and I thought these songs were really puerile"
	(*
	set theURL to "https://en.wikipedia.org/wiki/Tadao_Ando"
	set theTEXT to "Tadao Ando (安藤 å¿ é›„ Andō Tadao?, born September 13"
	*)
	tell application "Safari"
		activate
		open location theURL
		delay 1
		repeat until (do JavaScript "document.readyState" in document 1) is "complete"
			delay 1
		end repeat
		tell application "System Events" to tell process "Safari"
			set frontmost to true
			tell window 1
				key code 115 # go to beginning so the search would scan from the beginning of the text
			end tell
		end tell
		delay 0.5 # required to let the cursor reach the beginning
		-- theTEXT might need to be doctored, depending on the characters it contained.
		do JavaScript "window.find(\"" & theTEXT & "\")" in document 1 --> boolean indicating found or otherwise.
		
		delay 0.5 # to see that the string is selected
		if (pass mod 3) ≠ 0 then
			# select a string near the end of the document
			set theTEXT to "Spitz, Marc (2011). Jagger: Rebel, Rock Star, Rambler, Rogue. Gotham Books. ISBN 978-1-59240-655-5."
			-- set theTEXT to "Works å®‰è—¤å¿ é›„ Tadao Ando"
			do JavaScript "window.find(\"" & theTEXT & "\")" in document 1
		else
			tell application "System Events" to tell process "Safari"
				set frontmost to true
				tell window 1
					key code 119 # go to very bottom of the webpage
				end tell
			end tell
		end if
		delay 0.5 # to see that the string is selected
	end tell
	
	if (pass mod 5) = 0 then
		# If pass is a multiple of 5, force Safari to quit
		tell application "Safari" to quit
		delay 0.2 # required to let Safari finish its quit process
	end if
end repeat

I’m just wondering what is the meaning of the “2nd problem” : – theTEXT might need to be doctored, depending on the characters it contained.

I assumed that it may be about “foreign” unicode text so I tested with :
set theURL to “https://en.wikipedia.org/wiki/Tadao_Ando
set theTEXT to “Tadao Ando (安藤 å¿ é›„ Andō Tadao?, born September 13”
and
set theTEXT to “Works å®‰è—¤å¿ é›„ Tadao Ando” as second string to search

I had nothing special to change to the datas.

Yvan KOENIG running Sierra 10.12.2 in French (VALLAURIS, France) jeudi 29 décembre 2016 14:10:51