Repository files navigation

Keb

Web Testing, Browser Automation, Page Object Pattern and more

Content

  1. Page object pattern
  2. Modules
  3. Navigation
  4. Waiting
  5. Installation
  6. Usage

Page object pattern

Keb is a https://gebish.org inspired Selenium wrapper written in Kotlin that allows you to modularize pages of your web application into logic units represented by Kotlin classes.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"
}

Page elements

To select web element on your page use the following methods.

css(".my-selector") // returns single DOM element by CSS selector
cssList(".my-select") // returns list of all elements found by CSS selector// following selectors are also available
html("h1")
htmlList("h1")
xpath("/html/body/h1")
xpathList("/html/body/h1")
by(MyCustomBy()) // can be used with custom implementation of org.openqa.selenium.By
byList(MyCustomBy())

In order to lazily access page content use the content delegate.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"val header by content { css(".global-header-logo") }
val headerText by content { header.text }
}

There are multiple options how to handle the selected content:

  • cache
    • content can be cached - i.e. only initialized on first access, otherwise invoked on every access
    • usage: val title by content(cache=true) { html("h1") }
    • default value: false
  • required:
    • you can specify whether the content is required to be present on the page
    • if required and the content is not present, then the org.openqa.selenium.NoSuchElementException exception is thrown, instance of org.kebish.core.EmptyContent is returned otherwise
    • usage: val title by content(required=true) { html("h1") }
    • default value: true
  • wait:
    • wheter the keb should wait for the given content
    • usage:
    val title by content(wait =true) { html("h1") } // uses the default wait presetval title by content(wait =60) { html("h1") } // uses custom timeout value with retryInterval from the default wait presetval title by content(wait ="quick") { html("h1") } // uses the given wait presetval title by content(waitTimeout =10, waitRetryInterval =2) { html("h1") } // specify both timeout and retry interval
    • if content is still not present after the specified interval, the org.kebish.core.WaitTimeoutException exception is thrown
    • default value: false
    • for more info on waiting, refer to the Waiting section below

Page verifier

In order to verify, that you successfully landed on your page, you can use at method.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"overridefunat() = header
val header by content { css(".global-header-logo") }
}

Whether every page has to define at verifier can be controlled by configuration property atVerifierRequired. If no verifier is defined and neither is required, no page verification is performed.

Modules

If you want to reuse page content present on multiple pages, you can use modules. Module has an optional constructor parameter scope, which can be used as a search root for all the module's content. To initialize module with use the module method.

classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"// either initialize the module by passing the module object into the 'module' methodval menu by content { module(NavMenuModule(css(".nav-links"))) }
// or initialize the module by calling the 'module' method on a WebElement and pass reference to your moduleval menu2 by content { css(".nav-links").module(::NavMenuModule) }
}

Navigation

at(::MyPage) create instance of page and wait until validator at() is satisfied

to(::MyPage) write url of page into browser and call method at()

at(::MyPage) { methodOnMyPage() } methods can have closure. Inside closure this is created page

at(::MyPage) { clickMainLink() } return result of closure

All the methods defined above can be used with constructor reference to(::MyPage) or with instance of a page to(MyPage()). Please note that constructor reference variant can only be used if the required page has no primary constructor parameters.

Both to and at methods can wait for the transition/verification of page to happen. Wait definition is same as for the content method.

val myPage = to(::MyPage, wait =true)
val myPage = to(::MyPage, wait =30)
val myPage = to(::MyPage, wait ="quick")
val myPage = to(::MyPage, waitTimeout =10, waitRetryInterval =1)
// same for the 'at' methods

Navigating pages using fluent API

val joeContact = to(::MyPage)
.via { clickOnContacts() }
.via { findContact("joe") } 
// Same as method above, but explicitly validate, that method is called on correct pageval joeContact = to(::MyPage)
.via(MyPage::class) { clickOnContacts() }
.via(ContactsPage::class) { findContact("joe") } 

Waiting

Waiting for some page element to be present, you can use waitFor method.

css(".form-input").click()
val result = waitFor {
css(".result")
}

Timeout duration and retry interval can be defined either by custom wait preset or directly through waitFor method.

waitFor(preset ="quick") { css(".result") }
waitFor(timeout =2, retryInterval =0.1) { css(".result") }

Custom wait presets can be defined using Kotlin DSL in the Configuration. Default values when no custom configuration is defined are 15 seconds for timeout and 1 second for retry interval.

kebConfig {
waiting {
timeout =15
retryInterval =1"quick" {
timeout =2
retryInterval =0.1
}
"slow" {
timeout =60 }
}
}

Installation

Relesed jar files are available in jcenter().

Gradle setup:

repositories {
jcenter()
}
val kebVersion ="<insert_actual_version>"
dependencies{
implementation("org.kebish:keb-core:$kebVersion")
implementation("org.kebish:keb-junit5:$kebVersion")
implementation("org.kebish:keb-bobril:$kebVersion")
}

If you use Keb in tests you will probably use configuration testImplementation instead of implementation

Full usage - keb + JUnit

packageorg.kebish.usageimportio.github.bonigarcia.wdm.WebDriverManagerimportorg.junit.jupiter.api.Assertionsimportorg.junit.jupiter.api.Testimportorg.kebish.core.*importorg.kebish.junit5.KebTestimportorg.openqa.selenium.WebElementimportorg.openqa.selenium.firefox.FirefoxDriverclassKotlinSiteKebTest : KebTest(Browser(kebConfig {
WebDriverManager.firefoxdriver().setup()
driver = { FirefoxDriver() }
baseUrl = "https://kotlinlang.org"
})) {
@Test
fun`testing kotlin lang page`() {
// givenval homePage = to(::KotlinHomePage)
// whenval title = homePage.header
// thenAssertions.assertEquals("Kotlin", title.text)
Assertions.assertTrue(homePage.licensedUnderApacheLicense())
// whenval docsPage = homePage.openDocumentation()
// thenAssertions.assertEquals("Learn Kotlin", docsPage.title.text)
}
}
classKotlinHomePage : Page() {
overridefunurl() ="/"overridefunat() = header
val header by content { css(".global-header-logo") }
val menu by content { module(NavMenuModule(css(".nav-links"))) }
val footer by content { module(FooterModule(html("footer"))) }
funopenDocumentation(): KotlinDocumentationPage {
menu.menuItems.first { it.text.contains("learn", ignoreCase =true) }.click()
return at(::KotlinDocumentationPage)
}
funlicensedUnderApacheLicense() = footer.licenseNotice.text.contains("apache", ignoreCase =true)
}
classKotlinDocumentationPage : Page() {
overridefunurl() ="/docs/reference"overridefunat() = title
val title by content { html("h1") }
}
classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classFooterModule(scope:WebElement) : Module(scope) {
val licenseNotice by content { css(".terms-copyright") }
val sponsor by content { css(".terms-sponsor") }
}

For full usage example please refer to /keb-core/src/test/kotlin/org/kebish/usage.

About

Keb - Kebish. Selenium wrapper with Page Object Pattern implementation written in Kotlin.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Keb

