Hi,
Does anyone know how to make an applescript pause until a Safari page has completed loading? In my script right now, I have a pause for “x” number of seconds, but this is less than ideal since on a fast connection I am waiting around, and on a really slow connection, sometimes the script tries to move along too early. I have looked around for other solutions, but so far haven’t found anything.
I’m using this handler, it returns true when the page has loaded successfully,
otherwise false. You can pass a timeout value.
on page_loaded(timeout_value)
delay 2
repeat with i from 1 to the timeout_value
tell application "Safari"
if (do JavaScript "document.readyState" in document 1) is "complete" then
return true
else if i is the timeout_value then
return false
else
delay 1
end if
end tell
end repeat
return false
end page_loaded
I’ve set it to activate safari and beep when the page finishes loading.
I’ve not tried it with LION, but the OSX hint suggests:
Anyway, this works for me using OSX 10.6.7 Safari 5.0.5
tell application "Safari"
activate
repeat until SafariWindowHasLoaded(1) of me is true
end repeat
beep
end tell
on SafariWindowHasLoaded(inWindowIndex)
tell application "System Events" to ¬
tell application process "Safari"
set theStatusText to name of static text 1 of group 1 of window inWindowIndex as text
if theStatusText begins with "Contacting" or ¬
theStatusText begins with "Loading" or ¬
theStatusText begins with "Waiting" then
set theReturnValue to false
else
set theReturnValue to true
end if
end tell
return theReturnValue
end SafariWindowHasLoaded
error "System Events got an error: Can't get static text 1 of group 1 of window 1 of application process \"Safari\". Invalid index." number -1719 from static text 1 of group 1 of window 1 of application process "Safari"
error "System Events got an error: Can't get static text 1 of group 2 of window 1 of application process \"Safari\". Invalid index." number -1719 from static text 1 of group 2 of window 1 of application process "Safari"
Are you are opening pages on a the same web site where you know certain facts about the page are always going to be there? You can customize your script where it waits for the page until meets that certain criteria.
for instance if you know how how large the page is supposed to be when fully loaded:
on wait4page()
delay .5
set safe to false
repeat until safe
delay 0.2
tell application "Safari"
do JavaScript "document.readyState" in document 1
if result is "Complete" then exit repeat
end tell
tell application "Safari" to set PageText to the text of document 1
if length of PageText is greater than 1000 then set safe to true
end repeat
end wait4page
Or if you know there is always the same text in the content on every page. (Maybe the footer, or the last items loaded on page load.)
on wait4page()
delay .5
set safe to false
repeat until safe
delay 0.2
tell application "Safari"
do JavaScript "document.readyState" in document 1
if result is "Complete" then exit repeat
end tell
tell application "Safari" to set PageText to the text of document 1
if PageText contains "My Unique text" then set safe to true
end if
end repeat
end wait4page
I kind of think the JavaScript above no longer works in the current version of Safari, also waiting for “loading” in the window name is not longer usable either. Not sure about the first, but definitely on the second.
This sort of works but it doesn’t really check if the full page is loaded in Safari 6.0.1 for me.
--tell application "Safari" to open location "http://www.macosxhints.com/article.php?story=20091101035318405"
tell application "Safari" to open location "http://macscripter.net/viewtopic.php?id=35176"
if page_loaded(20) then
say "Page Loaded"
tell application "Safari"
activate
end tell
else
say "Page Failed to load or Safari didn't open"
end if
on page_loaded(timeout_value) -- in seconds
delay 1
repeat with i from 1 to timeout_value
tell application "Safari"
if name of current tab of window 1 is not "Loading" then exit repeat
end tell
delay 1
end repeat
if i is timeout_value then return false
tell application "Safari"
repeat until (do JavaScript "document.readyState" in document 1) is "complete"
delay 0.5
end repeat
end tell
return true
end page_loaded
According to the docs for document.readystate it should be fully loaded.
I wonder if there are any discrepancies you know of.
Edit
I got it, that it returns “complete”, without it actually being so, so nevermind.
(It would have been interesting to know if the tcp packets that the page are consisting of are received and acknowleged, and that it is just the rendering that is not fulfilled though.
Or if Safari is taking a very optimistic approach, having received a some packets, when stating that the page load is completed.)
Anyways, the fix for my case is just to add a delay loop if the getElementById should fail.
Is there any chance you could post an example of using getElementById to make this work?
Until now, I’ve been using a combination of the first two solutions, but with Safari 6, there is no text for Loading, Contacting, or Waiting that I can find.
The idea behind getElementById is that you then see that you can get a value out of that element, that you have found with the domInspector. This seems to be a very hard thing to do, I haven’t the time to do it right now, but it really should be possible!
I use this:
tell application "Safari"
# loading and stuff comes here...
repeat
local pageState
set pageState to do JavaScript "document.readyState" in document 1
if pageState = "Complete" then exit repeat
delay 0.2
end repeat
end tell
If it doesn’t return that value, then the page isn’t loaded. if you need further assurance, then you can go here and grab Red Menace’s handler.
I thing getElementById will work, if you have something that retuns text, so you better get a value off some DOM element that contains text.
This is a handler I just made, that you may rework that will return true if the page is fully loaded, it uses text items to split the pagesource of Safari’s first document into a list. If the element is there, clearly the page is fully loaded, and it will return true, since the list then has two elements.
to fullyLoaded()
local tids, thelist, theText
set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, "</html>"}
tell application "Safari" to set theText to source of its document 1
set thelist to text items of theText
set AppleScript's text item delimiters to tids
if length of thelist = 1 then return false
return true
end fullyLoaded
This will actually log you in at MacScripter if you turn off keychain access, and provide the password, and provide your own user name.
tell application "Safari"
open location "http://macscripter.net/login.php"
repeat
set theReturn to do JavaScript "document.getElementById('navlogin').innerHTML" in document 1
if theReturn is not missing value then exit repeat
delay 0.2
end repeat
do JavaScript "oFormObject = document.forms['login']; oFormObject.elements['req_username'].value = 'McUsr'; oFormObject.elements['req_password'].value = 'XXXXXXXX';document.forms['login'].submit();" in document 1
end tell
As you can see, if the do javascript doesn’t returns something else than missing value, then the page is loaded.
It works for me, very well, I am on Safari 5.1.7 and I do have JavaScript enabled. (Just saying).
If you load Macscripters so that it is in Safari’s frontmost window, and run this from your ASEditor:
set theReturn to do JavaScript "document.getElementById('navlogin').innerHTML" in document 1
log theReturn
Don’t you get any result? I am sorry if they have changed Safari v.6 to not get results of the getElementById, but I don’t to believe it. This is the safest way to check if a page is loaded, as document.readystate, is saying it is, when it isn’t. But when the DOM tree is working, well, then it is working, and the page must be loaded.
I am also running 5.1.7 and did the following script. I had to remove the last line to show the result, but the result is “missing value”. It could really use this script if I could get it to work.
tell application "Safari" to set theReturn to do JavaScript "document.getElementById('navlogin').innerHTML" in document 1
log theReturn
I tested it for the navlogin link, pleas try to show source of Macscripter.net before you log in, and find another element with id then, I can’t fathom that it is happening to you really.
Try with some other element with id, that has contents, that is the only advice I can give you. See if you get it working, the page with Macscripter.net must be the frontmost document open in Safari, before any source window or anything else.
I have also the developer menu enabled, so I can see the responses when I execute JavaScript from AppleScript, at least I then can see if there are any error messages from the JavaScript console.
Ok, I think I know what is going on. I was running the script on this MacScripter post page thinking the script was just checking to see if the page was fully loaded. Looking a little closer at the script, let me ask a question. Is the script searching for the text “navlogin” in the source?
It more looks it up in the dom tree, and returns the html that is hanging on that element. I think id used like this can only be used with span and nav tags, and some other, so it basically works with block elements. To use this on other sites, you’ll have to find such an element with contents, on the page that are intitially loaded.
Maybe I should have stated that, I did somewhat take the context for granted. Especially since you have delivered two handlers.
I am glad we sorted that out, because this seems to work good for me, so I hoped it could work well for other people too.