True, the problem was on my end. I tried the same on my working PC (without any profile) and it works just fine. So I always need to make a new profile for this to work? it doesn't seem very time saving if I have to do this every time. hahah I guess i will have to wait until we get it to work on other than empty profiles.Shadowpheonix wrote:This sounds like a problem with either your Chrome profile or with an extension/plugin you have installed in Chrome.fenchai wrote:Is there any reason everytime chrome opens, it shows something about windows defender wanting to reset my chrome preferences and adobe and google drive keep wanting to install? this is weird.
If you haven't already done so, try running with a brand new profile in Chrome and see if the issue still occurs. I am not sure what else to suggest if that fails.
[Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No IE!
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
-
- Posts: 1259
- Joined: 16 Apr 2015, 09:41
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
A new profile should not be needed every time. I use the same profile every time on my system and Chrome gives me no issues.fenchai wrote:True, the problem was on my end. I tried the same on my working PC (without any profile) and it works just fine. So I always need to make a new profile for this to work? it doesn't seem very time saving if I have to do this every time. hahah I guess i will have to wait until we get it to work on other than empty profiles.Shadowpheonix wrote:This sounds like a problem with either your Chrome profile or with an extension/plugin you have installed in Chrome.fenchai wrote:Is there any reason everytime chrome opens, it shows something about windows defender wanting to reset my chrome preferences and adobe and google drive keep wanting to install? this is weird.
If you haven't already done so, try running with a brand new profile in Chrome and see if the issue still occurs. I am not sure what else to suggest if that fails.
If you are getting the same issue the second time you use a profile, then I suspect you are logging into your Google account and Chrome is having trouble syncing extensions, bookmarks, etcetera. Try making a new profile and disabling the sync on it. If that clears up the issue, enable one type of sync and try again - keep enabling sync types & relaunching with that profile until you find the one that triggers the issue.
- Joe Glines
- Posts: 771
- Joined: 30 Sep 2013, 20:49
- Location: Dallas
- Contact:
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
Sorry- I thought you were trying to set a value. I have something similar for getting values but am busy today preparing for our webinar on Neural Networks with AutoHotkey. I'll try and get to it later this week / this weekend.Shadowpheonix wrote:Joe Glines wrote:this still leaves me clueless as to how to read an existing value from a web page in Chrome without having to install Selenium.
Regards,
Joe
Sign-up for the HK Newsletter
AHK Tutorials:Web Scraping | | Webservice APIs | AHK and Excel | Chrome | RegEx | Functions
Training: AHK Webinars Courses on AutoHotkey
YouTube
Quick Access Popup, the powerful Windows folders, apps and documents launcher!
AHK Tutorials:Web Scraping | | Webservice APIs | AHK and Excel | Chrome | RegEx | Functions
Training: AHK Webinars Courses on AutoHotkey
YouTube
Quick Access Popup, the powerful Windows folders, apps and documents launcher!
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
Chrome.ahk v1.1 has been released
Please see the original post for a link to the release and revision history pages.
Changes
Please see the original post for a link to the release and revision history pages.
Changes
- class Chrome's constructor now accepts additional flags for chrome
- Pages are no longer connected to using Chrome.GetTab()
- New methods for finding the right page to connect to
- New method Chrome.Kill() for ending the Chrome process
- New example script ExportPDF.ahk demonstrating headless chrome and PDF exports.
- Joe Glines
- Posts: 771
- Joined: 30 Sep 2013, 20:49
- Location: Dallas
- Contact:
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
Awesome stuff GeekDude!
Sign-up for the HK Newsletter
AHK Tutorials:Web Scraping | | Webservice APIs | AHK and Excel | Chrome | RegEx | Functions
Training: AHK Webinars Courses on AutoHotkey
YouTube
Quick Access Popup, the powerful Windows folders, apps and documents launcher!
AHK Tutorials:Web Scraping | | Webservice APIs | AHK and Excel | Chrome | RegEx | Functions
Training: AHK Webinars Courses on AutoHotkey
YouTube
Quick Access Popup, the powerful Windows folders, apps and documents launcher!
-
- Posts: 1259
- Joined: 16 Apr 2015, 09:41
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
What is the replacement for this process with v1.1? GetTab() no longer exists in the class, and PageInst := Chrome.GetPage() returns a "connection with the server could not be established" error.GeekDude wrote:If you have a chrome instance already running in debug mode, you can skip the initialization of the Chrome class and just call Chrome.GetTab() directly. For example:
Code: Select all
#Include Chrome.ahk TabInst := Chrome.GetTab() TabInst.Evaluate("alert('hi!');")
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
I would have expected it to output \u00E7 with a backslash. The sequence \u marks the next four characters as a hexadecimal representation of a Unicode character's code point. Code point 00E7 corresponds to ç, which can be seen on Wikipedia's list of Unicode characters.ramonstart wrote:Hello
Why does the Jxon_Dump method return the "menor preu00E7o do livro" value when the correct text is "menor preço do livro"?
I'm using the call below to bring the content of the list that is in the url https://www.estantevirtual.com.br/livro ... 20Kiyosaki
Would you help me?
Something like MsgBox, % PageInst.Evaluate("document.querySelector('input[name=desc]').value").value should work.Shadowpheonix wrote:Joe Glines wrote:I don't have an example I can post right now but I "solved this" by inserting Java script.Shadowpheonix wrote:In the "pastebin.ahk" example that GeekDude was nice enough to include in the release package, the Tab.Call("DOM.setAttributeValue", {"nodeId": NameNode.NodeId, "name": "value", "value": "ChromeBot"}) line assigns a value to an input field. How would I go about retrieving the value after it was assigned?
If you happen to come up with an example you can post, it would be most appreciated.
You may be able to disable those warnings by providing the --no-first-run and --disable-extensions flags in the Flags parameter of new Chrome(ProfilePath, URL, Flags) in Chrome.ahk_v1.1. For example, ChromeInst := new Chrome("ChromeProfile",, "--no-first-run --disable-extensions")fenchai wrote:Is there any reason everytime chrome opens, it shows something about windows defender wanting to reset my chrome preferences and adobe and google drive keep wanting to install? this is weird.
You're meant to create an instance of the Chrome class before calling GetTab()/GetPage(). If you're already running a copy of chrome with debugging enabled I can see how it could have worked with Chrome.ahk v1.0, but not anymore with v1.1.Shadowpheonix wrote:What is the replacement for this process with v1.1? GetTab() no longer exists in the class, and PageInst := Chrome.GetPage() returns a "connection with the server could not be established" error.GeekDude wrote:If you have a chrome instance already running in debug mode, you can skip the initialization of the Chrome class and just call Chrome.GetTab() directly. For example:
Code: Select all
#Include Chrome.ahk TabInst := Chrome.GetTab() TabInst.Evaluate("alert('hi!');")
If you don't want to launch a new instance of chrome to connect to, you could probably set Chrome.DebugPort := 9222 before calling Chrome.GetPage() and it would behave like you were expecting.
As far as I can tell, there is no way to change the printer settings through this interface.kunkel321 wrote:Thanks for your work on this, GeekDude! It's a bit above my ability to fully understand, but I wonder: Would it be possible to use this for setting the default printer in Chrome? I have a nice AHK script that (at system startup) reads my IP address, then sets my default Windows printer. Chrome doesn't use the Windows default printer though. It merely reverts to whatever printer was last used.
-
- Posts: 1259
- Joined: 16 Apr 2015, 09:41
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
Thank you! Both work perfectly, and will help me solve some long standing issues I have had with automating a few web interactions for my work.GeekDude wrote:Something like MsgBox, % PageInst.Evaluate("document.querySelector('input[name=desc]').value").value should work.Shadowpheonix wrote:If you happen to come up with an example you can post, it would be most appreciated.Joe Glines wrote:I don't have an example I can post right now but I "solved this" by inserting Java script.Shadowpheonix wrote:In the "pastebin.ahk" example that GeekDude was nice enough to include in the release package, the Tab.Call("DOM.setAttributeValue", {"nodeId": NameNode.NodeId, "name": "value", "value": "ChromeBot"}) line assigns a value to an input field. How would I go about retrieving the value after it was assigned?
You're meant to create an instance of the Chrome class before calling GetTab()/GetPage(). If you're already running a copy of chrome with debugging enabled I can see how it could have worked with Chrome.ahk v1.0, but not anymore with v1.1.Shadowpheonix wrote:What is the replacement for this process with v1.1? GetTab() no longer exists in the class, and PageInst := Chrome.GetPage() returns a "connection with the server could not be established" error.GeekDude wrote:If you have a chrome instance already running in debug mode, you can skip the initialization of the Chrome class and just call Chrome.GetTab() directly. For example:Code: Select all
#Include Chrome.ahk TabInst := Chrome.GetTab() TabInst.Evaluate("alert('hi!');")
If you don't want to launch a new instance of chrome to connect to, you could probably set Chrome.DebugPort := 9222 before calling Chrome.GetPage() and it would behave like you were expecting.
- Joe Glines
- Posts: 771
- Joined: 30 Sep 2013, 20:49
- Location: Dallas
- Contact:
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
I posted my function for getting text / data from a page with Chrome & AutoHotkey. Check it out and the below video walking through how to use it.Shadowpheonix wrote:Joe Glines wrote:However, this still leaves me clueless as to how to read an existing value from a web page in Chrome without having to install Selenium.
Sign-up for the HK Newsletter
AHK Tutorials:Web Scraping | | Webservice APIs | AHK and Excel | Chrome | RegEx | Functions
Training: AHK Webinars Courses on AutoHotkey
YouTube
Quick Access Popup, the powerful Windows folders, apps and documents launcher!
AHK Tutorials:Web Scraping | | Webservice APIs | AHK and Excel | Chrome | RegEx | Functions
Training: AHK Webinars Courses on AutoHotkey
YouTube
Quick Access Popup, the powerful Windows folders, apps and documents launcher!
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
GeekDude,
Thanks a lot for this tool. This is way better than working with IE or the selenium basic with ahk.
There is a minor bug that I found, when opening multiple url's you have to wait for like 700 ms for the about:blank on page before calling Chrome.GetPage() or it will use the old tab opened to return the object.
Thanks a lot for this tool. This is way better than working with IE or the selenium basic with ahk.
There is a minor bug that I found, when opening multiple url's you have to wait for like 700 ms for the about:blank on page before calling Chrome.GetPage() or it will use the old tab opened to return the object.
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
@joeglines
I haven't studied your Chm_Set() function very closely yet but will you be adding "frame support" to Chm_Set() so you can control forms in say a "content" frame (or frame[2]) document.parentwindow.frames("content").document.
I haven't studied your Chm_Set() function very closely yet but will you be adding "frame support" to Chm_Set() so you can control forms in say a "content" frame (or frame[2]) document.parentwindow.frames("content").document.
- Joe Glines
- Posts: 771
- Joined: 30 Sep 2013, 20:49
- Location: Dallas
- Contact:
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
I wasn't planning on it but that's something that, theoretically, shouldn't be too hard for you to do... I don't anticipate actually using the chrome functionality myself as I do all of my scraping with IE. I'm just excited that the basics are available for Chrome so people can automate logging-in, etc.
Sign-up for the HK Newsletter
AHK Tutorials:Web Scraping | | Webservice APIs | AHK and Excel | Chrome | RegEx | Functions
Training: AHK Webinars Courses on AutoHotkey
YouTube
Quick Access Popup, the powerful Windows folders, apps and documents launcher!
AHK Tutorials:Web Scraping | | Webservice APIs | AHK and Excel | Chrome | RegEx | Functions
Training: AHK Webinars Courses on AutoHotkey
YouTube
Quick Access Popup, the powerful Windows folders, apps and documents launcher!
- Lateralus138
- Posts: 49
- Joined: 30 Aug 2015, 20:52
- Location: Decatur, IL.
- Contact:
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
Much appreciated!!! I've been expanding (learning) my AutoHotkey skills more in browser com objects lately and testing and looking through scripts and functions like this help me understand how things work a lot better than plain documentation usually does.
K̴̡̛̻̮̼͕̬̑̋̀̂͆͛̍̑͢ȩ̮̞͍̩̯̋̈͒͌̕ę̶͓̗͖͔̹̪͗̂̈͛̓͘p̠͉̙̟̒̊͌̐͘͘͟͡͞ S̸͖̖̮̞̥͇̖̓̌͛̽̿̓̊̓̾̚͜w͇̮͓̱͇̘̯͆̓͑̋̇̉͜͝i̢͔̝̳̻̱̋̾͐̾͗͊̀̕͜͡͡n̷̡͔̦̤̝̼̩̎͌̈́̀͛̄͆̎͠ǵ̸̘̝̭̦̠̗͖͌͐͑̑̿̅̈͜͜ḯ̡̬̥̙̩̼̪̑͆̿̌́n̛̼͎̲̬͇̲͉̗̞͊̓̃̂̈͝g̸͕̜͖̪͉͔̩̓̃̀̃͌̑̋̕͘.̪̜̜̜̯̂͂̈́͛̆͗̇̍̇.̟͔͍̙̜̫̗̂̿͛͋͋̈́̾̾̿͑.̡̣̟̝̭͉̦̪́̓̀͛̑̓̐̈͘͘
-
- Posts: 38
- Joined: 25 Apr 2016, 18:00
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
It's a good job!!! Great thanks!!!
But how can i add event listener in JavaScript-code, which launches the functions in AHK-code?
Next simple example shows the coordinates of a click in a fixed div-block, if click on the button "Click inform" when the page loads:
How to call a AHK-function that shows this coordinates of a click in "MsgBox"?
Update:
If I understand correctly, the ActiveX control in the object of the class acts as a server for the browser. Then how do I send a message from the browser page?
Then it could be processed in "ws.onmessage". Right?
But how can i add event listener in JavaScript-code, which launches the functions in AHK-code?
Next simple example shows the coordinates of a click in a fixed div-block, if click on the button "Click inform" when the page loads:
Code: Select all
#NoEnv
#SingleInstance, Force
SetBatchLines, -1
#Include ../Chrome.ahk
; --- Create a new Chrome instance ---
addr1 := "https://autohotkey.com/"
addr2 := "https://autohotkey.com/boards/viewtopic.php?f=6&t=42890&sid=100a6baa98d3de10e3cdf42616ae10d9"
chPath := "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
profileName := "ChromeProfile"
recycleProfile := true
showEvalResult := false
FileCreateDir, %profileName%
ChromeInst := new Chrome(profileName, addr2,, chPath)
informerCode :=
(LTrim Join`s
"function handlerF(ev) {
info.innerHTML = ``${ev.clientX}x ${ev.clientY}y``;
}
var body = document.querySelector('body');
var info = document.createElement('div');
info.id = 'info';
info.style.position = 'fixed';
info.style.top = '10px';
info.style.right = '10px';
info.style.width = '200px';
info.style.border = '2px solid #fff';
info.style.color = '#fff';
info.style.fontWeight = 'bold';
info.style.fontSize = '20px';
info.style.textShadow = '1px 1px 1px #000, 1px -1px 1px #000, -1px -1px 1px #000, -1px 1px 1px #000';
info.style.textAlign = 'center';
body.appendChild(info);
info.innerHTML = 'Click on the page';
body.addEventListener('click', handlerF);"
)
; --- Connect to the page ---
if !(PageInst := ChromeInst.GetPage()) {
MsgBox, Could not retrieve page!
ChromeInst.Kill()
GoSub, CloseMe
} else {
Gui,Input: Margin, 10, 10
Gui,Input: +Hwndinput_h
Gui,Input: Add, Button,gEvalMe,Run
Gui,Input: Add, Text,x+10 yp+5,Run a JS-script on the page:
Gui,Input: Font, s10 Bold, Consolas
Gui,Input: Add, Edit,xm y+7 w600 r20 vmyCode cBlue,alert('Hello World!!!');
Gui,Input: Font
Gui,Input: Add, Button,xm y+5 gAddInform,Click inform
Gui,Input: Add, Button,x+10 gCloseMe,Close
Gui,Input: Show,,Input
}
return
EvalMe:
Gui,Input: Submit, NoHide
if (myCode == "")
return
GoSub, EvaluateHim
return
AddInform:
myCode := informerCode
GoSub, EvaluateHim
return
EvaluateHim:
try
Result := PageInst.Evaluate(myCode)
catch e
{
MsgBox, % "Exception encountered in " e.What ":`n`n"
. e.Message "`n`n"
. "Specifically:`n`n"
. Chrome.Jxon_Dump(Chrome.Jxon_Load(e.Extra), "`t")
} if (showEvalResult)
MsgBox, % "Result:`n" Chrome.Jxon_Dump(Result, "`t")
return
CloseMe:
InputGuiClose:
; --- Close the Chrome instance ---
try
PageInst.Call("Browser.close") ; Fails when running headless
catch
ChromeInst.Kill()
PageInst.Disconnect()
fPath := Format("{1}\{2}",A_ScriptDir,profileName)
if (recycleProfile) {
FileRemoveDir,% fPath, 1
i := 0
while (i < 10, i++) {
Sleep, 1000
if (FileExist(fPath))
FileRemoveDir,% fPath, 1
else
break
}
}
ExitApp
Update:
If I understand correctly, the ActiveX control in the object of the class acts as a server for the browser. Then how do I send a message from the browser page?
Then it could be processed in "ws.onmessage". Right?
-
- Posts: 38
- Joined: 25 Apr 2016, 18:00
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
The only trouble-free solution that we managed to find is to activate the console domain:
PageInst.Call("Console.enable")
And catch incoming messages "Console.messageAdded"
I had to change the "Chrome" class a little, adding the sixth parameter to the call constructor, here and there to transfer it further for the nested class "Page", and rename it to "PageBase" and extend him, redefining the constructor and the method "Event":
Thus, if set the page "Click informer", as well press the button "Get event data" then when the clicks will be called function MSGFunc(), getting the same text as div-tag.
May I ask you to add this opportunity to the future editions of your work? Having a this opportunity would be very convenient.
And sorry for my English. =/
PageInst.Call("Console.enable")
And catch incoming messages "Console.messageAdded"
I had to change the "Chrome" class a little, adding the sixth parameter to the call constructor, here and there to transfer it further for the nested class "Page", and rename it to "PageBase" and extend him, redefining the constructor and the method "Event":
Code: Select all
; Chrome.ahk v1.1
; Copyright GeekDude 2018
; https://github.com/G33kDude/Chrome.ahk
class Chrome
{
/*
Escape a string in a manner suitable for command line parameters
*/
CliEscape(Param)
{
return """" RegExReplace(Param, "(\\*)""", "$1$1\""") """"
}
/*
ProfilePath - Path to the user profile directory to use. Will use the standard if left blank.
URL - The page for Chrome to load when it opens
Flags - Additional flags for chrome when launching
ChromePath - Path to chrome.exe, will detect from start menu when left blank
DebugPort - What port should Chrome's remote debugging server run on
*/
__New(ProfilePath:="", URL:="about:blank", Flags:="", ChromePath:="", DebugPort:=9222, funcName := "")
{
this.funcName := funcName
; Verify ProfilePath
if (ProfilePath != "" && !InStr(FileExist(ProfilePath), "D"))
throw Exception("The given ProfilePath does not exist")
this.ProfilePath := ProfilePath
; Verify ChromePath
; TODO: Perform a more rigorous search for Chrome
if (ChromePath == "")
FileGetShortcut, %A_StartMenuCommon%\Programs\Google Chrome.lnk, ChromePath
if !FileExist(ChromePath)
throw Exception("Chrome could not be found")
this.ChromePath := ChromePath
; Verify DebugPort
if DebugPort is not integer
throw Exception("DebugPort must be a positive integer")
if (DebugPort <= 0)
throw Exception("DebugPort must be a positive integer")
this.DebugPort := DebugPort
; TODO: Support an array of URLs
Run, % this.CliEscape(ChromePath)
. " --remote-debugging-port=" this.DebugPort
. (ProfilePath ? " --user-data-dir=" this.CliEscape(ProfilePath) : "")
. (Flags ? " " Flags : "")
. (URL ? " " this.CliEscape(URL) : "")
,,, OutputVarPID
this.PID := OutputVarPID
}
/*
End Chrome by terminating the process.
*/
Kill()
{
Process, Close, % this.PID
}
/*
Queries chrome for a list of pages that expose a debug interface.
In addition to standard tabs, these include pages such as extension
configuration pages.
*/
GetPageList()
{
http := ComObjCreate("WinHttp.WinHttpRequest.5.1")
http.open("GET", "http://127.0.0.1:" this.DebugPort "/json")
http.send()
return this.Jxon_Load(http.responseText)
}
/*
Returns a connection to the debug interface of a page that matches the
provided criteria. When multiple pages match the criteria, they appear
ordered by how recently the pages were opened.
Key - The key from the page list to search for, such as "url" or "title"
Value - The value to search for in the provided key
MatchMode - What kind of search to use, such as "exact", "contains", "startswith", or "regex"
Index - If multiple pages match the given criteria, which one of them to return
*/
GetPageBy(Key, Value, MatchMode:="exact", Index:=1)
{
Count := 0
for n, PageData in this.GetPageList()
{
if (((MatchMode = "exact" && PageData[Key] = Value) ; Case insensitive
|| (MatchMode = "contains" && InStr(PageData[Key], Value))
|| (MatchMode = "startswith" && InStr(PageData[Key], Value) == 1)
|| (MatchMode = "regex" && PageData[Key] ~= Value))
&& ++Count == Index)
return new this.Page(PageData.webSocketDebuggerUrl, this.funcName)
}
}
/*
Shorthand for GetPageBy("url", Value, "startswith")
*/
GetPageByURL(Value, MatchMode:="startswith", Index:=1)
{
this.GetPageBy("url", Value, MatchMode, Index)
}
/*
Shorthand for GetPageBy("title", Value, "startswith")
*/
GetPageByTitle(Value, MatchMode:="startswith", Index:=1)
{
this.GetPageBy("title", Value, MatchMode, Index)
}
/*
Shorthand for GetPageBy("type", Type, "exact")
The default type to search for is "page", which is the visible area of
a normal Chrome tab.
*/
GetPage(Index:=1, Type:="page")
{
return this.GetPageBy("type", Type, "exact", Index)
}
/*
Connects to the debug interface of a page given its WebSocket URL.
*/
class PageBase
{
Connected := False
ID := 0
Responses := []
/*
wsurl - The desired page's WebSocket URL
*/
__New(wsurl)
{
this.BoundKeepAlive := this.Call.Bind(this, "Browser.getVersion",, False)
; TODO: Throw exception on invalid objects
if IsObject(wsurl)
wsurl := wsurl.webSocketDebuggerUrl
wsurl := StrReplace(wsurl, "localhost", "127.0.0.1")
this.ws := {"base": this.WebSocket, "_Event": this.Event, "Parent": this}
this.ws.__New(wsurl)
while !this.Connected
Sleep, 50
}
/*
Calls the specified endpoint and provides it with the given
parameters.
DomainAndMethod - The endpoint domain and method name for the
endpoint you would like to call. For example:
PageInst.Call("Browser.close")
PageInst.Call("Schema.getDomains")
Params - An associative array of parameters to be provided to the
endpoint. For example:
PageInst.Call("Page.printToPDF", {"scale": 0.5 ; Numeric Value
, "landscape": Chrome.Jxon_True() ; Boolean Value
, "pageRanges: "1-5, 8, 11-13"}) ; String value
PageInst.Call("Page.navigate", {"url": "https://autohotkey.com/"})
WaitForResponse - Whether to block until a response is received from
Chrome, which is necessary to receive a return value, or whether
to continue on with the script without waiting for a response.
*/
Call(DomainAndMethod, Params:="", WaitForResponse:=True)
{
if !this.Connected
throw Exception("Not connected to tab")
; Use a temporary variable for ID in case more calls are made
; before we receive a response.
ID := this.ID += 1
this.ws.Send(Chrome.Jxon_Dump({"id": ID
, "params": Params ? Params : {}
, "method": DomainAndMethod}))
if !WaitForResponse
return
; Wait for the response
this.responses[ID] := False
while !this.responses[ID]
Sleep, 50
; Get the response, check if it's an error
response := this.responses.Delete(ID)
if (response.error)
throw Exception("Chrome indicated error in response",, Chrome.Jxon_Dump(response.error))
return response.result
}
/*
Run some JavaScript on the page. For example:
PageInst.Evaluate("alert(""I can't believe it's not IE!"");")
PageInst.Evaluate("document.getElementsByTagName('button')[0].click();")
*/
Evaluate(JS)
{
response := this.Call("Runtime.evaluate",
( LTrim Join
{
"expression": JS,
"objectGroup": "console",
"includeCommandLineAPI": Chrome.Jxon_True(),
"silent": Chrome.Jxon_False(),
"returnByValue": Chrome.Jxon_False(),
"userGesture": Chrome.Jxon_True(),
"awaitPromise": Chrome.Jxon_False()
}
))
if (response.exceptionDetails)
throw Exception(response.result.description,, Chrome.Jxon_Dump(response.exceptionDetails))
return response.result
}
/*
Waits for the page's readyState to match the DesiredState.
DesiredState - The state to wait for the page's ReadyState to match
Interval - How often it should check whether the state matches
*/
WaitForLoad(DesiredState:="complete", Interval:=100)
{
while this.Evaluate("document.readyState").value != DesiredState
Sleep, Interval
}
/*
Internal function triggered when the script receives a message on
the WebSocket connected to the page.
*/
Event(EventName, Event)
{
; If it was called from the WebSocket adjust the class context
if this.Parent
this := this.Parent
; TODO: Handle Error events
if (EventName == "Open")
{
this.Connected := True
BoundKeepAlive := this.BoundKeepAlive
SetTimer, %BoundKeepAlive%, 15000
}
else if (EventName == "Message")
{
data := Chrome.Jxon_Load(Event.data)
if this.responses.HasKey(data.ID)
this.responses[data.ID] := data
}
else if (EventName == "Close")
{
this.Disconnect()
}
}
/*
Disconnect from the page's debug interface, allowing the instance
to be garbage collected.
This method should always be called when you are finished with a
page or else your script will leak memory.
*/
Disconnect()
{
if !this.Connected
return
this.Connected := False
this.ws.Delete("Parent")
this.ws.Disconnect()
BoundKeepAlive := this.BoundKeepAlive
SetTimer, %BoundKeepAlive%, Delete
this.Delete("BoundKeepAlive")
}
class WebSocket
{
__New(WS_URL)
{
static wb
; Create an IE instance
Gui, +hWndhOld
Gui, New, +hWndhWnd
this.hWnd := hWnd
Gui, Add, ActiveX, vWB, Shell.Explorer
Gui, %hOld%: Default
; Write an appropriate document
WB.Navigate("about:<!DOCTYPE html><meta http-equiv='X-UA-Compatible'"
. "content='IE=edge'><body></body>")
while (WB.ReadyState < 4)
sleep, 50
this.document := WB.document
; Add our handlers to the JavaScript namespace
this.document.parentWindow.ahk_savews := this._SaveWS.Bind(this)
this.document.parentWindow.ahk_event := this._Event.Bind(this)
this.document.parentWindow.ahk_ws_url := WS_URL
; Add some JavaScript to the page to open a socket
Script := this.document.createElement("script")
Script.text := "ws = new WebSocket(ahk_ws_url);`n"
. "ws.onopen = function(event){ ahk_event('Open', event); };`n"
. "ws.onclose = function(event){ ahk_event('Close', event); };`n"
. "ws.onerror = function(event){ ahk_event('Error', event); };`n"
. "ws.onmessage = function(event){ ahk_event('Message', event); };"
this.document.body.appendChild(Script)
}
; Called by the JS in response to WS events
_Event(EventName, Event)
{
this["On" EventName](Event)
}
; Sends data through the WebSocket
Send(Data)
{
this.document.parentWindow.ws.send(Data)
}
; Closes the WebSocket connection
Close(Code:=1000, Reason:="")
{
this.document.parentWindow.ws.close(Code, Reason)
}
; Closes and deletes the WebSocket, removing
; references so the class can be garbage collected
Disconnect()
{
if this.hWnd
{
this.Close()
Gui, % this.hWnd ": Destroy"
this.hWnd := False
}
}
}
}
Class Page Extends Chrome.PageBase
{
__New(wsurl, funcName)
{
(funcName && this.bindedFunction := Func(funcName))
this.BoundKeepAlive := this.Call.Bind(this, "Browser.getVersion",, False)
; TODO: Throw exception on invalid objects
if IsObject(wsurl)
wsurl := wsurl.webSocketDebuggerUrl
wsurl := StrReplace(wsurl, "localhost", "127.0.0.1")
this.ws := {"base": this.WebSocket, "_Event": this.Event, "Parent": this}
this.ws.__New(wsurl)
while !this.Connected
Sleep, 50
}
Event(EventName, Event)
{
; If it was called from the WebSocket adjust the class context
if this.Parent
this := this.Parent
; TODO: Handle Error events
if (EventName == "Open")
{
this.Connected := True
BoundKeepAlive := this.BoundKeepAlive
SetTimer, %BoundKeepAlive%, 15000
}
else if (EventName == "Message")
{
data := Chrome.Jxon_Load(Event.data)
if (this.bindedFunction && data.method == "Console.messageAdded" && (msg := data.params.message).level == "info")
this.bindedFunction.Call(msg.text)
if this.responses.HasKey(data.ID)
this.responses[data.ID] := data
}
else if (EventName == "Close")
{
this.Disconnect()
}
}
}
Jxon_Load(ByRef src, args*)
{
static q := Chr(34)
key := "", is_key := false
stack := [ tree := [] ]
is_arr := { (tree): 1 }
next := q . "{[01234567890-tfn"
pos := 0
while ( (ch := SubStr(src, ++pos, 1)) != "" )
{
if InStr(" `t`n`r", ch)
continue
if !InStr(next, ch, true)
{
ln := ObjLength(StrSplit(SubStr(src, 1, pos), "`n"))
col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1))
msg := Format("{}: line {} col {} (char {})"
, (next == "") ? ["Extra data", ch := SubStr(src, pos)][1]
: (next == "'") ? "Unterminated string starting at"
: (next == "\") ? "Invalid \escape"
: (next == ":") ? "Expecting ':' delimiter"
: (next == q) ? "Expecting object key enclosed in double quotes"
: (next == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'"
: (next == ",}") ? "Expecting ',' delimiter or object closing '}'"
: (next == ",]") ? "Expecting ',' delimiter or array closing ']'"
: [ "Expecting JSON value(string, number, [true, false, null], object or array)"
, ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1]
, ln, col, pos)
throw Exception(msg, -1, ch)
}
is_array := is_arr[obj := stack[1]]
if i := InStr("{[", ch)
{
val := (proto := args[i]) ? new proto : {}
is_array? ObjPush(obj, val) : obj[key] := val
ObjInsertAt(stack, 1, val)
is_arr[val] := !(is_key := ch == "{")
next := q . (is_key ? "}" : "{[]0123456789-tfn")
}
else if InStr("}]", ch)
{
ObjRemoveAt(stack, 1)
next := stack[1]==tree ? "" : is_arr[stack[1]] ? ",]" : ",}"
}
else if InStr(",:", ch)
{
is_key := (!is_array && ch == ",")
next := is_key ? q : q . "{[0123456789-tfn"
}
else ; string | number | true | false | null
{
if (ch == q) ; string
{
i := pos
while i := InStr(src, q,, i+1)
{
val := StrReplace(SubStr(src, pos+1, i-pos-1), "\\", "\u005C")
static end := A_AhkVersion<"2" ? 0 : -1
if (SubStr(val, end) != "\")
break
}
if !i ? (pos--, next := "'") : 0
continue
pos := i ; update pos
val := StrReplace(val, "\/", "/")
, val := StrReplace(val, "\" . q, q)
, val := StrReplace(val, "\b", "`b")
, val := StrReplace(val, "\f", "`f")
, val := StrReplace(val, "\n", "`n")
, val := StrReplace(val, "\r", "`r")
, val := StrReplace(val, "\t", "`t")
i := 0
while i := InStr(val, "\",, i+1)
{
if (SubStr(val, i+1, 1) != "u") ? (pos -= StrLen(SubStr(val, i)), next := "\") : 0
continue 2
; \uXXXX - JSON unicode escape sequence
xxxx := Abs("0x" . SubStr(val, i+2, 4))
if (A_IsUnicode || xxxx < 0x100)
val := SubStr(val, 1, i-1) . Chr(xxxx) . SubStr(val, i+6)
}
if is_key
{
key := val, next := ":"
continue
}
}
else ; number | true | false | null
{
val := SubStr(src, pos, i := RegExMatch(src, "[\]\},\s]|$",, pos)-pos)
; For numerical values, numerify integers and keep floats as is.
; I'm not yet sure if I should numerify floats in v2.0-a ...
static number := "number", integer := "integer"
if val is %number%
{
if val is %integer%
val += 0
}
; in v1.1, true,false,A_PtrSize,A_IsUnicode,A_Index,A_EventInfo,
; SOMETIMES return strings due to certain optimizations. Since it
; is just 'SOMETIMES', numerify to be consistent w/ v2.0-a
else if (val == "true" || val == "false")
val := %value% + 0
; AHK_H has built-in null, can't do 'val := %value%' where value == "null"
; as it would raise an exception in AHK_H(overriding built-in var)
else if (val == "null")
val := ""
; any other values are invalid, continue to trigger error
else if (pos--, next := "#")
continue
pos += i-1
}
is_array? ObjPush(obj, val) : obj[key] := val
next := obj==tree ? "" : is_array ? ",]" : ",}"
}
}
return tree[1]
}
Jxon_Dump(obj, indent:="", lvl:=1)
{
static q := Chr(34)
if IsObject(obj)
{
static Type := Func("Type")
if Type ? (Type.Call(obj) != "Object") : (ObjGetCapacity(obj) == "")
throw Exception("Object type not supported.", -1, Format("<Object at 0x{:p}>", &obj))
prefix := SubStr(A_ThisFunc, 1, InStr(A_ThisFunc, ".",, 0))
fn_t := prefix "Jxon_True", obj_t := this ? %fn_t%(this) : %fn_t%()
fn_f := prefix "Jxon_False", obj_f := this ? %fn_f%(this) : %fn_f%()
if (&obj == &obj_t)
return "true"
else if (&obj == &obj_f)
return "false"
is_array := 0
for k in obj
is_array := k == A_Index
until !is_array
static integer := "integer"
if indent is %integer%
{
if (indent < 0)
throw Exception("Indent parameter must be a postive integer.", -1, indent)
spaces := indent, indent := ""
Loop % spaces
indent .= " "
}
indt := ""
Loop, % indent ? lvl : 0
indt .= indent
this_fn := this ? Func(A_ThisFunc).Bind(this) : A_ThisFunc
lvl += 1, out := "" ; Make #Warn happy
for k, v in obj
{
if IsObject(k) || (k == "")
throw Exception("Invalid object key.", -1, k ? Format("<Object at 0x{:p}>", &obj) : "<blank>")
if !is_array
out .= ( ObjGetCapacity([k], 1) ? %this_fn%(k) : q . k . q ) ;// key
. ( indent ? ": " : ":" ) ; token + padding
out .= %this_fn%(v, indent, lvl) ; value
. ( indent ? ",`n" . indt : "," ) ; token + indent
}
if (out != "")
{
out := Trim(out, ",`n" . indent)
if (indent != "")
out := "`n" . indt . out . "`n" . SubStr(indt, StrLen(indent)+1)
}
return is_array ? "[" . out . "]" : "{" . out . "}"
}
; Number
else if (ObjGetCapacity([obj], 1) == "")
return obj
; String (null -> not supported by AHK)
if (obj != "")
{
obj := StrReplace(obj, "\", "\\")
, obj := StrReplace(obj, "/", "\/")
, obj := StrReplace(obj, q, "\" . q)
, obj := StrReplace(obj, "`b", "\b")
, obj := StrReplace(obj, "`f", "\f")
, obj := StrReplace(obj, "`n", "\n")
, obj := StrReplace(obj, "`r", "\r")
, obj := StrReplace(obj, "`t", "\t")
static needle := (A_AhkVersion<"2" ? "O)" : "") . "[^\x20-\x7e]"
while RegExMatch(obj, needle, m)
obj := StrReplace(obj, m[0], Format("\u{:04X}", Ord(m[0])))
}
return q . obj . q
}
Jxon_True()
{
static obj := {}
return obj
}
Jxon_False()
{
static obj := {}
return obj
}
}
Code: Select all
#NoEnv
#SingleInstance, Force
SetBatchLines, -1
#Include ../Chrome.ahk
; --- Create a new Chrome instance ---
addr := "https://autohotkey.com/boards/viewtopic.php?f=6&t=42890"
chPath := "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
profileName := "ChromeTest_Profile"
recycleProfile := false
showEvalResult := false
FileCreateDir, %profileName%
ChromeInst := new Chrome(profileName, addr,"--no-first-run", chPath,,"MSGFunc")
informerCode :=
(LTrim Join`s
"function handlerF(ev) {
info.innerHTML = ``${ev.clientX}x ${ev.clientY}y``;
console.info(``${ev.clientX}x ${ev.clientY}y``);
}
var body = document.querySelector('body');
var info = document.createElement('div');
info.id = 'info';
info.style.position = 'fixed';
info.style.top = '50px';
info.style.right = '10px';
info.style.width = '200px';
info.style.border = '2px solid #fff';
info.style.boxShadow = '1px 2px 2px #000';
info.style.color = '#fff';
info.style.fontWeight = 'bold';
info.style.fontSize = '20px';
info.style.textShadow = '1px 1px 1px #000, 1px -1px 1px #000, -1px -1px 1px #000, -1px 1px 1px #000';
info.style.textAlign = 'center';
body.appendChild(info);
info.innerHTML = 'Click on the page';
body.addEventListener('click', handlerF);"
)
xmpl := "
(LTrim Join`r`n
alert('Hello World!!!');
/*
var x = 5;
alert(``x = ${x + 5}``);
*/
)"
; --- Connect to the page ---
if !(PageInst := ChromeInst.GetPage()) {
MsgBox, Could not retrieve page!
ChromeInst.Kill()
GoSub, CloseMe
} else {
Gui,Input: Margin, 10, 10
Gui,Input: +Hwndinput_h
Gui,Input: Add, Button,gEvalMe,Run
Gui,Input: Add, Text,x+10 yp+5,Run a JS-script on the page:
Gui,Input: Font, s10 Bold, Consolas
Gui,Input: Add, Edit,xm y+7 w600 r20 vmyCode cBlue,% xmpl
Gui,Input: Font
Gui,Input: Add, Button,xm y+5 gAddInform,Click inform
Gui,Input: Add, Button,x+10 gCloseMe,Close
Gui,Input: Add, Button,x+10 gTestMe,Get event data
Gui,Input: Show,,Input
}
return
MSGFunc(msg) {
ToolTip,% msg
}
TestMe:
PageInst.Call("Console.enable")
return
EvalMe:
Gui,Input: Submit, NoHide
if (myCode == "")
return
GoSub, EvaluateHim
return
AddInform:
myCode := informerCode
GoSub, EvaluateHim
return
EvaluateHim:
try
Result := PageInst.Evaluate(myCode)
catch e
{
MsgBox, % "Exception encountered in " e.What ":`n`n"
. e.Message "`n`n"
. "Specifically:`n`n"
. Chrome.Jxon_Dump(Chrome.Jxon_Load(e.Extra), "`t")
} if (showEvalResult)
MsgBox, % "Result:`n" Chrome.Jxon_Dump(Result, "`t")
return
CloseMe:
InputGuiClose:
; --- Close the Chrome instance ---
try
PageInst.Call("Browser.close") ; Fails when running headless
catch
ChromeInst.Kill()
PageInst.Disconnect()
fPath := Format("{1}\{2}",A_ScriptDir,profileName)
if (recycleProfile) {
FileRemoveDir,% fPath, 1
i := 0
while (i < 10, i++) {
Sleep, 1000
if (FileExist(fPath))
FileRemoveDir,% fPath, 1
else
break
}
}
ExitApp
And sorry for my English. =/
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
This exactly the kind of thing I had in mind, but had not gotten around to implementing it. It's definitely on the to-do list to get this into the library, though rather than passing it into the constructor I might have it as a class property you would set (ChromeInst.OnConsole := Func("MyHandler") for example). I'll have to take a long look into it to make sure it's as future-proof and extensible as it needs to be. Who knows, I might incorporate your ideas directlyKusochekDobra wrote:The only trouble-free solution that we managed to find is to activate the console domain:
PageInst.Call("Console.enable")
And catch incoming messages "Console.messageAdded"
I had to change the "Chrome" class a little, adding the sixth parameter to the call constructor, here and there to transfer it further for the nested class "Page", and rename it to "PageBase" and extend him, redefining the constructor and the method "Event"
...
May I ask you to add this opportunity to the future editions of your work? Having a this opportunity would be very convenient.
And sorry for my English. =/
Your work is very good, and so is your English. Thank you for sharing with us!
And now for some hype
-
- Posts: 38
- Joined: 25 Apr 2016, 18:00
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
Oh, it's so great!!!
In a large family AHK it is customary to be generous to knowledge. It's nice to be a part of this!
Look forward to continuing!
In a large family AHK it is customary to be generous to knowledge. It's nice to be a part of this!
Look forward to continuing!
- yawikflame
- Posts: 21
- Joined: 02 Jan 2017, 06:19
- Contact:
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
Awesome stuff GeekDude!
small question,
is there a way to retrieve the source code of the current working tab?
thank you so much for this great code!!
cheers
yawik
small question,
is there a way to retrieve the source code of the current working tab?
thank you so much for this great code!!
cheers
yawik
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
PageInst.Evaluate("document.getElementsByTagName('html')[0].textContent;").Valueyawikflame wrote:is there a way to retrieve the source code of the current working tab?
-
- Posts: 38
- Joined: 25 Apr 2016, 18:00
Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!
If under the control of the script will be several pages and all of them will be send a messages, then we need implement the concept of the event stack for make a manage all of them without transmittance. For example:
While MsgBox is showed, we can more press Ctrl+LButton and all other coordinates of Click, will be showed later.
I suggest adding event processing by default in Class Page and add external simple class EventStacker, who will do this:
This will be convenient for the developer in that it will not interfere in the code of Class Chrome. For change main behavior, it will only be necessary to make changes to his method PopEvent(), and the Class EventStacker itself can be easily put in the body of the main script, for easy editing. Then, you just need to make a couple of simple changes, first, in the constructor Class Page, for example:
And the second, in the condition else if (EventName == "Message") of the method Event(), of the same class. For example:
Thus, by default, if the console has been enabled, all events from the browser console at the level "info", will be added at event stack.
It would be great to see a something similar.
Edit:
More precisely not a stack, but a queue.
Code: Select all
s := New Stacker()
Esc::
ExitApp
Class Stacker {
__New() {
this.eventStack := []
modify := "^" ; Ctrl
key := "LButton" ; LMouseButton
forState := "Up" ; For release
this.mPos := ""
%key%%forState% := ObjBindMethod(this, "_InsertEvent")
this.ePop := ObjBindMethod(this, "_PopEvent")
Hotkey,% Format("~*{1}{2} {3}",modify,key,forState),% %key%%forState%
}
_InsertEvent() {
MouseGetPos, xx, yy
this.eventStack.InsertAt(1, [xx, yy])
ePop := this.ePop
SetTimer,% ePop, -10
}
_PopEvent() {
while (this.eventStack.Length()) {
fifo := this.eventStack.Pop()
MsgBox,,Title,% Format("x = '{1}' <|> y = '{2}'", fifo[1], fifo[2])
}
}
}
I suggest adding event processing by default in Class Page and add external simple class EventStacker, who will do this:
Code: Select all
Class EventStacker {
__New() {
this.eventStack := []
this.ePop := ObjBindMethod(this, "PopEvent")
}
InsertEvent(msg) {
this.eventStack.InsertAt(1, msg)
ePop := this.ePop
SetTimer,% ePop, -10
}
PopEvent() {
while (this.eventStack.Length()) {
fifo := this.eventStack.Pop()
MsgBox,,Title,% fifo
}
}
}
Code: Select all
this.e := New EventStacker()
Code: Select all
else if (EventName == "Message")
{
data := Chrome.Jxon_Load(Event.data)
if (data.method == "Console.messageAdded" && (msg := data.params.message).level == "info")
this.e.InsertEvent(msg.text)
if this.responses.HasKey(data.ID)
this.responses[data.ID] := data
}
It would be great to see a something similar.
Edit:
More precisely not a stack, but a queue.
Return to “Scripts and Functions (v1)”
Who is online
Users browsing this forum: No registered users and 69 guests