【问题标题】:Track dead WebDriver instances during parallel task在并行任务期间跟踪失效的 WebDriver 实例
【发布时间】:2012-05-06 19:29:52
【问题描述】:

我发现使用 Selenium WebDriver 运行并行嵌套循环 Web 压力测试时出现了一些死实例怪异现象,简单的例子是,例如,点击 300 个独特页面,每个页面有 100 次展示。

我“成功地”让 4 - 8 个 WebDriver 实例使用 ThreadLocal<FirefoxWebDriver> 将它们隔离在每个任务线程上,并在 ParallelOptions 实例上使用 MaxDegreeOfParallelism 来限制线程。我只对外部循环(页面集合)进行分区和并行化,并在每个分区的“长时间运行任务”方法的开头内的ThreadLocal<> 容器上检查.IsValueCreated。为了便于稍后进行清理,我将每个新实例添加到以线程 ID 为键的 ConcurrentDictionary 中。

无论我使用何种并行化或分区策略,WebDriver 实例偶尔都会执行以下操作之一:

  • 启动但从不显示 URL 或进行展示
  • 启动,运行任意数量的展示次数,然后在某个时候闲置

当其中任何一种发生时,并行循环最终似乎注意到一个线程没有做任何事情,它产生了一个新的分区。如果 n 是允许的线程数,这将导致 n 个生产线程只有大约 50-60% 的时间。

清理最后仍然可以正常工作;可能有 2n 个或更多打开的浏览器,但有效率的和无效率的都会被清理干净。

有没有办法监控这些无用的 WebDriver 实例,并且 a) 立即清除它们,以及 b) 让并行循环立即替换任务段,而不是像现在经常那样滞后几分钟?

【问题讨论】:

  • 这看起来你的一些线程被阻塞了。您是否尝试过调试以查看它们在哪里被阻止?
  • 在实例化IWebDriver 时,Selenium 控制服务器的异步初始化过程似乎有点慢。问题是,new FirefoxDriver() 的调用停止阻塞之后,工作仍在继续。如果您尝试太快地创建其他实例,它们中的大多数将无法连接。由于没有OnReady 事件和IsReady 属性,我在创建每个实例后通过休眠线程几秒钟来解决它。这样做似乎给了我 100% 持久、正常运行的 WebDriver 实例。
  • 只是好奇,您是针对 Selenium Grid 2 执行的吗?
  • 不,我使用的是普通的 ol' webDriver 类。也准备拿 NHtmlUnit 来试驾。

标签: c# webdriver task-parallel-library thread-local


【解决方案1】:

由于没有OnReady 事件和IsReady 属性,我通过在创建每个实例后将线程休眠几秒钟来解决它。这样做似乎给了我 100% 持久、正常运行的 WebDriver 实例。

感谢您的suggestion,我在我的开源项目Webinator 中实现了IsReady 功能。如果需要,可以使用它,或者使用下面列出的代码。

我尝试实例化 25 个实例,并且它们都可以正常工作,所以我现在对算法非常有信心(我利用 HtmlAgilityPack 来查看元素是否存在,但为了简单起见,我将在此处跳过它):

public void WaitForReady(IWebDriver driver)
{
    var js = @"{ var temp=document.createElement('div'); temp.id='browserReady';" +
             @"b=document.getElementsByTagName('body')[0]; b.appendChild(temp); }";
    ((IJavaScriptExecutor)driver).ExecuteScript(js);

    WaitForSuccess(() =>
    {
        IWebElement element = null;
        try
        {
            element = driver.FindElement(By.Id("browserReady"));
        }
        catch
        {
            // element not found
        }

        return element != null;
    },
    timeoutInMilliseconds: 10000);

    js = @"{var temp=document.getElementById('browserReady');" +
         @" temp.parentNode.removeChild(temp);}";
    ((IJavaScriptExecutor)driver).ExecuteScript(js);
}

private bool WaitForSuccess(Func<bool> action, int timeoutInMilliseconds)
{
    if (action == null) return false;

    bool success;
    const int PollRate = 250;
    var maxTries = timeoutInMilliseconds / PollRate;
    int tries = 0;
    do
    {
        success = action();
        tries++;
        if (!success && tries <= maxTries)
        {
            Thread.Sleep(PollRate);
        }
    }
    while (!success && tries < maxTries);
    return success;
}

假设如果浏览器正在响应 javascript 函数并正在查找元素,那么它可能是一个可靠的实例并且可以使用。

【讨论】:

    【解决方案2】:

    我遇到了类似的问题。事实证明,WebDriver 没有找到打开端口的最佳方法。如here 所述,它在端口上获得系统范围的锁定,找到一个打开的端口,然后启动实例。这可能会使您尝试启动端口的其他实例饿死。

    我通过直接在 ThreadLocal&lt;IWebDriver&gt; 的委托中指定一个随机端口号来解决这个问题,如下所示:

            var ports = new List<int>();
            var rand = new Random((int)DateTime.Now.Ticks & 0x0000FFFF);
    
            var driver = new ThreadLocal<IWebDriver>(() =>
            {
                var profile = new FirefoxProfile();
                var port = rand.Next(50) + 7050;
                while(ports.Contains(port) && ports.Count != 50) port = rand.Next(50) + 7050;
                profile.Port = port;
                ports.Add(port);
                return new FirefoxDriver(profile);
            });
    

    这对我来说非常有效,尽管如果您最终使用了未解决的列表中的所有 50 个问题,则会出现问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-18
      • 2014-12-04
      • 2015-11-15
      • 1970-01-01
      相关资源
      最近更新 更多