2

How do you get the process ID (PID) of the Chrome/Chromium or Firefox browser launched by Selenium? I'm looking for a solution that also works when the browser is launched in a Selenium Grid.

Using driver.service.process.pid is not possible with Selenium Grid.

tsorn
  • 3,365
  • 1
  • 29
  • 48

2 Answers2

3

To retrieve the process ID (PID) of the Chrome/Firefox browsers launched by Selenium you can use the following solutions:

Firefox

  • Code Block:

    from selenium import webdriver
    
    driver = webdriver.Firefox(executable_path=r'C:\WebDrivers\geckodriver.exe')
    my_dict = driver.capabilities
    print("PID of the browser process is: " + str(my_dict['moz:processID']))
    
  • Console Output:

    PID of the browser process is: 2172
    

Chrome

  • Code Block:

    from selenium import webdriver
    from contextlib import suppress
    import psutil
    
    driver = webdriver.Chrome(executable_path=r'C:\WebDrivers\chromedriver.exe')
    driver.get('https://www.google.com/')
    for process in psutil.process_iter():
      if process.name() == 'chrome.exe' and '--test-type=webdriver' in process.cmdline():
              with suppress(psutil.NoSuchProcess):
                  print(process.pid)
    driver.quit()
    
  • Console Output:

    12384
    13656
    13800
    

References

You can find a couple of relevant detailed discussions in:

undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
  • The solution for Chrome there does not work if you have more than 1 Chrome instance running – tsorn Nov 17 '20 at 21:07
  • @tsorn Selenium tests are to be executed through a single Chrome instance unless multi-threaded and for each Chrome instance there will be multiple processes involved, the reason you see multiple PIDs. – undetected Selenium Nov 17 '20 at 21:10
2

When launching a browser with Selenium, it creates a new temporary directory for the profile settings (unless otherwise specified). We can use this to identify the specific process(es) that the browser uses:


In [470]: driver.capabilities
Out[470]: 
{'acceptInsecureCerts': False,
 'browserName': 'chrome',
 'browserVersion': '86.0.4240.198',
 'chrome': {'chromedriverVersion': '85.0.4183.87 (cd6713ebf92fa1cacc0f1a598df280093af0c5d7-refs/branch-heads/4183@{#1689})',
  'userDataDir': '/tmp/.com.google.Chrome.nW2W6p'},
 'goog:chromeOptions': {'debuggerAddress': 'localhost:44047'},
...}

This approach works for both Firefox and Chrome, even if you have multiple instances of a browser running, and also if the browsers are launched through Selenium Grid (if the code is run on the node server).

You will need to install psutil:

pip install psutil
import psutil
import re
from typing import List


def pgrep(term, regex=False, full=True) -> List[psutil.Process]:
    """
    If `full`, then `term` is matched against the command line
    the process has been called with,
    else it is only matched against the process name.
    """
    procs = []
    for proc in psutil.process_iter(['pid', 'name', 'username', 'cmdline']):
        if full:
            name = ' '.join(proc.cmdline())
        else:
            name = proc.name()
        try:
            if regex and re.search(term, name):
                procs.append(proc)
            elif term in name:
                procs.append(proc)
        except psutil.NoSuchProcess:
            pass
    return procs


def browser_procs(driver) -> List[psutil.Process]:
    """
    Return the Processes associated with the browser
    (excluding geckodriver/chromedriver)
    """
    if driver.capabilities['browserName'] == 'firefox':
        directory = driver.capabilities['moz:profile']
    else:
        directory = driver.capabilities['chrome']['userDataDir']
    procs = pgrep(directory, full=True)
    procs.sort(key=lambda p: p.pid)
    return procs


def browser_proc(driver) -> psutil.Process:
    """
    Return the main Process of the browser
    (excluding geckodriver/chromedriver)
    """
    procs = browser_procs(driver)
    for proc in procs:
        name = proc.parent().name()
        if 'chromedriver' in name or 'geckodriver' in name:
            return proc
    raise ValueError



def driver_proc(driver) -> psutil.Process:
    """
    Return the Process of the geckodriver/chromedriver
    """
    return browser_proc(driver).parent()

Let's see it in action:

In [436]: driver = Chrome()

In [451]: browser_procs(driver)
Out[468]: 
[psutil.Process(pid=38453, name='chrome', status='sleeping', started='18:45:51'),
 psutil.Process(pid=38462, name='chrome', status='sleeping', started='18:45:51'),
 psutil.Process(pid=38463, name='chrome', status='sleeping', started='18:45:51'),
 psutil.Process(pid=38467, name='chrome', status='sleeping', started='18:45:51'),
 psutil.Process(pid=38486, name='chrome', status='sleeping', started='18:45:52'),
 psutil.Process(pid=38489, name='chrome', status='sleeping', started='18:45:52'),
 psutil.Process(pid=38521, name='chrome', status='sleeping', started='18:45:52'),
 psutil.Process(pid=38522, name='chrome', status='sleeping', started='18:45:52')]

In [471]: p = browser_proc(driver)
In [472]: p.pid
Out[472]: 38453

If you want the PID of the chromedriver or geckodriver, then you can get that process through browser_proc(driver).parent()

tsorn
  • 3,365
  • 1
  • 29
  • 48
  • For PID of the chromedriver it's better to opt for driver.service.process.pid. An additional route can be using the dev port through driver.service.port and finding the chrome. Using the user data dir is pretty good though, well done. – Benoni Nov 18 '20 at 07:48
  • @Benoni Using `driver.service.process.pid` is not possible for selenium grid – tsorn Nov 18 '20 at 08:33