├── .github └── workflows │ ├── gradle.yml │ └── maven.yml ├── .gitignore ├── README.md ├── build.gradle ├── circle.yml ├── gradle ├── libraries.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── pom.xml ├── serenity.properties └── src ├── main └── java │ └── starter │ └── README.md └── test ├── java └── starter │ ├── CucumberTestSuite.java │ ├── matchers │ └── TextMatcher.java │ ├── navigation │ ├── DuckDuckGoHomePage.java │ └── NavigateTo.java │ ├── search │ ├── SearchFor.java │ ├── SearchForm.java │ ├── SearchResult.java │ └── SearchResultList.java │ └── stepdefinitions │ └── SearchOnDuckDuckGoStepDefinitions.java └── resources ├── features └── search │ └── search_by_keyword.feature ├── logback-test.xml ├── serenity.conf └── webdriver ├── linux ├── chromedriver └── geckodriver ├── mac ├── chromedriver └── geckodriver └── windows ├── IEDriverServer.exe ├── chromedriver.exe └── geckodriver.exe /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Grant execute permission for gradlew 17 | run: chmod +x gradlew 18 | - name: Build with Gradle 19 | run: ./gradlew test 20 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Build with Maven 17 | run: mvn -B package --file pom.xml 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | build 3 | out 4 | .gradle 5 | pom.xml.tag 6 | pom.xml.releaseBackup 7 | pom.xml.versionsBackup 8 | pom.xml.next 9 | release.properties 10 | dependency-reduced-pom.xml 11 | buildNumber.properties 12 | .mvn/timing.properties 13 | .idea 14 | *.iml 15 | 16 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 17 | !/.mvn/wrapper/maven-wrapper.jar 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting started with Serenity and Cucumber 4 2 | 3 | [![CircleCI](https://circleci.com/gh/serenity-bdd/serenity-cucumber-starter.svg?style=svg)](https://circleci.com/gh/serenity-bdd/serenity-cucumber4-starter) 4 | 5 | Serenity BDD is a library that makes it easier to write high quality automated acceptance tests, with powerful reporting and living documentation features. It has strong support for both web testing with Selenium, and API testing using RestAssured. 6 | 7 | Serenity strongly encourages good test automation design, and supports several design patterns, including classic Page Objects, the newer Lean Page Objects/ Action Classes approach, and the more sophisticated and flexible Screenplay pattern. 8 | 9 | The latest version of Serenity supports both Cucumber 2.4 and the more recent Cucumber 4.x. Cucumber 4 is not backward compatible with Cucumber 2. This article walks you through how to get started with Serenity and Cucumber 4, and also gives you a quick introduction to some of Cucumber 4’s new features. 10 | 11 | ## Get the code 12 | 13 | Git: 14 | 15 | git clone https://github.com/serenity-bdd/serenity-cucumber4-starter.git 16 | cd serenity-cucumber4-starter 17 | 18 | 19 | Or simply [download a zip](https://github.com/serenity-bdd/serenity-cucumber4-starter/archive/master.zip) file. 20 | 21 | ## The starter project 22 | The best place to start with Serenity and Cucumber is to clone or download the starter project on Github ([https://github.com/serenity-bdd/serenity-cucumber4-starter](https://github.com/serenity-bdd/serenity-cucumber4-starter)). This project gives you a basic project setup, along with some sample tests and supporting classes. There are two versions to choose from. The master branch uses a more classic approach, using action classes and lightweight page objects, whereas the **[screenplay](https://github.com/serenity-bdd/serenity-cucumber4-starter/tree/screenplay)** branch shows the same sample test implemented using Screenplay. 23 | 24 | ### The project directory structure 25 | The project has build scripts for both Maven and Gradle, and follows the standard directory structure used in most Serenity projects: 26 | ```Gherkin 27 | src 28 | + main 29 | + test 30 | + java Test runners and supporting code 31 | + resources 32 | + features Feature files 33 | + search Feature file subdirectories
 34 | search_by_keyword.feature 35 | + webdriver Bundled webdriver binaries 36 | + linux 37 | + mac 38 | + windows 39 | chromedriver.exe OS-specific Webdriver binaries 40 | geckodriver.exe 41 | 42 | ``` 43 | 44 | ### Adding the Cucumber 4 dependency 45 | Serenity seamlessly supports both Cucumber 2.x and Cucumber 4. However, this flexibility requires a little tweaking in the build dependencies. 46 | 47 | If you are using Maven, you need to do the following: 48 | - exclude the default `cucumber-core` dependency from your `serenity-core` dependency 49 | - Replace your `serenity-cucumber` dependency with the `serenity-cucumber4` dependency 50 | - Add dependencies on the Cucumber 4.x version of `cucumber-java` and `cucumber-junit` into your project 51 | 52 | An example of the correctly configured dependencies is shown below: 53 | ```xml 54 | 55 | net.serenity-bdd 56 | serenity-core 57 | 2.0.38 58 | test 59 | 60 | 61 | io.cucumber 62 | cucumber-core 63 | 64 | 65 | 66 | 67 | net.serenity-bdd 68 | serenity-cucumber4 69 | 1.0.4 70 | test 71 | 72 | 73 | io.cucumber 74 | cucumber-java 75 | 4.2.0 76 | 77 | 78 | io.cucumber 79 | cucumber-junit 80 | 4.2.0 81 | 82 | ``` 83 | 84 | If you are using Gradle, you need to ensure that the 4.x version of `cucumber-core` is used using the _resolutionStrategy_ element, and also add the Cucumber 4.x version of `cucumber-java` and `cucumber-junit` dependencies as mentioned above: 85 | ```Gradle 86 | configurations.all { 87 | resolutionStrategy { 88 | force "io.cucumber:cucumber-core:4.2.0" 89 | } 90 | } 91 | 92 | dependencies { 93 | testCompile "net.serenity-bdd:serenity-core:2.0.38", 94 | "net.serenity-bdd:serenity-cucumber4:1.0.4", 95 | "io.cucumber:cucumber-core:4.2.0", 96 | "io.cucumber:cucumber-junit:4.2.0" 97 | } 98 | ``` 99 | 100 | In the rest of this article, we will walk through some of the highlights of both versions. Let’s start off with the version on the master branch, which uses lightweight page objects and actions. 101 | 102 | ## The Cucumber 4 sample scenario 103 | Both variations of the sample project uses the sample Cucumber scenario. In this scenario, Sergey (who likes to search for stuff) is performing a search on the DuckDuckGo search engine: 104 | 105 | ```Gherkin 106 | Feature: Search by keyword 107 | 108 | Scenario: Searching for a term 109 | Given Sergey is on the DuckDuckGo home page 110 | When he searches for "cucumber" 111 | Then all the result titles should contain the word "cucumber" 112 | ``` 113 | 114 | This scenario lets us explore a few of the new Cucumber 4 expressions. Cucumber 4 supports both the classic regular expressions, and the new _Cucumber Expressions_, which are more readable albeit not as powerful in some cases. 115 | 116 | The glue code for this scenario uses both regular expressions and cucumber expressions. The glue code looks this this: 117 | 118 | ```java 119 | @Given("^(?:.*) is on the DuckDuckGo home page") 120 | public void i_am_on_the_DuckDuckGo_home_page() { 121 | navigateTo.theDuckDuckGoHomePage(); 122 | } 123 | 124 | @When("(s)he searches for {string}") 125 | public void i_search_for(String term) { 126 | searchFor.term(term); 127 | } 128 | 129 | @Then("all the result titles should contain the word {string}") 130 | public void all_the_result_titles_should_contain_the_word(String term) { 131 | assertThat(searchResult.titles()) 132 | .matches(results -> results.size() > 0) 133 | .allMatch(title -> 134 | textOf(title).containsIgnoringCase(term)); 135 | } 136 | ``` 137 | 138 | The `@Given` step uses a regular expression; the action class approach we use here is action-centric, not actor-centric, so we ignore the name of the actor. 139 | 140 | The `@When` and `@Then` steps uses Cucumber expressions, and highlights two useful features. Rather than using a regular expression to match the search term, we use the more readable Cucumber expression _{string}_. This matches a single or double-quoted string (the quotes themselves are dropped). Cucumber 4 also supports other typed expressions, such as _{int}_, _{word}_, and _ {float}_. 141 | 142 | Parentheses can be used to indicate optional text, so _“(s)he”_ will match both “he” and “she”. We could also write this using a slash: _“she/he”_. 143 | 144 | ### Lean Page Objects and Action Classes 145 | The glue code shown above uses Serenity step libraries as _action classes_ to make the tests easier to read, and to improve maintainability. 146 | 147 | These classes are declared using the Serenity `@Steps` annotation, shown below: 148 | ```java 149 | @Steps 150 | NavigateTo navigateTo; 151 | 152 | @Steps 153 | SearchFor searchFor; 154 | 155 | @Steps 156 | SearchResult searchResult; 157 | ``` 158 | 159 | The `@Steps`annotation tells Serenity to create a new instance of the class, and inject any other steps or page objects that this instance might need. 160 | 161 | Each action class models a particular facet of user behaviour: navigating to a particular page, performing a search, or retrieving the results of a search. These classes are designed to be small and self-contained, which makes them more stable and easier to maintain. 162 | 163 | The `NavigateTo` class is an example of a very simple action class. In a larger application, it might have some other methods related to high level navigation, but in our sample project, it just needs to open the DuckDuckGo home page: 164 | ```java 165 | public class NavigateTo { 166 | 167 | DuckDuckGoHomePage duckDuckGoHomePage; 168 | 169 | @Step("Open the DuckDuckGo home page") 170 | public void theDuckDuckGoHomePage() { 171 | duckDuckGoHomePage.open(); 172 | } 173 | } 174 | ``` 175 | 176 | It does this using a standard Serenity Page Object. Page Objects are often very minimal, storing just the URL of the page itself: 177 | ```java 178 | @DefaultUrl("https://duckduckgo.com") 179 | class DuckDuckGoHomePage extends PageObject {} 180 | ``` 181 | 182 | The second class, `SearchFor`, is an interaction class. It needs to interact with the web page, and to enable this, we make the class extend the Serenity `UIInteractionSteps`. This gives the class full access to the powerful Serenity WebDriver API, including the `$()` method used below, which locates a web element using a `By` locator or an XPath or CSS expression: 183 | ```java 184 | public class SearchFor extends UIInteractionSteps { 185 | 186 | @Step("Search for term {0}") 187 | public void term(String term) { 188 | $(SearchForm.SEARCH_FIELD).clear(); 189 | $(SearchForm.SEARCH_FIELD).type(term); 190 | $(SearchForm.SEARCH_BUTTON).click(); 191 | } 192 | } 193 | ``` 194 | 195 | The `SearchForm` class is typical of a light-weight Page Object: it is responsible uniquely for locating elements on the page, and it does this by defining locators or occasionally by resolving web elements dynamically. 196 | ```java 197 | class SearchForm { 198 | static By SEARCH_FIELD = By.cssSelector(".js-search-input"); 199 | static By SEARCH_BUTTON = By.cssSelector(".js-search-button"); 200 | } 201 | ``` 202 | 203 | The last step library class used in the step definition code is the `SearchResult` class. The job of this class is to query the web page, and retrieve a list of search results that we can use in the AssertJ assertion at the end of the test. This class also extends `UIInteractionSteps` and 204 | ```java 205 | public class SearchResult extends UIInteractionSteps { 206 | public List titles() { 207 | return findAll(SearchResultList.RESULT_TITLES) 208 | .stream() 209 | .map(WebElementFacade::getTextContent) 210 | .collect(Collectors.toList()); 211 | } 212 | } 213 | ``` 214 | 215 | The `SearchResultList` class is a lean Page Object that locates the search result titles on the results page: 216 | ```java 217 | class SearchResultList { 218 | static By RESULT_TITLES = By.cssSelector(".result__title"); 219 | } 220 | ``` 221 | 222 | The main advantage of the approach used in this example is not in the lines of code written, although Serenity does reduce a lot of the boilerplate code that you would normally need to write in a web test. The real advantage is in the use of many small, stable classes, each of which focuses on a single job. This application of the _Single Responsibility Principle_ goes a long way to making the test code more stable, easier to understand, and easier to maintain. 223 | 224 | ## The Screenplay starter project 225 | If you prefer to use the Screenplay pattern, or want to try it out, check out the _screenplay_ branch instead of the _master_ branch. In this version of the starter project, the same scenario is implemented using the Screenplay pattern. 226 | 227 | The Screenplay pattern describes tests in terms of actors and the tasks they perform. Tasks are represented as objects performed by an actor, rather than methods. This makes them more flexible and composable, at the cost of being a bit more wordy. Here is an example: 228 | ```java 229 | @Before 230 | public void setTheStage() { 231 | OnStage.setTheStage(new OnlineCast()); 232 | } 233 | 234 | @Given("^(.*) is on the DuckDuckGo home page") 235 | public void on_the_DuckDuckGo_home_page(String actor) { 236 | theActorCalled(actor).attemptsTo(
 NavigateTo.theDuckDuckGoHomePage()
 ); 237 | } 238 | 239 | @When("she/he searches for {string}") 240 | public void search_for(String term) { 241 | theActorInTheSpotlight().attemptsTo( 242 | SearchFor.term(term) 
 ); 243 | } 244 | 245 | @Then("all the result titles should contain the word {string}") 246 | public void all_the_result_titles_should_contain_the_word(String term) { 247 | theActorInTheSpotlight().should( 248 | seeThat("search result titles", 249 | SearchResult.titles(),
 hasSize(greaterThan(0))), 250 | seeThat("search result titles", 251 | SearchResult.titles(),
 everyItem(containsIgnoringCase(term))) 252 | ); 253 | } 254 | ``` 255 | 256 | In both approaches, the Page Objects very close or identical. The differences are mainly in the action classes. Screenplay classes emphasise reusable components and a very readable declarative style, whereas Lean Page Objects and Action Classes opt for a more imperative style. 257 | 258 | The `NavigateTo` class performs the same role as it’s equivalent in the Lean Page Object/Action Class version, and looks quite similar: 259 | ```java 260 | public class NavigateTo { 261 | 262 | public static Performable theDuckDuckGoHomePage() { 263 | return Task.where("{0} opens the DuckDuckGo home page", 264 | Open.browserOn().the(DuckDuckGoHomePage.class) 265 | ); 266 | } 267 | } 268 | ``` 269 | 270 | The `SearchFor` class is also similar: it is shown below: 271 | ```java 272 | public class SearchFor { 273 | 274 | public static Performable term(String term) { 275 | return Task.where("{0} attempts to search for #term", 276 | Clear.field(SearchForm.SEARCH_FIELD),
 Enter.theValue(term).into(SearchForm.SEARCH_FIELD), 277 | Click.on(SearchForm.SEARCH_BUTTON) 278 | ).with("term").of(term); 279 | } 280 | } 281 | ``` 282 | 283 | In Screenplay, there is a clear distinction between actions (which change the system state) and questions (which read the system state). In Screenplay, we fetch the search results using a Question class, like this: 284 | ```java 285 | public class SearchResult { 286 | public static Question> titles() { 287 | return actor -> 288 | TextContent.of(SearchResultList.RESULT_TITLES) 289 | .viewedBy(actor) 290 | .asList(); 291 | } 292 | } 293 | ``` 294 | 295 | The Screenplay DSL is rich and flexible, and well suited to teams working on large test automation projects with many team members, and who are reasonably comfortable with Java and design patterns. The Lean Page Objects/Action Classes approach proposes a gentler learning curve, but still provides significant advantages in terms of maintainability and reusability. 296 | 297 | ## Executing the tests 298 | To run the sample project, you can either just run the `CucumberTestSuite` test runner class, or run either `mvn verify` or `gradle test` from the command line. 299 | 300 | By default, the tests will run using Chrome. You can run them in Firefox by overriding the `driver` system property, e.g. 301 | ```json 302 | $ mvn clean verify -Ddriver=firefox 303 | ``` 304 | Or 305 | ```json 306 | $ gradle clean test -Pdriver=firefox 307 | ``` 308 | 309 | The test results will be recorded in the `target/site/serenity` directory. 310 | 311 | ## Simplified WebDriver configuration and other Serenity extras 312 | The sample projects both use some Serenity features which make configuring the tests easier. In particular, Serenity uses the `serenity.conf` file in the `src/test/resources` directory to configure test execution options. 313 | ### Webdriver configuration 314 | The WebDriver configuration is managed entirely from this file, as illustrated below: 315 | ```java 316 | webdriver { 317 | driver = chrome 318 | } 319 | headless.mode = true 320 | 321 | chrome.switches="""--start-maximized;--test-type;--no-sandbox;--ignore-certificate-errors; 322 | --disable-popup-blocking;--disable-default-apps;--disable-extensions-file-access-check; 323 | --incognito;--disable-infobars,--disable-gpu""" 324 | 325 | ``` 326 | 327 | The project also bundles some of the WebDriver binaries that you need to run Selenium tests in the `src/test/resources/webdriver` directories. These binaries are configured in the `drivers` section of the `serenity.conf` config file: 328 | ```json 329 | drivers { 330 | windows { 331 | webdriver.chrome.driver = "src/test/resources/webdriver/windows/chromedriver.exe" 332 | webdriver.gecko.driver = "src/test/resources/webdriver/windows/geckodriver.exe" 333 | } 334 | mac { 335 | webdriver.chrome.driver = "src/test/resources/webdriver/mac/chromedriver" 336 | webdriver.gecko.driver = "src/test/resources/webdriver/mac/geckodriver" 337 | } 338 | linux { 339 | webdriver.chrome.driver = "src/test/resources/webdriver/linux/chromedriver" 340 | webdriver.gecko.driver = "src/test/resources/webdriver/linux/geckodriver" 341 | } 342 | } 343 | ``` 344 | This configuration means that development machines and build servers do not need to have a particular version of the WebDriver drivers installed for the tests to run correctly. 345 | 346 | ### Environment-specific configurations 347 | We can also configure environment-specific properties and options, so that the tests can be run in different environments. Here, we configure three environments, __dev__, _staging_ and _prod_, with different starting URLs for each: 348 | ```json 349 | environments { 350 | default { 351 | webdriver.base.url = "https://duckduckgo.com" 352 | } 353 | dev { 354 | webdriver.base.url = "https://duckduckgo.com/dev" 355 | } 356 | staging { 357 | webdriver.base.url = "https://duckduckgo.com/staging" 358 | } 359 | prod { 360 | webdriver.base.url = "https://duckduckgo.com/prod" 361 | } 362 | } 363 | ``` 364 | 365 | You use the `environment` system property to determine which environment to run against. For example to run the tests in the staging environment, you could run: 366 | ```json 367 | $ mvn clean verify -Denvironment=staging 368 | ``` 369 | 370 | See [**this article**](https://johnfergusonsmart.com/environment-specific-configuration-in-serenity-bdd/) for more details about this feature. 371 | 372 | ## Want to learn more? 373 | For more information about Serenity BDD, you can read the [**Serenity BDD Book**](https://serenity-bdd.github.io/theserenitybook/latest/index.html), the official online Serenity documentation source. Other sources include: 374 | * **[Byte-sized Serenity BDD](https://www.youtube.com/channel/UCav6-dPEUiLbnu-rgpy7_bw/featured)** - tips and tricks about Serenity BDD 375 | * [**Serenity BDD Blog**](https://johnfergusonsmart.com/category/serenity-bdd/) - regular articles about Serenity BDD 376 | * [**The Serenity BDD Dojo**](https://serenitydojo.teachable.com) - Online training on Serenity BDD and on test automation and BDD in general. 377 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | defaultTasks 'clean','test','aggregate' 2 | 3 | repositories { 4 | mavenLocal() 5 | jcenter() 6 | } 7 | 8 | buildscript { 9 | repositories { 10 | mavenLocal() 11 | jcenter() 12 | } 13 | dependencies { 14 | classpath("net.serenity-bdd:serenity-gradle-plugin:2.1.13") 15 | } 16 | } 17 | 18 | apply plugin: 'java' 19 | apply plugin: 'eclipse' 20 | apply plugin: 'idea' 21 | apply plugin: 'net.serenity-bdd.aggregator' 22 | apply from: "$rootDir/gradle/libraries.gradle" 23 | 24 | 25 | sourceCompatibility = 1.8 26 | targetCompatibility = 1.8 27 | 28 | /** 29 | * This is needed to make sure there are no Cucumber 2 dependencies in the classpath. 30 | */ 31 | configurations.all { 32 | resolutionStrategy { 33 | force "io.cucumber:cucumber-core:${cucumberVersion}" 34 | } 35 | } 36 | dependencies { 37 | compile libs.logback 38 | 39 | testCompile libs.test.cucumber.java, 40 | libs.test.cucumber.junit, 41 | libs.test.serenity.core, 42 | libs.test.serenity.screenplay, 43 | libs.test.serenity.junit, 44 | libs.test.serenity.screenplayWebdriver, 45 | libs.test.serenity.cucumber, 46 | libs.test.junit, 47 | libs.test.assertj 48 | } 49 | 50 | test { 51 | testLogging.showStandardStreams = true 52 | systemProperties System.getProperties() 53 | } 54 | 55 | gradle.startParameter.continueOnFailure = true 56 | 57 | test.finalizedBy(aggregate) 58 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | # Java Maven CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-java/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | - image: circleci/openjdk:11.0.2-jdk-stretch-node-browsers-legacy 10 | 11 | # Specify service dependencies here if necessary 12 | # CircleCI maintains a library of pre-built images 13 | # documented at https://circleci.com/docs/2.0/circleci-images/ 14 | # - image: circleci/postgres:9.4 15 | 16 | working_directory: ~/repo 17 | 18 | environment: 19 | # Customize the JVM maximum heap limit 20 | MAVEN_OPTS: -Xmx3200m 21 | 22 | steps: 23 | - checkout 24 | 25 | # Download and cache dependencies 26 | - restore_cache: 27 | keys: 28 | - v1-dependencies-{{ checksum "pom.xml" }} 29 | # fallback to using the latest cache if no exact match is found 30 | - v1-dependencies- 31 | 32 | - run: mvn dependency:go-offline 33 | 34 | - save_cache: 35 | paths: 36 | - ~/.m2 37 | key: v1-dependencies-{{ checksum "pom.xml" }} 38 | 39 | # run tests! 40 | - run: mvn verify 41 | -------------------------------------------------------------------------------- /gradle/libraries.gradle: -------------------------------------------------------------------------------- 1 | ext{ 2 | slf4jVersion = '1.7.7' 3 | serenityCoreVersion = '2.1.13' 4 | serenityCucumberVersion = '2.1.2' 5 | junitVersion='4.12' 6 | assertJVersion='3.8.0' 7 | logbackVersion='1.2.3' 8 | cucumberVersion = '4.8.0' 9 | 10 | libs = [ 11 | slf4jApi: "org.slf4j:slf4j-api:$slf4jVersion", 12 | logback: "ch.qos.logback:logback-classic:${logbackVersion}", 13 | 14 | test: [ 15 | serenity: [ 16 | core: "net.serenity-bdd:serenity-core:${serenityCoreVersion}", 17 | junit: "net.serenity-bdd:serenity-junit:${serenityCoreVersion}", 18 | screenplay: "net.serenity-bdd:serenity-screenplay:${serenityCoreVersion}", 19 | screenplayWebdriver: "net.serenity-bdd:serenity-screenplay-webdriver:${serenityCoreVersion}", 20 | cucumber: "net.serenity-bdd:serenity-cucumber4:${serenityCucumberVersion}", 21 | ], 22 | cucumber: [ 23 | java: "io.cucumber:cucumber-core:${cucumberVersion}", 24 | java: "io.cucumber:cucumber-java:${cucumberVersion}", 25 | junit: "io.cucumber:cucumber-junit:${cucumberVersion}" 26 | ], 27 | junit: "junit:junit:${junitVersion}", 28 | assertj: "org.assertj:assertj-core:${assertJVersion}" 29 | ] 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serenity-bdd/serenity-cucumber4-starter/cff36f96dca32bf536f2e8450853e634d56c350b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Feb 03 16:07:35 IST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip 7 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | net.serenitybdd.starter 6 | serenity-cucumber4-smoketests 7 | 1.0.0-SNAPSHOT 8 | jar 9 | 10 | Sample Serenity BDD project using Cucumber 11 | 12 | 13 | UTF-8 14 | 2.1.13 15 | 2.1.13 16 | 2.1.2 17 | 4.8.0 18 | UTF-8 19 | 20 | 4 21 | 22 | 23 | 24 | 25 | 26 | 27 | false 28 | 29 | central 30 | bintray 31 | https://jcenter.bintray.com 32 | 33 | 34 | 35 | 36 | 37 | false 38 | 39 | central 40 | bintray-plugins 41 | https://jcenter.bintray.com 42 | 43 | 44 | 45 | 46 | 47 | ch.qos.logback 48 | logback-classic 49 | 1.0.13 50 | 51 | 55 | 56 | net.serenity-bdd 57 | serenity-core 58 | ${serenity.version} 59 | test 60 | 61 | 62 | io.cucumber 63 | cucumber-core 64 | 65 | 66 | 67 | 68 | io.cucumber 69 | cucumber-java 70 | ${cucumber.version} 71 | 72 | 73 | io.cucumber 74 | cucumber-junit 75 | ${cucumber.version} 76 | 77 | 78 | net.serenity-bdd 79 | serenity-junit 80 | ${serenity.version} 81 | test 82 | 83 | 84 | net.serenity-bdd 85 | serenity-screenplay 86 | ${serenity.version} 87 | test 88 | 89 | 90 | net.serenity-bdd 91 | serenity-screenplay-webdriver 92 | ${serenity.version} 93 | test 94 | 95 | 96 | net.serenity-bdd 97 | serenity-cucumber4 98 | ${serenity.cucumber.version} 99 | test 100 | 101 | 102 | junit 103 | junit 104 | 4.12 105 | test 106 | 107 | 108 | org.assertj 109 | assertj-core 110 | 3.6.2 111 | test 112 | 113 | 114 | org.hamcrest 115 | hamcrest-all 116 | 1.3 117 | test 118 | 119 | 120 | 121 | 122 | 123 | org.apache.maven.plugins 124 | maven-surefire-plugin 125 | 2.22.1 126 | 127 | true 128 | 129 | 130 | 131 | maven-failsafe-plugin 132 | 2.22.1 133 | 134 | 135 | **/*Test.java 136 | **/Test*.java 137 | **/*TestSuite.java 138 | **/When*.java 139 | 140 | 141 | ${webdriver.base.url} 142 | 143 | classes 144 | ${parallel.tests} 145 | ${parallel.tests} 146 | 147 | 148 | 149 | 150 | integration-test 151 | verify 152 | 153 | 154 | 155 | 156 | 157 | org.apache.maven.plugins 158 | maven-compiler-plugin 159 | 3.8.0 160 | 161 | 1.8 162 | 1.8 163 | 164 | 165 | 166 | net.serenity-bdd.maven.plugins 167 | serenity-maven-plugin 168 | ${serenity.maven.version} 169 | 170 | ${tags} 171 | 172 | 173 | 174 | net.serenity-bdd 175 | serenity-core 176 | ${serenity.version} 177 | 178 | 179 | 180 | 181 | serenity-reports 182 | post-integration-test 183 | 184 | aggregate 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /serenity.properties: -------------------------------------------------------------------------------- 1 | serenity.project.name=Serenity and Cucumber 4 Quick Start -------------------------------------------------------------------------------- /src/main/java/starter/README.md: -------------------------------------------------------------------------------- 1 | ### Application code 2 | 3 | These packages generally contain application code. 4 | If you are writing a reusable test library, you can also place reusable test components such as Page Objects or Tasks here. -------------------------------------------------------------------------------- /src/test/java/starter/CucumberTestSuite.java: -------------------------------------------------------------------------------- 1 | package starter; 2 | 3 | import io.cucumber.junit.CucumberOptions; 4 | import net.serenitybdd.cucumber.CucumberWithSerenity; 5 | import org.junit.runner.RunWith; 6 | 7 | @RunWith(CucumberWithSerenity.class) 8 | @CucumberOptions( 9 | plugin = {"pretty"}, 10 | features = "src/test/resources/features" 11 | ) 12 | public class CucumberTestSuite {} 13 | -------------------------------------------------------------------------------- /src/test/java/starter/matchers/TextMatcher.java: -------------------------------------------------------------------------------- 1 | package starter.matchers; 2 | 3 | public class TextMatcher { 4 | private String text; 5 | 6 | public TextMatcher(String text) { 7 | this.text = text; 8 | } 9 | 10 | public static TextMatcher textOf(String text) { 11 | return new TextMatcher(text); 12 | } 13 | 14 | public boolean containsIgnoringCase(String expectedText) { 15 | return text.toLowerCase().contains(expectedText.toLowerCase()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/starter/navigation/DuckDuckGoHomePage.java: -------------------------------------------------------------------------------- 1 | package starter.navigation; 2 | 3 | import net.serenitybdd.core.pages.PageObject; 4 | import net.thucydides.core.annotations.DefaultUrl; 5 | 6 | @DefaultUrl("https://duckduckgo.com") 7 | class DuckDuckGoHomePage extends PageObject {} 8 | -------------------------------------------------------------------------------- /src/test/java/starter/navigation/NavigateTo.java: -------------------------------------------------------------------------------- 1 | package starter.navigation; 2 | 3 | import net.thucydides.core.annotations.Step; 4 | 5 | public class NavigateTo { 6 | 7 | DuckDuckGoHomePage duckDuckGoHomePage; 8 | 9 | @Step("Open the DuckDuckGo home page") 10 | public void theDuckDuckGoHomePage() { 11 | duckDuckGoHomePage.open(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/starter/search/SearchFor.java: -------------------------------------------------------------------------------- 1 | package starter.search; 2 | 3 | import net.serenitybdd.core.steps.UIInteractionSteps; 4 | import net.thucydides.core.annotations.Step; 5 | 6 | public class SearchFor extends UIInteractionSteps { 7 | 8 | @Step("Search for term {0}") 9 | public void term(String term) { 10 | $(SearchForm.SEARCH_FIELD).clear(); 11 | $(SearchForm.SEARCH_FIELD).type(term); 12 | $(SearchForm.SEARCH_BUTTON).click(); 13 | } 14 | 15 | @Step("Search for term {0}") 16 | public void termOnSearchResultsPage(String term) { 17 | $(SearchForm.SEARCH_FIELD_ON_RESULT_PAGE).clear(); 18 | $(SearchForm.SEARCH_FIELD_ON_RESULT_PAGE).type(term); 19 | $(SearchForm.SEARCH_BUTTON_ON_RESULT_PAGE).click(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/starter/search/SearchForm.java: -------------------------------------------------------------------------------- 1 | package starter.search; 2 | 3 | import org.openqa.selenium.By; 4 | 5 | class SearchForm { 6 | static By SEARCH_FIELD = By.name("q"); 7 | static By SEARCH_BUTTON = By.cssSelector(".search__button"); 8 | static By SEARCH_FIELD_ON_RESULT_PAGE = By.cssSelector("#search_form_input"); 9 | static By SEARCH_BUTTON_ON_RESULT_PAGE = By.cssSelector("#search_button"); 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/starter/search/SearchResult.java: -------------------------------------------------------------------------------- 1 | package starter.search; 2 | 3 | import net.serenitybdd.core.pages.WebElementFacade; 4 | import net.serenitybdd.core.steps.UIInteractionSteps; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | public class SearchResult extends UIInteractionSteps { 10 | public List titles() { 11 | return findAll(SearchResultList.RESULT_TITLES) 12 | .stream() 13 | .map(WebElementFacade::getTextContent) 14 | .filter(text -> !text.equalsIgnoreCase("Ad")) 15 | .collect(Collectors.toList()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/starter/search/SearchResultList.java: -------------------------------------------------------------------------------- 1 | package starter.search; 2 | 3 | import org.openqa.selenium.By; 4 | 5 | class SearchResultList { 6 | static By RESULT_TITLES = By.cssSelector("#links .result__title a:nth-child(1)"); 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/starter/stepdefinitions/SearchOnDuckDuckGoStepDefinitions.java: -------------------------------------------------------------------------------- 1 | package starter.stepdefinitions; 2 | 3 | import io.cucumber.java.en.Given; 4 | import io.cucumber.java.en.When; 5 | import io.cucumber.java.en.Then; 6 | import net.thucydides.core.annotations.Steps; 7 | import starter.navigation.NavigateTo; 8 | import starter.search.SearchFor; 9 | import starter.search.SearchResult; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | import static starter.matchers.TextMatcher.textOf; 13 | 14 | public class SearchOnDuckDuckGoStepDefinitions { 15 | 16 | @Steps 17 | NavigateTo navigateTo; 18 | 19 | @Steps 20 | SearchFor searchFor; 21 | 22 | @Steps 23 | SearchResult searchResult; 24 | 25 | @Given("^(?:.*) is on the DuckDuckGo home page") 26 | public void i_am_on_the_DuckDuckGo_home_page() { 27 | navigateTo.theDuckDuckGoHomePage(); 28 | } 29 | 30 | @When("^s?he (?:searches|has searched) for \"(.*)\"") 31 | public void i_search_for(String term) { 32 | searchFor.term(term); 33 | } 34 | 35 | 36 | @When("^s?he (?:searches|has searched) again for \"(.*)\"") 37 | public void i_search_again_for(String term) { 38 | searchFor.termOnSearchResultsPage(term); 39 | } 40 | 41 | @Then("all the result titles should contain the word {string}") 42 | public void all_the_result_titles_should_contain_the_word(String expectedTerm) { 43 | assertThat(searchResult.titles()) 44 | .allMatch(title -> textOf(title).containsIgnoringCase(expectedTerm)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/resources/features/search/search_by_keyword.feature: -------------------------------------------------------------------------------- 1 | Feature: Search by keyword 2 | 3 | Scenario: Searching for a term 4 | Given Sergey is on the DuckDuckGo home page 5 | When he searches for "Cucumber" 6 | Then all the result titles should contain the word "Cucumber" 7 | 8 | Scenario: Refining a search using two terms 9 | Given Sergey is on the DuckDuckGo home page 10 | And he has searched for "Cucumber" 11 | When he searches again for "zucchini" 12 | Then all the result titles should contain the word "zucchini" -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/test/resources/serenity.conf: -------------------------------------------------------------------------------- 1 | webdriver { 2 | driver = chrome 3 | use.driver.service.pool = false 4 | } 5 | headless.mode = true 6 | 7 | # 8 | # Chrome options can be defined using the chrome.switches property 9 | # 10 | chrome.switches="""--start-maximized;--test-type;--no-sandbox;--ignore-certificate-errors; 11 | --disable-popup-blocking;--disable-default-apps;--disable-extensions-file-access-check; 12 | --incognito;--disable-infobars,--disable-gpu""" 13 | # 14 | # Define drivers for different platforms. Serenity will automatically pick the correct driver for the current platform 15 | # 16 | drivers { 17 | windows { 18 | webdriver.chrome.driver = "src/test/resources/webdriver/windows/chromedriver.exe" 19 | webdriver.gecko.driver = "src/test/resources/webdriver/windows/geckodriver.exe" 20 | webdriver.ie.driver = "src/test/resources/webdriver/windows/IEDriverServer.exe" 21 | } 22 | mac { 23 | webdriver.chrome.driver = "src/test/resources/webdriver/mac/chromedriver" 24 | webdriver.gecko.driver = "src/test/resources/webdriver/mac/geckodriver" 25 | } 26 | linux { 27 | webdriver.chrome.driver = "src/test/resources/webdriver/linux/chromedriver" 28 | webdriver.gecko.driver = "src/test/resources/webdriver/linux/geckodriver" 29 | } 30 | } 31 | 32 | # 33 | # This section defines environment-specific configuration for different environments. 34 | # You can define normal Serenity properties, such as webdriver.base.url, or custom ones 35 | # You can find more details about this feature at https://johnfergusonsmart.com/environment-specific-configuration-in-serenity-bdd/ 36 | # 37 | 38 | environments { 39 | default { 40 | webdriver.base.url = "https://duckduckgo.com" 41 | } 42 | dev { 43 | webdriver.base.url = "https://duckduckgo.com/dev" 44 | } 45 | staging { 46 | webdriver.base.url = "https://duckduckgo.com/staging" 47 | } 48 | prod { 49 | webdriver.base.url = "https://duckduckgo.com/prod" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/resources/webdriver/linux/chromedriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serenity-bdd/serenity-cucumber4-starter/cff36f96dca32bf536f2e8450853e634d56c350b/src/test/resources/webdriver/linux/chromedriver -------------------------------------------------------------------------------- /src/test/resources/webdriver/linux/geckodriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serenity-bdd/serenity-cucumber4-starter/cff36f96dca32bf536f2e8450853e634d56c350b/src/test/resources/webdriver/linux/geckodriver -------------------------------------------------------------------------------- /src/test/resources/webdriver/mac/chromedriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serenity-bdd/serenity-cucumber4-starter/cff36f96dca32bf536f2e8450853e634d56c350b/src/test/resources/webdriver/mac/chromedriver -------------------------------------------------------------------------------- /src/test/resources/webdriver/mac/geckodriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serenity-bdd/serenity-cucumber4-starter/cff36f96dca32bf536f2e8450853e634d56c350b/src/test/resources/webdriver/mac/geckodriver -------------------------------------------------------------------------------- /src/test/resources/webdriver/windows/IEDriverServer.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serenity-bdd/serenity-cucumber4-starter/cff36f96dca32bf536f2e8450853e634d56c350b/src/test/resources/webdriver/windows/IEDriverServer.exe -------------------------------------------------------------------------------- /src/test/resources/webdriver/windows/chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serenity-bdd/serenity-cucumber4-starter/cff36f96dca32bf536f2e8450853e634d56c350b/src/test/resources/webdriver/windows/chromedriver.exe -------------------------------------------------------------------------------- /src/test/resources/webdriver/windows/geckodriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serenity-bdd/serenity-cucumber4-starter/cff36f96dca32bf536f2e8450853e634d56c350b/src/test/resources/webdriver/windows/geckodriver.exe --------------------------------------------------------------------------------