Web Testing, Browser Automation, Page Object Pattern and more

Content

  1. Page object pattern
  2. Modules
  3. Navigation
  4. Waiting
  5. Installation
  6. Usage

Page object pattern

Keb is a https://gebish.org inspired Selenium wrapper written in Kotlin that allows you to modularize pages of your web application into logic units represented by Kotlin classes.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"
}

Page elements

To select web element on your page use the following methods.

css(".my-selector") // returns single DOM element by CSS selector
cssList(".my-select") // returns list of all elements found by CSS selector// following selectors are also available
html("h1")
htmlList("h1")
xpath("/html/body/h1")
xpathList("/html/body/h1")
by(MyCustomBy()) // can be used with custom implementation of org.openqa.selenium.By
byList(MyCustomBy())

In order to lazily access page content use the content delegate.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"val header by content { css(".global-header-logo") }
val headerText by content { header.text }
}

There are multiple options how to handle the selected content:

  • cache
    • content can be cached - i.e. only initialized on first access, otherwise invoked on every access
    • usage: val title by content(cache=true) { html("h1") }
    • default value: false
  • required:
    • you can specify whether the content is required to be present on the page
    • if required and the content is not present, then the org.openqa.selenium.NoSuchElementException exception is thrown, instance of org.kebish.core.EmptyContent is returned otherwise
    • usage: val title by content(required=true) { html("h1") }
    • default value: true
  • wait:
    • wheter the keb should wait for the given content
    • usage:
    val title by content(wait =true) { html("h1") } // uses the default wait presetval title by content(wait =60) { html("h1") } // uses custom timeout value with retryInterval from the default wait presetval title by content(wait ="quick") { html("h1") } // uses the given wait presetval title by content(waitTimeout =10, waitRetryInterval =2) { html("h1") } // specify both timeout and retry interval
    • if content is still not present after the specified interval, the org.kebish.core.WaitTimeoutException exception is thrown
    • default value: false
    • for more info on waiting, refer to the Waiting section below

Page verifier

In order to verify, that you successfully landed on your page, you can use at method.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"overridefunat() = header
val header by content { css(".global-header-logo") }
}

Whether every page has to define at verifier can be controlled by configuration property atVerifierRequired. If no verifier is defined and neither is required, no page verification is performed.

Modules

If you want to reuse page content present on multiple pages, you can use modules. Module has an optional constructor parameter scope, which can be used as a search root for all the module's content. To initialize module with use the module method.

classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"// either initialize the module by passing the module object into the 'module' methodval menu by content { module(NavMenuModule(css(".nav-links"))) }
// or initialize the module by calling the 'module' method on a WebElement and pass reference to your moduleval menu2 by content { css(".nav-links").module(::NavMenuModule) }
}

Navigation

at(::MyPage) create instance of page and wait until validator at() is satisfied

to(::MyPage) write url of page into browser and call method at()

at(::MyPage) { methodOnMyPage() } methods can have closure. Inside closure this is created page

at(::MyPage) { clickMainLink() } return result of closure

All the methods defined above can be used with constructor reference to(::MyPage) or with instance of a page to(MyPage()). Please note that constructor reference variant can only be used if the required page has no primary constructor parameters.

Both to and at methods can wait for the transition/verification of page to happen. Wait definition is same as for the content method.

val myPage = to(::MyPage, wait =true)
val myPage = to(::MyPage, wait =30)
val myPage = to(::MyPage, wait ="quick")
val myPage = to(::MyPage, waitTimeout =10, waitRetryInterval =1)
// same for the 'at' methods

Navigating pages using fluent API

val joeContact = to(::MyPage)
.via { clickOnContacts() }
.via { findContact("joe") } 
// Same as method above, but explicitly validate, that method is called on correct pageval joeContact = to(::MyPage)
.via(MyPage::class) { clickOnContacts() }
.via(ContactsPage::class) { findContact("joe") } 

Waiting

Waiting for some page element to be present, you can use waitFor method.

css(".form-input").click()
val result = waitFor {
css(".result")
}

Timeout duration and retry interval can be defined either by custom wait preset or directly through waitFor method.

waitFor(preset ="quick") { css(".result") }
waitFor(timeout =2, retryInterval =0.1) { css(".result") }

Custom wait presets can be defined using Kotlin DSL in the Configuration. Default values when no custom configuration is defined are 15 seconds for timeout and 1 second for retry interval.

kebConfig {
waiting {
timeout =15
retryInterval =1"quick" {
timeout =2
retryInterval =0.1
}
"slow" {
timeout =60 }
}
}

Installation

Relesed jar files are available in jcenter().

Gradle setup:

repositories {
jcenter()
}
val kebVersion ="<insert_actual_version>"
dependencies{
implementation("org.kebish:keb-core:$kebVersion")
implementation("org.kebish:keb-junit5:$kebVersion")
implementation("org.kebish:keb-bobril:$kebVersion")
}

If you use Keb in tests you will probably use configuration testImplementation instead of implementation

Full usage - keb + JUnit

packageorg.kebish.usageimportio.github.bonigarcia.wdm.WebDriverManagerimportorg.junit.jupiter.api.Assertionsimportorg.junit.jupiter.api.Testimportorg.kebish.core.*importorg.kebish.junit5.KebTestimportorg.openqa.selenium.WebElementimportorg.openqa.selenium.firefox.FirefoxDriverclassKotlinSiteKebTest : KebTest(Browser(kebConfig {
WebDriverManager.firefoxdriver().setup()
driver = { FirefoxDriver() }
baseUrl = "https://kotlinlang.org"
})) {
@Test
fun`testing kotlin lang page`() {
// givenval homePage = to(::KotlinHomePage)
// whenval title = homePage.header
// thenAssertions.assertEquals("Kotlin", title.text)
Assertions.assertTrue(homePage.licensedUnderApacheLicense())
// whenval docsPage = homePage.openDocumentation()
// thenAssertions.assertEquals("Learn Kotlin", docsPage.title.text)
}
}
classKotlinHomePage : Page() {
overridefunurl() ="/"overridefunat() = header
val header by content { css(".global-header-logo") }
val menu by content { module(NavMenuModule(css(".nav-links"))) }
val footer by content { module(FooterModule(html("footer"))) }
funopenDocumentation(): KotlinDocumentationPage {
menu.menuItems.first { it.text.contains("learn", ignoreCase =true) }.click()
return at(::KotlinDocumentationPage)
}
funlicensedUnderApacheLicense() = footer.licenseNotice.text.contains("apache", ignoreCase =true)
}
classKotlinDocumentationPage : Page() {
overridefunurl() ="/docs/reference"overridefunat() = title
val title by content { html("h1") }
}
classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classFooterModule(scope:WebElement) : Module(scope) {
val licenseNotice by content { css(".terms-copyright") }
val sponsor by content { css(".terms-sponsor") }
}

For full usage example please refer to /keb-core/src/test/kotlin/org/kebish/usage.

About

Keb - Kebish. Selenium wrapper with Page Object Pattern implementation written in Kotlin.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Keb

