From 5beb38e2da12b4e8fdb1348f7c20493e495412ae Mon Sep 17 00:00:00 2001 From: ivakertusha Date: Wed, 30 Apr 2025 09:48:54 +0200 Subject: [PATCH 01/15] Dropdown test --- .../dropdown/DropDownWebApplication.java | 1 - .../dropdownselector/DropDownController.java | 11 ++++++ .../web/dropdownselector/DropDownEMTest.java | 39 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 e2e-tests/spring-web/src/test/java/com/foo/web/examples/spring/dropdownselector/DropDownController.java create mode 100644 e2e-tests/spring-web/src/test/java/org/evomaster/e2etests/spring/web/dropdownselector/DropDownEMTest.java diff --git a/e2e-tests/spring-web/src/main/java/com/foo/web/examples/spring/dropdown/DropDownWebApplication.java b/e2e-tests/spring-web/src/main/java/com/foo/web/examples/spring/dropdown/DropDownWebApplication.java index 310f5e638f..dde33a761e 100644 --- a/e2e-tests/spring-web/src/main/java/com/foo/web/examples/spring/dropdown/DropDownWebApplication.java +++ b/e2e-tests/spring-web/src/main/java/com/foo/web/examples/spring/dropdown/DropDownWebApplication.java @@ -3,7 +3,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; -import org.springframework.web.bind.annotation.GetMapping; @SpringBootApplication(exclude = SecurityAutoConfiguration.class) public class DropDownWebApplication { diff --git a/e2e-tests/spring-web/src/test/java/com/foo/web/examples/spring/dropdownselector/DropDownController.java b/e2e-tests/spring-web/src/test/java/com/foo/web/examples/spring/dropdownselector/DropDownController.java new file mode 100644 index 0000000000..d1d9f72d55 --- /dev/null +++ b/e2e-tests/spring-web/src/test/java/com/foo/web/examples/spring/dropdownselector/DropDownController.java @@ -0,0 +1,11 @@ +package com.foo.web.examples.spring.dropdownselector; + +import com.foo.web.examples.spring.SpringController; +import com.foo.web.examples.spring.base.BaseWebApplication; + +public class DropDownController extends SpringController { + public DropDownController() { + super(BaseWebApplication.class, "/dropdown/index.html"); + } + +} diff --git a/e2e-tests/spring-web/src/test/java/org/evomaster/e2etests/spring/web/dropdownselector/DropDownEMTest.java b/e2e-tests/spring-web/src/test/java/org/evomaster/e2etests/spring/web/dropdownselector/DropDownEMTest.java new file mode 100644 index 0000000000..e8c1206980 --- /dev/null +++ b/e2e-tests/spring-web/src/test/java/org/evomaster/e2etests/spring/web/dropdownselector/DropDownEMTest.java @@ -0,0 +1,39 @@ +package org.evomaster.e2etests.spring.web.dropdownselector; + +import com.foo.web.examples.spring.dropdownselector.DropDownController; +import org.evomaster.core.problem.webfrontend.WebIndividual; +import org.evomaster.core.search.Solution; +import org.evomaster.e2etests.spring.web.SpringTestBase; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DropDownEMTest extends SpringTestBase { + @BeforeAll + public static void initClass() throws Exception { + SpringTestBase.initClass(new DropDownController()); + } + + @Disabled + @Test + public void testRunEM() throws Throwable { + + runTestHandlingFlakyAndCompilation( + "DropDownEM", + "org.DropDownEM", + 50, + (args) -> { + + Solution solution = initAndRun(args); + + assertTrue(solution.getIndividuals().size() > 0); + + assertHasVisitedUrlPath(solution, "/dropdown/index.html", "/dropdown/page1.html", "/dropdown/page2.html", "/dropdown/page3.html"); + assertNoHtmlErrors(solution); // statement ok - gives no errors + } + ); + } + +} From cea4aec95ea5f9873b09740ee8f562c12f8432b5 Mon Sep 17 00:00:00 2001 From: arcuri82 Date: Wed, 30 Apr 2025 10:16:42 +0200 Subject: [PATCH 02/15] starting handling DropDown, but realized cannot use Jsoup. need refactoring --- .../webfrontend/BrowserActionBuilder.kt | 37 ++++++++++++++++--- .../problem/webfrontend/UserActionType.kt | 12 +++++- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/BrowserActionBuilder.kt b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/BrowserActionBuilder.kt index 8d4dcab9cd..115d681c9c 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/BrowserActionBuilder.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/BrowserActionBuilder.kt @@ -1,13 +1,22 @@ package org.evomaster.core.problem.webfrontend import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.openqa.selenium.WebElement +import org.openqa.selenium.support.ui.Select import java.net.URI import java.net.URISyntaxException -import java.net.URL object BrowserActionBuilder { + /** + * FIXME this might require refactoring + * checking on HTML as string might (or might not???) lose dynamic info added to the page. + * TODO need to verify this. + * Might be better to pass in a Selenium Document. + * We need to verify before making the refactoring + */ fun computePossibleUserInteractions(html: String) : List{ @@ -22,10 +31,30 @@ object BrowserActionBuilder { //TODO all cases + handleALinks(document, list) + handleDropDowns(document, list) + + return list + } + + private fun handleDropDowns(document: Document, list: MutableList) { + + document.getElementsByTag("select") + .forEach { + //TODO + // val type = + // list.add(WebUserInteraction(it.cssSelector(), UserActionType.SELECT)) + } + } + + private fun handleALinks( + document: Document, + list: MutableList + ) { document.getElementsByTag("a") .forEach { val href = it.attr("href") - val canClick = if(!href.isNullOrBlank()) { + val canClick = if (!href.isNullOrBlank()) { val uri = try { URI(href) } catch (e: URISyntaxException) { @@ -38,12 +67,10 @@ object BrowserActionBuilder { val onclick = it.attr("onclick") !onclick.isNullOrBlank() } - if(canClick){ + if (canClick) { list.add(WebUserInteraction(it.cssSelector(), UserActionType.CLICK)) } } - - return list } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/UserActionType.kt b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/UserActionType.kt index b209079254..aacd11f04c 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/UserActionType.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/UserActionType.kt @@ -4,5 +4,15 @@ enum class UserActionType { CLICK, - FILL_TEXT + FILL_TEXT, + + /** + * Select only a single element in a dropdown elements might allow to select more than one element + */ + SELECT_MULTI, } \ No newline at end of file From 9f360cfdd2ab4f705deb1dece51f4f4f037a75a9 Mon Sep 17 00:00:00 2001 From: arcuri82 Date: Wed, 30 Apr 2025 10:40:03 +0200 Subject: [PATCH 03/15] defining actions for Select --- .../webfrontend/BrowserActionBuilder.kt | 42 +++++++++++-------- .../problem/webfrontend/WebUserInteraction.kt | 3 +- .../webfrontend/service/BrowserController.kt | 6 ++- .../problem/webfrontend/service/WebFitness.kt | 2 +- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/BrowserActionBuilder.kt b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/BrowserActionBuilder.kt index 115d681c9c..2762a3436d 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/BrowserActionBuilder.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/BrowserActionBuilder.kt @@ -2,7 +2,8 @@ package org.evomaster.core.problem.webfrontend import org.jsoup.Jsoup import org.jsoup.nodes.Document -import org.openqa.selenium.WebElement +import org.openqa.selenium.By +import org.openqa.selenium.remote.RemoteWebDriver import org.openqa.selenium.support.ui.Select import java.net.URI import java.net.URISyntaxException @@ -11,17 +12,12 @@ object BrowserActionBuilder { /** - * FIXME this might require refactoring - * checking on HTML as string might (or might not???) lose dynamic info added to the page. - * TODO need to verify this. - * Might be better to pass in a Selenium Document. - * We need to verify before making the refactoring */ - fun computePossibleUserInteractions(html: String) : List{ + fun computePossibleUserInteractions(driver: RemoteWebDriver) : List{ val document = try{ - Jsoup.parse(html) + Jsoup.parse(driver.pageSource) }catch (e: Exception){ //TODO double-check return listOf() @@ -31,24 +27,36 @@ object BrowserActionBuilder { //TODO all cases - handleALinks(document, list) - handleDropDowns(document, list) + handleALinks(document, driver, list) + handleDropDowns(document, driver, list) return list } - private fun handleDropDowns(document: Document, list: MutableList) { + private fun handleDropDowns( + document: Document, + driver: RemoteWebDriver, + list: MutableList + ) { document.getElementsByTag("select") - .forEach { - //TODO - // val type = - // list.add(WebUserInteraction(it.cssSelector(), UserActionType.SELECT)) + .forEach { jsoup -> + val dropdown = Select(driver.findElement(By.cssSelector(jsoup.cssSelector()))) + val type = if(dropdown.isMultiple) { + UserActionType.SELECT_MULTI + } else { + UserActionType.SELECT_SINGLE + } + val options = dropdown.options + .filter{it.isEnabled} + .map{it.text} + list.add(WebUserInteraction(jsoup.cssSelector(), type, options)) } } private fun handleALinks( document: Document, + driver: RemoteWebDriver, list: MutableList ) { document.getElementsByTag("a") @@ -74,9 +82,9 @@ object BrowserActionBuilder { } - fun createPossibleActions(html: String) : List{ + fun createPossibleActions(driver: RemoteWebDriver) : List{ - val interactions = computePossibleUserInteractions(html) + val interactions = computePossibleUserInteractions(driver) val inputs = interactions.filter { it.userActionType == UserActionType.FILL_TEXT } val others = interactions.filter { it.userActionType != UserActionType.FILL_TEXT } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/WebUserInteraction.kt b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/WebUserInteraction.kt index 2254755e5a..ba16764f67 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/WebUserInteraction.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/WebUserInteraction.kt @@ -3,5 +3,6 @@ package org.evomaster.core.problem.webfrontend data class WebUserInteraction( val cssSelector : String, - val userActionType : UserActionType + val userActionType : UserActionType, + val inputs : List = listOf() ) \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/BrowserController.kt b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/BrowserController.kt index 2e28dcb3f8..cdde0cd2ed 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/BrowserController.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/BrowserController.kt @@ -67,7 +67,7 @@ class BrowserController { } fun computePossibleUserInteractions() : List{ - return BrowserActionBuilder.computePossibleUserInteractions(getCurrentPageSource()) + return BrowserActionBuilder.computePossibleUserInteractions(driver) } fun getCurrentPageSource(): String { @@ -78,6 +78,10 @@ class BrowserController { return driver.currentUrl } + fun getDriver(): RemoteWebDriver{ + return driver + } + fun clickAndWaitPageLoad(cssSelector: String){ SeleniumEMUtils.clickAndWaitPageLoad(driver, cssSelector) } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/WebFitness.kt b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/WebFitness.kt index 38659dacde..0081fb6e07 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/WebFitness.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/WebFitness.kt @@ -104,7 +104,7 @@ class WebFitness : EnterpriseFitness() { val pageBeforeExecutingAction = browserController.getCurrentPageSource() val urlBeforeExecutingAction = browserController.getCurrentUrl() - val possibilities = BrowserActionBuilder.createPossibleActions(pageBeforeExecutingAction) + val possibilities = BrowserActionBuilder.createPossibleActions(browserController.getDriver()) var blocking = false From fb289bd4aca65c5ad3afca3ec4a8da8b0ece915d Mon Sep 17 00:00:00 2001 From: arcuri82 Date: Wed, 30 Apr 2025 11:06:32 +0200 Subject: [PATCH 04/15] starting points for dropdown --- .../service/BrowserControllerDockerTest.kt | 3 +++ .../webfrontend/BrowserActionBuilder.kt | 17 ++++++++++++- .../core/problem/webfrontend/WebAction.kt | 25 ++++++++++++++++--- .../webfrontend/service/BrowserController.kt | 4 +++ .../problem/webfrontend/service/WebFitness.kt | 7 ++++++ 5 files changed, 52 insertions(+), 4 deletions(-) diff --git a/core-it/src/test/kotlin/org/evomaster/core/problem/webfrontend/service/BrowserControllerDockerTest.kt b/core-it/src/test/kotlin/org/evomaster/core/problem/webfrontend/service/BrowserControllerDockerTest.kt index 27ae64125b..5f705a0ffa 100644 --- a/core-it/src/test/kotlin/org/evomaster/core/problem/webfrontend/service/BrowserControllerDockerTest.kt +++ b/core-it/src/test/kotlin/org/evomaster/core/problem/webfrontend/service/BrowserControllerDockerTest.kt @@ -83,4 +83,7 @@ internal class BrowserControllerDockerTest{ browser.goBack() assertEquals(aPage, browser.getCurrentUrl()) } + + //TODO @IVa add test for Select + } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/BrowserActionBuilder.kt b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/BrowserActionBuilder.kt index 2762a3436d..7552ab1cc9 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/BrowserActionBuilder.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/BrowserActionBuilder.kt @@ -1,5 +1,6 @@ package org.evomaster.core.problem.webfrontend +import org.evomaster.core.search.gene.numeric.IntegerGene import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.openqa.selenium.By @@ -7,6 +8,7 @@ import org.openqa.selenium.remote.RemoteWebDriver import org.openqa.selenium.support.ui.Select import java.net.URI import java.net.URISyntaxException +import kotlin.math.min object BrowserActionBuilder { @@ -91,7 +93,20 @@ object BrowserActionBuilder { //TODO genes for inputs - return others.map { WebAction(mutableListOf(it)) } + return others.map { + when(it.userActionType) { + UserActionType.CLICK -> WebAction(mutableListOf(it)) + UserActionType.SELECT_SINGLE -> { + val selection = IntegerGene(it.cssSelector, min=0, max=it.inputs.size) + WebAction(mutableListOf(it), singleSelection = mutableMapOf(it.cssSelector to selection)) + } + //TODO multi + else -> { + //TODO log warn + WebAction(mutableListOf(it)) + } + } + } } } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/WebAction.kt b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/WebAction.kt index dd46ef791c..796ed9477e 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/WebAction.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/WebAction.kt @@ -2,7 +2,10 @@ package org.evomaster.core.problem.webfrontend import org.evomaster.core.problem.gui.GuiAction import org.evomaster.core.search.StructuralElement +import org.evomaster.core.search.gene.BooleanGene import org.evomaster.core.search.gene.Gene +import org.evomaster.core.search.gene.collection.ArrayGene +import org.evomaster.core.search.gene.numeric.IntegerGene import org.evomaster.core.search.gene.string.StringGene import org.jsoup.Jsoup @@ -15,8 +18,20 @@ class WebAction( /** * Map from cssLocator (coming from [userInteractions]) for text input to StringGene representing its value */ - val textData : MutableMap = mutableMapOf() -) : GuiAction(textData.values.map { it }) { + val textData : MutableMap = mutableMapOf(), + /** + * TODO explanation + */ + val singleSelection: MutableMap = mutableMapOf(), + /** + * TODO explanation + */ + val multiSelection: MutableMap> = mutableMapOf(), +) : GuiAction( + textData.values.map { it } + .plus(singleSelection.values.map { it }) + .plus(multiSelection.values.map { it }) +) { init { val nFillText = userInteractions.count { it.userActionType == UserActionType.FILL_TEXT } @@ -28,6 +43,7 @@ class WebAction( throw IllegalArgumentException("Missing info for input: $key") } } + //TODO constraint checks on singleSelection and multiSelection } override fun isDefined() : Boolean { @@ -65,7 +81,9 @@ class WebAction( override fun copyContent(): StructuralElement { return WebAction( userInteractions.map { it.copy() }.toMutableList(), - textData.entries.associate { it.key to it.value.copy() as StringGene }.toMutableMap() + textData.entries.associate { it.key to it.value.copy() as StringGene }.toMutableMap(), + singleSelection.entries.associate { it.key to it.value.copy() as IntegerGene }.toMutableMap(), + multiSelection.entries.associate { it.key to it.value.copy() as ArrayGene }.toMutableMap(), ) } @@ -74,6 +92,7 @@ class WebAction( userInteractions.addAll(other.userInteractions) //immutable elements textData.clear() textData.putAll(other.textData.entries.associate { it.key to it.value.copy() as StringGene }) + //TODO singleSelection multiSelection } fun getIdentifier() : String { diff --git a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/BrowserController.kt b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/BrowserController.kt index cdde0cd2ed..045c8cc546 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/BrowserController.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/BrowserController.kt @@ -86,6 +86,10 @@ class BrowserController { SeleniumEMUtils.clickAndWaitPageLoad(driver, cssSelector) } + fun selectAndWaitPageLoad(cssSelector: String, values: List){ + //TODO + } + fun goBack(){ driver.navigate().back() SeleniumEMUtils.waitForPageToLoad(driver,2) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/WebFitness.kt b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/WebFitness.kt index 0081fb6e07..7c702cce53 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/WebFitness.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/service/WebFitness.kt @@ -140,6 +140,13 @@ class WebFitness : EnterpriseFitness() { browserController.clickAndWaitPageLoad(it.cssSelector) //TODO better wait } + UserActionType.SELECT_SINGLE, UserActionType.SELECT_MULTI -> { + //TODO + /* + not just clicking, but deciding which options to select. + this is based on values in the genes + */ + } else -> { log.error("Not handled action type ${it.userActionType}") } From 69a6d7c857add265a0444397dfece888b2e5fc9c Mon Sep 17 00:00:00 2001 From: ivakertusha Date: Wed, 30 Apr 2025 11:26:07 +0200 Subject: [PATCH 05/15] different user action types --- .../core/problem/webfrontend/UserActionType.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/UserActionType.kt b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/UserActionType.kt index aacd11f04c..02b26711b2 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/UserActionType.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/webfrontend/UserActionType.kt @@ -15,4 +15,17 @@ enum class UserActionType { * Some dropdown