如何确定浏览器何时关闭?

Sha*_*ntu 5 java selenium selenium-webdriver

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

Selenium 似乎不支持浏览器本身的任何事件侦听器。当关闭浏览器driver.quit()不叫 和不能使用的驱动器保持其抛出各种异常。无法知道浏览器何时关闭,我无法创建新的驱动程序实例。

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

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

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

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

Sha*_*ntu 4

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

  1. tasklist在启动 WebDriver 之前收集所有浏览器进程 ID(这可以使用诸如或 powershell 之类的实用程序来完成Get-Process)。如果您确定在启动应用程序之前不会运行浏览器进程,则可以省略此步骤。
  2. 初始化驱动程序。
  3. 再次收集所有浏览器进程,然后取两者之间的差异。
  4. 使用 LittlePanda 注释中链接的代码作为基础,我们可以在新线程上创建一个观察者服务。当所有进程关闭时,这将提醒所有侦听器。
  5. 该方法允许我们从 pidKernel32.INSTANCE.OpenProcess创建对象。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;
}
Run Code Online (Sandbox Code Playgroud)

以及观察者的代码:

/**
* 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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