Web Testing, Browser Automation, Page Object Pattern and more

Content

  1. Page object pattern
  2. Modules
  3. Navigation
  4. Waiting
  5. Installation
  6. Usage

Page object pattern

Keb is a https://gebish.org inspired Selenium wrapper written in Kotlin that allows you to modularize pages of your web application into logic units represented by Kotlin classes.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"
}

Page elements

To select web element on your page use the following methods.

css(".my-selector") // returns single DOM element by CSS selector
cssList(".my-select") // returns list of all elements found by CSS selector// following selectors are also available
html("h1")
htmlList("h1")
xpath("/html/body/h1")
xpathList("/html/body/h1")
by(MyCustomBy()) // can be used with custom implementation of org.openqa.selenium.By
byList(MyCustomBy())

In order to lazily access page content use the content delegate.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"val header by content { css(".global-header-logo") }
val headerText by content { header.text }
}

There are multiple options how to handle the selected content:

  • cache
    • content can be cached - i.e. only initialized on first access, otherwise invoked on every access
    • usage: val title by content(cache=true) { html("h1") }
    • default value: false
  • required:
    • you can specify whether the content is required to be present on the page
    • if required and the content is not present, then the org.openqa.selenium.NoSuchElementException exception is thrown, instance of org.kebish.core.EmptyContent is returned otherwise
    • usage: val title by content(required=true) { html("h1") }
    • default value: true
  • wait:
    • wheter the keb should wait for the given content
    • usage:
    val title by content(wait =true) { html("h1") } // uses the default wait presetval title by content(wait =60) { html("h1") } // uses custom timeout value with retryInterval from the default wait presetval title by content(wait ="quick") { html("h1") } // uses the given wait presetval title by content(waitTimeout =10, waitRetryInterval =2) { html("h1") } // specify both timeout and retry interval
    • if content is still not present after the specified interval, the org.kebish.core.WaitTimeoutException exception is thrown
    • default value: false
    • for more info on waiting, refer to the Waiting section below

Page verifier

In order to verify, that you successfully landed on your page, you can use at method.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"overridefunat() = header
val header by content { css(".global-header-logo") }
}

Whether every page has to define at verifier can be controlled by configuration property atVerifierRequired. If no verifier is defined and neither is required, no page verification is performed.

Modules

If you want to reuse page content present on multiple pages, you can use modules. Module has an optional constructor parameter scope, which can be used as a search root for all the module's content. To initialize module with use the module method.

classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"// either initialize the module by passing the module object into the 'module' methodval menu by content { module(NavMenuModule(css(".nav-links"))) }
// or initialize the module by calling the 'module' method on a WebElement and pass reference to your moduleval menu2 by content { css(".nav-links").module(::NavMenuModule) }
}

Navigation

at(::MyPage) create instance of page and wait until validator at() is satisfied

to(::MyPage) write url of page into browser and call method at()

at(::MyPage) { methodOnMyPage() } methods can have closure. Inside closure this is created page

at(::MyPage) { clickMainLink() } return result of closure

All the methods defined above can be used with constructor reference to(::MyPage) or with instance of a page to(MyPage()). Please note that constructor reference variant can only be used if the required page has no primary constructor parameters.

Both to and at methods can wait for the transition/verification of page to happen. Wait definition is same as for the content method.

val myPage = to(::MyPage, wait =true)
val myPage = to(::MyPage, wait =30)
val myPage = to(::MyPage, wait ="quick")
val myPage = to(::MyPage, waitTimeout =10, waitRetryInterval =1)
// same for the 'at' methods

Navigating pages using fluent API

val joeContact = to(::MyPage)
.via { clickOnContacts() }
.via { findContact("joe") } 
// Same as method above, but explicitly validate, that method is called on correct pageval joeContact = to(::MyPage)
.via(MyPage::class) { clickOnContacts() }
.via(ContactsPage::class) { findContact("joe") } 

Waiting

Waiting for some page element to be present, you can use waitFor method.

css(".form-input").click()
val result = waitFor {
css(".result")
}

Timeout duration and retry interval can be defined either by custom wait preset or directly through waitFor method.

waitFor(preset ="quick") { css(".result") }
waitFor(timeout =2, retryInterval =0.1) { css(".result") }

Custom wait presets can be defined using Kotlin DSL in the Configuration. Default values when no custom configuration is defined are 15 seconds for timeout and 1 second for retry interval.

kebConfig {
waiting {
timeout =15
retryInterval =1"quick" {
timeout =2
retryInterval =0.1
}
"slow" {
timeout =60 }
}
}

Installation

Relesed jar files are available in jcenter().

Gradle setup:

repositories {
jcenter()
}
val kebVersion ="<insert_actual_version>"
dependencies{
implementation("org.kebish:keb-core:$kebVersion")
implementation("org.kebish:keb-junit5:$kebVersion")
implementation("org.kebish:keb-bobril:$kebVersion")
}

If you use Keb in tests you will probably use configuration testImplementation instead of implementation

Full usage - keb + JUnit

packageorg.kebish.usageimportio.github.bonigarcia.wdm.WebDriverManagerimportorg.junit.jupiter.api.Assertionsimportorg.junit.jupiter.api.Testimportorg.kebish.core.*importorg.kebish.junit5.KebTestimportorg.openqa.selenium.WebElementimportorg.openqa.selenium.firefox.FirefoxDriverclassKotlinSiteKebTest : KebTest(Browser(kebConfig {
WebDriverManager.firefoxdriver().setup()
driver = { FirefoxDriver() }
baseUrl = "https://kotlinlang.org"
})) {
@Test
fun`testing kotlin lang page`() {
// givenval homePage = to(::KotlinHomePage)
// whenval title = homePage.header
// thenAssertions.assertEquals("Kotlin", title.text)
Assertions.assertTrue(homePage.licensedUnderApacheLicense())
// whenval docsPage = homePage.openDocumentation()
// thenAssertions.assertEquals("Learn Kotlin", docsPage.title.text)
}
}
classKotlinHomePage : Page() {
overridefunurl() ="/"overridefunat() = header
val header by content { css(".global-header-logo") }
val menu by content { module(NavMenuModule(css(".nav-links"))) }
val footer by content { module(FooterModule(html("footer"))) }
funopenDocumentation(): KotlinDocumentationPage {
menu.menuItems.first { it.text.contains("learn", ignoreCase =true) }.click()
return at(::KotlinDocumentationPage)
}
funlicensedUnderApacheLicense() = footer.licenseNotice.text.contains("apache", ignoreCase =true)
}
classKotlinDocumentationPage : Page() {
overridefunurl() ="/docs/reference"overridefunat() = title
val title by content { html("h1") }
}
classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classFooterModule(scope:WebElement) : Module(scope) {
val licenseNotice by content { css(".terms-copyright") }
val sponsor by content { css(".terms-sponsor") }
}

For full usage example please refer to /keb-core/src/test/kotlin/org/kebish/usage.

About

Keb - Kebish. Selenium wrapper with Page Object Pattern implementation written in Kotlin.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Keb

Web Testing, Browser Automation, Page Object Pattern and more

