[Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!

Post your working scripts, libraries and tools
User avatar
JoeWinograd
Posts: 1630
Joined: 10 Feb 2014, 20:00

Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!

18 Jun 2020, 09:59

teadrinker wrote:You can try to increase also this value: const timeout = setTimeout('scrollPage()', 100);.
I increased that Timeout to 1000 and decreased the Sleep time back to 1000...tried it with Eval3...does not work...13 of the 19 images are links, not images. But thanks for the idea...worth a try. Regards, Joe
Tre4shunter
Posts: 73
Joined: 26 Jan 2016, 16:05

Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!

01 Jul 2020, 14:02

Slightly improved code to a previous post i made about finding multiple instance of Chrome Debug sessions in a multi user environment like a Terminal Server:

UPDATED - at G33kDudes suggestion - hardcoding in domain admin credentials is obviously unwise, so updated the PS to use a secure key. There are simple instructions online how to do this. Thanks G33k

Code: Select all

FindInstances()
{
			PS = 
			(
				$password = Get-Content F:\UserFolders\md\MCSTool\Chrome\password.txt | ConvertTo-SecureString -Key (Get-Content F:\UserFolders\md\MCSTool\Chrome\aes.key)
				$Cred = New-Object System.Management.Automation.PsCredential(\"vgesinc\md\", $password)
				start-process -WindowStyle hidden -FilePath "$env:comspec" -ArgumentList \"/c wmic Process where ``\"name = 'chrome.exe'``\" get commandline, processid > $env:TEMP\test.txt\" -Credential $Cred
			)
		
		runwait % "PowerShell.exe -Command & {" PS "}", % A_Temp,"Hide"
		
		FileGetSize, oSize, %A_Temp%\test.txt
		oSize = -1
		While (fSize <> oSize) {
			
			if(fSize > 0)
				oSize := fSize
			sleep, 100
			FileGetSize, fSize, %A_Temp%\test.txt
		}
		
		FileRead, rfile, %A_Temp%\test.txt
		FileDelete, %A_Temp%\test.txt
		
		Needle := "--remote-debugging-port=(\d+)"
		Out := {}, i = 0
		Loop, parse, rFile, `n,`r
		{
			if RegExMatch(A_Loopfield, Needle, Match){
				Out[Match1,i++] := {"cmd":Item.CommandLine,"pid":Item.ProcessId,"port":Match1}
			}
		}
		return, Out
}
Main difference is that the PS is now just a string, rather than an external file being loaded. I had a need for this due to a terminal server application where the regular "FindInstances()" does not return all open chrome processes for all users due to privileges. So, this is just a workaround.

The username/password must be someone with proper privileges so that the PS can execute and read all the chrome processes correctly.

If anyone has a better way to handle the temporary .txt file creation - id love to hear it!

Thanks,

-Tre4
Tre4shunter
Posts: 73
Joined: 26 Jan 2016, 16:05

Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!

02 Jul 2020, 15:31

Yet another Iteration...

Realized i can just let PS handle the file creation...so no need to loop over the file checking for filesize. Also - like in my edited post above, no longer have the password credential hardcoded in plain text.

Code: Select all

	FindInstances()
	{
			PS = 
			(
				$password = Get-Content F:\UserFolders\md\MCSTool\Chrome\password.txt | ConvertTo-SecureString -Key (Get-Content F:\UserFolders\md\MCSTool\Chrome\aes.key)
				$Cred = New-Object System.Management.Automation.PsCredential(\"vgesinc\md\", $password)
				start-process -WindowStyle hidden -Wait -FilePath "$env:comspec" -ArgumentList \"/c wmic Process where ``\"name = 'chrome.exe'``\" get commandline, processid\" -RedirectStandardOutput $env:TEMP\test.txt -Credential $Cred
			)			
			runwait, % "PowerShell.exe -Command & {" PS "}", % A_Temp , "Hide"
			
			FileRead, rfile, %A_Temp%\test.txt
			FileDelete, %A_Temp%\test.txt
			
			Needle := "--remote-debugging-port=(\d+)"
			Out := {}, i = 0
			Loop, parse, rFile, `n,`r
			{
				if RegExMatch(A_Loopfield, Needle, Match){
					Out[Match1,i++] := {"cmd":Item.CommandLine,"pid":Item.ProcessId,"port":Match1}
				}
			}
			return, Out
	}
Thanks,

Tre4
Newer2AHK
Posts: 59
Joined: 24 Jun 2020, 10:32

Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!

15 Jul 2020, 22:46

Can I do this in Firefox?

I posted a question in the help forum asking about how to enter text in a text box in a browser running in the background. A response from @gregster cited Chrome.ahk as a possible solution. I'm working in Firefox, and I'm not excited about installing Chrome, but I can do that if necessary.

Gregster posted sample code that used Chrome.ahk to execute a javascript command that sets the value of a document element in a webpage. Can I use AHK to do that in Firefox?

There's some detail about what I've been trying to figure this out in my reply to gregster in the other post.
gregster
Posts: 5381
Joined: 30 Sep 2013, 06:48

Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!

16 Jul 2020, 00:25

Newer2AHK wrote:
15 Jul 2020, 22:46
Can I do this in Firefox?
As Geekdude explained in this post, Chrome.ahk can be generalized to automate Firefox (but currently only the "nightly builds", which is not the main release channel).

Btw I don't think that Firefox is Chromium-based (in contrast to the new Edge browser or Opera), but it seems that its developers started to support a subset of the Chrome debug protocol, probably to help standardize browser debugging across different platforms. That said, not even all Chromium-based browsers support that protocol (Brave's and Vivaldi's developers don't seem to care for it - at least, the last time I looked).
Newer2AHK
Posts: 59
Joined: 24 Jun 2020, 10:32

Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!

16 Jul 2020, 06:01

Thanks for that link, gregster. The problem there is that instead of installing Chrome, I have to install a different implementation of Firefox, and possibly one that is not fully tested, so maybe not a great idea.

Something I would like to know is, if all I want to do is to change the value of a document element on the webpage, do I need all the capability of Chrome.ahk in order to do that, or is there a way to do it directly in AHK?
malcev
Posts: 802
Joined: 12 Aug 2014, 12:37

Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!

16 Jul 2020, 07:53

Use WebDriver api:
https://www.autohotkey.com/boards/viewtopic.php?f=6&t=69390
Or You can run JS from address bar.
Or You can set value with IAccessible or IUIAutomation help.
Newer2AHK
Posts: 59
Joined: 24 Jun 2020, 10:32

Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!

17 Jul 2020, 15:55

I don't want to hijack this topic away from its intended focus on Chrome.ahk, so I've responded further to @gregster and @malcev back in the other topic on how to send text to an inactive window. I'd really appreciate further input there if people know how to do that. In the end, that post concludes that I'm going to have to start using Chrome instead of Firefox and learn Chrome.ahk in order to do it, but I'm really surprised that there doesn't seem to be a simpler way, but I'd love to find out that I'm wrong and that there is a simpler way.
Newer2AHK
Posts: 59
Joined: 24 Jun 2020, 10:32

Documentation of Chrome.ahk

19 Jul 2020, 09:39

To help myself understand what I'm working with, I compiled the following notes, adapted from the documentation of the code in the Chrome.ahk script. Perhaps this may be helpful to other newbies. If @GeekDude or other experts here let me know of any errors or suggested improvements, I'll edit accordingly.

If this information exists somewhere else besides in the code, let me know where, and I'll delete this post.

-------------------------------------------------------------------------------------

Documentation of Chrome.ahk, version 1.2
Compiled from documentation in the script file, "Chrome.ahk".

The script consists of one definition:
  • Class: Chrome
    The class consists of one variable assignment (DebugPort) and definitions of 13 methods and one class.
    • Methods:
      • __New() -- Called by constructor. Parameters:
        • ProfilePath: Path to the user profile directory to use. Will use the standard if left blank.
        • URLs: The page or array of pages 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
      • CliEscape() -- Escape a string in a manner suitable for command line parameters.
      • FindInstances() -- Find instances of Chrome in debug mode and the ports they're running on. If no instances are found, return a false value. If one or more instances are found, return an associative array where the keys are the ports, and the values are the full command line texts used to start the processes.
        Example usage:
        • Open Chrome on a different port if an instance of Chrome is already open on the port you wanted to use.

          Code: Select all

          ; If the wanted port is taken, use the largest taken port plus one
          DebugPort := 9222
          if (Chromes := Chrome.FindInstances()).HasKey(DebugPort)
              DebugPort := Chromes.MaxIndex() + 1
          ChromeInst := new Chrome(ProfilePath,,,, DebugPort)
          					
        • Scan for running instances and attach to one instead of starting a new instance.

          Code: Select all

          if (Chromes := Chrome.FindInstances())
              ChromeInst := {"base": Chrome, "DebugPort": Chromes.MinIndex()}
            else
              ChromeInst := new Chrome(ProfilePath)
          					
      • GetPageList() -- Query Chrome for a list of pages that expose a debug interface. In addition to standard tabs, these include pages such as extension configuration pages.
      • GetPageBy() -- Return 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.
      • GetPageByURL() (Shorthand for GetPageBy("url", Value, "startswith") )
      • GetPageByTitle() (Shorthand for GetPageBy("title", Value, "startswith") )
      • GetPage() (Shorthand for GetPageBy("type", Type, "exact") )
        The default type to search for is "page", which is the visible area of a normal Chrome tab.
      • Kill() -- End Chrome by terminating the process.
      • Jxon_Load() (No documentation. Called by .GetPageList() and .Event() .)
      • Jxon_Dump() (No documentation. Called by .Call() and .Evaluate() .)
      • Jxon_True() (No documentation. Called by .Evaluate() .)
      • Jxon_False() (No documentation. Called by .Evaluate() .)
    • Class: Page -- Connects to the debug interface of a page given its WebSocket URL.
      The class consists of definitions of six methods and one class.
      • Methods:
        • __New() -- Called by constructor. Parameters:
          • wsurl: The desired page's WebSocket URL
          • fnCallback: A function to be called whenever message is received
        • Call() -- Call the specified endpoint and provide it with the given parameters.
        • Evaluate() -- 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();")
        • WaitForLoad() -- Wait for the page's readyState to match the DesiredState.
        • 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.
        • Event() (Internal function triggered when the script receives a message on the WebSocket connected to the page.)
      • Class: WebSocket (No documentation.)
        The class consists of definitions of five methods.
        • Methods:
          • __New() -- Called by constructor. Parameter: WS_URL
          • _Event() -- Called by the JS in response to WS events.
          • Send() -- Send data through the WebSocket.
          • Close() -- Close the WebSocket connection.
          • Disconnect() -- Close and delete the WebSocket, removing references so the class can be garbage collected.
Newer2AHK
Posts: 59
Joined: 24 Jun 2020, 10:32

How to run an onClick function?

24 Jul 2020, 09:30

Okay, I've got Chrome and Chrome.ahk up and working. Looking good.

So now, when I need to put text in a text box on a webpage, instead of simulating a click in the box and keystrokes, I just set the value of the text box with JavaScript. Very cool.

But now, what about this: A page I'm working with has two buttons, one to search by city and one by zip code. I need to set the value of either the city or zip code to search for, and then do the equivalent of clicking the corresponding button. Problem is, the buttons don't have IDs, so I can't use "getElementById" to determine which one to execute .click() on. Is there a way to call the appropriate onclick function directly without simulating a button click? Or another way to do this? I suppose this may be more a question on Javascript than Chrome.ahk, but does someone here know how to do this?

Here is the code from the webpage:

Code: Select all

<h3>City Search</h3>
<div style="padding-left: 10px;">
    City:<br />
    <input type="text" class="notranslate" id="OCity" />
    <input type="button" value="Search" onclick="doCSearch()" />
</div>
<h3>ZIP Code Search</h3>
<div style="padding-left: 10px;">
    ZIP Code*:<br />
    <input type="text" id="OZipCode" maxlength="5" style="width: 65px" />
    <input type="button" value="Search" onclick="doZSearch()" />
</div>
GeekDude
Posts: 887
Joined: 02 Oct 2013, 22:13

Re: [Library] Chrome.ahk - Automate Google Chrome using native AutoHotkey. No Selenium!

24 Jul 2020, 09:56

This may be a good opportunity to use the relatively uncommon CSS selector + which is used for selecting adjacent elements. document.querySelector("#OCity+input[type=button]").click() may work for you here.
Newer2AHK
Posts: 59
Joined: 24 Jun 2020, 10:32

Warning on URLString

28 Jul 2020, 19:31

When I run a script that #includes Chrome.ahk with the #warn directive, I get the following warning:

Code: Select all

Warning in #include file "...\Chrome.ahk":
     This variable has not been assigned a value.

Specifically: URLString  (a local variable)

	Line#
...
--->	093: URLString .= " " this.CliEscape(URL)  
...
I have addressed this issue by inserting, two lines above that, the line:

Code: Select all

URLString := ""
Right?
gregster
Posts: 5381
Joined: 30 Sep 2013, 06:48

Re: Warning on URLString

28 Jul 2020, 19:33

Newer2AHK wrote:
28 Jul 2020, 19:31
Right?
Sure.
Newer2AHK
Posts: 59
Joined: 24 Jun 2020, 10:32

Use page.evaluate() to get element value?

29 Jul 2020, 11:59

I learned above that I can use Javascript in page.evaluate() to set the value of a webpage element. Now I'm trying to do the reverse of that. In Chrome.ahk, the code for page.evaluate() says that it returns "response.result", which it appears is supposed to be whatever the Javascript command returns. So with the following code:

Code: Select all

#NoEnv
#include Chrome.ahk
oChrome := new Chrome()
SetTitleMatchMode 2
WinWait, - Google Chrome
if not(oWebpage := oChrome.GetPage())		 
   {MsgBox, Could not retrieve page!
     oChrome.kill()
     }
  else
     oWebpage.WaitForLoad()
oWebpage.call("Page.navigate", {"url": "https://82187.csb.app/"})
oWebpage.WaitForLoad()
oWebpage.evaluate("document.getElementById('OZipCode').value = 11111") 
sZipSet := oWebpage.evaluate("document.getElementById('OZipCode').value") 
MsgBox % "Zip set to: """ . sZipSet . """"
return
I expect the message box to say:

Code: Select all

Zip set to: "11111"
but it says:

Code: Select all

Zip set to: ""
However, if I open the DevTools console and enter:

Code: Select all

document.getElementById('OZipCode').value
it replies with:

Code: Select all

"11111"
So the Javascript command is doing what I expect, but page.evaluate() is not returning its result. What am I doing wrong?
Newer2AHK
Posts: 59
Joined: 24 Jun 2020, 10:32

Re: Use page.evaluate() to get element value?

29 Jul 2020, 12:12

Newer2AHK wrote:
29 Jul 2020, 11:59
What am I doing wrong?
Oops. In the debugger, I looked at sZipSet, and it told me it was an object. So I suffixed ".value" on page.evaluate(), so that line 15 of the above code became:

Code: Select all

sZipSet := oWebpage.evaluate("document.getElementById('OZipCode').value").value
With that, the message box gave the right message.
Newer2AHK
Posts: 59
Joined: 24 Jun 2020, 10:32

How to do a find text in a Chrome window in the background?

29 Jul 2020, 17:45

How do I open the find box in a Chrome window in the background and do a search for text?

Manually, I can do this with Ctrl-F. If the window is active, I can send that with the "send" command. But that doesn't work, and neither does ControlSend, when the window is in the background.

The find box is not a document element, so I can't access it that way. Is it a window element?

I figure there's a way to get Chrome.ahk to do this, but I haven't been able to find it.
Newer2AHK
Posts: 59
Joined: 24 Jun 2020, 10:32

Re: How to do a find text in a Chrome window in the background?

31 Jul 2020, 15:57

From experimenting, I have a partial answer to my previous question. I have not found out how to bring up the "Find" box, but I have succeeded in (a) simulating the behavior of the "Find" box, and (b) bringing up a dialog box that is in the browser window and is not part of the webpage (i.e., not part of "window.document"). These two things are demonstrated in the following script:

Code: Select all

#NoEnv

#include Chrome.ahk
oChrome := new Chrome()
SetTitleMatchMode, 2
WinWait, - Google Chrome
if not(oWebpage := oChrome.GetPage())		 
   {MsgBox, Could not retrieve page!
     oChrome.kill()
     }
  else
     oWebpage.WaitForLoad()
oWebpage.call("Page.navigate", {"url": "https://www.google.com/search?q=hello"})
oWebpage.WaitForLoad()

oWebpage.evaluate("window.window.find('hello')")
MsgBox First execution of "window.window.find('hello')" has no observed effect.
oWebpage.evaluate("window.window.find('hello')")
MsgBox Executing it again has highlighted the first occurrence of "hello" on the webpage.
oWebpage.evaluate("window.window.find('hello')")
MsgBox Executing it again has highlighted the next occurrence. So this is simulating the behavior of the browser's "Find" box.

MsgBox Next, I'll demonstrate popping up a dialog box in the browser window, not in the webpage.
sResponse := oWebpage.evaluate("window.window.prompt('Type something and click Ok.')").value
MsgBox % "You typed: """ . sResponse . """."

return
I'd still be interested in knowing how to bring up the "Find" box if someone knows.

In DevTools, the tab "Elements" maps out the entire structure of the webpage and all its elements in the form of the HTML code in a hierarchical structure. This is the "document", which is an element of "window". It would be interesting to know if there's a way to get an analogous map of "window", with all of its elements, of which "document" would be one element among many others. I've been searching and haven't found anything like that.
Newer2AHK
Posts: 59
Joined: 24 Jun 2020, 10:32

Warning on %value%

01 Aug 2020, 23:12

I'm getting another warning on Chrome.ahk. The previous one was happening all the time, but this one just started, so I don't know if there's something in my code that has caused it to occur, but the code in Chrome.ahk does seem to be problematic. The warning is:

Code: Select all

<stderr>:
Warning in #include file "Chrome.ahk":
     This variable has not been assigned a value.

Specifically: value  (a local variable)

	Line#
...
	538: if (val == "true" || val == "false")  
--->	539: val := %value% + 0
...
Those lines and their adjacent comments are:

Code: Select all

; 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)
This code is in the method Jxon_Load(), and the variable "value" does not occur anywhere else in that method. There is a parameter "Value" in the methods GetPageBy*(), but those parameters don't seem to be accessible in this method. So this does seem to be an error. I tried changing "%value%" to "null" and to "%null%", but those still gave the same warning. I then changed "%value%" to " "" " (i.e., the empty string). After that, the warning ceased, but I worry about whether this is the right correction or might cause some other problem later. Could someone please say if what I've done is right?
gregster
Posts: 5381
Joined: 30 Sep 2013, 06:48

Re: Warning on %value%

02 Aug 2020, 04:23

Newer2AHK wrote:
01 Aug 2020, 23:12
This code is in the method Jxon_Load(), and the variable "value" does not occur anywhere else in that method. There is a parameter "Value" in the methods GetPageBy*(), but those parameters don't seem to be accessible in this method. So this does seem to be an error. I tried changing "%value%" to "null" and to "%null%", but those still gave the same warning. I then changed "%value%" to " "" " (i.e., the empty string). After that, the warning ceased, but I worry about whether this is the right correction or might cause some other problem later. Could someone please say if what I've done is right?
Afaik, that's a known bug/typo in Coco's Jxon_Load() function, which is used here - and should read val := %val% + 0 (or an equivalent).
Adding 0 here just makes sure that no string, but a number, is used for the boolean variable.

Return to “Scripts and Functions”

Who is online

Users browsing this forum: c7aesa7r, Eik and 20 guests