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

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

Selenium似乎不支持浏览器本身的任何事件监听器。 当浏览器关闭时, 不会调用 driver.quit()并且会出现一个不可用的驱动程序,它会抛出各种exception。 无法知道浏览器何时关闭,我无法创建新的驱动程序实例。

我需要的是在浏览器关闭时通知我的程序的一些方法。

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

当浏览器死亡时,抛出的错误在浏览器中并不统一,这个问题变得更加复杂。 使用Firefox,我可能会使用Chrome NullPointer和WebDriverExceptions获得UnreachableBrowserException。

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

我已经在Selenium中已经包含的JNA(Java Native Access)3.4的帮助下解决了这个问题。 我的目标平台仅限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 pidsBefore = getBrowserPIDs(browserType); automator.initDriver(browserType); //calls new ChromeDriver() for example Set 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 getBrowserPIDs(String browserType) throws IOException { Set processIds = new HashSet(); //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 Set of Process IDs and notifies all listeners when all * of them have exited.
*/ public class ProcessGroupExitWatcher extends Thread { private List processHandles; private List listeners = new ArrayList(); /** * Initializes the object and takes a set of pids and creates a list of * HANDLEs from them. * * @param processIds * process id numbers of the processes to watch. * @see HANDLE */ public ProcessGroupExitWatcher(Set processIds) { processHandles = new ArrayList(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时,将执行用户的配置文件脚本。 这可能会产生意外的输出,从而破坏了上述代码。 因此,建议使用任务列表。