Content

  1. Page object pattern
  2. Modules
  3. Navigation
  4. Waiting
  5. Installation
  6. Usage

Page object pattern

Keb is a https://gebish.org inspired Selenium wrapper written in Kotlin that allows you to modularize pages of your web application into logic units represented by Kotlin classes.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"
}

Page elements

To select web element on your page use the following methods.

css(".my-selector") // returns single DOM element by CSS selector
cssList(".my-select") // returns list of all elements found by CSS selector// following selectors are also available
html("h1")
htmlList("h1")
xpath("/html/body/h1")
xpathList("/html/body/h1")
by(MyCustomBy()) // can be used with custom implementation of org.openqa.selenium.By
byList(MyCustomBy())

In order to lazily access page content use the content delegate.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"val header by content { css(".global-header-logo") }
val headerText by content { header.text }
}

There are multiple options how to handle the selected content:

  • cache
    • content can be cached - i.e. only initialized on first access, otherwise invoked on every access
    • usage: val title by content(cache=true) { html("h1") }
    • default value: false
  • required:
    • you can specify whether the content is required to be present on the page
    • if required and the content is not present, then the org.openqa.selenium.NoSuchElementException exception is thrown, instance of org.kebish.core.EmptyContent is returned otherwise
    • usage: val title by content(required=true) { html("h1") }
    • default value: true
  • wait:
    • wheter the keb should wait for the given content
    • usage:
    val title by content(wait =true) { html("h1") } // uses the default wait presetval title by content(wait =60) { html("h1") } // uses custom timeout value with retryInterval from the default wait presetval title by content(wait ="quick") { html("h1") } // uses the given wait presetval title by content(waitTimeout =10, waitRetryInterval =2) { html("h1") } // specify both timeout and retry interval
    • if content is still not present after the specified interval, the org.kebish.core.WaitTimeoutException exception is thrown
    • default value: false
    • for more info on waiting, refer to the Waiting section below

Page verifier

In order to verify, that you successfully landed on your page, you can use at method.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"overridefunat() = header
val header by content { css(".global-header-logo") }
}

Whether every page has to define at verifier can be controlled by configuration property atVerifierRequired. If no verifier is defined and neither is required, no page verification is performed.

Modules

If you want to reuse page content present on multiple pages, you can use modules. Module has an optional constructor parameter scope, which can be used as a search root for all the module's content. To initialize module with use the module method.

classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"// either initialize the module by passing the module object into the 'module' methodval menu by content { module(NavMenuModule(css(".nav-links"))) }
// or initialize the module by calling the 'module' method on a WebElement and pass reference to your moduleval menu2 by content { css(".nav-links").module(::NavMenuModule) }
}

Navigation

at(::MyPage) create instance of page and wait until validator at() is satisfied

to(::MyPage) write url of page into browser and call method at()

at(::MyPage) { methodOnMyPage() } methods can have closure. Inside closure this is created page

at(::MyPage) { clickMainLink() } return result of closure

All the methods defined above can be used with constructor reference to(::MyPage) or with instance of a page to(MyPage()). Please note that constructor reference variant can only be used if the required page has no primary constructor parameters.

Both to and at methods can wait for the transition/verification of page to happen. Wait definition is same as for the content method.

val myPage = to(::MyPage, wait =true)
val myPage = to(::MyPage, wait =30)
val myPage = to(::MyPage, wait ="quick")
val myPage = to(::MyPage, waitTimeout =10, waitRetryInterval =1)
// same for the 'at' methods

Navigating pages using fluent API

val joeContact = to(::MyPage)
.via { clickOnContacts() }
.via { findContact("joe") } 
// Same as method above, but explicitly validate, that method is called on correct pageval joeContact = to(::MyPage)
.via(MyPage::class) { clickOnContacts() }
.via(ContactsPage::class) { findContact("joe") } 

Waiting

Waiting for some page element to be present, you can use waitFor method.

css(".form-input").click()
val result = waitFor {
css(".result")
}

Timeout duration and retry interval can be defined either by custom wait preset or directly through waitFor method.

waitFor(preset ="quick") { css(".result") }
waitFor(timeout =2, retryInterval =0.1) { css(".result") }

Custom wait presets can be defined using Kotlin DSL in the Configuration. Default values when no custom configuration is defined are 15 seconds for timeout and 1 second for retry interval.

kebConfig {
waiting {
timeout =15
retryInterval =1"quick" {
timeout =2
retryInterval =0.1
}
"slow" {
timeout =60 }
}
}

Installation

Relesed jar files are available in jcenter().

Gradle setup:

repositories {
jcenter()
}
val kebVersion ="<insert_actual_version>"
dependencies{
implementation("org.kebish:keb-core:$kebVersion")
implementation("org.kebish:keb-junit5:$kebVersion")
implementation("org.kebish:keb-bobril:$kebVersion")
}

If you use Keb in tests you will probably use configuration testImplementation instead of implementation

Full usage - keb + JUnit

packageorg.kebish.usageimportio.github.bonigarcia.wdm.WebDriverManagerimportorg.junit.jupiter.api.Assertionsimportorg.junit.jupiter.api.Testimportorg.kebish.core.*importorg.kebish.junit5.KebTestimportorg.openqa.selenium.WebElementimportorg.openqa.selenium.firefox.FirefoxDriverclassKotlinSiteKebTest : KebTest(Browser(kebConfig {
WebDriverManager.firefoxdriver().setup()
driver = { FirefoxDriver() }
baseUrl = "https://kotlinlang.org"
})) {
@Test
fun`testing kotlin lang page`() {
// givenval homePage = to(::KotlinHomePage)
// whenval title = homePage.header
// thenAssertions.assertEquals("Kotlin", title.text)
Assertions.assertTrue(homePage.licensedUnderApacheLicense())
// whenval docsPage = homePage.openDocumentation()
// thenAssertions.assertEquals("Learn Kotlin", docsPage.title.text)
}
}
classKotlinHomePage : Page() {
overridefunurl() ="/"overridefunat() = header
val header by content { css(".global-header-logo") }
val menu by content { module(NavMenuModule(css(".nav-links"))) }
val footer by content { module(FooterModule(html("footer"))) }
funopenDocumentation(): KotlinDocumentationPage {
menu.menuItems.first { it.text.contains("learn", ignoreCase =true) }.click()
return at(::KotlinDocumentationPage)
}
funlicensedUnderApacheLicense() = footer.licenseNotice.text.contains("apache", ignoreCase =true)
}
classKotlinDocumentationPage : Page() {
overridefunurl() ="/docs/reference"overridefunat() = title
val title by content { html("h1") }
}
classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classFooterModule(scope:WebElement) : Module(scope) {
val licenseNotice by content { css(".terms-copyright") }
val sponsor by content { css(".terms-sponsor") }
}

For full usage example please refer to /keb-core/src/test/kotlin/org/kebish/usage.

About

Keb - Kebish. Selenium wrapper with Page Object Pattern implementation written in Kotlin.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Keb

Web Testing, Browser Automation, Page Object Pattern and more

