diff --git a/pogen4selenium-api/pom.xml b/pogen4selenium-api/pom.xml index ccc0919..6457656 100644 --- a/pogen4selenium-api/pom.xml +++ b/pogen4selenium-api/pom.xml @@ -9,7 +9,7 @@ io.toolisticon.pogen4selenium pogen4selenium - 0.4.1 + 0.5.0 pogen4selenium-api diff --git a/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/Action.java b/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/Action.java new file mode 100644 index 0000000..dfae2f1 --- /dev/null +++ b/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/Action.java @@ -0,0 +1,20 @@ +package io.toolisticon.pogen4selenium.api; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Meta-Annotation to allow custom annotations. + * + */ + +@Documented +@Retention(RUNTIME) +@Target(ANNOTATION_TYPE) +public @interface Action { + +} diff --git a/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/ActionClick.java b/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/ActionClick.java index d153aca..c7d9be3 100644 --- a/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/ActionClick.java +++ b/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/ActionClick.java @@ -7,6 +7,16 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) +@Action public @interface ActionClick { + + /** + * The locator type to use. Can be ELEMENT for using a generated element or any kind of locator provided by selenium. + * @return the locator to use. + */ + _By by() default _By.ELEMENT; + + /** The locator string to use. */ String value(); + } diff --git a/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/ActionMoveToAndClick.java b/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/ActionMoveToAndClick.java index ca11aaf..7b3b274 100644 --- a/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/ActionMoveToAndClick.java +++ b/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/ActionMoveToAndClick.java @@ -7,6 +7,16 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) +@Action public @interface ActionMoveToAndClick { + + /** + * The locator type to use. Can be ELEMENT for using a generated element or any kind of locator provided by selenium. + * @return the locator to use. + */ + _By by() default _By.ELEMENT; + + /** The locator string to use. */ String value(); + } diff --git a/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/ActionWrite.java b/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/ActionWrite.java index 8d89ef7..88bce1b 100644 --- a/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/ActionWrite.java +++ b/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/api/ActionWrite.java @@ -7,6 +7,16 @@ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) +@Action public @interface ActionWrite { + + /** + * The locator type to use. Can be ELEMENT for using a generated element or any kind of locator provided by selenium. + * @return the locator to use. + */ + _By by() default _By.ELEMENT; + + /** The locator string to use. */ String value(); + } diff --git a/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/runtime/PageObjectParentImpl.java b/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/runtime/PageObjectParentImpl.java index 14b239c..6dcd357 100644 --- a/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/runtime/PageObjectParentImpl.java +++ b/pogen4selenium-api/src/main/java/io/toolisticon/pogen4selenium/runtime/PageObjectParentImpl.java @@ -87,38 +87,50 @@ protected void waitUntilUrl(String urlRegex) { wait.until(ExpectedConditions.urlMatches(urlRegex)); } - protected void waitForElementToBeClickable(String xpath) { + + protected void waitForElementToBeInteractable(String xpath) { + waitForElementToBeInteractable(By.xpath(xpath)); + } + + protected WebElement waitForElementToBeInteractable(By by) { Wait wait = new FluentWait<>(driver) .withTimeout(Duration.ofSeconds(15)) .pollingEvery(Duration.ofMillis(300)) .ignoring(ElementNotInteractableException.class); - wait.until(ExpectedConditions.elementToBeClickable(By.xpath(xpath))); + return wait.until(ExpectedConditions.elementToBeClickable(by)); } - protected void waitForElementToBeClickable(WebElement element) { + protected WebElement waitForElementToBeInteractable(WebElement element) { + if(element == null) { + return null; + } + Wait wait = new FluentWait<>(driver) .withTimeout(Duration.ofSeconds(15)) .pollingEvery(Duration.ofMillis(300)) - .ignoring(NullPointerException.class); + .ignoring(ElementNotInteractableException.class); - wait.until(ExpectedConditions.elementToBeClickable(element)); + return wait.until(ExpectedConditions.elementToBeClickable(element)); } protected void waitForElementToBePresent(String xpath) { + waitForElementToBePresent(By.xpath(xpath)); + } + + protected WebElement waitForElementToBePresent(By by) { Wait wait = new FluentWait<>(driver) .withTimeout(Duration.ofSeconds(15)) .pollingEvery(Duration.ofMillis(300)) .ignoring(NoSuchElementException.class); - wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath(xpath))); + return wait.until(ExpectedConditions.presenceOfElementLocated(by)); + } - - protected void waitForElementToBeAbsent(String xpath) { Wait wait = new FluentWait<>(driver) diff --git a/pogen4selenium-example/pom.xml b/pogen4selenium-example/pom.xml index 5031b16..e704001 100644 --- a/pogen4selenium-example/pom.xml +++ b/pogen4selenium-example/pom.xml @@ -9,7 +9,7 @@ io.toolisticon.pogen4selenium pogen4selenium - 0.4.1 + 0.5.0 pogen4selenium-example @@ -33,7 +33,7 @@ io.toolisticon.pogen4selenium pogen4selenium-api - 0.4.1 + 0.5.0 diff --git a/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/withoutpagefactory/TestPagePageObject.java b/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/withoutpagefactory/TestPagePageObject.java new file mode 100644 index 0000000..eb31500 --- /dev/null +++ b/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/withoutpagefactory/TestPagePageObject.java @@ -0,0 +1,53 @@ +package io.toolisticon.pogen4selenium.example.withoutpagefactory; + +import java.util.List; + +import org.openqa.selenium.WebDriver; + +import io.toolisticon.pogen4selenium.api.ActionMoveToAndClick; +import io.toolisticon.pogen4selenium.api.ActionWrite; +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; +import io.toolisticon.pogen4selenium.api.PageObject; +import io.toolisticon.pogen4selenium.api.PageObjectElement; +import io.toolisticon.pogen4selenium.api.PageObjectParent; +import io.toolisticon.pogen4selenium.api.Pause; + +@PageObject +public interface TestPagePageObject extends PageObjectParent{ + + static final String DATA_EXTRACTION_FROM_TABLE_XPATH = "//table//tr[contains(@class,'data')]"; + + + TestPagePageObject writeToInputField(@ActionWrite(by=_By.ID, value="input_field") String value); + + @ExtractDataValue(by=_By.ID, value = "input_field", kind=Kind.ATTRIBUTE, name="value") + String readInputFieldValue(); + + @ActionMoveToAndClick(by=_By.XPATH, value = "//fieldset[@name='counter']/input[@type='button']") + @Pause(value = 500L) + TestPagePageObject clickCounterIncrementButton(); + + @ExtractData(by = io.toolisticon.pogen4selenium.api._By.XPATH, value = DATA_EXTRACTION_FROM_TABLE_XPATH) + List getTableEntries(); + + @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']") + 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(); + } + + // Custom entry point for starting your tests + public static TestPagePageObject init(WebDriver driver) { + driver.get("http://localhost:9090/start"); + return new TestPagePageObjectImpl(driver); + } + +} diff --git a/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/withoutpagefactory/TestPageTableEntry.java b/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/withoutpagefactory/TestPageTableEntry.java new file mode 100644 index 0000000..f3b1092 --- /dev/null +++ b/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/withoutpagefactory/TestPageTableEntry.java @@ -0,0 +1,23 @@ +package io.toolisticon.pogen4selenium.example.withoutpagefactory; + +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]") + String name(); + + @ExtractDataValue(by = _By.XPATH, value = "./td[2]") + String age(); + + @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) + String linkText(); + +} diff --git a/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/withpagefactory/TestPagePageObject.java b/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/withpagefactory/TestPagePageObject.java new file mode 100644 index 0000000..bc14112 --- /dev/null +++ b/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/withpagefactory/TestPagePageObject.java @@ -0,0 +1,62 @@ +package io.toolisticon.pogen4selenium.example.withpagefactory; + +import java.util.List; + +import org.openqa.selenium.WebDriver; + +import io.toolisticon.pogen4selenium.api.ActionMoveToAndClick; +import io.toolisticon.pogen4selenium.api.ActionWrite; +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; +import io.toolisticon.pogen4selenium.api.PageObject; +import io.toolisticon.pogen4selenium.api.PageObjectElement; +import io.toolisticon.pogen4selenium.api.PageObjectParent; +import io.toolisticon.pogen4selenium.api.Pause; + +@PageObject +public interface TestPagePageObject extends PageObjectParent{ + + 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" ) + static final String INPUT_FIELD_ID = "inputField"; + + @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"; + + @PageObjectElement(elementVariableName=TestPagePageObject.DATA_EXTRACTION_FROM_TABLE_ID, by = _By.XPATH, value=DATA_EXTRACTION_FROM_TABLE_XPATH ) + static final String DATA_EXTRACTION_FROM_TABLE_ID= "dataExtractionFromTable"; + + TestPagePageObject writeToInputField(@ActionWrite(INPUT_FIELD_ID) String 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.ELEMENT, value = DATA_EXTRACTION_FROM_TABLE_ID) + List getTableEntries(); + + @ExtractData(by = io.toolisticon.pogen4selenium.api._By.ELEMENT, value = DATA_EXTRACTION_FROM_TABLE_ID) + TestPageTableEntry getFirstTableEntry(); + + + @ExtractDataValue(by = _By.XPATH, 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(); + } + + // Custom entry point for starting your tests + public static TestPagePageObject init(WebDriver driver) { + driver.get("http://localhost:9090/start"); + return new TestPagePageObjectImpl(driver); + } + +} diff --git a/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/withpagefactory/TestPageTableEntry.java b/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/withpagefactory/TestPageTableEntry.java new file mode 100644 index 0000000..3877378 --- /dev/null +++ b/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/withpagefactory/TestPageTableEntry.java @@ -0,0 +1,23 @@ +package io.toolisticon.pogen4selenium.example.withpagefactory; + +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]") + String name(); + + @ExtractDataValue(by = _By.XPATH, value = "./td[2]") + String age(); + + @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) + String linkText(); + +} diff --git a/pogen4selenium-example/src/test/java/io/toolisticon/pogen4selenium/example/withoutpagefactory/TestPageTest.java b/pogen4selenium-example/src/test/java/io/toolisticon/pogen4selenium/example/withoutpagefactory/TestPageTest.java new file mode 100644 index 0000000..fabacee --- /dev/null +++ b/pogen4selenium-example/src/test/java/io/toolisticon/pogen4selenium/example/withoutpagefactory/TestPageTest.java @@ -0,0 +1,136 @@ +package io.toolisticon.pogen4selenium.example.withoutpagefactory; + +import java.time.Duration; +import java.util.List; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.edge.EdgeDriver; + +import io.toolisticon.pogen4selenium.example.JettyServer; +import io.toolisticon.pogen4selenium.example.withoutpagefactory.TestPagePageObject; +import io.toolisticon.pogen4selenium.example.withoutpagefactory.TestPageTableEntry; + +public class TestPageTest { + + + private WebDriver webDriver; + private JettyServer jettyServer; + + @Before + public void init() throws Exception{ + + jettyServer = new JettyServer(); + jettyServer.start(); + + webDriver = new EdgeDriver(); + webDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); + } + + @After + public void cleanup() throws Exception{ + webDriver.quit(); + jettyServer.stop(); + } + + @Test + public void extractDatasetsTest() { + TestPagePageObject.init(webDriver) + .doAssertions(e -> { + + // Do assertions here + List results = e.getTableEntries(); + + MatcherAssert.assertThat(results, Matchers.hasSize(2)); + + MatcherAssert.assertThat(results.get(0).name(), Matchers.is("Max")); + MatcherAssert.assertThat(results.get(0).age(), Matchers.is("9")); + MatcherAssert.assertThat(results.get(0).link(), Matchers.is("https://de.wikipedia.org/wiki/Max_und_Moritz")); + MatcherAssert.assertThat(results.get(0).linkText(), Matchers.is("Max und Moritz Wikipedia")); + + + MatcherAssert.assertThat(results.get(1).name(), Matchers.is("Moritz")); + MatcherAssert.assertThat(results.get(1).age(), Matchers.is("10")); + MatcherAssert.assertThat(results.get(1).link(), Matchers.is("https://de.wikipedia.org/wiki/Wilhelm_Busch")); + MatcherAssert.assertThat(results.get(1).linkText(), Matchers.is("Wilhelm Busch Wikipedia")); + + + }) + ; + } + + @Test + public void extractFirstDatasetTest() { + TestPagePageObject.init(webDriver) + .doAssertions(e -> { + + // Do assertions here + TestPageTableEntry result = e.getFirstTableEntry(); + + + MatcherAssert.assertThat(result.name(), Matchers.is("Max")); + MatcherAssert.assertThat(result.age(), Matchers.is("9")); + MatcherAssert.assertThat(result.link(), Matchers.is("https://de.wikipedia.org/wiki/Max_und_Moritz")); + MatcherAssert.assertThat(result.linkText(), Matchers.is("Max und Moritz Wikipedia")); + + + + }) + ; + } + + @Test + public void extractFirstDatasetFromElementTest() { + TestPagePageObject.init(webDriver) + .doAssertions(e -> { + + // Do assertions here + TestPageTableEntry result = e.getFirstTableEntry(); + + + MatcherAssert.assertThat(result.name(), Matchers.is("Max")); + MatcherAssert.assertThat(result.age(), Matchers.is("9")); + MatcherAssert.assertThat(result.link(), Matchers.is("https://de.wikipedia.org/wiki/Max_und_Moritz")); + MatcherAssert.assertThat(result.linkText(), Matchers.is("Max und Moritz Wikipedia")); + + + + }) + ; + } + + @Test + public void incrementCounterTest() { + TestPagePageObject.init(webDriver) + .doAssertions(e -> { + MatcherAssert.assertThat(e.getCounter(), Matchers.is("1")); + }).clickCounterIncrementButton() + .doAssertions(e -> { + MatcherAssert.assertThat(e.getCounter(), Matchers.is("2")); + }) + .clickCounterIncrementButton() + .clickCounterIncrementButton() + .doAssertions(e -> { + MatcherAssert.assertThat(e.getCounter(), Matchers.is("4")); + }) + ; + } + + @Test + public void writeToAndReadFromInputField() { + + TestPagePageObject.init(webDriver) + .writeToInputField("TEST!!!") + .pause(Duration.ofMillis(200L)) + .doAssertions(e -> { + MatcherAssert.assertThat(e.readInputFieldValue(), Matchers.is("TEST!!!")); + }); + + } + + +} diff --git a/pogen4selenium-example/src/test/java/io/toolisticon/pogen4selenium/example/TestPageTest.java b/pogen4selenium-example/src/test/java/io/toolisticon/pogen4selenium/example/withpagefactory/TestPageTest.java similarity index 84% rename from pogen4selenium-example/src/test/java/io/toolisticon/pogen4selenium/example/TestPageTest.java rename to pogen4selenium-example/src/test/java/io/toolisticon/pogen4selenium/example/withpagefactory/TestPageTest.java index 16cb6bf..fe6bd39 100644 --- a/pogen4selenium-example/src/test/java/io/toolisticon/pogen4selenium/example/TestPageTest.java +++ b/pogen4selenium-example/src/test/java/io/toolisticon/pogen4selenium/example/withpagefactory/TestPageTest.java @@ -1,4 +1,4 @@ -package io.toolisticon.pogen4selenium.example; +package io.toolisticon.pogen4selenium.example.withpagefactory; import java.time.Duration; import java.util.List; @@ -11,6 +11,8 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.edge.EdgeDriver; +import io.toolisticon.pogen4selenium.example.JettyServer; + public class TestPageTest { @@ -41,24 +43,20 @@ public void extractDatasetsTest() { // Do assertions here List results = e.getTableEntries(); - MatcherAssert.assertThat(results, Matchers.hasSize(2)); + MatcherAssert.assertThat(results, Matchers.hasSize(1)); MatcherAssert.assertThat(results.get(0).name(), Matchers.is("Max")); MatcherAssert.assertThat(results.get(0).age(), Matchers.is("9")); MatcherAssert.assertThat(results.get(0).link(), Matchers.is("https://de.wikipedia.org/wiki/Max_und_Moritz")); MatcherAssert.assertThat(results.get(0).linkText(), Matchers.is("Max und Moritz Wikipedia")); - - MatcherAssert.assertThat(results.get(1).name(), Matchers.is("Moritz")); - MatcherAssert.assertThat(results.get(1).age(), Matchers.is("10")); - MatcherAssert.assertThat(results.get(1).link(), Matchers.is("https://de.wikipedia.org/wiki/Wilhelm_Busch")); - MatcherAssert.assertThat(results.get(1).linkText(), Matchers.is("Wilhelm Busch Wikipedia")); - }) ; } + + @Test public void extractFirstDatasetTest() { TestPagePageObject.init(webDriver) @@ -79,6 +77,8 @@ public void extractFirstDatasetTest() { ; } + + @Test public void incrementCounterTest() { TestPagePageObject.init(webDriver) diff --git a/pogen4selenium-integrationTest/.factorypath b/pogen4selenium-integrationTest/.factorypath new file mode 100644 index 0000000..3b80346 --- /dev/null +++ b/pogen4selenium-integrationTest/.factorypath @@ -0,0 +1,5 @@ + + + + + diff --git a/pogen4selenium-processor/dependency-reduced-pom.xml b/pogen4selenium-processor/dependency-reduced-pom.xml index de3e3be..d9ae552 100644 --- a/pogen4selenium-processor/dependency-reduced-pom.xml +++ b/pogen4selenium-processor/dependency-reduced-pom.xml @@ -3,7 +3,7 @@ pogen4selenium io.toolisticon.pogen4selenium - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT 4.0.0 pogen4selenium-processor diff --git a/pogen4selenium-processor/pom.xml b/pogen4selenium-processor/pom.xml index ead17e7..2ad787d 100644 --- a/pogen4selenium-processor/pom.xml +++ b/pogen4selenium-processor/pom.xml @@ -9,7 +9,7 @@ io.toolisticon.pogen4selenium pogen4selenium - 0.4.1 + 0.5.0 pogen4selenium-processor diff --git a/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/ExtractDataWrapperExtension.java b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/ExtractDataWrapperExtension.java index 4b14a9a..d56caec 100644 --- a/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/ExtractDataWrapperExtension.java +++ b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/ExtractDataWrapperExtension.java @@ -12,6 +12,7 @@ import io.toolisticon.aptk.tools.wrapper.TypeElementWrapper; import io.toolisticon.pogen4selenium.api.DataObject; import io.toolisticon.pogen4selenium.api.ExtractData; +import io.toolisticon.pogen4selenium.api._By; public class ExtractDataWrapperExtension { @@ -46,6 +47,30 @@ static TypeMirrorWrapper getReturnExtractDataTypeMirror(ExtractDataWrapper dataT } + @CustomCodeMethod(ExtractData.class) + public static String getFinalMethodCall(ExtractDataWrapper dataToExtractWrapper) { + + if(dataToExtractWrapper.by() == _By.ELEMENT) { + + String singleValue = "new " + dataToExtractWrapper.getExtractedDataImplName() +"(" + dataToExtractWrapper.value() + "Element)"; + if (isList(dataToExtractWrapper)) { + return "java.util.Arrays.asList(" + singleValue + ");"; + } else { + return singleValue + ";"; + } + + } else { + + if (isList(dataToExtractWrapper)) { + return "getDriver().findElements(By." + dataToExtractWrapper.by().getCorrespondingByMethodName() + "(\""+ dataToExtractWrapper.value() + "\")).stream().map(" + dataToExtractWrapper.getExtractedDataImplName() + "::new).collect(Collectors.toList());"; + } else { + return "new " + dataToExtractWrapper.getExtractedDataImplName() +"(getDriver().findElement(By." + dataToExtractWrapper.by().getCorrespondingByMethodName() + "(\"" + dataToExtractWrapper.value() + "\")));"; + } + + } + + } + @CustomCodeMethod(ExtractData.class) @DeclareCompilerMessage(code = "ERROR_002", enumValueName = "ERROR_RETURN_TYPE_MUST_BE_ANNOTATED_WITH_DATATOEXTRACT", message = "Return type of method annotated with {0} must be annotated with {1} or a List with component type annotated with {1}", processorClass = PageObjectProcessor.class) public static boolean validate(ExtractDataWrapper dataToExtractWrapper) { diff --git a/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/MethodsToImplementHelper.java b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/MethodsToImplementHelper.java index b03140a..a5426a4 100644 --- a/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/MethodsToImplementHelper.java +++ b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/MethodsToImplementHelper.java @@ -1,6 +1,5 @@ package io.toolisticon.pogen4selenium.processor.pageobject; -import java.lang.annotation.Annotation; import java.time.Duration; import java.util.HashSet; import java.util.List; @@ -8,17 +7,18 @@ import java.util.Set; import java.util.stream.Collectors; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + import org.openqa.selenium.interactions.Actions; -import io.toolisticon.aptk.tools.TypeMirrorWrapper; +import io.toolisticon.aptk.tools.wrapper.ElementWrapper; import io.toolisticon.aptk.tools.wrapper.ExecutableElementWrapper; import io.toolisticon.aptk.tools.wrapper.TypeElementWrapper; -import io.toolisticon.pogen4selenium.api.ActionClick; -import io.toolisticon.pogen4selenium.api.ActionMoveToAndClick; -import io.toolisticon.pogen4selenium.api.ActionWrite; -import io.toolisticon.pogen4selenium.api.ExtractData; +import io.toolisticon.aptk.tools.wrapper.VariableElementWrapper; +import io.toolisticon.pogen4selenium.api.Action; import io.toolisticon.pogen4selenium.api.PageObject; -import io.toolisticon.pogen4selenium.api.PageObjectParent; +import io.toolisticon.pogen4selenium.processor.pageobject.actions.ActionWrapper; public class MethodsToImplementHelper { @@ -54,23 +54,28 @@ public Set getImports() { return imports; } - public List getElementsToWriteStrings() { - return this.executableElementWrapper.getParameters().stream() - .filter(e -> e.hasAnnotation((Class) ActionWrite.class)) - .map(e -> new ElementsToWrite(ActionWriteWrapper.wrap(e.unwrap()).value(), e.getSimpleName())) - .collect(Collectors.toList()); - } - - public Optional getElementToClick() { - return this.executableElementWrapper.hasAnnotation(ActionClick.class) ? - Optional.of(ActionClickWrapper.wrap(this.executableElementWrapper.unwrap()).value()) - : Optional.empty(); + public List getActions() { + + // Must get annotations by Meta annotation Action + List actions = getActionsForElement(this.executableElementWrapper); + + List annotatedParameters = this.executableElementWrapper.getParameters(); + + for (VariableElementWrapper annotatedParameter : annotatedParameters) { + actions.addAll(getActionsForElement(annotatedParameter)); + } + + return actions; } - public Optional getElementToMoveToAndClick() { - return this.executableElementWrapper.hasAnnotation(ActionMoveToAndClick.class) ? - Optional.of(ActionMoveToAndClickWrapper.wrap(this.executableElementWrapper.unwrap()).value()) - : Optional.empty(); + private static List getActionsForElement(ElementWrapper element) { + return element.getAnnotations().stream() + .filter(e -> e.asElement().hasAnnotation(Action.class)) + .map(e -> { + + return new ActionWrapper(TypeElementWrapper.wrap((TypeElement)(e.getAnnotationType().asElement())).getQualifiedName(), element.unwrap()); + }) + .collect(Collectors.toList()); } public Optional getExtractData() { diff --git a/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionClickHandler.java b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionClickHandler.java new file mode 100644 index 0000000..7e63038 --- /dev/null +++ b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionClickHandler.java @@ -0,0 +1,40 @@ +package io.toolisticon.pogen4selenium.processor.pageobject.actions; + +import java.util.Collections; +import java.util.Set; + +import javax.lang.model.element.Element; + +import io.toolisticon.pogen4selenium.api.ActionClick; +import io.toolisticon.pogen4selenium.api.ActionWrite; +import io.toolisticon.pogen4selenium.api._By; +import io.toolisticon.pogen4selenium.processor.pageobject.ActionClickWrapper; +import io.toolisticon.pogen4selenium.processor.pageobject.ActionWriteWrapper; +import io.toolisticon.spiap.api.SpiService; + +@SpiService(ActionHandler.class) +public class ActionClickHandler implements ActionHandler { + + @Override + public String getSupportedActionAnnotationClassFqn() { + return ActionClick.class.getCanonicalName(); + } + + @Override + public String generateCode(Element element) { + ActionClickWrapper wrapper = ActionClickWrapper.wrap(element); + + if (wrapper.by() == _By.ELEMENT) { + return "waitForElementToBeInteractable( " + wrapper.value() + "Element).click();\n"; + } else { + return "waitForElementToBeInteractable( By." + wrapper.by().getCorrespondingByMethodName() + "(\"" + wrapper.value() +"\")).click();\n"; + } + + } + + @Override + public Set getImports(Element element) { + return Collections.emptySet(); + } + +} diff --git a/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionHandler.java b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionHandler.java new file mode 100644 index 0000000..3d37da7 --- /dev/null +++ b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionHandler.java @@ -0,0 +1,25 @@ +package io.toolisticon.pogen4selenium.processor.pageobject.actions; + +import java.lang.annotation.Annotation; +import java.util.Set; + +import javax.lang.model.element.Element; + +import io.toolisticon.spiap.api.Spi; + + +/** + * Service provider interface to bind implementations for actions. + */ + +@Spi +public interface ActionHandler { + + String getSupportedActionAnnotationClassFqn(); + + public String generateCode(Element element); + + public Set getImports(Element element); + + +} diff --git a/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionMoveAndClickHandler.java b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionMoveAndClickHandler.java new file mode 100644 index 0000000..41949d3 --- /dev/null +++ b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionMoveAndClickHandler.java @@ -0,0 +1,39 @@ +package io.toolisticon.pogen4selenium.processor.pageobject.actions; + +import java.util.Collections; +import java.util.Set; + +import javax.lang.model.element.Element; + +import io.toolisticon.pogen4selenium.api.ActionMoveToAndClick; +import io.toolisticon.pogen4selenium.api._By; +import io.toolisticon.pogen4selenium.processor.pageobject.ActionClickWrapper; +import io.toolisticon.pogen4selenium.processor.pageobject.ActionMoveToAndClickWrapper; +import io.toolisticon.spiap.api.SpiService; + +@SpiService(ActionHandler.class) +public class ActionMoveAndClickHandler implements ActionHandler { + + @Override + public String getSupportedActionAnnotationClassFqn() { + return ActionMoveToAndClick.class.getCanonicalName(); + } + + @Override + public String generateCode(Element element) { + ActionMoveToAndClickWrapper wrapper = ActionMoveToAndClickWrapper.wrap(element); + + if (wrapper.by() == _By.ELEMENT) { + return "new Actions(getDriver()).moveToElement(waitForElementToBeInteractable( " + wrapper.value() + "Element)).pause(300).click().build().perform();\n"; + } else { + return "new Actions(getDriver()).moveToElement(waitForElementToBeInteractable( By." + wrapper.by().getCorrespondingByMethodName() + "(\"" + wrapper.value() +"\"))).pause(300).click().build().perform();\n"; + } + + } + + @Override + public Set getImports(Element element) { + return Collections.emptySet(); + } + +} diff --git a/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionWrapper.java b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionWrapper.java new file mode 100644 index 0000000..54eb722 --- /dev/null +++ b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionWrapper.java @@ -0,0 +1,52 @@ +package io.toolisticon.pogen4selenium.processor.pageobject.actions; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.lang.model.element.Element; + +import io.toolisticon.pogen4selenium.processor.pageobject.PageObjectProcessor; + +public class ActionWrapper { + + private final String annotationTypeFqn; + private final Element annotatedElement; + + private final ActionHandler actionHandler; + + static { + // enforce classloader of processor + ActionHandlerServiceLocator.setClassLoaderToUse(PageObjectProcessor.class.getClassLoader()); + } + + public ActionWrapper(String annotationTypeFqn, Element annotatedElement) { + super(); + + this.annotationTypeFqn = annotationTypeFqn; + this.annotatedElement = annotatedElement; + + this.actionHandler = locateActionHandler(annotationTypeFqn); + + } + + static ActionHandler locateActionHandler (String annotationTypeFqn) { + + List matchingHandlers = ActionHandlerServiceLocator.locateAll().stream().filter(e -> annotationTypeFqn.equals(e.getSupportedActionAnnotationClassFqn())).collect(Collectors.toList()); + + return !matchingHandlers.isEmpty() ? matchingHandlers.get(0) : new DefaultActionHandler(); + + } + + + public String generateCode() { + return actionHandler.generateCode(annotatedElement); + } + + public Set getImports() { + return actionHandler.getImports(annotatedElement); + } + + + +} diff --git a/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionWriteHandler.java b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionWriteHandler.java new file mode 100644 index 0000000..4d80bbb --- /dev/null +++ b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/ActionWriteHandler.java @@ -0,0 +1,41 @@ +package io.toolisticon.pogen4selenium.processor.pageobject.actions; + +import java.util.Collections; +import java.util.Set; + +import javax.lang.model.element.Element; + +import io.toolisticon.pogen4selenium.api.ActionWrite; +import io.toolisticon.pogen4selenium.api._By; +import io.toolisticon.pogen4selenium.processor.pageobject.ActionWriteWrapper; +import io.toolisticon.spiap.api.SpiService; + +@SpiService(ActionHandler.class) +public class ActionWriteHandler implements ActionHandler { + + @Override + public String getSupportedActionAnnotationClassFqn() { + return ActionWrite.class.getCanonicalName(); + } + + @Override + public String generateCode(Element element) { + ActionWriteWrapper wrapper = ActionWriteWrapper.wrap(element); + + + if (wrapper.by() == _By.ELEMENT) { + return "writeToElement((" + wrapper.value() + "Element), " + element.getSimpleName() + ");"; + } else { + return "writeToElement(waitForElementToBePresent(By." + wrapper.by().getCorrespondingByMethodName() + "(\"" + wrapper.value() + "\")), " + element.getSimpleName() + ");"; + } + + + + } + + @Override + public Set getImports(Element element) { + return Collections.emptySet(); + } + +} diff --git a/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/DefaultActionHandler.java b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/DefaultActionHandler.java new file mode 100644 index 0000000..b8c6df7 --- /dev/null +++ b/pogen4selenium-processor/src/main/java/io/toolisticon/pogen4selenium/processor/pageobject/actions/DefaultActionHandler.java @@ -0,0 +1,26 @@ +package io.toolisticon.pogen4selenium.processor.pageobject.actions; + +import java.util.Collections; +import java.util.Set; + +import javax.lang.model.element.Element; + +public class DefaultActionHandler implements ActionHandler { + + @Override + public String getSupportedActionAnnotationClassFqn() { + // not needed... + return null; + } + + @Override + public String generateCode(Element element) { + return ""; + } + + @Override + public Set getImports(Element element) { + return Collections.emptySet(); + } + +} diff --git a/pogen4selenium-processor/src/main/resources/PageObject.tpl b/pogen4selenium-processor/src/main/resources/PageObject.tpl index a3ae7cb..10deed2 100644 --- a/pogen4selenium-processor/src/main/resources/PageObject.tpl +++ b/pogen4selenium-processor/src/main/resources/PageObject.tpl @@ -42,7 +42,7 @@ public class ${ toImplementHelper.implementationClassName } ${toImplementHelper. !{for element : pageObject.value}!{if element.usedForVerify.name == 'PRESENT'} waitForElementToBePresent(${element.locatorConstantName}); !{elseif element.usedForVerify.name == 'CLICKABLE'} - waitForElementToBeClickable(${element.locatorConstantName}); + waitForElementToBeInteractable(${element.locatorConstantName}); !{/if}!{/for} return !{if toImplementHelper.hasTypeParameters}(PAGEOBJECT)!{/if} this; } @@ -53,28 +53,15 @@ public class ${ toImplementHelper.implementationClassName } ${toImplementHelper. public ${method.methodSignature}{ pause(Duration.ofMillis(${method.beforePause}L)); - - // Elements to write to -!{for toWrite : method.elementsToWriteStrings} - writeToElement(${toWrite.elementVarName}Element, ${toWrite.toWriteParameterName}); + +!{for action : method.actions} + ${action.generateCode} !{/for} -!{if method.getElementToClick.isPresent} - // Button to click - ${method.getElementToClick.get}Element.click(); -!{/if} -!{if method.getElementToMoveToAndClick.isPresent} - // Move to Element and click - new Actions(getDriver()).moveToElement(${method.getElementToMoveToAndClick.get}Element).pause(300).click().build().perform(); -!{/if} !{if method.getExtractDataValue.isPresent} return ${method.getExtractDataValue.get.getFinalMethodCall} !{elseif method.getExtractData.isPresent} -!{if method.getExtractData.get.isList} - return getDriver().findElements(By.${method.getExtractData.get.by.correspondingByMethodName}("${method.getExtractData.get.value}")).stream().map( ${method.getExtractData.get.extractedDataImplName}::new).collect(Collectors.toList()); -!{else} - return new ${method.getExtractData.get.extractedDataImplName}(getDriver().findElement(By.${method.getExtractData.get.by.correspondingByMethodName}("${method.getExtractData.get.value}"))); -!{/if} + return ${method.getExtractData.get.getFinalMethodCall} !{else} return new ${method.getNextImplClassName}(getDriver()).pause(Duration.ofMillis(${method.afterPause}L)); !{/if} diff --git a/pogen4selenium-processor/src/test/java/io/toolisticon/pogen4selenium/processor/pageobject/PageObjectProcessorTest.java b/pogen4selenium-processor/src/test/java/io/toolisticon/pogen4selenium/processor/pageobject/PageObjectProcessorTest.java index ace30a8..c155c80 100644 --- a/pogen4selenium-processor/src/test/java/io/toolisticon/pogen4selenium/processor/pageobject/PageObjectProcessorTest.java +++ b/pogen4selenium-processor/src/test/java/io/toolisticon/pogen4selenium/processor/pageobject/PageObjectProcessorTest.java @@ -9,6 +9,7 @@ import io.toolisticon.aptk.tools.MessagerUtils; import io.toolisticon.cute.Cute; import io.toolisticon.cute.CuteApi; +import io.toolisticon.pogen4selenium.processor.datatoextract.DataObjectProcessor; import io.toolisticon.pogen4selenium.processor.pageobject.PageObjectProcessor; @@ -81,6 +82,32 @@ public void test_valid_withInterPackageReferences() { .executeTest(); } + + @Test + public void test_valid_withLocatorInActions() { + + compileTestBuilder + .andSourceFiles("testcases/pageobject/locatorInActions/TestcaseValidUsage.java") + .whenCompiled() + .thenExpectThat() + .compilationSucceeds() + .executeTest(); + } + + @Test + public void test_valid_example() { + + Cute + .blackBoxTest() + .given() + .processors(PageObjectProcessor.class, DataObjectProcessor.class) + .andSourceFiles("testcases/pageobject/example/TestPagePageObject.java", "testcases/pageobject/example/TestPageTableEntry.java") + .whenCompiled() + .thenExpectThat() + .compilationSucceeds() + .executeTest(); + } + /*- @Test public void test_readAnnotatedValue() { diff --git a/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/TestPagePageObject.java b/pogen4selenium-processor/src/test/resources/testcases/pageobject/example/TestPagePageObject.java similarity index 81% rename from pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/TestPagePageObject.java rename to pogen4selenium-processor/src/test/resources/testcases/pageobject/example/TestPagePageObject.java index 22ff400..617b154 100644 --- a/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/TestPagePageObject.java +++ b/pogen4selenium-processor/src/test/resources/testcases/pageobject/example/TestPagePageObject.java @@ -26,6 +26,8 @@ public interface TestPagePageObject extends PageObjectParent @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"; + @PageObjectElement(elementVariableName=TestPagePageObject.DATA_EXTRACTION_FROM_TABLE_ID, by = _By.XPATH, value=DATA_EXTRACTION_FROM_TABLE_XPATH ) + static final String DATA_EXTRACTION_FROM_TABLE_ID= "dataExtractionFromTable"; TestPagePageObject writeToInputField(@ActionWrite(INPUT_FIELD_ID) String value); @@ -42,6 +44,13 @@ public interface TestPagePageObject extends PageObjectParent @ExtractData(by = io.toolisticon.pogen4selenium.api._By.XPATH, value = DATA_EXTRACTION_FROM_TABLE_XPATH) TestPageTableEntry getFirstTableEntry(); + @ExtractData(by = io.toolisticon.pogen4selenium.api._By.ELEMENT, value = DATA_EXTRACTION_FROM_TABLE_ID) + List getTableEntriesFromElement(); + + @ExtractData(by = io.toolisticon.pogen4selenium.api._By.ELEMENT, value = DATA_EXTRACTION_FROM_TABLE_ID) + TestPageTableEntry getFirstTableEntryFromElement(); + + @ExtractDataValue(by = _By.XPATH, value="//fieldset[@name='counter']/span[@id='counter']") String getCounter(); diff --git a/pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/TestPageTableEntry.java b/pogen4selenium-processor/src/test/resources/testcases/pageobject/example/TestPageTableEntry.java similarity index 100% rename from pogen4selenium-example/src/main/java/io/toolisticon/pogen4selenium/example/TestPageTableEntry.java rename to pogen4selenium-processor/src/test/resources/testcases/pageobject/example/TestPageTableEntry.java diff --git a/pogen4selenium-processor/src/test/resources/testcases/pageobject/locatorInActions/TestcaseValidUsage.java b/pogen4selenium-processor/src/test/resources/testcases/pageobject/locatorInActions/TestcaseValidUsage.java new file mode 100644 index 0000000..11941aa --- /dev/null +++ b/pogen4selenium-processor/src/test/resources/testcases/pageobject/locatorInActions/TestcaseValidUsage.java @@ -0,0 +1,25 @@ +package io.toolisticon.pogen4selenium.processor.tests; + +import io.toolisticon.cute.PassIn; +import io.toolisticon.pogen4selenium.api.ActionClick; +import io.toolisticon.pogen4selenium.api.ActionWrite; +import io.toolisticon.pogen4selenium.api._By; +import io.toolisticon.pogen4selenium.api.ExtractDataValue; +import io.toolisticon.pogen4selenium.api.PageObject; +import io.toolisticon.pogen4selenium.api.PageObjectElement; +import io.toolisticon.pogen4selenium.api.PageObjectParent; + + +public class TestcaseValidUsage { + + @PageObject + public interface LoginPage extends PageObjectParent{ + + LoginPage writeUserName(@ActionWrite(by = _By.XPATH, value = "//input[@formcontrolname='username' and @type='text']" ) String username); + + @ActionClick(by = _By.XPATH, value = "//button[@type='submit']") + LoginPage clickSubmitButton(); + + } + +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1b689f4..e268f14 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.toolisticon.pogen4selenium pogen4selenium - 0.4.1 + 0.5.0 pom pogen4selenium Please refer to https://github.com/toolisticon/pogen4selenium