框架 – 你的第一个框架 – 第 3 部分






2.22/5 (2投票s)
框架 – 你的第一个框架 – 第 3 部分
在第二部分中,我们通过设置允许我们设置和释放驱动程序实例的类,为框架奠定了基础。但是,仅仅打开一个空窗口然后关闭它,我们并不能进行太多测试。接下来,我们将围绕基本的 Selenium 功能创建一些包装方法,这些方法将允许我们与新的浏览器窗口以及我们访问的页面上的任何元素进行交互。
如果您还记得上一篇文章,我们创建了一个名为 Driver
的类,我们决定使用 partial
关键字将其拆分为几个不同的文件。这使我们能够创建一个超类,同时通过创建更小、更易于维护的使用该类的文件来保持代码的可读性。
我们的 Dispose
方法位于一个 partial Driver
类中,而接下来的三个类也将是 partial
类。它们将被命名为 DriverElement
、DriverWindow
和 DriverScript
。让我们先来看看我们的 DriverWindow
类。
public static partial class Driver
{
#region Properties
public static string Title()
{
try
{
return DriverBase.Instance.Title;
}
catch(WebDriverException)
{
throw new WebDriverException
("Failed to retrieve the title of the current webdriver window");
}
}
public static string Url()
{
try
{
return DriverBase.Instance.Url;
}
catch (WebDriverException)
{
throw new WebDriverException
("Failed to retrieve the url of the current webdriver window");
}
}
#endregion
#region Navigate Methods
public static void NavigateTo(string url)
{
try
{
DriverBase.Instance.Navigate().GoToUrl(url);
}
catch (WebDriverException)
{
throw new WebDriverException($"Failed to navigate to the url: {url}");
}
}
public static void NavigateTo(Uri url)
{
try
{
DriverBase.Instance.Navigate().GoToUrl(url);
}
catch (WebDriverException)
{
throw new WebDriverException($"Failed to navigate to the url: {url}");
}
}
public static void NavigateForward()
{
try
{
DriverBase.Instance.Navigate().Forward();
}
catch (WebDriverException)
{
throw new WebDriverException($"Failed to navigate forward in the current window");
}
}
public static void NavigateBack()
{
try
{
DriverBase.Instance.Navigate().Back();
}
catch (WebDriverException)
{
throw new WebDriverException($"Failed to navigate back in the current window");
}
}
public static void Refresh()
{
try
{
DriverBase.Instance.Navigate().Refresh();
}
catch (WebDriverException)
{
throw new WebDriverException($"Failed to refresh the current window");
}
}
#endregion
#region Alert Methods
public static IAlert WaitAndGetAlert()
{
return WaitAndGetAlert(TimeOut, PollingInterval);
}
public static IAlert WaitAndGetAlert(TimeSpan timeout, TimeSpan pollInterval)
{
DriverBase.Instance.Manage().Timeouts().ImplicitWait = timeout;
var wait = new WebDriverWait(DriverBase.Instance, timeout)
{
PollingInterval = pollInterval
};
try
{
return wait.Until(d =>
{
try
{
return DriverBase.Instance.SwitchTo().Alert();
}
catch (WebDriverException)
{
return null;
}
});
}
catch(WebDriverException)
{
DriverBase.Instance.Manage().Timeouts().ImplicitWait = TimeOut;
throw new WebDriverException
("Failed to wait and get an alert in the current browser instance");
}
}
public static void WaitAndAcceptAlert(TimeSpan timeout, TimeSpan pollInterval)
{
try
{
var alert = WaitAndGetAlert(timeout, pollInterval);
alert.Accept();
}
catch(WebDriverException)
{
throw new WebDriverException("Failed to accept alert in the current browser window");
}
}
#endregion
#region Switch Methods
public static void SwitchToWindow(string windowName)
{
try
{
DriverBase.Instance.SwitchTo().Window(windowName);
}
catch (WebDriverException)
{
throw new WebDriverException($"Unable to switch to window: {windowName}");
}
}
#endregion
}
您可能立即会注意到,我们类中的方法在外观上非常相似。所有方法都通过 try catch
块进行良好的异常处理,并在每个方法的 try
语句内包含我们的简单代码。您可能还会注意到的另一件事是,许多方法只是通过我们的 WebDriver
实例变量调用 Selenium 中现有的 WebDriver
功能。既然如此,我们为什么还要费劲,而不直接调用那些代码呢?
有两个原因。第一个是异常处理。这样做可以让我们创建自己的异常消息,用于调试任何潜在问题,还可以指定抛出的异常类型。这将在我们以后修复代码以及在框架级别出现问题时定位测试问题时,使我们的生活更加轻松。
其次,我们的基本框架围绕使用我们在整个框架项目中使用的 static WebDriver
实例而设计。通过创建这些包装方法,我们可以跨 Driver
类或稍后的 Page Objects
类调用这些方法中的任何一个,而无需为每个类初始化 WebDriver
实例。这有助于您编写更简单的代码。
因此,回到该类,使用区域,我们将类分为四个区域:Properties
、Navigate
、Alert
和 Switch
。Properties
将允许我们返回浏览器实例所需的任何信息,例如页面标题或我们正在查看的页面的当前 URL。Navigate
方法用于转到我们期望的页面,并在需要时模拟用户在窗口中按后退或前进按钮。此时我们甚至可以刷新页面。
您可能会注意到我们有一个重载的 NavigateTo
方法。一个接受 string
URL,另一个接受 URI。如果我们创建一个 URI,它只需允许我们对 URL 进行某种验证,并确保它格式正确。建议尽可能使用此方法,但 string
版本提供了一种更快但更易出错的导航到 URL 的方法。
我们的 Alert
方法允许我们处理可能出现的任何浏览器特定的警报消息。这些与站点级别的弹出窗口不同。这引出了我们的最后一个区域,我们的 Windows
方法。这些是用来处理站点级别的弹出窗口,例如登录窗口,或者可能是站点在新窗口中打开页面,而不是在新选项卡中打开。
我们的下一个类是 Elements
类,所以让我们看一下。
public static partial class Driver
{
#region Properties Methods
public static string ReturnElementUrl(this By by)
{
return DriverBase.Instance.FindElement(by).Location.ToString();
}
#endregion
#region Bool Methods
public static bool DoesElementExist(this By by)
{
return by.DoesElementExist(TimeOut, PollingInterval);
}
public static bool DoesElementExist(this By by, TimeSpan timeout, TimeSpan pollInterval)
{
try
{
by.WaitUntilElementIsVisible(timeout, pollInterval);
}
catch(Exception)
{
return false;
}
return true;
}
public static bool IsElementVisible(this By by, bool cssVisibleOnly = false)
{
IWebElement element;
try
{
element = by.FindElement();
}
catch(Exception)
{
return false;
}
if(!cssVisibleOnly)
{
return true;
}
try
{
return element.Displayed;
}
catch(StaleElementReferenceException)
{
return by.FindElement().Displayed;
}
}
#endregion
#region Find Methods
public static IWebElement FindElement(this By by)
{
try
{
return WaitUntilElementIsVisible(by);
}
catch (WebDriverException)
{
throw new WebDriverException($"Failed to find element by '{by}'");
}
}
#endregion
#region Wait Methods
public static IWebElement WaitUntilElementIsVisible(this By by)
{
return WaitUntilElementIsVisible(by, TimeOut, PollingInterval);
}
public static IWebElement WaitUntilElementIsVisible
(this By by, TimeSpan timeout, TimeSpan pollInterval)
{
IWebElement element;
try
{
var wait = new WebDriverWait(DriverBase.Instance, timeout)
{ PollingInterval = pollInterval };
wait.IgnoreExceptionTypes(typeof(StaleElementReferenceException));
wait.Until(ExpectedConditions.ElementIsVisible(by));
element = DriverBase.Instance.FindElement(by);
}
catch(Exception exception) when (exception is WebDriverException ||
exception is StaleElementReferenceException)
{
DriverBase.Instance.Manage().Timeouts().ImplicitWait = TimeOut;
throw new Exception($"Timeout after {timeout.TotalSeconds}
seconds of waiting for element {by} to become visible");
}
return element;
}
public static void WaitAndClickElement(this By by)
{
WaitAndClickElement(by, TimeOut, PollingInterval);
}
public static void WaitAndClickElement(this By by, TimeSpan timeout, TimeSpan pollInterval)
{
try
{
var elementToClick = WaitUntilElementIsVisible(by, timeout, pollInterval);
try
{
elementToClick.Click();
}
catch(StaleElementReferenceException)
{
var wait = new WebDriverWait(DriverBase.Instance, timeout)
{ PollingInterval = pollInterval };
wait.Until(ExpectedConditions.StalenessOf(DriverBase.Instance.FindElement(by)));
DriverBase.Instance.FindElement(by);
}
}
catch(Exception exception) when (exception is WebDriverException ||
exception is InvalidOperationException)
{
DriverBase.Instance.Manage().Timeouts().ImplicitWait = TimeOut;
throw new Exception($"Unable to click element {by}
due to the following error: {exception.Message}");
}
}
#endregion
}
这里的代码比 Window
类稍微复杂一些,因为我们不仅仅是包装现有功能。相反,我们将以扩展方法的形式向 Selenium 添加功能。
扩展方法允许您向现有类或类型添加新方法。在此上下文中,我们正在添加扩展方法来处理可能可见或不可见的元素,或者甚至可能不存在的元素。
添加此类方法使我们能够决定如何处理这些类型的事件。没有这些扩展方法,如果我们的测试遇到一个不存在的元素,它将直接崩溃并抛出异常。但是,有时我们可能期望某个元素不可见,在这种情况下,我们根本不想崩溃。
扩展方法可以通过其第一个参数开头具有 ‘this
’ 关键字来识别,例如 ‘this By by
’。这告诉我们的代码,我们想向 By
类型添加一个方法。因此,您可以看到在此类中,我们正在向 By
添加许多扩展方法,这极大地改善了我们与元素交互的方式。
代码本身,除了扩展方法之外,并没有做什么太复杂的事情,并且遵循我们之前的代码,因为它进行了大量的彻底异常处理,我只想介绍的新代码是这个。
我们声明的 wait
变量允许我们向我们的 wait
添加特定条件,这些条件决定了我们等待的时间或特定事件。我们甚至可以设置要忽略的事件。在此示例中,我们正在忽略 StaleElementReferenceExceptions
。陈旧元素本质上是旧元素,这可能就像一个我们尝试过快操作的文本框,而文本框在 DOM 中的位置已更改。在等待元素时,我们可能会期望发生此类事件,因此我们选择忽略它。
而在代码的另一部分,您可以看到我们选择不忽略它。这是因为我们实际上正在通过单击来与元素交互。我们绝对不想忽略陈旧元素的任何潜在问题。
我们的 wait
停止的触发器是通过 Until
方法完成的。在这里,我们将一个特殊变量类型 ExpectedConditions
传递给它,ExpectedConditions
的一个特定类型是 ElementIsVisible
。一旦我们的代码检测到该元素现在可见,它将停止等待并继续,但是,如果它超时或出现问题,我们将捕获并抛出异常。
{
public static partial class Driver
{
public static string InvokeJavaScript(string script)
{
return ((IJavaScriptExecutor)DriverBase.Instance).ExecuteScript(script).ToString();
}
}
}
我们的最后一个类是 script
类。它很简单,因为它只是允许我们在访问的网页上执行任何 JavaScript。这有什么用呢?嗯,页面上通常有一些脚本允许我们检查页面是否已完成加载,我们可以用它来在新页面导航时等待,而不是使用像 Thread.Sleep
这样懒惰的方法。根据页面和编写代码的开发人员,可能还有其他脚本可用。
好了,就是这样,我们现在有了 Driver
类。我们现在可以启动一个 driver
实例,操作窗口并与页面上的任何元素进行交互。我们还拥有出色的异常处理功能,可以处理可能发生的任何问题。
在下一篇文章中,我们将扩展我们的元素交互功能,并添加一个控件接口,以改进我们初始化和与不同类型元素交互的方式。
这篇 框架 – 您的第一个框架 – 第 3 部分 帖子首次出现在 Learn Automation。