Content

  1. Page object pattern
  2. Modules
  3. Navigation
  4. Waiting
  5. Installation
  6. Usage

Page object pattern

Keb is a https://gebish.org inspired Selenium wrapper written in Kotlin that allows you to modularize pages of your web application into logic units represented by Kotlin classes.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"
}

Page elements

To select web element on your page use the following methods.

css(".my-selector") // returns single DOM element by CSS selector
cssList(".my-select") // returns list of all elements found by CSS selector// following selectors are also available
html("h1")
htmlList("h1")
xpath("/html/body/h1")
xpathList("/html/body/h1")
by(MyCustomBy()) // can be used with custom implementation of org.openqa.selenium.By
byList(MyCustomBy())

In order to lazily access page content use the content delegate.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"val header by content { css(".global-header-logo") }
val headerText by content { header.text }
}

There are multiple options how to handle the selected content:

  • cache
    • content can be cached - i.e. only initialized on first access, otherwise invoked on every access
    • usage: val title by content(cache=true) { html("h1") }
    • default value: false
  • required:
    • you can specify whether the content is required to be present on the page
    • if required and the content is not present, then the org.openqa.selenium.NoSuchElementException exception is thrown, instance of org.kebish.core.EmptyContent is returned otherwise
    • usage: val title by content(required=true) { html("h1") }
    • default value: true
  • wait:
    • wheter the keb should wait for the given content
    • usage:
    val title by content(wait =true) { html("h1") } // uses the default wait presetval title by content(wait =60) { html("h1") } // uses custom timeout value with retryInterval from the default wait presetval title by content(wait ="quick") { html("h1") } // uses the given wait presetval title by content(waitTimeout =10, waitRetryInterval =2) { html("h1") } // specify both timeout and retry interval
    • if content is still not present after the specified interval, the org.kebish.core.WaitTimeoutException exception is thrown
    • default value: false
    • for more info on waiting, refer to the Waiting section below

Page verifier

In order to verify, that you successfully landed on your page, you can use at method.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"overridefunat() = header
val header by content { css(".global-header-logo") }
}

Whether every page has to define at verifier can be controlled by configuration property atVerifierRequired. If no verifier is defined and neither is required, no page verification is performed.

Modules

If you want to reuse page content present on multiple pages, you can use modules. Module has an optional constructor parameter scope, which can be used as a search root for all the module's content. To initialize module with use the module method.

classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"// either initialize the module by passing the module object into the 'module' methodval menu by content { module(NavMenuModule(css(".nav-links"))) }
// or initialize the module by calling the 'module' method on a WebElement and pass reference to your moduleval menu2 by content { css(".nav-links").module(::NavMenuModule) }
}

Navigation

at(::MyPage) create instance of page and wait until validator at() is satisfied

to(::MyPage) write url of page into browser and call method at()

at(::MyPage) { methodOnMyPage() } methods can have closure. Inside closure this is created page

at(::MyPage) { clickMainLink() } return result of closure

All the methods defined above can be used with constructor reference to(::MyPage) or with instance of a page to(MyPage()). Please note that constructor reference variant can only be used if the required page has no primary constructor parameters.

Both to and at methods can wait for the transition/verification of page to happen. Wait definition is same as for the content method.

val myPage = to(::MyPage, wait =true)
val myPage = to(::MyPage, wait =30)
val myPage = to(::MyPage, wait ="quick")
val myPage = to(::MyPage, waitTimeout =10, waitRetryInterval =1)
// same for the 'at' methods

Navigating pages using fluent API

val joeContact = to(::MyPage)
.via { clickOnContacts() }
.via { findContact("joe") } 
// Same as method above, but explicitly validate, that method is called on correct pageval joeContact = to(::MyPage)
.via(MyPage::class) { clickOnContacts() }
.via(ContactsPage::class) { findContact("joe") } 

Waiting

Waiting for some page element to be present, you can use waitFor method.

css(".form-input").click()
val result = waitFor {
css(".result")
}

Timeout duration and retry interval can be defined either by custom wait preset or directly through waitFor method.

waitFor(preset ="quick") { css(".result") }
waitFor(timeout =2, retryInterval =0.1) { css(".result") }

Custom wait presets can be defined using Kotlin DSL in the Configuration. Default values when no custom configuration is defined are 15 seconds for timeout and 1 second for retry interval.

kebConfig {
waiting {
timeout =15
retryInterval =1"quick" {
timeout =2
retryInterval =0.1
}
"slow" {
timeout =60 }
}
}

Installation

Relesed jar files are available in jcenter().

Gradle setup:

repositories {
jcenter()
}
val kebVersion ="<insert_actual_version>"
dependencies{
implementation("org.kebish:keb-core:$kebVersion")
implementation("org.kebish:keb-junit5:$kebVersion")
implementation("org.kebish:keb-bobril:$kebVersion")
}

If you use Keb in tests you will probably use configuration testImplementation instead of implementation

Full usage - keb + JUnit

packageorg.kebish.usageimportio.github.bonigarcia.wdm.WebDriverManagerimportorg.junit.jupiter.api.Assertionsimportorg.junit.jupiter.api.Testimportorg.kebish.core.*importorg.kebish.junit5.KebTestimportorg.openqa.selenium.WebElementimportorg.openqa.selenium.firefox.FirefoxDriverclassKotlinSiteKebTest : KebTest(Browser(kebConfig {
WebDriverManager.firefoxdriver().setup()
driver = { FirefoxDriver() }
baseUrl = "https://kotlinlang.org"
})) {
@Test
fun`testing kotlin lang page`() {
// givenval homePage = to(::KotlinHomePage)
// whenval title = homePage.header
// thenAssertions.assertEquals("Kotlin", title.text)
Assertions.assertTrue(homePage.licensedUnderApacheLicense())
// whenval docsPage = homePage.openDocumentation()
// thenAssertions.assertEquals("Learn Kotlin", docsPage.title.text)
}
}
classKotlinHomePage : Page() {
overridefunurl() ="/"overridefunat() = header
val header by content { css(".global-header-logo") }
val menu by content { module(NavMenuModule(css(".nav-links"))) }
val footer by content { module(FooterModule(html("footer"))) }
funopenDocumentation(): KotlinDocumentationPage {
menu.menuItems.first { it.text.contains("learn", ignoreCase =true) }.click()
return at(::KotlinDocumentationPage)
}
funlicensedUnderApacheLicense() = footer.licenseNotice.text.contains("apache", ignoreCase =true)
}
classKotlinDocumentationPage : Page() {
overridefunurl() ="/docs/reference"overridefunat() = title
val title by content { html("h1") }
}
classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classFooterModule(scope:WebElement) : Module(scope) {
val licenseNotice by content { css(".terms-copyright") }
val sponsor by content { css(".terms-sponsor") }
}

For full usage example please refer to /keb-core/src/test/kotlin/org/kebish/usage.

About

Keb - Kebish. Selenium wrapper with Page Object Pattern implementation written in Kotlin.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Keb

Web Testing, Browser Automation, Page Object Pattern and more

