关于Selenium WebDriverWait的适当性

我对Selenium WebDriver的了解越近,我就越发疑问为什么要以这种方式而不是其他方式实现此功能。 Alexey Barantsev在他的演讲“ Selenium WebDriver中的问题”中阐明了实现此自动化工具的精妙之处,并区分了“错误”和“功能”。 在视频中,您会发现很多有趣的东西,但阴影中仍然有一些点(至少对我而言)。

在本文中,我想讨论使用WebDriverWait类及其主要的直到方法实现的,用于等待页面上事件的常用工具。 我想知道是否完全需要WebDriverWait,并且可以拒绝它吗?

尽管我认为此类的实现逻辑对于其他编程语言不会有任何不同,但是将在C#上下文中提出想法。

创建WebDriverWait实例时,驱动程序实例将传递到构造函数,该构造函数存储在输入输入字段中。 直到方法使用一个委托,其输入参数应为IWebDriver,该输入的输入是一个实例。

让我们看一下直到方法的源代码。 他的逻辑主干是一个无限循环,有两个条件可以退出该循环:所需事件或超时的开始。 其他“好东西”将忽略预定义的异常,并且如果bes不充当TResult,则返回对象(稍后会详细介绍)。

我看到的第一个限制是,我们始终完全需要IWebDriver的实例,尽管在Never方法中(准确地说,作为条件的输入参数),我们可以完全管理ISearchContext。 的确,在大多数情况下,我们希望元素的某些值或其属性发生变化,然后使用FindElement进行搜索。

我敢说,使用ISearchContext会更具逻辑性,因为客户端代码(类)不仅是页面对象,在搜索子对象时,它是从页面根目录中驱除的。 有时,这是一个描述复合元素的类,该复合元素的根是页面的另一个元素,而不是页面本身。 SelectElement就是一个例子,它在构造函数中接受对父IWebElement的引用。

让我们回到初始化WebDriverWait的问题。 此操作需要驱动程序的实例。 即 我们总是以一种或另一种方式需要从客户端代码外部抛出IWebDriver实例,即使它是某种已经接受“父”元素的复合元素的类(有关SelectElement的示例)也是如此。 在我看来,这是不必要的。

当然,我们可以类比地声明一个类
SearchContextWait : DefaultWait<ISearchContext> 
但是,我们不要着急。 我们不需要他。

让我们看看如何使用传递给condition的驱动程序实例。 通常看起来像这样:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); wait.Until( d => d.FindElements(By.XPath("locator")).Count > 0 ); 

出现问题了,如果条件始终可以从客户端代码获得,为什么必须有驱动程序的“本地”版本? 而且,这与之前通过构造函数传递的实例相同。 即 代码可能看起来像这样:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); wait.Until( d => Driver.FindElements(By.XPath("locator")).Count > 0 ); 

甚至西蒙·斯图尔特(Simon Stewart)在讲话中也采用了这种方法。

图片

他没有写“ d-> d。”,而是写了“ d-> driver”。 传递给该方法的驱动程序实例将被忽略。 但是有必要进行传输,因为方法签名需要它!

为什么要在方法条件内传递驱动程序? 可以隔离此方法中的搜索,如ExpectedConditions中所实现的那样。 看一下TextToBePresentInElement方法的实现。 或VisibilityOfAllElementsLocatedBy 。 或TextToBePresentInElementValue 。 转移的驱动程序甚至没有在其中使用!

因此,首先想到的是,我们不需要驱动程序接受的带有委托参数的直到方法。

现在让我们弄清楚“直到”方法是否需要返回值? 如果bool充当TResult,则否,则没有必要。 的确, 如果成功,您将获得成功;如果失败,您将获得TimeoutException。 这种行为的信息内容是什么?

但是,如果对象是TResult,该怎么办? 假定以下构造:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); wait.IgnoreExceptionTypes(typeof(NoSuchElementException)); var element = wait.Until(d => d.FindElement(By.XPath("locator"))); 

即 我们不仅要等待元素的出现,还要使用它(如果我们已经等待了),从而删除了对DOM的一个不必要的调用。 好啊

让我们仔细看看这三行代码。 在Never方法的实现内部,归结为一种相似性(条件代码)

 try { FindElement } catch (NoSuchElementException) {} 

即 每次在元素出现在DOM中之前都会引发一个异常。 由于异常的产生是相当昂贵的事件,因此我希望避免使用异常,特别是在不难的地方。 我们可以如下重写代码:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); var elements = wait.Until(d => d.FindElements(By.XPath("locator"))); 

即 我们使用FindElements,它不会引发异常。 等等,这种设计会等待元素的出现吗? 不行 因为,如果您查看源代码 ,则当条件返回非null时,无限循环立即完成。 并且在失败的情况下,FindElements返回一个空集合,但是无论如何都不返回null。 即 对于项目列表,使用直到没有意义。

好的,清单很清楚。 但是,仍然如何返回找到的元素而不抛出异常? 代码可能看起来像这样:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); var element = wait.Until(d => d.FindElements(By.XPath("locator")).FirstOrDefault()); 

在这种情况下,在循环的每次迭代中,我们不仅会获取IWebElement列表(可能为空),而且还会尝试从中提取第一个元素。 如果元素仍未显示在页面上,我们将获得null(对象的默认值)并继续进行循环的下一个迭代。 如果找到了元素,我们将退出该方法,并将使用返回值初始化element变量。

但是,第二个想法是,在大多数情况下,不使用直到方法的返回值。

传递的值是不必要的,不使用返回值。 直到的用途是什么? 仅在周期和频率上调用条件方法? 该方法已经在C#中的SpinWait.SpinUntil方法中实现。 唯一的区别是它不会引发超时异常。 可以将其固定如下:

 public void Wait(Func<bool> condition, TimeSpan timeout) { var waited = SpinWait.SpinUntil(condition, timeout); if (!waited) { throw new TimeoutException(); } } 

即 在大多数情况下,这几行代码替换了整个WebDriverWait类的逻辑。 付出的努力值得吗?

更新资料

在对文章的评论中, KSA用户对条件执行的频率对SpinUntil和直到之间的区别进行了明智的评论。 对于WebDriverWait,此值是可调整的 ,默认值为500毫秒。 即 直到方法在循环迭代之间有一个延迟。 对于SpinUntil, 逻辑有些复杂,等待时间通常不超过1毫秒。

在实践中,这会导致一种情况,在等待2秒钟内出现一个元素时,Unitl方法执行4次迭代,而SpinUntil方法花费大约200或更多。

让我们放弃SpinUntil并重写Wait方法,如下所示。

 public void Wait(Func<bool> condition, TimeSpan timeout, int evaluatedInterval = 500) { Stopwatch sw = Stopwatch.StartNew(); while (sw.Elapsed < timeout) { if (condition()) { return; } Thread.Sleep(evaluatedInterval); } throw new TimeoutException(); } 


我们添加了几行代码,与此同时,我们更加接近了直到方法的逻辑。

Source: https://habr.com/ru/post/zh-CN443754/


All Articles