转帖|行业资讯|编辑:郑恭琳|2020-06-08 14:17:11.677|阅读 342 次
概述:在使用Selenium一段时间后,如果可以舒适地编写测试用例,则可以专注于技术和设计原则,以将UI测试自动化提高到一个新的水平。
# 慧都年终大促·界面/图表报表/文档/IDE等千款热门软控件火热促销中 >>
相关链接:
在使用Selenium一段时间后,如果可以舒适地编写测试用例,则可以专注于技术和设计原则,以将UI测试自动化提高到一个新的水平。
本文假定您一直在使用Selenium,并且可以轻松编写测试用例。您已经经历了检查DOM来创建自己的XPath的尝试。也许您正在使用页面对象模型。到目前为止,您可能已经非常擅长在Internet上查找解决方案。(如果您需要该部门的帮助,我强烈建议您的同事阅读这篇文章,并提供一些出色的Selenium技巧。)
接下来的内容分为技术和设计模式。您可能已经对其中一些有所了解。很好,只需跳到您感兴趣的部分即可。我将在这里使用Java,但是这些技术和实践通常应该适用。
更好的定位器
不良的定位器会导致虚假的测试失败。它们浪费时间并分散测试的预期功能。定位错误通常取决于网页的某些不稳定质量,无论是动态值还是元素在页面上的位置。当这些质量总是变化时,定位器就会损坏。好的定位器可以工作。他们正确地标识了预期的元素并允许测试完成任务。
此处的关键是确定稳定元素的质量,然后选择这些质量的最小子集以唯一地标识该元素,以减少变化的风险。我们都知道绝对XPath是不好的,但是最好弄清楚为什么。
/html/body/div[25]/div[2]/div/span/span[2]/div/h2/p
对此XPath进行一个小节:div[25]/div[2]。这代表以下品质:
在这一小节中,我们至少使用4种品质来标识元素。如果其中任何一个发生更改,我们的定位器就会损坏。我们没有理由相信它们不会改变,因为它们都没有实际描述元素。查看此XPath,我们甚至无法猜测该元素的用途。
反映元素目的的定位符不太可能被破坏。虽然元素的位置或外观可能会更改,但其功能不应更改。理想情况下,您可以通过查看其定位器来猜测该元素的功能。
//p[contains(@class, “content”)][contains(.,”Guidance for Success”)]
通过上面的操作,很明显该元素表示描述“成功指南”的内容。请注意,通常用于控制外观的class属性也描述了元素的功能。
通常,在元素本身中找不到元素的独特,稳定的质量。取而代之的是,您需要转到元素的某些亲戚,其功能在DOM中已得到很好的描述。以“成功指导”元素中的上述示例为例。虽然此定位器很好,但是复制文本通常可以更改,如果站点支持多种语言,则复制文本可能不是一个选择。搜索DOM,我们可能会发现父div具有描述性ID。在这种情况下,我们可能更喜欢以下定位器:
//div[@id=”success_guide”]//p
显式等待
这里的诱惑是设置一个隐含的等待和希望,以解决大多数问题。问题是这种广泛的方法将所有情况都相同,甚至没有解决与等待相关的大多数问题。如果该元素在几秒钟后出现但还没有准备好被单击该怎么办?如果该元素存在但被覆盖层遮盖了怎么办?解决方案是使用精心设计的显式等待。
显式等待条件的形式为:
WebDriverWait wait = new WebDriverWait(webdriver, timeOutInSeconds)
wait.until(/* some condition is true */)
显式等待功能强大,因为它们具有描述性。它们允许您陈述必要的条件以便继续进行。一个常见的示例是测试需要单击某个元素时。仅存在该元素是不够的;它需要可见并启用。显式等待使您可以在测试中直接描述这些要求。我们可以确信在尝试继续测试之前已经满足了条件,从而使您的测试更加稳定:
WebDriverWait wait = new WebDriverWait(webdriver, timeOutInSeconds)
wait.until(ExpectedConditions.elementToBeClickable(element))
描述性显式等待还使您可以编写针对应用程序行为方面的测试。由于应用程序的行为不会经常更改,因此您可以创建更稳定的测试。以上述示例为例,有必要使一个元素可见并被单击,但也不必被另一个元素遮挡。如果有一个较大的“加载”叠加层覆盖您的元素,则单击将失败。我们可以创建一个描述此加载行为的显式等待,并等待满足必要条件:
WebDriverWait wait = new WebDriverWait(webdriver, timeOutInSeconds)
wait.until(ExpectedConditions.invisibilityOf(loadingOverlay))
预期条件
您可能已经注意到上述示例中使用了ExpectedConditions实用程序方法。此类包含编写Selenium测试时要使用的大量有用条件。如果您还没有花时间,请花点时间浏览完整的API。您通常会使用ExpectedConditions.elementToBeClickable(..)或ExpectedConditions.invisibilityOf(..),但也可能会用到alertIsPresent(),jsReturnsValue(...)或titleContains(..)。您甚至可以使用ExpectedConditions.and(..)或ExpectedConditions.or(..)将条件链接在一起。
执行JavaScript
WebDrivers提供了在浏览器上下文中执行JavaScript的功能。这是一个简单的功能,具有令人难以置信的多功能性。可以将其用于常见任务,例如强制页面滚动到元素,例如:
driver.executeScript("arguments[0].scrollIntoView(false)", element)
它也可以用于利用JQuery或React等应用程序中使用的JavaScript库。例如,您可以通过调用以下命令来检查富编辑器中的文本是否已更改:
driver.executeScript(“return EDITOR.instances.editor.checkDirty()”)
executeScript功能为您的测试打开了整个库API。这些API通常可以提供有用的洞察应用程序状态的信息,否则将无法使用WebElements进行查询或不稳定。
使用库API确实会将您的测试耦合到库实现。在开发应用程序时,通常可以交换库,因此以这种方式使用executeScript时需要格外小心。您应该考虑在一个更抽象的界面之后抽象这些特定于库的调用,以帮助减少测试的不稳定性,例如Bot模式(请参阅下文)。
机器人模式
机器人模式将Selenium API调用抽象为动作。然后可以在整个测试中使用这些操作,以使其更具可读性和简洁性。
在本文中,我们已经看到了一些有用的示例。因为在尝试单击某个元素之前必须先单击它,所以我们可能总是希望在每次单击之前都等待该元素可单击:
void test() { /* test code */ WebDriverWait wait = new WebDriverWait(driver, 5); wait.until(ExpectedConditions.elementToBeClickable(element)); element.click(); wait.until(ExpectedConditions.elementToBeClickable(element2)); element2.click(); }
可以将代码抽象为自己的方法,而不是每次测试单击元素时都写等待条件:
public class Bot { public void waitAndClick(WebElement element, long timeout) { WebDriverWait wait = new WebDriverWait(driver, timeout); wait.until(ExpectedConditions.elementToBeClickable(element)); element.click(); } }
然后我们的代码变为:
void test() { /* test code */ bot.waitAndClick(element, 5); bot.waitAndClick(element2, 5); }
该机器人还可以扩展为创建特定于库的实现。如果应用程序开始使用其他库,则所有测试代码可以保持不变,并且仅需要更新Bot:
public class Bot { private WebDriver driver; private RichEditorBot richEditor; public Bot(WebDriver driver, RichEditorBot richEditor) { this.driver = driver; this.richEditor = richEditor; } public boolean isEditorDirty() { richEditor.isEditorDirty(); } } public class RichEditorBot() { public boolean isEditorDirty() { return ((JavascriptExecutor) driver).executeScript(“return EDITOR.instances.editor.checkDirty()”); } } void test() { /* test code */ bot.isEditorDirty(); }
Bot示例可作为WebDriverExtensions库的一部分以及特定于库的实现获得:
Bot模式和Page Object模型可以一起使用。在您的测试中,顶级抽象是表示每个组件功能元素的Page对象。然后,页面对象包含一个可在页面对象功能中使用的Bot,从而使它们的实现更简单易懂:
public class LoginComponent { private Bot bot; @FindBy(id = “login”) private WebElement loginButton; public LoginComponent(Bot bot) { PageFactory.initElements(bot.getDriver(), this); this.bot = bot; } public void clickLogin() { bot.waitAndClick(loginButton, 5); } }
WebDriver Factory
直接在测试代码中实例化和配置WebDriver实例(例如ChromeDriver或FirefoxDriver)意味着该测试现在有两个问题:构建特定的WebDriver和测试应用程序。WebDriver Factory通过将所有WebDriver实例化和配置移出测试来分离这些问题。
这可以通过多种方法来实现,但是概念很简单:创建一个提供完全配置的WebDriver的工厂。在测试代码中,从工厂获取WebDriver,而不是直接构造它。现在,有关WebDriver的任何问题都可以在一个地方处理。任何更改都可能在该位置发生,并且每个测试都将获取更新的WebDriver。
Web Driver Factory项目使用此概念来管理多个测试中WebDrivers的寿命。这个复杂的任务被抽象到工厂,允许测试仅请求具有提供的选项的WebDriver:
//github.com/barancev/webdriver-factory
WebDriver工厂使您可以轻松地在多个浏览器中重用单个测试。可以通过外部文件处理所有配置。该测试仅向WebDriver工厂询问WebDriver的实例,然后工厂将处理详细信息。一个示例用于在TestNG框架中支持并行网格测试:
//www.swtestacademy.com/selenium-parallel-tests-grid-testng/
扩展页面对象模型
Page Object模型提供了一个抽象层,该层提供了应用程序组件的功能,同时隐藏了Selenium如何与这些组件交互的详细信息。这是一种功能强大的设计模式,可以使代码可重用并且更易于理解。但是,为每个页面和组件创建一个类可能会有很多开销。每个类都有一个样板,然后在类之间共享组件,例如初始化实例以及传递WebDriver或Bot对象。可以通过扩展页面对象模型来减少开销。
如果您将Bot模式与Page Object模型一起使用,则每个Page Object将需要一个Bot实例。可能看起来像:
public class LoginComponent { private Bot bot; @FindBy(id = “login”) private WebElement loginButton; public LoginComponent(Bot bot) { PageFactory.initElements(bot.getDriver(), this); this.bot = bot; } public void clickLogin() { bot.waitAndClick(loginButton, 5); } }
除了将Bot代码包含在每个构造函数中之外,还可以将该代码移到每个组件扩展的另一个类中。这使单个组件代码可以专注于组件的细节,而不是初始化代码或传递Bot:
public class Component { private Bot bot; public Component(Bot bot) { PageFactory.initElements(bot.getDriver(), this); this.bot = bot; } public Bot getBot() { return bot; } } public class LoginComponent extends Component { @FindBy(id = “login”) private WebElement loginButton; public LoginComponent(Bot bot) { super(bot); } public void clickLogin() { getBot().waitAndClick(loginButton, 5); } }
同样,对于组件来说,通常需要验证它是否在正确的时刻被实例化,以便在错误使用组件时更容易进行调试。我们可能要检查标题是否正确:
public class LoginPage extends Component { public LoginPage(Bot bot) { super(bot); bot.waitForTitleContains(“Please login”); } }
我们可以将此检查移至其他页面扩展的特定版本的Component中,而不是在每个类中都包括对Bot的调用。这提供了一个小的好处,在创建许多页面对象时会加起来:
public class TitlePage extends Component { public LoginPage(Bot bot, String title) { super(bot); bot.waitForTitleContains(title); } } public class LoginPage extends TitlePage { public LoginPage(Bot bot) { super(bot, “Please login”); } }
其他库恰恰为此目的提供了帮助程序类。Selenium Java库包含LoadableComponent对象,该对象抽象功能并检查加载页面的过程:
//github.com/SeleniumHQ/selenium/wiki/LoadableComponent
WebDriverExtensions通过将围绕页面对象的许多代码抽象为注释,从而创建了更简单、更易于阅读的组件,从而进一步发展了:
//github.com/webdriverextensions/webdriverextensions
本文仅涉及一些有用的技术和设计模式。关于这个主题的书已经写好了。编写好的测试意味着编写好的软件,而编写好的软件是一项复杂的任务。
我已经介绍了多年以来我学到的一些信息,以创建更好的Selenium测试。 Parasoft Selenic利用我们的专业知识进一步简化了任务。有了适当的工具和知识,我们可以改善过程并创建稳定、可读和可维护的测试。
本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@pclwef.cn