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

时间:2015-04-07 12:14:22

标签: java selenium selenium-webdriver

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

  

Selenium似乎不支持浏览器的任何事件侦听器   他们自己。关闭浏览器时driver.quit() 未被调用   还有一个无法使用的驱动程序,它会抛出各种异常。有   无法知道浏览器何时关闭,我无法创建新的   驱动程序实例。

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

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

问题进一步复杂化的事实是,当浏览器死亡时,抛出的错误在浏览器中并不统一。使用Firefox,我可能会使用Chrome NullPointer和WebDriverExceptions获得UnreachableBrowserException。

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

1 个答案:

答案 0 :(得分:4)

我已经在Selenium中已经包含的JNA(Java Native Access)3.4的帮助下解决了这个问题。我的目标平台仅限Windows,但制作这种跨平台并不需要太多工作。我必须做以下事情:

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

  7. 这是我使用的代码,以便它可以帮助其他人:

    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时,会执行用户的配置文件脚本。这可能会产生意外的输出,从而破坏了上述代码。因此,建议使用任务列表。