如何判断浏览器何时关闭?

How to determine when the browser is closed?

我正在使用 Selenium Webdriver 自动化网站(填写表格和点击)以节省我的用户的时间。不过我遇到了一个恼人的问题:

Selenium does not seem to support any event listeners for the browsers themselves. When the browser is closed driver.quit() is not called and an unusable driver remains which throws various exceptions. Having no way to know when the browser is closed, I cannot create a new driver instance.

我需要的是在浏览器关闭时通知我的程序的某种方式。

用户可能出于任何原因关闭浏览器,这会破坏我的程序。如果不重新启动应用程序,用户将无法再次 运行 自动化任务。知道浏览器何时关闭将允许我调用 driver.quit() 并在用户想要再次 运行 时创建一个新的驱动程序实例。

当浏览器死机时,抛出的错误在浏览器之间并不统一,这一事实使问题变得更加复杂。使用 Firefox 我可能会得到一个 UnreachableBrowserException,带有 Chrome NullPointer 和 WebDriverExceptions。

澄清一下,我知道如何关闭驱动程序和浏览器,但我不知道它们何时被外部源关闭。这可以在 Selenium 2 中以跨浏览器的方式完成(如果是,如何)或者我是否需要找到另一种方式(如另一个库)来观看浏览器 window?

我已经在 J​​NA (Java Native Access) 3.4 的帮助下解决了这个问题,它已经包含在 Selenium 中。我的目标平台仅为 Windows,但实现跨平台应该不需要太多工作。我必须执行以下操作:

  1. 在启动 WebDriver 之前收集所有浏览器进程 ID(这可以使用 tasklist 或 powershell Get-Process 等实用程序来完成)。如果您确定在启动应用程序之前不会有浏览器进程 运行,则可以省略此步骤。
  2. 初始化驱动程序。
  3. 再次收集所有浏览器进程,然后取两者的差值。
  4. 使用LittlePanda 评论中链接的代码作为基础,我们可以在新线程上创建观察者服务。这将在所有进程关闭时提醒所有侦听器。
  5. Kernel32.INSTANCE.OpenProcess 方法允许我们从 pids 创建 HANDLE 对象。
  6. 有了这些句柄,我们可以让线程等待,直到所有进程都用 Kernel32.INSTANCE.WaitForMultipleObjects 方法发出信号。

这是我使用的代码,可能会对其他人有所帮助:

public void startAutomation() throws IOException { 
        Set<Integer> pidsBefore = getBrowserPIDs(browserType);
        automator.initDriver(browserType); //calls new ChromeDriver() for example
        Set<Integer> pidsAfter = getBrowserPIDs(browserType);
        pidsAfter.removeAll(pidsBefore);

        ProcessGroupExitWatcher watcher = new ProcessGroupExitWatcher(pidsAfter);
        watcher.addProcessExitListener(new ProcessExitListener() {
            @Override
            public void processFinished() {
                if (automator != null) {
                    automator.closeDriver(); //calls driver.quit()
                    automator = null;
                }
            }
        });
        watcher.start();
        //do webdriver stuff
}

private Set<Integer> getBrowserPIDs(String browserType) throws IOException {
    Set<Integer> processIds = new HashSet<Integer>();

    //powershell was convenient, tasklist is probably safer but requires more parsing
    String cmd = "powershell get-process " + browserType + " | foreach { $_.id }";
    Process processes = Runtime.getRuntime().exec(cmd);
    processes.getOutputStream().close(); //otherwise powershell hangs
    BufferedReader input = new BufferedReader(new InputStreamReader(processes.getInputStream()));
    String line;

    while ((line = input.readLine()) != null) {
        processIds.add(Integer.parseInt(line));
    }
    input.close();

    return processIds;
}

观察者的代码:

/**
* Takes a <code>Set</code> of Process IDs and notifies all listeners when all
* of them have exited.<br>
*/
public class ProcessGroupExitWatcher extends Thread {
    private List<HANDLE> processHandles;
    private List<ProcessExitListener> listeners = new ArrayList<ProcessExitListener>();

    /**
     * Initializes the object and takes a set of pids and creates a list of
     * <CODE>HANDLE</CODE>s from them.
     *
     * @param processIds
     *           process id numbers of the processes to watch.
     * @see HANDLE
     */
    public ProcessGroupExitWatcher(Set<Integer> processIds) {
        processHandles = new ArrayList<HANDLE>(processIds.size());
        //create Handles from the process ids
        for (Integer pid : processIds) {
            processHandles.add(Kernel32.INSTANCE.OpenProcess(Kernel32.SYNCHRONIZE, false, pid)); //synchronize must be used
        }
    }

    public void run() {
        //blocks the thread until all handles are signaled
        Kernel32.INSTANCE.WaitForMultipleObjects(processHandles.size(), processHandles.toArray(new HANDLE[processHandles.size()]), true,
            Kernel32.INFINITE);
       for (ProcessExitListener listener : listeners) {
            listener.processFinished();
       }
    }

    /**
     * Adds the listener to the list of listeners who will be notified when all
     * processes have exited.
     *
     * @param listener
     */
     public void addProcessExitListener(ProcessExitListener listener) {
         listeners.add(listener);
     }

    /**
     * Removes the listener.
     * 
     * @param listener
     */
    public void removeProcessExitListener(ProcessExitListener listener) {
         listeners.remove(listener);
    }
}

注意:当以这种方式使用 powershell 时,将执行用户的配置文件脚本。这会产生破坏上述代码的意外输出。因此,更推荐使用任务列表。