You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm not sure if this is the right place but it might be helpful to others if I share this
Firstly, thanks so much to Gabriel Trigo for this.
I have also used CEF4Delphi and I think one benefit of TSWebDriver4Delphi is how quickly you can get started.
There are some inherent complications in using a webdriver, for example if you want to use multiple automations simultaneously.
The problems I encountered were related to the webdriver constantly using the default installation and profile of Chrome.exe on the system. Thus I spent many, many hours getting a truly independent version of Chrome up and running.
Getting automations to run independently
I was tearing my hair out trying to get each automation to run independently. Pro tip:
Navigate to chrome://version/ to see exactly which chrome.exe you are running and exactly which user profile you are running. This was a life-saver.
I downloaded Chrome Portable.
Next, ignore the file GoogleChromePortable\GoogleChromePortable.exe that it installs; the real file you want is GoogleChromePortable\App\Chrome-bin\chrome.exe
Make sure you download the latest chromedriver for that version and place it in the same folder as chrome.exe. Later we will tell the webdriver.exe to open that chrome.exe (and not the default chrome.exe)
Next, each server will need its own port number.
See the code changes below for how this gets implemented.
We will need to specifiy:
The path to chrome.exe
The path to webdriver.exe
The path to the profiles directory, where it will load the 'default' profile
A port for the server (you can use the default port of 9515 if you have only 1 automation at a time).
Evading bot detection
I was also unsuccessful in fully evading bot detection, despite implementing all the suggestions here
In my application, the bot detection was only a problem at the login page for the particular website I was visiting, so I had to code a solution where when the login page is detected, I close the automation, launch the browser without automation, log in, then close again, re-launch the automation, and continue!
For my logging in, I used TamperMonkey to automatically log in, and then I used my own browser-extension to close the browser after login.
So yes, one of the problems with chromedriver.exe is if you have a bot running an automation, and you try to launch chrome.exe as a human, it will fail bot detection. You have to close the automation.
Here is the code to launch chrome.exe but not as a bot, and wait for it to close. Reminder: if the bot is already running, this will just open another window for the bot! Make sure the bot is closed first
function LaunchUncontrolledBrowser(aURL : string = 'about:blank') : boolean;
var
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
ExecuteFile, Params, CommandLine, UserDataDir, ProfileDir: string;
ExitCode: DWORD;
begin
Result := False;
ExecuteFile := 'C:\xxx\xxx\xx\xxx\GoogleChromePortable\App\Chrome-bin\chrome.exe';
Params := aURL;
UserDataDir := 'C:\xxx\xxx\xxx\xxx\GoogleChromePortable\Data'; //This is not the path to a specific profile, just the containing folder
ProfileDir := 'default'; // Specify the profile directory if needed
CommandLine := Format('"%s" --user-data-dir="%s" --profile-directory="%s" %s', [ExecuteFile, UserDataDir, ProfileDir, Params]);
FillChar(StartupInfo, SizeOf(StartupInfo), 0);
StartupInfo.cb := SizeOf(StartupInfo);
if CreateProcess(nil, PChar(CommandLine), nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo) then
begin
// Wait for the process to finish
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
// Get the exit code
GetExitCodeProcess(ProcessInfo.hProcess, ExitCode);
// Close the process and thread handles
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
Result := True;
end;
end;
My code to solve these problems
function StartWebSession: Boolean;
var
WebDriverPath : string;
port : integer;
ChromePath : string;
ChromeProfilesPath : string;
HomePageURL : string;
HideDriverWindow : boolean;
begin
result := false;
//Initialise
//I put the webdriver in the same folder as the chrome.exe. Make sure it is the right version for the chrome you are using
WebDriverPath := 'C:\xxx\xxx\xxx\xxx\GoogleChromePortable\App\Chrome-bin\webdriver.exe';
//The default port is 9515. If you are running multiple servers / automations, then each one should have its own port
port := 9516
ChromePath :='C:\xxx\xxx\xx\xxx\GoogleChromePortable\App\Chrome-bin\chrome.exe';
//The 'Data' directors can contain multiple profiles. Do not link to the profile itself, just the containing folder
ChromeProfilesPath := 'C:\xxx\xxx\xxx\xxx\GoogleChromePortable\Data';
HideDriverWindow := true;
HomePageURL := 'www.example.com';
//ReportMemoryLeaksOnShutdown := True;
///////////////////
//Create the "Driver" (Server)
///////////////////
FDriver := TTSWebDriver.New.Driver();
//WebDriver
WebDriverPath := StringReplace(WebDriverPath, '\', '/', [rfReplaceAll]);
FDriver.Options.DriverPath(WebDriverPath);
//ChromePath
ChromePath := StringReplace(ChromePath, '\', '/', [rfReplaceAll]);
FDriver.Options.BinaryPath(ChromePath);
//////////////
//Create the ChromeDriver
//////////////
FChromeDriver := FDriver.Browser().Chrome();
FChromeDriver.AddArgument('disable-blink-features=AutomationControlled');
FChromeDriver.AddArgument('excludeSwitches','enable-automation');
FChromeDriver.AddArgument('useAutomationExtension','False');
FChromeDriver.AddArgument('binary', ChromePath);
//If using Headlessmode, which makes the browser completely invisible to the user:
//FChromeDriver.AddArgument('--headless=new');
//User data dir
ChromeProfilesPath := StringReplace(ChromeProfilesPath, '\', '/', [rfReplaceAll]);
FChromeDriver.AddArgument('user-data-dir', ChromeProfilesPath);
//.AddArgument('window-size', '1000,800')
//////////////
//Start the server!
//////////////
//"Driver" (Server) Start
//Here I have modified the 'Start' function to accept these other parameters (see code below)
FDriver.Start(WebDriverPath, port, ChromePath, HideDriverWindow);
//New Session
FChromeDriver.NewSession();
//Set webdriver to undefined
FChromeDriver.ExecuteSyncScript('Object.defineProperty(navigator, ''webdriver'', {get: () => undefined})'); //{"script":"return document.title","parameters":{},"args":[]}
//Network.setUserAgentOverride
//============================
//I was unable to execute 'Network.setUserAgentOverride' despite the right syntax due to the issue:
//The Network.setUserAgentOverride command is part of the Chrome DevTools Protocol and must be executed within a context that supports this protocol.
//Directly embedding this command in JavaScript running in the browser's console will not work, hence the "Network is not defined" error.
//FChromeDriver.ExecuteSyncScript('Network.setUserAgentOverride', '{"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"}');
//Navigate to...
HomePageURL := StringReplace(HomePageURL, '\', '/', [rfReplaceAll]);
FChromeDriver.NavigateTo(HomePageURL);
result := true;
end;
Now modify the wrapper:
In unit TSWebDriver.Interfaces;
change function Start(): ITSWebDriverBase; to: function Start(executable_path : string; port : integer; binary_location : string; hidden: boolean = false): ITSWebDriverBase;
Now modify the unit unit TSWebDriver.Driver;:
change function Start(): ITSWebDriverBase;
to function Start(executable_path : string; port : integer ; binary_location : string; hidden: boolean = false): ITSWebDriverBase;
And make the function look like this:
function TTSWebDriverBase.Start(executable_path : string; port : integer; binary_location : string; hidden: boolean = false): ITSWebDriverBase;
var
CommandLine: string;
begin
FProccessName := ExtractFileName(FSWebDriverBaseOptions.DriverPath);
if port = 0 then
port := 9515;
FPort := port;
if not FileExists(FSWebDriverBaseOptions.DriverPath) then
raise Exception.Create('driver file not exists.' + FSWebDriverBaseOptions.DriverPath);
if Self.IsRunning() or (FProcessInfo.hProcess <> 0) then Exit;
// StartupInfo
FillChar(FStartupInfo, SizeOf(FStartupInfo), 0);
if hidden then
FStartupInfo.wShowWindow := SW_HIDE
else
FStartupInfo.wShowWindow := SW_NORMAL;
FStartupInfo.dwFlags := STARTF_USESHOWWINDOW;
// ProcessInfo
FillChar(FProcessInfo, SizeOf(FProcessInfo), 0);
// Construct the command line with the parameters
CommandLine := Format('%s --port=%d --executable_path="' + executable_path + '" --binary_location="' + binary_location + '"', [FSWebDriverBaseOptions.DriverPath, port]);
//CommandLine := Format('%s --executable_path="' + executable_path + '" --binary_location="' + binary_location + '"', [FSWebDriverBaseOptions.DriverPath]);
if CreateProcess(nil, // lpApplicationName: PChar; // pointer to name of executable module
PChar(CommandLine), // lpCommandLine: PChar; // pointer to command line string
nil, // lpProcessAttributes: PSecurityAttributes; // pointer to process security attributes
nil, // lpThreadAttributes: PSecurityAttributes; // pointer to thread security attributes
False, // bInheritHandles: BOOL; // handle inheritance flag
NORMAL_PRIORITY_CLASS, // dwCreationFlags: DWORD; // creation flags
nil, // lpEnvironment: Pointer; // pointer to new environment block
nil, // lpCurrentDirectory: PChar; // pointer to current directory name
FStartupInfo, // const lpStartupInfo: TStartupInfo; // pointer to STARTUPINFO
FProcessInfo // var lpProcessInformation: TProcessInformation // pointer to PROCESS_INFORMATION
) then
begin
// Process started successfully
end
else
begin
// Handle error
RaiseLastOSError;
end;
end;
Modify unit TSWebDriver.Consts;:
under interface, add this global variable:
var
FPort : integer;
The text was updated successfully, but these errors were encountered:
I'm not sure if this is the right place but it might be helpful to others if I share this
Firstly, thanks so much to Gabriel Trigo for this.
I have also used CEF4Delphi and I think one benefit of TSWebDriver4Delphi is how quickly you can get started.
There are some inherent complications in using a webdriver, for example if you want to use multiple automations simultaneously.
The problems I encountered were related to the webdriver constantly using the default installation and profile of Chrome.exe on the system. Thus I spent many, many hours getting a truly independent version of Chrome up and running.
Getting automations to run independently
I was tearing my hair out trying to get each automation to run independently.
Pro tip:
Navigate to
chrome://version/
to see exactly whichchrome.exe
you are running and exactly whichuser profile
you are running. This was a life-saver.I downloaded Chrome Portable.
Next, ignore the file
GoogleChromePortable\GoogleChromePortable.exe
that it installs; the real file you want isGoogleChromePortable\App\Chrome-bin\chrome.exe
Make sure you download the latest chromedriver for that version and place it in the same folder as chrome.exe. Later we will tell the webdriver.exe to open that chrome.exe (and not the default chrome.exe)
Next, each server will need its own port number.
See the code changes below for how this gets implemented.
We will need to specifiy:
Evading bot detection
I was also unsuccessful in fully evading bot detection, despite implementing all the suggestions here
In my application, the bot detection was only a problem at the login page for the particular website I was visiting, so I had to code a solution where when the login page is detected, I close the automation, launch the browser without automation, log in, then close again, re-launch the automation, and continue!
For my logging in, I used TamperMonkey to automatically log in, and then I used my own browser-extension to close the browser after login.
So yes, one of the problems with chromedriver.exe is if you have a bot running an automation, and you try to launch chrome.exe as a human, it will fail bot detection. You have to close the automation.
Here is the code to launch chrome.exe but not as a bot, and wait for it to close. Reminder: if the bot is already running, this will just open another window for the bot! Make sure the bot is closed first
My code to solve these problems
Now modify the wrapper:
In
unit TSWebDriver.Interfaces;
change
function Start(): ITSWebDriverBase;
to:function Start(executable_path : string; port : integer; binary_location : string; hidden: boolean = false): ITSWebDriverBase;
Now modify the unit
unit TSWebDriver.Driver;
:change
function Start(): ITSWebDriverBase;
to
function Start(executable_path : string; port : integer ; binary_location : string; hidden: boolean = false): ITSWebDriverBase;
And make the function look like this:
Modify
unit TSWebDriver.Consts;
:under interface, add this global variable:
The text was updated successfully, but these errors were encountered: