Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiasstamann committed Oct 25, 2024
2 parents 94089d9 + 1e273a8 commit 403f627
Show file tree
Hide file tree
Showing 22 changed files with 226 additions and 51 deletions.
39 changes: 26 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ By doing that it drastically increases readability of page objects and reduces t
# Features

- generates page object class implementations based on annotated interfaces
- page objects support inheritance - interfaces can extend multiple other interfaces and will inherit all of their elements and methods
- generates extraction data class implementations base on annotated interfaces.
- actions like clicking of elements or writing to input fields can be configured via annotations
- the api enforces creation of a fluent api that improves writing of tests. Doing assertions or executing of custom code is also embedded into this fluent api
Expand Down Expand Up @@ -86,29 +87,36 @@ public interface TestPagePageObject extends PageObjectParent<TestPagePageObject>

static final String DATA_EXTRACTION_FROM_TABLE_XPATH = "//table//tr[contains(@class,'data')]";

@PageObjectElement(elementVariableName=TestPagePageObject.INPUT_FIELD_ID, by = By.ID, value="" )
@PageObjectElement(
elementVariableName = TestPagePageObject.INPUT_FIELD_ID,
by = _By.ID,
value=""
)
static final String INPUT_FIELD_ID = "searchField";

@PageObjectElement(elementVariableName=TestPagePageObject.COUNTER_INCREMENT_BUTTON_ID, by = By.XPATH, value="//fieldset[@name='counter']/input[@type='button']" )
@PageObjectElement(
elementVariableName = TestPagePageObject.COUNTER_INCREMENT_BUTTON_ID,
value = "//fieldset[@name='counter']/input[@type='button']"
)
static final String COUNTER_INCREMENT_BUTTON_ID = "counterIncrementButton";

@ActionMoveToAndClick(COUNTER_INCREMENT_BUTTON_ID)
@Pause(value = 500L)
TestPagePageObject clickCounterIncrementButton();


@ExtractData(by = io.toolisticon.pogen4selenium.api.By.XPATH, value = DATA_EXTRACTION_FROM_TABLE_XPATH)
@ExtractData(value = DATA_EXTRACTION_FROM_TABLE_XPATH)
List<TestPageTableEntry> getTableEntries();

@ExtractData(by = io.toolisticon.pogen4selenium.api.By.XPATH, value = DATA_EXTRACTION_FROM_TABLE_XPATH)
@ExtractData(value = DATA_EXTRACTION_FROM_TABLE_XPATH)
TestPageTableEntry getFirstTableEntry();

@ExtractDataValue(by = By.XPATH, value="//fieldset[@name='counter']/span[@id='counter']")
@ExtractDataValue(value = "//fieldset[@name='counter']/span[@id='counter']")
String getCounter();

// you can always provide your own methods and logic
default String providedGetCounter() {
return getDriver().findElement(org.openqa.selenium.By.xpath("//fieldset[@name='counter']/span[@id='counter']")).getText();
return getDriver().findElement(By.xpath("//fieldset[@name='counter']/span[@id='counter']")).getText();
}

// Custom entry point for starting your tests
Expand All @@ -132,16 +140,17 @@ Extracting table data for our small [example](pogen4selenium-example/src/test/re
@DataObject
public interface TestPageTableEntry {

@ExtractDataValue(by = By.XPATH, value = "./td[1]")
// will use XPATH locator by default if by attribute isn't set explicitly
@ExtractDataValue(by = _By.XPATH, value = "./td[1]")
String name();

@ExtractDataValue(by = By.XPATH, value = "./td[2]")
@ExtractDataValue( value = "./td[2]")
String age();

@ExtractDataValue(by = By.XPATH, value = "./td[3]/a", kind = Kind.ATTRIBUTE, name = "href")
@ExtractDataValue( value = "./td[3]/a", kind = Kind.ATTRIBUTE, name = "href")
String link();

@ExtractDataValue(by = By.XPATH, value = "./td[3]/a", kind = Kind.TEXT)
@ExtractDataValue(, value = "./td[3]/a", kind = Kind.TEXT)
String linkText();

}
Expand Down Expand Up @@ -245,15 +254,19 @@ public class TestPageTest {
There are some default methods provided by the fluent api:

##### verify
By using the verify methods it's possible to do check state of elements, i.e. if elements are present or clickable. Expected state is configured in PageObjectElement annotation. If not set explicitely all elements are expected to be present byx default.
By using the verify methods it's possible to do check state of elements, i.e. if elements are present or clickable. Expected state is configured in PageObjectElement annotation. If not set explicitly all elements are expected to be present by default.

##### doAssertions
It's possible to inline assertions done via your favourite testing tools.
It's possible to inline assertions done via your favorite testing tools.
By providing this method it's not necessary to hassle with local variables anymore.

##### execute
The execute method allows you to do test steps dynamically, like reading data from the web page and doing things based on the extracted data.
It can also be used to switch to another page object type. This can be useful if input data is expected to be validated and should stay on the same page and show an error message.
It can also be used to switch to another page object type. This can be useful if input data is expected to be validated and should stay on the same page and show an error message.

#### pause
It's possible to enforce an explicit pause time by using this method



## Best practices
Expand Down
2 changes: 1 addition & 1 deletion pogen4selenium-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>io.toolisticon.pogen4selenium</groupId>
<artifactId>pogen4selenium</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
</parent>

<name>pogen4selenium-api</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtractData {
By by() default By.XPATH;
_By by() default _By.XPATH;
String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
@Target(METHOD)
public @interface ExtractDataValue {

By by() default By.XPATH;
/** The locator type to use. */
_By by() default _By.XPATH;

/** The locator string used together with locator configured in by. Be sure to use './/', if your relative xpath locator string starts with '//', otherwise the whole document will be scanned. */
String value();

/** The kind of data to extract from element. */
Kind kind() default Kind.TEXT;

/** attribute or property name. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,52 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Defines a page object element.
*
* This annotation must be placed on a final static String constant inside an interface.
* The surrounding interface must not be annotated with a {@link PageObject}, if the interface is used as a parent interface of another page object.
* Otherwise the surrounding interface must be annotated with {@link PageObject}.
*/

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PageObjectElement {

/**
* The element variable name. Must refer to the String based constant that's annotated with this annotation.
* Sorry for doing so. This is the only way to read the constants value inside the annotation.
* @return the value of the annotated constant
*/
String elementVariableName();

By by() default By.XPATH;
/**
* Defines how the element should be located.
* @return The locator method to be used. Defaults to XPATH
*/
_By by() default _By.XPATH;

/**
* Defines the locator string used by the locator method defined in by.
* @return the locator string
*/
String value();

/**
* Defines if and how the element should be verified in verify method.
* @return the verification type used in verify.
*/
VerifyType usedForVerify() default VerifyType.PRESENT;

/**
* The verify types.
*/
enum VerifyType {
/** Skip verification */
NONE,
/** Check if element is present */
PRESENT,
/** Check if element is clickable */
CLICKABLE
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,45 @@
import io.toolisticon.pogen4selenium.runtime.AssertionInterface;
import io.toolisticon.pogen4selenium.runtime.ExecuteBlock;

/**
* The page object parent interface. Must be extended by all interfaces annotated with {@link PageObject}
* @param <PAGEOBJECT> The page object type
*/
public interface PageObjectParent<PAGEOBJECT extends PageObjectParent<PAGEOBJECT>> {

/**
* Verifies all elements marked via {@link PageObjectElement} annotated fields according to the configuration in the annotation.
* @return The next fluent interface
*/
PAGEOBJECT verify();

/**
* Gets the seleium driver.
* @return the selenium driver currently used
*/
WebDriver getDriver();

/**
* Applies a fixed time pause.
* @param duration the duration to pause
* @return next fluent interface
*/
PAGEOBJECT pause(Duration duration);

/**
* Allows to do inline assertions done with your favourite test tools without the need to create local variables to keep state.
* @param function a function containing the custom assertions
* @return next fluent interface
*/
PAGEOBJECT doAssertions(AssertionInterface<PAGEOBJECT> function);

/**
* Allows to do inline execution of custom code.
* This is pretty helpful to for example do actions based on the data currently displayed on current page object.
* @param <OPO> The function allows to return a custom page object type instance
* @param function a function containing the custom code to be executed
* @return the next fluent interface
*/
<OPO extends PageObjectParent<OPO>> OPO execute(ExecuteBlock<PAGEOBJECT, OPO> function);

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
package io.toolisticon.pogen4selenium.api;

public enum By {
/**
* Defines the type of locator method to use.
* Sorry about the name, added '_' up front to prevent a name clash with selenium.
*/
public enum _By {
ID("id"),
XPATH("xpath"),
LINK_TEXT("linkText"),
PARTIAL_LINK_TEXT("partialLinkText"),
NAME("name"),
TAG_NAME("tagName"),
CLASS_NAME("className"),
CSS_SELECTOR("cssSelector"),
/** ELEMENT MUST ONLY BE USED TO EXTRACT DATA IN PAGE OBJECTS */
ELEMENT("");

private final String correspondingByMethodName;

By(String correspondingByMethodName) {
_By(String correspondingByMethodName) {
this.correspondingByMethodName = correspondingByMethodName;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import org.openqa.selenium.WebElement;

import io.toolisticon.pogen4selenium.api.By;
import io.toolisticon.pogen4selenium.api._By;
import io.toolisticon.pogen4selenium.api.ExtractDataValue;

public class DataObjectParentImpl {
Expand All @@ -17,7 +17,7 @@ protected WebElement getRelativeParentWebElement() {
return this.relativeParentWebElement;
}

protected WebElement getValueWebElement(By by, String locatorString) {
protected WebElement getValueWebElement(_By by, String locatorString) {

org.openqa.selenium.By relativeLocator = null;
switch(by) {
Expand All @@ -40,7 +40,7 @@ protected WebElement getValueWebElement(By by, String locatorString) {
return null;
}

protected String getValue(By by, String locatorString, ExtractDataValue.Kind kind, String name) {
protected String getValue(_By by, String locatorString, ExtractDataValue.Kind kind, String name) {
WebElement valueWebElement = this.getValueWebElement(by, locatorString);

if (valueWebElement != null) {
Expand Down
4 changes: 2 additions & 2 deletions pogen4selenium-example/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>io.toolisticon.pogen4selenium</groupId>
<artifactId>pogen4selenium</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
</parent>

<name>pogen4selenium-example</name>
Expand All @@ -33,7 +33,7 @@
<dependency>
<groupId>io.toolisticon.pogen4selenium</groupId>
<artifactId>pogen4selenium-api</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import io.toolisticon.pogen4selenium.api.ActionMoveToAndClick;
import io.toolisticon.pogen4selenium.api.ActionWrite;
import io.toolisticon.pogen4selenium.api.By;
import io.toolisticon.pogen4selenium.api._By;
import io.toolisticon.pogen4selenium.api.ExtractData;
import io.toolisticon.pogen4selenium.api.ExtractDataValue;
import io.toolisticon.pogen4selenium.api.ExtractDataValue.Kind;
Expand All @@ -20,29 +20,29 @@ public interface TestPagePageObject extends PageObjectParent<TestPagePageObject>

static final String DATA_EXTRACTION_FROM_TABLE_XPATH = "//table//tr[contains(@class,'data')]";

@PageObjectElement(elementVariableName=TestPagePageObject.INPUT_FIELD_ID, by = By.ID, value="input_field" )
@PageObjectElement(elementVariableName=TestPagePageObject.INPUT_FIELD_ID, by = _By.ID, value="input_field" )
static final String INPUT_FIELD_ID = "inputField";

@PageObjectElement(elementVariableName=TestPagePageObject.COUNTER_INCREMENT_BUTTON_ID, by = By.XPATH, value="//fieldset[@name='counter']/input[@type='button']" )
@PageObjectElement(elementVariableName=TestPagePageObject.COUNTER_INCREMENT_BUTTON_ID, by = _By.XPATH, value="//fieldset[@name='counter']/input[@type='button']" )
static final String COUNTER_INCREMENT_BUTTON_ID = "counterIncrementButton";


TestPagePageObject writeToInputField(@ActionWrite(INPUT_FIELD_ID) String value);

@ExtractDataValue(by=By.ELEMENT, value = INPUT_FIELD_ID, kind=Kind.ATTRIBUTE, name="value")
@ExtractDataValue(by=_By.ELEMENT, value = INPUT_FIELD_ID, kind=Kind.ATTRIBUTE, name="value")
String readInputFieldValue();

@ActionMoveToAndClick(COUNTER_INCREMENT_BUTTON_ID)
@Pause(value = 500L)
TestPagePageObject clickCounterIncrementButton();

@ExtractData(by = io.toolisticon.pogen4selenium.api.By.XPATH, value = DATA_EXTRACTION_FROM_TABLE_XPATH)
@ExtractData(by = io.toolisticon.pogen4selenium.api._By.XPATH, value = DATA_EXTRACTION_FROM_TABLE_XPATH)
List<TestPageTableEntry> getTableEntries();

@ExtractData(by = io.toolisticon.pogen4selenium.api.By.XPATH, value = DATA_EXTRACTION_FROM_TABLE_XPATH)
@ExtractData(by = io.toolisticon.pogen4selenium.api._By.XPATH, value = DATA_EXTRACTION_FROM_TABLE_XPATH)
TestPageTableEntry getFirstTableEntry();

@ExtractDataValue(by = By.XPATH, value="//fieldset[@name='counter']/span[@id='counter']")
@ExtractDataValue(by = _By.XPATH, value="//fieldset[@name='counter']/span[@id='counter']")
String getCounter();

// you can always provide your own methods and logic
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
package io.toolisticon.pogen4selenium.example;

import io.toolisticon.pogen4selenium.api.By;
import io.toolisticon.pogen4selenium.api._By;
import io.toolisticon.pogen4selenium.api.DataObject;
import io.toolisticon.pogen4selenium.api.ExtractDataValue;
import io.toolisticon.pogen4selenium.api.ExtractDataValue.Kind;

@DataObject
public interface TestPageTableEntry {

@ExtractDataValue(by = By.XPATH, value = "./td[1]")
@ExtractDataValue(by = _By.XPATH, value = "./td[1]")
String name();

@ExtractDataValue(by = By.XPATH, value = "./td[2]")
@ExtractDataValue(by = _By.XPATH, value = "./td[2]")
String age();

@ExtractDataValue(by = By.XPATH, value = "./td[3]/a", kind = Kind.ATTRIBUTE, name = "href")
@ExtractDataValue(by = _By.XPATH, value = "./td[3]/a", kind = Kind.ATTRIBUTE, name = "href")
String link();

@ExtractDataValue(by = By.XPATH, value = "./td[3]/a", kind = Kind.TEXT)
@ExtractDataValue(by = _By.XPATH, value = "./td[3]/a", kind = Kind.TEXT)
String linkText();

}
2 changes: 1 addition & 1 deletion pogen4selenium-processor/dependency-reduced-pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<artifactId>pogen4selenium</artifactId>
<groupId>io.toolisticon.pogen4selenium</groupId>
<version>0.1.1-SNAPSHOT</version>
<version>0.2.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pogen4selenium-processor</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion pogen4selenium-processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>io.toolisticon.pogen4selenium</groupId>
<artifactId>pogen4selenium</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
</parent>

<name>pogen4selenium-processor</name>
Expand Down
Loading

0 comments on commit 403f627

Please sign in to comment.