我正在尝试整理我的第一个数据驱动测试框架,该框架在多个浏览器上通过Selenium Grid / WebDriver运行测试。现在,我将每个测试用例放在它自己的类中,并且我对浏览器进行参数化,因此每个浏览器运行一次测试用例。
这在大型测试框架中是否常见?或者,是否应将每个测试用例复制并微调到其自己的类中的每个浏览器?所以,如果我正在测试chrome,firefox和IE,那么每个应该有类,例如:“TestCase1Chrome”,“TestCase1FireFox”,“TestCase1IE”?或者只是“TestCase1”并将测试参数化为每个浏览器运行3次?只是想知道别人怎么做。
将测试参数化为每个测试用例的单个类,可以更轻松地维护非浏览器特定的代码,而复制类(每个浏览器案例一个)可以更轻松地维护特定于浏览器的代码。当我说特定于浏览器的代码时,例如,单击一个项目。在ChromeDriver上,您无法点击某些元素的中间,在FirefoxDriver上,您可以。因此,您可能需要两个不同的代码块才能单击一个元素(当它在中间不可点击时)。
对于那些受雇使用Selenium的QA工程师的人来说,这里最好的做法是什么?
答案 0 :(得分:4)
我目前正在开展一个每天运行约75k-90k测试的项目。我们将浏览器作为参数传递给测试。理由是:
我在代码结构和你提到的问题之间看到的区别是,我没有针对每个测试用例的测试类。测试根据我测试的功能进行划分,每个功能都有一个类。该类将所有测试作为方法。我使用testNG,以便可以并行调用这些方法。可能这不适合您的AUT。
答案 1 :(得分:3)
如果你保留你在问题中提到的代码结构,迟早维护它将成为一场噩梦。尝试坚持规则:所有浏览器(环境)的相同测试代码(一次编写)。
这种情况会迫使你解决两个问题:
1)如何为所有选择的浏览器运行测试
2)如何应用特定的浏览器变通方法而不会污染测试代码
实际上,这似乎是你的问题。
以下是我解决第一个问题的方法。 首先,我定义了我要测试的所有环境。我称之为“环境”我想要运行测试的所有条件:浏览器名称,版本号,操作系统等。因此,与测试代码分开,我创建了一个这样的枚举:
public enum Environments {
FF_18_WIN7("firefox", "18", Platform.WINDOWS),
CHR_24_WIN7("chrome", "24", Platform.WINDOWS),
IE_9_WIN7("internet explorer", "9", Platform.WINDOWS)
;
private final DesiredCapabilities capabilities;
private final String browserName;
private final String version;
private final Platform platform;
Environments(final String browserName, final String version, final Platform platform) {
this.browserName = browserName;
this.version = version;
this.platform = platform;
capabilities = new DesiredCapabilities();
}
public DesiredCapabilities capabilities() {
capabilities.setBrowserName(browserName);
capabilities.setVersion(version);
capabilities.setPlatform(platform);
return this.capabilities;
}
public String browserName() {
return browserName;
}
}
您可以随时轻松修改和添加环境。您可以注意到,我正在使用它来创建和检索DesiredCapabilities,稍后将用于创建特定的WebDriver。
为了让测试针对所有已定义的环境运行,我使用了JUnit(在我的情况下为4.10)org.junit.experimental.theories:
@RunWith(MyRunnerForSeleniumTests.class)
public class MyWebComponentTestClassIT {
@Rule
public MySeleniumRule selenium = new MySeleniumRule();
@DataPoints
public static Environments[] enviroments = Environments.values();
@Theory
public void sample_test(final Environments environment) {
Page initialPage = LoginPage.login(selenium.driverFor(environment), selenium.getUserName(), selenium.getUserPassword());
// your test code here
}
}
测试注释为@Theory
(而非@Test
,与普通的JUnit测试一样)并传递参数。然后,每个测试都将针对此参数的所有已定义值运行,该值应为注释为@DataPoints
的值数组。此外,您应该使用从org.junit.experimental.theories.Theories
延伸的跑步者。我使用org.junit.rules
来准备我的测试,并将所有必要的管道放在那里。正如您所看到的,我也通过规则获得了特定的功能驱动程序。虽然您可以在测试中使用以下代码:
RemoteWebDriver driver = new RemoteWebDriver(new URL(some_url_string), environment.capabilities());
关键是在规则中使用它一次编写代码并将其用于所有测试。 对于Page类,它是一个类,我将所有使用驱动程序功能的代码放入(查找元素,导航等)。这样,测试代码再次保持整洁清晰,再次编写一次并在所有测试中使用它。 所以,这是第一个问题的解决方案。 (我知道你可以用TestNG做类似的事情,但我没有尝试过。)
为解决第二个问题,我创建了一个特殊的包,其中保留了浏览器特定解决方法的所有代码。它由一个抽象类组成,例如BrowserSpecific,包含在某些浏览器中恰好不同(或有bug)的公共代码。在同一个包中,我有特定于测试中使用的每个浏览器的类,每个类都扩展了BrowserSpecific。
以下是您提到的Chrome驱动程序错误的工作原理。我在BrowserSpecific中创建了一个方法clickOnButton
,其中包含受影响行为的公共代码:
public abstract class BrowserSpecific {
protected final RemoteWebDriver driver;
protected BrowserSpecific(final RemoteWebDriver driver) {
this.driver = driver;
}
public static BrowserSpecific aBrowserSpecificFor(final RemoteWebDriver driver) {
BrowserSpecific browserSpecific = null;
if (Environments.FF_18_WIN7.browserName().contains(driver.getCapabilities().getBrowserName())) {
browserSpecific = new FireFoxSpecific(driver);
}
if (Environments.CHR_24_WIN7.browserName().contains(driver.getCapabilities().getBrowserName())) {
browserSpecific = new ChromeSpecific(driver);
}
if (Environments.IE_9_WIN7.browserName().contains(driver.getCapabilities().getBrowserName())) {
browserSpecific = new InternetExplorerSpecific(driver);
}
return browserSpecific;
}
public void clickOnButton(final WebElement button) {
button.click();
}
}
然后我在特定的类中重写此方法,例如ChromeSpecific,我在其中放置了变通方法代码:
public class ChromeSpecific extends BrowserSpecific {
ChromeSpecific(final RemoteWebDriver driver) {
super(driver);
}
@Override
public void clickOnButton(final WebElement button) {
// This is the Chrome workaround
String script = MessageFormat.format("window.scrollTo(0, {0});", button.getLocation().y);
driver.executeScript(script);
// Followed by common behaviour of all the browsers
super.clickOnButton(button);
}
}
当我必须考虑某些浏览器的特定行为时,我会执行以下操作:
aBrowserSpecificFor(driver).clickOnButton(logoutButton);
而不是:
button.click();
这样,在我的公共代码中,我可以轻松识别应用变通方法的位置,并将变通方法与公共代码隔离开来。我发现它很容易维护,因为通常会解决错误,可能或应该更改或消除变通方法。
关于执行测试的最后一句话。当您打算使用Selenium Grid时,您将希望使用并行运行测试的可能性,因此请记住为JUnit测试配置此功能(自4.7版开始提供)。
答案 2 :(得分:1)
我们在组织中使用testng,我们使用testng提供的参数选项来指定环境,即要使用的浏览器,要运行的计算机以及env config所需的任何其他配置。浏览器名称通过xml文件发送,该文件控制需要运行的内容和位置。它被设置为全局变量。我们所做的另外一点是,我们有自定义注释可以覆盖这些全局变量,即如果测试非常具体地仅在chrome上运行而没有其他浏览器,那么我们在自定义注释上指定相同的。所以,即使参数是在FF上运行,如果用chrome注释,它也总是在chrome上运行。
我不知何故相信为每个浏览器创建一个类并不是一个好主意。想象一下流量的变化,或者有一些在这里和那里,你有3个类而不是一个。如果浏览器数量增加,那么又增加一个类。
我建议将具有特定浏览器的代码提取出来。因此,如果点击行为是特定于浏览器的,则覆盖它以根据浏览器进行适当的检查或失败处理。
答案 3 :(得分:0)
我是这样做的,但请记住,这是纯粹的WebDriver,不考虑Grid或RC:
// Utility class snippet
// Test classes import this with: import static utility.*;
public static WebDriver driver;
public static void initializeBrowser( String type ) {
if ( type.equalsIgnoreCase( "firefox" ) ) {
driver = new FirefoxDriver();
} else if ( type.equalsIgnoreCase( "ie" ) ) {
driver = new InternetExplorerDriver();
}
driver.manage().timeouts().implicitlyWait( 10000, TimeUnit.MILLISECONDS );
driver.manage().window().setPosition(new Point(200, 10));
driver.manage().window().setSize(new Dimension(1200, 800));
}
现在,使用JUnit 4.11+,您的参数文件需要看起来像这样:
firefox, test1, param1, param2
firefox, test2, param1, param2
firefox, test3, param1, param2
ie, test1, param1, param2
ie, test2, param1, param2
ie, test3, param1, param2
然后,在@Before带注释的方法中,使用单个.CSV参数化测试类(您打算启动多个浏览器类型),执行以下操作:
当然,我的回答并没有告诉你如何处理这些参数:我留给你了解。