Skip to content

Commit

Permalink
Merge pull request #15 from CasparKielwein/move_into_view
Browse files Browse the repository at this point in the history
Adds functionality to move elements into view before interacitons.
Fix #14
  • Loading branch information
CasparKielwein authored Jun 14, 2020
2 parents 840fd09 + 6dc75c4 commit 0149694
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 36 deletions.
25 changes: 22 additions & 3 deletions src/main/kotlin/cadmium/Page.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ import java.net.URL
*
* @property b Browser instance driving this page
* @property waiter implementation of Waiter used by this page.
* @property autoScroll if true, cadmium will try to scroll WebElement into view
* before click() or enter() are executed.
*/
open class Page(internal val b: Browser, private val waiter: Waiter = DefaultWaiterImpl(b)) :
SearchContext, Waiter by waiter {

var autoScroll : Boolean = false

/**
* Get a WebElement and apply given actions on it
*
Expand All @@ -28,9 +32,13 @@ open class Page(internal val b: Browser, private val waiter: Waiter = DefaultWai
* if multiple elements match the locator, the first is returned
*/
override fun element(loc: Locator, actions: WebElement.() -> Unit): WebElement {
//don't use element returned by until as that does not seem to trigger ChangeEvents.
b.defaultWait.until(ExpectedConditions.presenceOfElementLocated(loc.by))
val e = WebElement(
b.defaultWait.until(ExpectedConditions.presenceOfElementLocated(loc.by))!!,
b.defaultWait
b.driver.findElement(loc.by)!!,
b.defaultWait,
b.driver,
autoScroll
)
e.actions()
return e
Expand All @@ -45,7 +53,7 @@ open class Page(internal val b: Browser, private val waiter: Waiter = DefaultWai
* @see element
*/
override fun elements(loc: Locator, waiter: WebDriverWait): List<WebElement> {
return b.driver.findElements(loc.by).map { WebElement(it, waiter) }
return b.driver.findElements(loc.by).map { WebElement(it, waiter, b.driver, autoScroll) }
}

/**
Expand Down Expand Up @@ -142,6 +150,17 @@ fun <T : Page> T.visit(actions: T.() -> Unit) {
actions()
}

/**
* Scope function which executes given action with autoscroll enabled
*
* @param actions applied to page object
*/
fun <T : Page> T.withAutoScroll(actions: T.() -> Unit) {
autoScroll = true
actions()
autoScroll = false
}

/**
* Provide access to WebDriver used in Page
*/
Expand Down
7 changes: 5 additions & 2 deletions src/main/kotlin/cadmium/Select.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,19 @@ class SelectOptions(private val element: WebElement) {
* List of all currently selected options in this element
*/
val allSelected
get() = option.allSelectedOptions.map { WebElement(it, element.wait) }
get() = option.allSelectedOptions.map { WebElement(it, element.wait, element.driver, element.autoScroll) }

/**
* @return The first selected option in this element
* @throws NoSuchElementException If no option is selected
*/
val firstSelected
get() = WebElement(option.firstSelectedOption, element.wait)
get() = WebElement(option.firstSelectedOption, element.wait, element.driver, element.autoScroll)
}

/**
* Locator Classes which can be only used to select options.
*/
sealed class SelectLocator

/**
Expand Down
53 changes: 25 additions & 28 deletions src/main/kotlin/cadmium/WebElement.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package cadmium

import cadmium.util.modifierKey
import org.openqa.selenium.Keys
import org.openqa.selenium.WebDriver
import org.openqa.selenium.interactions.Actions
import org.openqa.selenium.support.ui.ExpectedConditions
import org.openqa.selenium.support.ui.WebDriverWait

Expand All @@ -16,25 +18,18 @@ import org.openqa.selenium.support.ui.WebDriverWait
* Use its Documentation for detailed information.
* @see org.openqa.selenium.WebElement
*
* @property autoScroll if true, calls to enter and click will try to scroll
* the element into view if it is not displayed.
* @property wait default Wait, Methods use when trying to interact with WebElements
* @property rawElement org.openqa.selenium.WebElement wrapped in this
* @property rawElement org.openqa.selenium.WebElement wrapped in this object.
* Access to rawElement is possible for cases where selenium functionality not provided
* by cadmium is required. Access to rawElement is considered to be unstable
* and can be broken without a change in major version number.
*/
class WebElement : SearchContext {
internal var wait: WebDriverWait
internal val rawElement: org.openqa.selenium.WebElement

/**
* Construct a WebElement to wrap a org.openqa.selenium.WebElement
*
* @param rawElement org.openqa.selenium.WebElement wrapped by this WebElement
*/
constructor(
rawElement: org.openqa.selenium.WebElement,
wait: WebDriverWait
) {
this.wait = wait
this.rawElement = rawElement
}
class WebElement(val rawElement: org.openqa.selenium.WebElement,
internal var wait: WebDriverWait,
internal val driver: WebDriver,
var autoScroll: Boolean) : SearchContext {

private fun findNested(loc: Locator): org.openqa.selenium.WebElement {
//FIXME this might match wider than the find call for the nested element.
Expand All @@ -51,7 +46,7 @@ class WebElement : SearchContext {
* if multiple elements match the locator, the first is returned
*/
override fun element(loc: Locator, actions: WebElement.() -> Unit): WebElement {
val e = WebElement(findNested(loc), wait)
val e = WebElement(findNested(loc), wait, driver, autoScroll)
e.actions()
return e
}
Expand All @@ -67,7 +62,7 @@ class WebElement : SearchContext {
override fun elements(loc: Locator, waiter: WebDriverWait): List<WebElement> {
return rawElement
.findElements(loc.by)
.map { WebElement(it, waiter) }
.map { WebElement(it, waiter, driver, autoScroll) }
}

/**
Expand All @@ -78,15 +73,14 @@ class WebElement : SearchContext {
/**
* Click this element, wait default timeout for it to become visible
*
* @return this to allow chaining of operations
*
* The element must be visible and it must have a height and width
* greater then 0.
*/
fun click(): WebElement {
fun click() {
if (autoScroll && !displayed)
scrollIntoView()
wait.until(ExpectedConditions.elementToBeClickable(rawElement))
rawElement.click()
return this
}

/**
Expand All @@ -95,16 +89,15 @@ class WebElement : SearchContext {
* @param text character sequence to send to the element
* @param replace if true, [text] will replace the current content of WebElement.
* Use replace instead of a separate clear call, if the additional onChange event triggered by clear causes problems.
* @return this to allow chaining of operations
*/
fun enter(vararg text: CharSequence, replace: Boolean = false): WebElement {
wait.until(ExpectedConditions.visibilityOf(rawElement))
fun enter(vararg text: CharSequence, replace: Boolean = false) {
if (autoScroll && !displayed)
scrollIntoView()
if (replace) {
//select all current text by pressing control/command + a and delete it
rawElement.sendKeys(Keys.chord(modifierKey(), "a"), Keys.DELETE)
}
rawElement.sendKeys(*text)
return this
}

/**
Expand All @@ -115,7 +108,6 @@ class WebElement : SearchContext {
* @return this, to allow chaining of operation.
*/
fun clear(): WebElement {
wait.until(ExpectedConditions.visibilityOf(rawElement))
rawElement.clear()
return this
}
Expand All @@ -133,6 +125,11 @@ class WebElement : SearchContext {
return this
}

/**
* Will scroll until the WebElement is in the current view.
*/
fun scrollIntoView() = Actions(driver).moveToElement(rawElement)

/**
* Get Text of WebElement
*/
Expand Down
7 changes: 4 additions & 3 deletions src/test/kotlin/cadmium/chrome/ChromeKtTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ internal class TestChrome {
@Test
fun testMinimalExample() {
headlessChrome().browse(URL("https://en.wikipedia.org/wiki")) {
element(Id("searchInput"))
.enter("cheese")
.enter(Keys.ENTER)
element(Id("searchInput")) {
enter("cheese")
enter(Keys.ENTER)
}

assertEquals("Cheese", element(Id("firstHeading")).text)
}
Expand Down

0 comments on commit 0149694

Please sign in to comment.