Day to day collection from work and self learning in QA, middleware, tech support spaces ..
Friday, March 17, 2023
Chosen path for using feature files to call my test daa
Tuesday, March 14, 2023
Using feature files for API test automation - 3
In here the entire user payload is read from the examples table and stored as a Map in the step definition.
This approach is useful when you have a small number of fields in the request body, and you can easily read them from the feature file's data table.
Scenario Outline: Create User Successfully Given the user payload is: | first_name | <first_name> | | last_name | <last_name> | | email | <email> | | mobile_phone | <mobile_phone> | | date_of_birth | <date_of_birth> | | password | <password> | | username | <username> | When I send a <method> request to <path> Then the response status code should be <status_code> Examples: | first_name | last_name | email | mobile_phone | date_of_birth | password | username | method | path | status_code | | John | Doe | john.doe@example.com | 1234567890 | 1990-01-01 | password | johndoe | POST | /users/create | 201 |
public class CreateUserStepDefinitions { private RequestSpecification requestSpec; private Response response; private Map<String, String> userPayload = new HashMap<>(); @Given("the user payload is:") public void the_user_payload_is(Map<String, String> userData) { this.userPayload = userData; } @When("I send a {string} request to {string}") public void i_send_a_request_to(String method, String path) { requestSpec = RestAssured.given() .contentType(ContentType.JSON) .body(userPayload); response = requestSpec.when() .request(method, path); } @Then("the response status code should be {int}") public void the_response_status_code_should_be(int expectedStatusCode) { assertEquals(expectedStatusCode, response.getStatusCode()); } }
Monday, March 13, 2023
Two different flavours of feature files
Format 1 - Scenario outline with example table
Given I have a valid auth token
When I submit a POST request to create a user with the following details:
| first_name | last_name | email | mobile_phone | date_of_birth | password | username |
| <first_name>| <last_name> | <email> | <mobile_phone>| <date_of_birth> | <password> | <username> |
Then the response status code should be <status_code>
Examples:
| first_name | last_name| email | mobile_phone | date_of_birth | password | username | status_code |
| John | Doe | johndoe@example.com | 1234567890 | 1990-01-01 | password123 | johndoe123 | 201 |
| Jane | Smith | janesmith@example.com | 0987654321 | 1985-02-14 | password456 | janesmith12 | 201 |
This format gives a higher level of abstraction where the same scenario is run multiple times with different sets of data. Yes data including the expected result as well.
This format is useful when you don't want to repeat the same scenario. Also when you have a simpler payload.
Format 2 - Single Scenario with Given\When\Then steps
Feature: Create User API
Scenario: Create User Successfully
Given the user payload is:
| first_name | John |
| last_name | Doe |
| email | john.doe@example.com |
| mobile_phone | 1234567890 |
| date_of_birth | 1990-01-01 |
| password | password |
| username | johndoe |
When I send a {method} request to {path}
Then the response status code should be 201
|method | path | status_code |
| POST |/users/create"|201 |
This gives more detailed description of the scenario. Each @Given, @When, @Then step is elaborated with relevant data.
In this case you may be having more specific scenarios and conditions.
Sunday, March 12, 2023
Using feature files for API test autimation - 2
Feature file
Similar to the last post, I'm usinng RestAssured for API test automation. Cucumber is used for mapping the feature scenarios. Also extending AbstractTestNGCucmberTests to run the test suite.
Difference is the way the payload is handled. Instead of reading the payload from exmaple tables, I am loading it from a json file.
This way it gives me more flexibility in managing test data/payloads.
I am still using exmaple tables to read other paramaters via <> tags in my featuer file .
Feature: Create User API
Scenario Outline: Create User Successfully
Given the user payload file is <data_file>
When I send a <method> request to <path>
Then the response status code should be <status_code>
Examples:
| data_file | method | path | status_code |
|/test/java/resources/user_details.json | POST | /user_accounts/create | 201 |
Payload file
{
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"mobile_phone": "1234567890",
"date_of_birth": "1990-01-01",
"password": "p@ssword_1",
"username": "johndoe"
}
CreateUserStep.java
public class CreateUserSteps{
private RequestSpecification requestSpec;
private Response response;
private String requestBody;
@Given("the user payload file is {string}")
public void the_user_payload_file_is(String filePath) {
requestBody = TestDataUtil.getJsonRequestString(filePath);
}
@When("I send a {string} request to {string}")
public void i_send_a_request_to(String method, String path) {
response = requestSpec.contentType(ContentType.JSON)
.body(requestBody)
.when()
.request(method, path);
}
@Then("the response status code should be {int}")
public void the_response_status_code_should_be(int statusCode) {
response.then().statusCode(statusCode);
}
}
Using feature files for API test autimation - 1
I am usinng RestAssured for API test automation. Cucumber is used for mapping the feature scenarios with the implmentation. In other words we have use of gherkin language in feature files and replication of same in stepdefinitions which has my restAuured/java test implementations.
Since I am a fan of TestNG's capabilities I am extending AbstractTestNGCucmberTests.
Feature file
Feature: Create User API
Scenario: Create User Successfully
Given the user payload is:
| first_name | John |
| last_name | Doe |
| email | john.doe@example.com |
| mobile_phone | 1234567890 |
| date_of_birth | 1990-01-01 |
| password | password |
| username | johndoe |
When I send a {method} request to {path}
Then the response status code should be 201
|method | path | status_code |
| POST |/user_account/create"|201 |
StepDefinitions
public class CreateUserSteps {
private RequestSpecification requestSpec;
private Response response;
private String requestBody;
@Given("the user payload is:")
public void the_user_payload_is(Map<String, String> dataTable) {
String requestBody = TestDataUtil.updateJsonRequestString("create_user.json")
.replace("{{first_name}}", dataTable.get("first_name"))
.replace("{{last_name}}", dataTable.get("last_name"))
.replace("{{email}}", dataTable.get("email"))
.replace("{{mobile_phone}}", dataTable.get("mobile_phone"))
.replace("{{date_of_birth}}", dataTable.get("date_of_birth"))
.replace("{{password}}", dataTable.get("password"))
.replace("{{username}}", dataTable.get("username"));
}@When("I send a {string} request to {string}")
public void i_send_a_request_to(String method, String path) {
response = requestSpec.contentType(ContentType.JSON)
.body(requestBody)
.when()
.request(method, path);
}
@Then("the response status code should be {int}")
public void the_response_status_code_should_b(int expectedStatusCode) {
response.then().statusCode(expectedStatusCode);
}
}
Monday, December 12, 2022
Selenium - Configure TestNG to your Selenium framework.
- Go to mvnreposotory.com and type in "testng"in the search bar.
- In the search results, select the latest available for dependency version. At the time of writing this blog I got below 7.6.1 version.
- Copy maven syntax for the dependency, so that we can paste it to our pom.xml.
- Once copied append it to the pom.xml of your maven project (which the framework was being built.
- Accordingly I added below to my pom.xml.
<!-- https://mvnrepository.com/artifact/org.testng/testng --> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>7.6.1</version> <scope>test</scope> </dependency>
Select to install the plugin and you are good.
Thursday, December 1, 2022
TestNG - files structure
<suite> <test name = ""> <classes> <class name="" /> <classes> </test> <test name = ""> <classes> <class name="" /> <classes> </test> </suite>
- Each testNG xml pockets its content within the parent <suite> tags.
- test tags is the next main level and you can have many test tags. These specify the module.
- classes comes next. This helps goup the many classes you have under a certain test module
- class is the smallest element which carries the class name.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <suite parallel="tests" name="ShoppingCart"> <test thread-count ="5" name="Submit Order Tests"> <classes> <class name="learningspace.tests.SubmitOrderTest"/> </classes> </test> <test thread-count ="5" name="Error Validation Tests"> <classes> <class name="learningspace.tests.ErrorValidationTests"/> </classes> </test> </suite>
Sunday, November 20, 2022
Beginners spot - Pushing your work to GitHub
1. Login to your GitHub account and create the repo
2. Go to command prompt;
git config --global user.name "yumani" git config --global user.email "yumani@gmail.com"
3. Navigate to the location of the local repo and use the below commands.
It will initialize the repo, add the files and push to the origin with the comments.
git init
git add *
git status
git commit -m "Adding the initial file set"
git remote add origin https://github.com/yumaniranaweera/snuggery.git
git push origin master
Tuesday, November 15, 2022
Beginners Spot - Setup Maven in Test Automation environment
1) Install Maven (Windows)
- Download from maven website and unzip the file.
- Set MAVEN_HOME - <base dire>\apache-maven-3.8.6
- Add to path - <base dire>\apache-maven-3.8.6\bin
2) Test the installation
- Open cmd and type mvn --version
- Should return maven home, version etc.
3) Create a maven project in Eclipse ( You can use maven-quickstart-archetype)
4) Integrate to Eclipse
- Apply Maven Surefire plugin to Eclipse.
- Go to Maven Surefire plugin page >Usage (https://maven.apache.org/surefire/maven-surefire-plugin/usage.html)
- Copy the plugin management snippet and paste in the pom.xml in your eclipse project.
- <build>
- <pluginManagement>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-surefire-plugin</artifactId>
- <version>3.0.0-M7</version>
- </plugin>
- </plugins>
- </pluginManagement>
- </build>
Paste this above your dependencies section in the pom.xml.
- now you can run mvn clean, mvn test commands.
5) Integrate TestNG
- We need to add an additional configuration to the plugins section in the pom.xml
- Go to https://maven.apache.org/surefire/maven-surefire-plugin/examples/testng.html
- <configuration>
- <suiteXmlFiles>
- <suiteXmlFile>testng.xml</suiteXmlFile>
- </suiteXmlFiles>
- </configuration>
- Also you need to add the TestNG dependency to the pom.xml, if its not already added.
Saturday, November 12, 2022
Selenium - Explicit wait
This post is not about explicit wait concept in Selenium. But about how we could convert standard explicit wait command to an action method.
So our standard command set is like this:
WebDriverWait wait = new WebDriverWait(driver,Duration.ofSeconds(5)); wait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(By.cssSelector(".mb-3")));
public void waitExplicity(By findBy) { WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5)); wait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(findBy)); }
Selenium - POM and Action Methods - 2
Lets check how the below segment can be changed to page object model.
1 2 3 4 5 6 | List<WebElement> products = driver.findElements(By.cssSelector(".mb-3")); WebElement prod =products.stream().filter(product-> product.findElement(By.cssSelector("b")).getText().equals(productName)).findFirst().orElse(null); prod.findElement(By.xpath("//div[@class='card-body']/button[2]")).click(); |
Lets take the web element descriptions first; Here we are locating a products list (findElements by the classname csslocator). Since its a list we make it of the type List<WebElement> (line 1). This is how it is done by using @FindBy (we discussed in part 1)
@FindBy(css = ".mb-3") List<WebElement> products;
Afterwards we can do an action class to findElements. I have called a wait method from the abstract components. You can ignore that for the moment. -- let's not distract ourselves with that in this post peepsies ..
public List<WebElement> getProductList(){ waitExplicity(productBy); return products; }
Next (line 3-4) we go through the list and filter the product which matches the product name that we specify through a String. This is how it is converted to an action method.
public WebElement getProductByName(String productName){ WebElement prod =getProductList().stream().filter(product-> product.findElement(By.cssSelector("b")).getText().equals(productName)).findFirst().orElse(null); return prod; }
Let me add bit of details here; If you look at the line 3 of our first code snippet, the stream filter results are assigned to a variable called prod. How is the prod extracted? By traversing though the products, which is the list of products. With me? ook one step ahead-
How do we get the products from our POM & Action Methos approach? via the getProductList action method.
So that's what we have done above. We have getProductList().stream().filter .... instead previous products.stream().filter ....
Now next is the fun part! See what we do when we do addToCart.
public void addToCart(String productName){ WebElement chosenProduct = getProductByName(productName); chosenProduct.findElement(addtoCartBtn).click(); }
We use getProductByName(productName) and assign the returning WebElement to 'chosenProduct' variable. same concept as above. The list web element is replaced by the action method that we created to get the list webelement.
In the next line we do a findElement from within the chosenProduct context, not driver context (noticed!) This is not POM struff. Its about how Selenium facilitate different locator contexts.
However, we cannot use the elements defined in POM to other context. Only driver context could use them. So we have to use the long By.locator methods or we could defined them separately and use here. See.. notice the line 1 below.
By addtoCartBtn = By.xpath("//div[@class='card-body']/button[2]"); public void addToCart(String productName){ WebElement chosenProduct = getProductByName(productName); chosenProduct.findElement(addtoCartBtn).click(); //You cannot use Page objects when you are using a WebElement context }
So that's about what I learnt on this.. See below for the connected code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @FindBy(css = ".mb-3") List<WebElement> products; //WebElements defined under By type to be used in WebElement context. //You can use POs only in driver context By productBy = By.cssSelector(".mb-3"); By addtoCartBtn = By.xpath("//div[@class='card-body']/button[2]"); public List<WebElement> getProductList(){ waitExplicity(productBy); return products; } public WebElement getProductByName(String productName){ WebElement prod =getProductList().stream().filter(product-> product.findElement(By.cssSelector("b")).getText().equals(productName)).findFirst().orElse(null); return prod; } public void addToCart(String productName){ WebElement chosenProduct = getProductByName(productName); chosenProduct.findElement(addtoCartBtn).click(); } |
Selenium - Page Object Model and Action Methods
How we change this code to PageObjectModel and action classes.
1 2 3 | driver.findElement(By.id("userEmail")).sendKeys("testA1@gmail.com"); driver.findElement(By.id("userPassword")).sendKeys("testA1#"); driver.findElement(By.id("login")).click(); |
Page Objects
We can convert the locators to Page Objects this way
/* Page Objects Model*/ @FindBy(id="userEmail") WebElement useremail; @FindBy(id="userPassword") WebElement userpass; @FindBy (id="login") WebElement login;
How do we do it
1- Use @FindBy from org.openqa.selenium.By in Selenium. Consider this is replacing "driver.findElement(By" in our standard way of locating elements.
2- Next put the locator type; id, css, xpath, classname etc
3- Add the relative value next to the locator type. For an example, if you choose 'id' as the locator type, you should get the 'id' of the element. In my case the id of the element that I chose is 'userEmail'. So I build my line like this; @FindBy(id="userEmail")
4- That's not all; We need to let Selenium know what the web element is. So add this.
WebElement <element>. In my case WebElement username;
5- So it makes up our page objects this way;
@FindBy(id="userEmail")
WebElement userName;
Action Classes
As action we input username, pass and press login button. I will show you how these can be segmented out to an action class;
public void Login(String email, String pass){ useremail.sendKeys(email); userpass.sendKeys(pass); login.click(); }
I think you can easily see how we have used each of the above page objects to action.
eeasy ha !! wait for more cool stuffs peeps :)
Featured
Selenium - Page Object Model and Action Methods
How we change this code to PageObjectModel and action classes. 1 2 3 driver . findElement ( By . id ( "userEmail" )). sendKeys (...
Popular Posts
-
These days I am involved in testing a migration tool which demands in testing the application's migration against several databases. In ...
-
Came across this error while executing an oracle script: ORA-30036: unable to extend segment by 8 in undo tablespace 'UNDO' ORA...
-
Iterator mediator breaks a message from the given xpath pattern and produces smaller messages. If you need to collect an attribute value ...
-
In this scenario we will be monitoring requests and responses passed through a proxy service in WSO2 ESB. The proxy service is calling an in...