Content

  1. Page object pattern
  2. Modules
  3. Navigation
  4. Waiting
  5. Installation
  6. Usage

Page object pattern

Keb is a https://gebish.org inspired Selenium wrapper written in Kotlin that allows you to modularize pages of your web application into logic units represented by Kotlin classes.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"
}

Page elements

To select web element on your page use the following methods.

css(".my-selector") // returns single DOM element by CSS selector
cssList(".my-select") // returns list of all elements found by CSS selector// following selectors are also available
html("h1")
htmlList("h1")
xpath("/html/body/h1")
xpathList("/html/body/h1")
by(MyCustomBy()) // can be used with custom implementation of org.openqa.selenium.By
byList(MyCustomBy())

In order to lazily access page content use the content delegate.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"val header by content { css(".global-header-logo") }
val headerText by content { header.text }
}

There are multiple options how to handle the selected content:

  • cache
    • content can be cached - i.e. only initialized on first access, otherwise invoked on every access
    • usage: val title by content(cache=true) { html("h1") }
    • default value: false
  • required:
    • you can specify whether the content is required to be present on the page
    • if required and the content is not present, then the org.openqa.selenium.NoSuchElementException exception is thrown, instance of org.kebish.core.EmptyContent is returned otherwise
    • usage: val title by content(required=true) { html("h1") }
    • default value: true
  • wait:
    • wheter the keb should wait for the given content
    • usage:
    val title by content(wait =true) { html("h1") } // uses the default wait presetval title by content(wait =60) { html("h1") } // uses custom timeout value with retryInterval from the default wait presetval title by content(wait ="quick") { html("h1") } // uses the given wait presetval title by content(waitTimeout =10, waitRetryInterval =2) { html("h1") } // specify both timeout and retry interval
    • if content is still not present after the specified interval, the org.kebish.core.WaitTimeoutException exception is thrown
    • default value: false
    • for more info on waiting, refer to the Waiting section below

Page verifier

In order to verify, that you successfully landed on your page, you can use at method.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"overridefunat() = header
val header by content { css(".global-header-logo") }
}

Whether every page has to define at verifier can be controlled by configuration property atVerifierRequired. If no verifier is defined and neither is required, no page verification is performed.

Modules

If you want to reuse page content present on multiple pages, you can use modules. Module has an optional constructor parameter scope, which can be used as a search root for all the module's content. To initialize module with use the module method.

classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"// either initialize the module by passing the module object into the 'module' methodval menu by content { module(NavMenuModule(css(".nav-links"))) }
// or initialize the module by calling the 'module' method on a WebElement and pass reference to your moduleval menu2 by content { css(".nav-links").module(::NavMenuModule) }
}

Navigation

at(::MyPage) create instance of page and wait until validator at() is satisfied

to(::MyPage) write url of page into browser and call method at()

at(::MyPage) { methodOnMyPage() } methods can have closure. Inside closure this is created page

at(::MyPage) { clickMainLink() } return result of closure

All the methods defined above can be used with constructor reference to(::MyPage) or with instance of a page to(MyPage()). Please note that constructor reference variant can only be used if the required page has no primary constructor parameters.

Both to and at methods can wait for the transition/verification of page to happen. Wait definition is same as for the content method.

val myPage = to(::MyPage, wait =true)
val myPage = to(::MyPage, wait =30)
val myPage = to(::MyPage, wait ="quick")
val myPage = to(::MyPage, waitTimeout =10, waitRetryInterval =1)
// same for the 'at' methods

Navigating pages using fluent API

val joeContact = to(::MyPage)
.via { clickOnContacts() }
.via { findContact("joe") } 
// Same as method above, but explicitly validate, that method is called on correct pageval joeContact = to(::MyPage)
.via(MyPage::class) { clickOnContacts() }
.via(ContactsPage::class) { findContact("joe") } 

Waiting

Waiting for some page element to be present, you can use waitFor method.

css(".form-input").click()
val result = waitFor {
css(".result")
}

Timeout duration and retry interval can be defined either by custom wait preset or directly through waitFor method.

waitFor(preset ="quick") { css(".result") }
waitFor(timeout =2, retryInterval =0.1) { css(".result") }

Custom wait presets can be defined using Kotlin DSL in the Configuration. Default values when no custom configuration is defined are 15 seconds for timeout and 1 second for retry interval.

kebConfig {
waiting {
timeout =15
retryInterval =1"quick" {
timeout =2
retryInterval =0.1
}
"slow" {
timeout =60 }
}
}

Installation

Relesed jar files are available in jcenter().

Gradle setup:

repositories {
jcenter()
}
val kebVersion ="<insert_actual_version>"
dependencies{
implementation("org.kebish:keb-core:$kebVersion")
implementation("org.kebish:keb-junit5:$kebVersion")
implementation("org.kebish:keb-bobril:$kebVersion")
}

If you use Keb in tests you will probably use configuration testImplementation instead of implementation

Full usage - keb + JUnit

packageorg.kebish.usageimportio.github.bonigarcia.wdm.WebDriverManagerimportorg.junit.jupiter.api.Assertionsimportorg.junit.jupiter.api.Testimportorg.kebish.core.*importorg.kebish.junit5.KebTestimportorg.openqa.selenium.WebElementimportorg.openqa.selenium.firefox.FirefoxDriverclassKotlinSiteKebTest : KebTest(Browser(kebConfig {
WebDriverManager.firefoxdriver().setup()
driver = { FirefoxDriver() }
baseUrl = "https://kotlinlang.org"
})) {
@Test
fun`testing kotlin lang page`() {
// givenval homePage = to(::KotlinHomePage)
// whenval title = homePage.header
// thenAssertions.assertEquals("Kotlin", title.text)
Assertions.assertTrue(homePage.licensedUnderApacheLicense())
// whenval docsPage = homePage.openDocumentation()
// thenAssertions.assertEquals("Learn Kotlin", docsPage.title.text)
}
}
classKotlinHomePage : Page() {
overridefunurl() ="/"overridefunat() = header
val header by content { css(".global-header-logo") }
val menu by content { module(NavMenuModule(css(".nav-links"))) }
val footer by content { module(FooterModule(html("footer"))) }
funopenDocumentation(): KotlinDocumentationPage {
menu.menuItems.first { it.text.contains("learn", ignoreCase =true) }.click()
return at(::KotlinDocumentationPage)
}
funlicensedUnderApacheLicense() = footer.licenseNotice.text.contains("apache", ignoreCase =true)
}
classKotlinDocumentationPage : Page() {
overridefunurl() ="/docs/reference"overridefunat() = title
val title by content { html("h1") }
}
classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classFooterModule(scope:WebElement) : Module(scope) {
val licenseNotice by content { css(".terms-copyright") }
val sponsor by content { css(".terms-sponsor") }
}

For full usage example please refer to /keb-core/src/test/kotlin/org/kebish/usage.

About

Keb - Kebish. Selenium wrapper with Page Object Pattern implementation written in Kotlin.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Keb

Web Testing, Browser Automation, Page Object Pattern and more

Content

  1. Page object pattern
  2. Modules
  3. Navigation
  4. Waiting
  5. Installation
  6. Usage

