我正在使用一个相当典型的maven架构,javacumber,selenium,以及spring依赖注入测试系统来测试一个动态的前端网站(在pom.xml)架构中的版本它工作得非常好,我可以很容易地运行数百个测试,但是我不能像使用ruby watir那样“干燥”测试步骤。一篇文章指出ruby有一个java缺少的“world”对象,但是用于依赖注入的spring应该可以解决这个问题
我读过很多“retaining state”的帖子,但似乎没有什么适用于它的工作原理,很多都是cucumber和spring的几个版本,尽管我仍然在使用java8。大多数用于保留状态的post似乎都在单个文件中的步骤之间,在单个测试中。
主要的例子是,我希望能够有一个带有@given i login步骤的steps文件,而不必将该步骤放在其他一百个step文件中。
如果我有这样一个功能文件:
Feature: As an account holder I examine account details
Scenario: View personal summary widget info on details page
Given Log into "web" on "dev" as "username" with "password" using "chrome"
When I tap the first account section
Then I see a list of transactions
并将其与包含所有步骤的steps文件匹配,如下所示
@SpringBootTest
public class AccountsSteps {
private final MyAccountsPage page;
@Autowired
public AccountsSteps(MyAccountsPage page){
this.page = page;
}
@Given("Log into {string} on {string} as {string} with {string} using {string}")
public void logIntoOnAsWithUsing(String app, String env, String user, String pass, String browser) {
page.loadAny(env, app, browser);
page.sendUsername(user);
page.sendPassword(pass);
page.loginButtonClick();
}
@When("I tap the first account section")
public void iTapTheFirstAccountSection() {
page.waitForListOfElementType(WebElement);
page.clickFirstAccountLink();
}
@Then("I see a list of transactions")
public void iSeeAListOfTransactions() {
By selector = By.cssSelector("div.container");
page.waitForLocateBySelector(selector);
Assert.assertTrue(page.hasTextOnPage("Account details"));
}
}
一切都很好,但如果我有另一个功能,使用相同的@given,所以上面和下面的一个是准确的,所以它不会在新的步骤文件中创建一个新的步骤。
Feature: As an account owner I wish to edit my details
Scenario: My profile loads and verifies the correct member's name
Given Log into "web" on "dev" as "username" with "password" using "chrome"
When I use the link in the Self service drop down for My profile
Then the Contact Details tab loads the proper customer name "Firstname Lastname"
与此步骤文件匹配,请注意缺少给定步骤,因为它使用的是另一个文件中的步骤。
@SpringBootTest
public class MyProfileSteps {
private final MyProfilePage page;
@Autowired
public MyProfileSteps(MyProfilePage page){
this.page = page;
}
@When("I use the link in the Self service drop down for My profile")
public void iUseTheLinkInTheSelfServiceDropDownForMyProfile() {
page.clickSelfServiceLink();
page.clickMyProfileLink();
}
@Then("the Contact Details tab loads the proper customer name {string}")
public void theContactDetailsTabLoadsTheCustomerName(String fullName) {
System.out.println(page.getCustomerNameFromProfile().getText());
Assert.assertTrue(page.getCustomerNameFromProfile().getText().contains(fullName));
page.teardown();
}
}
我终于找到了问题的症结所在。在切换到其他步骤文件中的步骤时,它抛出异常。
When I use the link in the Self service drop down for My profile
java.lang.NullPointerException
at java.util.Objects.requireNonNull(Objects.java:203)
at org.openqa.selenium.support.ui.FluentWait.<init>(FluentWait.java:106)
at org.openqa.selenium.support.ui.FluentWait.<init>(FluentWait.java:97)
at projectname.pages.BasePage.waitForClickableThenClickByLocator(BasePage.java:417)
at projectname.pages.BasePageWeb.clickSelfServiceLink(BasePageWeb.java:858)
at projectname.steps.MyProfileSteps.iUseTheLinkInTheSelfServiceDropDownForMyProfile(MyProfileSteps.java:39)
at ✽.I use the link in the drop down for My profile(file:///Users/name/git/project/tests/projectname/src/test/resources/projectname/features/autocomplete/my_profile.feature:10)
我特别地将它们绑定在一起,这样一个测试在每个测试中只调用一个新的selenium示例,而且它肯定不会打开一个新的浏览器窗口,它只是崩溃和关闭。
public interface WebDriverInterface {
WebDriver getDriver();
WebDriver getDriverFire();
void shutdownDriver();
WebDriver stopOrphanSession();
}
有几个配置文件将运行不同的配置,但我的主要本地测试webdriverinterface如下所示。
@Profile("local")
@Primary
@Component
public class DesktopLocalBrowsers implements WebDriverInterface {
@Value("${browser.desktop.width}")
private int desktopWidth;
@Value("${browser.desktop.height}")
private int desktopHeight;
@Value("${webdriver.chrome.mac.driver}")
private String chromedriverLocation;
@Value("${webdriver.gecko.mac.driver}")
private String firefoxdriverLocation;
public WebDriver local;
public WebDriver local2;
public DesktopLocalBrowsers() {
}
@Override
public WebDriver getDriver() {
System.setProperty("webdriver.chrome.driver", chromedriverLocation);
System.setProperty("webdriver.chrome.silentOutput", "true");
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments("--disable-extensions");
chromeOptions.addArguments("window-size=" + desktopWidth + "," + desktopHeight);
local = new ChromeDriver(chromeOptions);
return local;
}
@Override
public WebDriver getDriverFire() {
System.setProperty("webdriver.gecko.driver", firefoxdriverLocation);
FirefoxBinary firefoxBinary = new FirefoxBinary();
FirefoxOptions firefoxOptions = new FirefoxOptions();
firefoxOptions.setLogLevel(FirefoxDriverLogLevel.FATAL);
firefoxOptions.setBinary(firefoxBinary);
local2 = new FirefoxDriver(firefoxOptions);
return local2;
}
@Override
public void shutdownDriver() {
try{
local.quit();
}catch (NullPointerException npe){
local2.quit();
}
}
public WebDriver stopOrphanSession(){
try{
if(local != null){
return local;
}
}catch (NullPointerException npe){
System.out.println("All Drivers Closed");
}
return local2;
}
}
我有相当标准的跑步者。我尝试了cucumber runner的几个变体,使用glue和extraglue配置,移动到不同的目录中,但是要么什么都没有改变,要么我完全破坏了它。这是一个正在工作的。
@RunWith(Cucumber.class)
@CucumberOptions(features = "src/test/resources/projectname/features/",
glue = "backbase",
// extraGlue = "common", // glue and extraGlue cannot be used together
plugin = {
"pretty",
"summary",
"de.monochromata.cucumber.report.PrettyReports:target/cucumber",
})
public class RunCucumberTest {
}
还有我的 Spring 跑步者
@RunWith(SpringRunner.class)
@CucumberContextConfiguration
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class SpringContextRunner {
}
以及开箱即用的应用程序页,以供参考。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
为了防止有人发现它对头脑风暴或诊断有用,我的页面对象从basepage开始,因为它包含了所有的常用方法,所以变得太庞大了,但是看起来像这样。
public abstract class BasePageWeb {
@Value("${projectname.retail.dev}")
private String devUrl;
@Value("${projectname.retail.sit}")
private String sitUrl;
@Value("${projectname.retail.uat}")
private String uatUrl;
protected WebDriver driver;
public WebDriverWait wait;
protected final WebDriverInterface webDriverInterface;
public BasePageWeb(WebDriverInterface webDriverInterface) {
this.webDriverInterface = webDriverInterface;
}
// env choices: lcl, dev, sit, uat -> app choices: web, id, emp, cxm -> browser choices: chrome, fire
public void loadAny(String env, String app, String browser) {
if (browser.equals("chrome")) {
driver = this.webDriverInterface.getDriver();
} else if (browser.equals("fire")) {
driver = this.webDriverInterface.getDriverFire();
}
wait = new WebDriverWait(driver, 30);
String url = "";
String title = "";
switch (app) {
case "web":
switch (env) {
case "dev":
url = devUrl;
title = "Log in to Project Name";
break;
case "sit":
url = sitUrl;
title = "Log in to Project Name";
break;
case "uat":
url = uatUrl;
title = "Log in to Project Name";
break;
}
break;
default:
System.out.println("There were no matches to your login choices.");
}
driver.get(url);
wait.until(ExpectedConditions.titleContains(title));
}
}
当我有特定的主题可以创建只应用于该子区域的方法时,我会扩展基本页,然后将子页插入步骤页。
@Component
public class MyAccountsPage extends BasePageWeb {
public MyAccountsPage(WebDriverInterface webDriverInterface) {
super(webDriverInterface);
}
// Find the Product Title Elements, Convert to Strings, and put them all in a simple List.
public List<String> getAccountInfoTitles(){
List<WebElement> accountInfoTitlesElements =
driver.findElements(By.cssSelector("div > .bb-account-info__title"));
return accountInfoTitlesElements.stream()
.map(WebElement::getText)
.collect(Collectors.toList());
}
}
如果有人能看到我做错了什么,或者提出调查建议,我会很感激的。我知道在6.6.0版本之后,框架如何扫描注解之类的内容发生了一些重大变化,但我还不能确定这是否相关。
供参考。包含所有版本和包含的依赖项的pom.xml。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>java-cucumber-generic</groupId>
<artifactId>java-cucumber-generic-web</artifactId>
<version>1.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<java.version>1.8</java.version>
<cucumber.version>6.6.0</cucumber.version>
<junit.version>4.13</junit.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<port>8358</port>
<cucumber.reporting.version>5.3.1</cucumber.reporting.version>
<cucumber.reporting.config.file>automation-web/src/test/resources/projectname/cucumber-reporting.properties</cucumber.reporting.config.file>
<org.mapstruct.version>1.3.1.Final</org.mapstruct.version>
<h2database.version>1.4.200</h2database.version>
<appium.java.client.version>7.3.0</appium.java.client.version>
<guava.version>29.0-jre</guava.version>
<reporting-plugin.version>4.0.83</reporting-plugin.version>
<commons-text.version>1.9</commons-text.version>
<commons-io.version>2.8.0</commons-io.version>
</properties>
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java8</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- Added beyond original archetype -->
<!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-surefire-plugin -->
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<scope>test</scope>
</dependency>
<!-- To make Wait Until work -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- Cucumber Reporting -->
<dependency>
<groupId>net.masterthought</groupId>
<artifactId>cucumber-reporting</artifactId>
<version>${cucumber.reporting.version}</version>
</dependency>
<dependency>
<groupId>de.monochromata.cucumber</groupId>
<artifactId>reporting-plugin</artifactId>
<version>${reporting-plugin.version}</version>
</dependency>
<!-- For dependency injection https://cucumber.io/docs/cucumber/state/#dependency-injection -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2database.version}</version>
<scope>test</scope>
</dependency>
<!-- To generate getters, setters, equals, hascode, toString methods -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Java client, wrapped by Appium -->
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>${appium.java.client.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-text -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons-text.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<encoding>UTF-8</encoding>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<!-- Added beyond original archetype -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
</plugins>
</build>
</project>
1条答案
按热度按时间f87krz0w1#
你有两个页面类
MyAccountsPage
以及MyProfilePage
. 当两者都延伸时BasePageWeb
因此,任何MyAccountsPage
以及MyProfilePage
也是示例BasePageWeb
它们不是同一个示例!这在一开始可能会很混乱,因为通常每个类只有一个示例,我们将示例和类视为同一事物。不如把类看作一个模板,从中可以生成许多示例。
现在,如果在使用页面之前附加调试器并检查页面,您应该会看到如下内容:
所以当你设置
WebDriver
使用中的步骤AccountsSteps
,的WebDriver is setup in
MyProfile页面but not
myprofilepage`。所以当你尝试使用
ProfileSteps
试图利用MyProfilePage
最终会出现空指针异常,因为WebDriver
在MyProfilePage
从未设置。这里有一些解决方案,但它们都归结为通过
BasePageWeb
使用组合而不是继承的组件。