Page object pattern

Keb is a https://gebish.org inspired Selenium wrapper written in Kotlin that allows you to modularize pages of your web application into logic units represented by Kotlin classes.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"
}

Page elements

To select web element on your page use the following methods.

css(".my-selector") // returns single DOM element by CSS selector
cssList(".my-select") // returns list of all elements found by CSS selector// following selectors are also available
html("h1")
htmlList("h1")
xpath("/html/body/h1")
xpathList("/html/body/h1")
by(MyCustomBy()) // can be used with custom implementation of org.openqa.selenium.By
byList(MyCustomBy())

In order to lazily access page content use the content delegate.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"val header by content { css(".global-header-logo") }
val headerText by content { header.text }
}

There are multiple options how to handle the selected content:

  • cache
    • content can be cached - i.e. only initialized on first access, otherwise invoked on every access
    • usage: val title by content(cache=true) { html("h1") }
    • default value: false
  • required:
    • you can specify whether the content is required to be present on the page
    • if required and the content is not present, then the org.openqa.selenium.NoSuchElementException exception is thrown, instance of org.kebish.core.EmptyContent is returned otherwise
    • usage: val title by content(required=true) { html("h1") }
    • default value: true
  • wait:
    • wheter the keb should wait for the given content
    • usage:
    val title by content(wait =true) { html("h1") } // uses the default wait presetval title by content(wait =60) { html("h1") } // uses custom timeout value with retryInterval from the default wait presetval title by content(wait ="quick") { html("h1") } // uses the given wait presetval title by content(waitTimeout =10, waitRetryInterval =2) { html("h1") } // specify both timeout and retry interval
    • if content is still not present after the specified interval, the org.kebish.core.WaitTimeoutException exception is thrown
    • default value: false
    • for more info on waiting, refer to the Waiting section below

Page verifier

In order to verify, that you successfully landed on your page, you can use at method.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"overridefunat() = header
val header by content { css(".global-header-logo") }
}

Whether every page has to define at verifier can be controlled by configuration property atVerifierRequired. If no verifier is defined and neither is required, no page verification is performed.

Modules

If you want to reuse page content present on multiple pages, you can use modules. Module has an optional constructor parameter scope, which can be used as a search root for all the module's content. To initialize module with use the module method.

classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"// either initialize the module by passing the module object into the 'module' methodval menu by content { module(NavMenuModule(css(".nav-links"))) }
// or initialize the module by calling the 'module' method on a WebElement and pass reference to your moduleval menu2 by content { css(".nav-links").module(::NavMenuModule) }
}

Navigation

at(::MyPage) create instance of page and wait until validator at() is satisfied

to(::MyPage) write url of page into browser and call method at()

at(::MyPage) { methodOnMyPage() } methods can have closure. Inside closure this is created page

at(::MyPage) { clickMainLink() } return result of closure

All the methods defined above can be used with constructor reference to(::MyPage) or with instance of a page to(MyPage()). Please note that constructor reference variant can only be used if the required page has no primary constructor parameters.

Both to and at methods can wait for the transition/verification of page to happen. Wait definition is same as for the content method.

val myPage = to(::MyPage, wait =true)
val myPage = to(::MyPage, wait =30)
val myPage = to(::MyPage, wait ="quick")
val myPage = to(::MyPage, waitTimeout =10, waitRetryInterval =1)
// same for the 'at' methods

Navigating pages using fluent API

val joeContact = to(::MyPage)
.via { clickOnContacts() }
.via { findContact("joe") } 
// Same as method above, but explicitly validate, that method is called on correct pageval joeContact = to(::MyPage)
.via(MyPage::class) { clickOnContacts() }
.via(ContactsPage::class) { findContact("joe") } 

Waiting

Waiting for some page element to be present, you can use waitFor method.

css(".form-input").click()
val result = waitFor {
css(".result")
}

Timeout duration and retry interval can be defined either by custom wait preset or directly through waitFor method.

waitFor(preset ="quick") { css(".result") }
waitFor(timeout =2, retryInterval =0.1) { css(".result") }

Custom wait presets can be defined using Kotlin DSL in the Configuration. Default values when no custom configuration is defined are 15 seconds for timeout and 1 second for retry interval.

kebConfig {
waiting {
timeout =15
retryInterval =1"quick" {
timeout =2
retryInterval =0.1
}
"slow" {
timeout =60 }
}
}

Installation

Relesed jar files are available in jcenter().

Gradle setup:

repositories {
jcenter()
}
val kebVersion ="<insert_actual_version>"
dependencies{
implementation("org.kebish:keb-core:$kebVersion")
implementation("org.kebish:keb-junit5:$kebVersion")
implementation("org.kebish:keb-bobril:$kebVersion")
}

If you use Keb in tests you will probably use configuration testImplementation instead of implementation

Full usage - keb + JUnit

packageorg.kebish.usageimportio.github.bonigarcia.wdm.WebDriverManagerimportorg.junit.jupiter.api.Assertionsimportorg.junit.jupiter.api.Testimportorg.kebish.core.*importorg.kebish.junit5.KebTestimportorg.openqa.selenium.WebElementimportorg.openqa.selenium.firefox.FirefoxDriverclassKotlinSiteKebTest : KebTest(Browser(kebConfig {
WebDriverManager.firefoxdriver().setup()
driver = { FirefoxDriver() }
baseUrl = "https://kotlinlang.org"
})) {
@Test
fun`testing kotlin lang page`() {
// givenval homePage = to(::KotlinHomePage)
// whenval title = homePage.header
// thenAssertions.assertEquals("Kotlin", title.text)
Assertions.assertTrue(homePage.licensedUnderApacheLicense())
// whenval docsPage = homePage.openDocumentation()
// thenAssertions.assertEquals("Learn Kotlin", docsPage.title.text)
}
}
classKotlinHomePage : Page() {
overridefunurl() ="/"overridefunat() = header
val header by content { css(".global-header-logo") }
val menu by content { module(NavMenuModule(css(".nav-links"))) }
val footer by content { module(FooterModule(html("footer"))) }
funopenDocumentation(): KotlinDocumentationPage {
menu.menuItems.first { it.text.contains("learn", ignoreCase =true) }.click()
return at(::KotlinDocumentationPage)
}
funlicensedUnderApacheLicense() = footer.licenseNotice.text.contains("apache", ignoreCase =true)
}
classKotlinDocumentationPage : Page() {
overridefunurl() ="/docs/reference"overridefunat() = title
val title by content { html("h1") }
}
classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classFooterModule(scope:WebElement) : Module(scope) {
val licenseNotice by content { css(".terms-copyright") }
val sponsor by content { css(".terms-sponsor") }
}

For full usage example please refer to /keb-core/src/test/kotlin/org/kebish/usage.

About

Keb - Kebish. Selenium wrapper with Page Object Pattern implementation written in Kotlin.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Keb

Web Testing, Browser Automation, Page Object Pattern and more

Content

  1. Page object pattern
  2. Modules
  3. Navigation
  4. Waiting
  5. Installation
  6. Usage

Page object pattern

Keb is a https://gebish.org inspired Selenium wrapper written in Kotlin that allows you to modularize pages of your web application into logic units represented by Kotlin classes.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"
}

Page elements

To select web element on your page use the following methods.

css(".my-selector") // returns single DOM element by CSS selector
cssList(".my-select") // returns list of all elements found by CSS selector// following selectors are also available
html("h1")
htmlList("h1")
xpath("/html/body/h1")
xpathList("/html/body/h1")
by(MyCustomBy()) // can be used with custom implementation of org.openqa.selenium.By
byList(MyCustomBy())

In order to lazily access page content use the content delegate.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"val header by content { css(".global-header-logo") }
val headerText by content { header.text }
}

There are multiple options how to handle the selected content:

  • cache
    • content can be cached - i.e. only initialized on first access, otherwise invoked on every access
    • usage: val title by content(cache=true) { html("h1") }
    • default value: false
  • required:
    • you can specify whether the content is required to be present on the page
    • if required and the content is not present, then the org.openqa.selenium.NoSuchElementException exception is thrown, instance of org.kebish.core.EmptyContent is returned otherwise
    • usage: val title by content(required=true) { html("h1") }
    • default value: true
  • wait:
    • wheter the keb should wait for the given content
    • usage:
    val title by content(wait =true) { html("h1") } // uses the default wait presetval title by content(wait =60) { html("h1") } // uses custom timeout value with retryInterval from the default wait presetval title by content(wait ="quick") { html("h1") } // uses the given wait presetval title by content(waitTimeout =10, waitRetryInterval =2) { html("h1") } // specify both timeout and retry interval
    • if content is still not present after the specified interval, the org.kebish.core.WaitTimeoutException exception is thrown
    • default value: false
    • for more info on waiting, refer to the Waiting section below

Page verifier

In order to verify, that you successfully landed on your page, you can use at method.

classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"overridefunat() = header
val header by content { css(".global-header-logo") }
}

Whether every page has to define at verifier can be controlled by configuration property atVerifierRequired. If no verifier is defined and neither is required, no page verification is performed.

Modules

If you want to reuse page content present on multiple pages, you can use modules. Module has an optional constructor parameter scope, which can be used as a search root for all the module's content. To initialize module with use the module method.

classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classKotlinHomePage : Page() {
overridefunurl() ="https://kotlinlang.org"// either initialize the module by passing the module object into the 'module' methodval menu by content { module(NavMenuModule(css(".nav-links"))) }
// or initialize the module by calling the 'module' method on a WebElement and pass reference to your moduleval menu2 by content { css(".nav-links").module(::NavMenuModule) }
}

Navigation

at(::MyPage) create instance of page and wait until validator at() is satisfied

to(::MyPage) write url of page into browser and call method at()

at(::MyPage) { methodOnMyPage() } methods can have closure. Inside closure this is created page

at(::MyPage) { clickMainLink() } return result of closure

All the methods defined above can be used with constructor reference to(::MyPage) or with instance of a page to(MyPage()). Please note that constructor reference variant can only be used if the required page has no primary constructor parameters.

Both to and at methods can wait for the transition/verification of page to happen. Wait definition is same as for the content method.

val myPage = to(::MyPage, wait =true)
val myPage = to(::MyPage, wait =30)
val myPage = to(::MyPage, wait ="quick")
val myPage = to(::MyPage, waitTimeout =10, waitRetryInterval =1)
// same for the 'at' methods

Navigating pages using fluent API

val joeContact = to(::MyPage)
.via { clickOnContacts() }
.via { findContact("joe") } 
// Same as method above, but explicitly validate, that method is called on correct pageval joeContact = to(::MyPage)
.via(MyPage::class) { clickOnContacts() }
.via(ContactsPage::class) { findContact("joe") } 

Waiting

Waiting for some page element to be present, you can use waitFor method.

css(".form-input").click()
val result = waitFor {
css(".result")
}

Timeout duration and retry interval can be defined either by custom wait preset or directly through waitFor method.

waitFor(preset ="quick") { css(".result") }
waitFor(timeout =2, retryInterval =0.1) { css(".result") }

Custom wait presets can be defined using Kotlin DSL in the Configuration. Default values when no custom configuration is defined are 15 seconds for timeout and 1 second for retry interval.

kebConfig {
waiting {
timeout =15
retryInterval =1"quick" {
timeout =2
retryInterval =0.1
}
"slow" {
timeout =60 }
}
}

Installation

Relesed jar files are available in jcenter().

Gradle setup:

repositories {
jcenter()
}
val kebVersion ="<insert_actual_version>"
dependencies{
implementation("org.kebish:keb-core:$kebVersion")
implementation("org.kebish:keb-junit5:$kebVersion")
implementation("org.kebish:keb-bobril:$kebVersion")
}

If you use Keb in tests you will probably use configuration testImplementation instead of implementation

Full usage - keb + JUnit

packageorg.kebish.usageimportio.github.bonigarcia.wdm.WebDriverManagerimportorg.junit.jupiter.api.Assertionsimportorg.junit.jupiter.api.Testimportorg.kebish.core.*importorg.kebish.junit5.KebTestimportorg.openqa.selenium.WebElementimportorg.openqa.selenium.firefox.FirefoxDriverclassKotlinSiteKebTest : KebTest(Browser(kebConfig {
WebDriverManager.firefoxdriver().setup()
driver = { FirefoxDriver() }
baseUrl = "https://kotlinlang.org"
})) {
@Test
fun`testing kotlin lang page`() {
// givenval homePage = to(::KotlinHomePage)
// whenval title = homePage.header
// thenAssertions.assertEquals("Kotlin", title.text)
Assertions.assertTrue(homePage.licensedUnderApacheLicense())
// whenval docsPage = homePage.openDocumentation()
// thenAssertions.assertEquals("Learn Kotlin", docsPage.title.text)
}
}
classKotlinHomePage : Page() {
overridefunurl() ="/"overridefunat() = header
val header by content { css(".global-header-logo") }
val menu by content { module(NavMenuModule(css(".nav-links"))) }
val footer by content { module(FooterModule(html("footer"))) }
funopenDocumentation(): KotlinDocumentationPage {
menu.menuItems.first { it.text.contains("learn", ignoreCase =true) }.click()
return at(::KotlinDocumentationPage)
}
funlicensedUnderApacheLicense() = footer.licenseNotice.text.contains("apache", ignoreCase =true)
}
classKotlinDocumentationPage : Page() {
overridefunurl() ="/docs/reference"overridefunat() = title
val title by content { html("h1") }
}
classNavMenuModule(scope:WebElement) : Module(scope) {
val menuItems by content { htmlList("a") }
}
classFooterModule(scope:WebElement) : Module(scope) {
val licenseNotice by content { css(".terms-copyright") }
val sponsor by content { css(".terms-sponsor") }
}

For full usage example please refer to /keb-core/src/test/kotlin/org/kebish/usage.

About

Keb - Kebish. Selenium wrapper with Page Object Pattern implementation written in Kotlin.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages