seleniumtestinglib.Core.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of selenium-testing-library Show documentation
Show all versions of selenium-testing-library Show documentation
Selenium locators that resemble the Testing Library (testing-library.com)
The newest version!
package seleniumtestinglib
import org.openqa.selenium.JavascriptExecutor
import org.openqa.selenium.SearchContext
import org.openqa.selenium.WebDriver
import org.openqa.selenium.WebElement
import seleniumtestinglib.TextMatch.JsExpression
import seleniumtestinglib.TextMatch.JsString
import java.util.regex.Pattern
import org.openqa.selenium.By as SeleniumBy
class TL {
@Suppress("unused")
companion object By {
/**
* https://testing-library.com/docs/queries/byalttext
*/
@JvmStatic
fun altText(text: String, exact: Boolean? = null, normalizer: JsFunction? = null) =
ByAltText(text)
.apply { exact?.let(::exact) }
.apply { normalizer?.let(::normalizer) }
@JvmStatic
fun altText(text: Pattern, normalizer: JsFunction? = null) =
ByAltText(text)
.apply { normalizer?.let(::normalizer) }
@JvmStatic
fun altText(text: JsFunction, normalizer: JsFunction? = null) =
ByAltText(text).apply { normalizer?.let(::normalizer) }
@JvmStatic
fun altText(text: String) = ByAltText(text)
@JvmStatic
fun altText(text: Pattern) = ByAltText(text)
@JvmStatic
fun altText(text: JsFunction) = ByAltText(text)
/**
* https://testing-library.com/docs/queries/bydisplayvalue
*/
@JvmStatic
fun displayValue(value: String, exact: Boolean? = null, normalizer: JsFunction? = null) =
ByDisplayValue(value)
.apply { exact?.let(::exact) }
.apply { normalizer?.let(::normalizer) }
@JvmStatic
fun displayValue(value: Pattern, normalizer: JsFunction? = null) =
ByDisplayValue(value).apply { normalizer?.let(::normalizer) }
@JvmStatic
fun displayValue(value: JsFunction, normalizer: JsFunction? = null) =
ByDisplayValue(value).apply { normalizer?.let(::normalizer) }
@JvmStatic
fun displayValue(value: String) = ByDisplayValue(value)
@JvmStatic
fun displayValue(value: Pattern) = ByDisplayValue(value)
@JvmStatic
fun displayValue(value: JsFunction) = ByDisplayValue(value)
/**
* https://testing-library.com/docs/queries/bylabeltext
*/
@JvmStatic
fun labelText(
text: String,
exact: Boolean? = null,
selector: String? = null,
normalizer: JsFunction? = null
) =
ByLabelText(text)
.apply { exact?.let(::exact) }
.apply { selector?.let(::selector) }
.apply { normalizer?.let(::normalizer) }
@JvmStatic
fun labelText(
text: Pattern,
selector: String? = null,
normalizer: JsFunction? = null
) =
ByLabelText(text)
.apply { selector?.let(::selector) }
.apply { normalizer?.let(::normalizer) }
@JvmStatic
fun labelText(
text: JsFunction,
selector: String? = null,
normalizer: JsFunction? = null
) =
ByLabelText(text)
.apply { selector?.let(::selector) }
.apply { normalizer?.let(::normalizer) }
@JvmStatic
fun labelText(text: String) = ByLabelText(text)
@JvmStatic
fun labelText(text: Pattern) = ByLabelText(text)
@JvmStatic
fun labelText(text: JsFunction) = ByLabelText(text)
/**
* https://testing-library.com/docs/queries/byplaceholdertext
*/
@JvmStatic
fun placeholderText(text: String, exact: Boolean? = null, normalizer: JsFunction? = null) =
ByPlaceholderText(text)
.apply { exact?.let(::exact) }
.apply { normalizer?.let(::normalizer) }
@JvmStatic
fun placeholderText(text: Pattern, normalizer: JsFunction? = null) =
ByPlaceholderText(text).apply { normalizer?.let(::normalizer) }
@JvmStatic
fun placeholderText(text: JsFunction, normalizer: JsFunction? = null) =
ByPlaceholderText(text).apply { normalizer?.let(::normalizer) }
@JvmStatic
fun placeholderText(text: String) = ByPlaceholderText(text)
@JvmStatic
fun placeholderText(text: Pattern) = ByPlaceholderText(text)
@JvmStatic
fun placeholderText(text: JsFunction) = ByPlaceholderText(text)
/**
* https://testing-library.com/docs/queries/bytestid
*/
@JvmStatic
fun testId(text: String, exact: Boolean? = null, normalizer: JsFunction? = null) =
ByTestId(text)
.apply { exact?.let(::exact) }
.apply { normalizer?.let(::normalizer) }
@JvmStatic
fun testId(text: Pattern, normalizer: JsFunction? = null) =
ByTestId(text).apply { normalizer?.let(::normalizer) }
@JvmStatic
fun testId(text: JsFunction, normalizer: JsFunction? = null) =
ByTestId(text).apply { normalizer?.let(::normalizer) }
@JvmStatic
fun testId(text: String) = ByTestId(text)
@JvmStatic
fun testId(text: Pattern) = ByTestId(text)
@JvmStatic
fun testId(text: JsFunction) = ByTestId(text)
/**
* https://testing-library.com/docs/queries/bytext
*/
@JvmStatic
fun text(
text: String,
selector: String? = null,
exact: Boolean? = null,
ignore: String? = null,
normalizer: JsFunction? = null
) =
ByText(text)
.apply { exact?.let(::exact) }
.apply { normalizer?.let(::normalizer) }
.apply { selector?.let(::selector) }
.apply { ignore?.let(::ignore) }
@JvmStatic
fun text(
text: Pattern,
selector: String? = null,
ignore: String? = null,
normalizer: JsFunction? = null
) =
ByText(text)
.apply { normalizer?.let(::normalizer) }
.apply { selector?.let(::selector) }
.apply { ignore?.let(::ignore) }
@JvmStatic
fun text(
text: JsFunction,
selector: String? = null,
ignore: String? = null,
normalizer: JsFunction? = null
) =
ByText(text)
.apply { normalizer?.let(::normalizer) }
.apply { selector?.let(::selector) }
.apply { ignore?.let(::ignore) }
@JvmStatic
fun text(text: String) = ByText(text)
@JvmStatic
fun text(text: Pattern) = ByText(text)
@JvmStatic
fun text(text: JsFunction) = ByText(text)
/**
* https://testing-library.com/docs/queries/bytitle
*/
@JvmStatic
fun title(title: String, exact: Boolean? = null, normalizer: JsFunction? = null) =
ByTitle(title)
.apply { exact?.let(::exact) }
.apply { normalizer?.let(::normalizer) }
@JvmStatic
fun title(title: Pattern, normalizer: JsFunction? = null) =
ByTitle(title).apply { normalizer?.let(::normalizer) }
@JvmStatic
fun title(title: JsFunction, normalizer: JsFunction? = null) =
ByTitle(title).apply { normalizer?.let(::normalizer) }
@JvmStatic
fun title(title: String) = ByTitle(title)
@JvmStatic
fun title(title: Pattern) = ByTitle(title)
@JvmStatic
fun title(title: JsFunction) = ByTitle(title)
/**
* https://testing-library.com/docs/queries/byrole
*/
@JvmStatic
fun role(
role: Role,
name: String? = null,
nameAsRegex: Pattern? = null,
nameAsFunction: JsFunction? = null,
description: String? = null,
descriptionAsRegex: Pattern? = null,
descriptionAsFunction: JsFunction? = null,
hidden: Boolean? = null,
normalizer: JsFunction? = null,
selected: Boolean? = null,
busy: Boolean? = null,
checked: Boolean? = null,
pressed: Boolean? = null,
suggest: Boolean? = null,
current: Current? = null,
currentAsBoolean: Boolean? = null,
expanded: Boolean? = null,
level: Int? = null,
value: Value? = null,
queryFallbacks: Boolean? = null,
) = role(role).apply {
require(listOfNotNull(name, nameAsFunction, nameAsRegex).size <= 1) { "Please provide name just once." }
require(listOfNotNull(description, descriptionAsFunction, descriptionAsRegex).size <= 1) {
"Please provide description just once."
}
require(listOfNotNull(currentAsBoolean, current).size <= 1) { "Please provide current just once." }
name?.let(::name)
name?.let(::name)
nameAsFunction?.let(::name)
nameAsRegex?.let(::name)
description?.let(::description)
descriptionAsFunction?.let(::description)
descriptionAsRegex?.let(::description)
hidden?.let(::hidden)
normalizer?.let(::normalizer)
selected?.let(::selected)
busy?.let(::busy)
checked?.let(::checked)
pressed?.let(::pressed)
suggest?.let(::suggest)
expanded?.let(::expanded)
value?.let(::value)
current?.let(::current)
currentAsBoolean?.let(::current)
level?.let(::level)
queryFallbacks?.let(::queryFallbacks)
}
@JvmStatic
fun role(role: Role) = ByRole(role)
}
}
abstract class TLBy internal constructor(private val textMatch: TextMatch) : SeleniumBy() {
private val by = javaClass.simpleName
private val options = mutableMapOf()
protected fun set(key: String, value: Any) = apply { options[key] = value }
@Suppress("unchecked_cast")
override fun findElements(context: SearchContext): List {
val jsExecutor = (getWebDriver(context) as JavascriptExecutor)
jsExecutor.ensureScript("testing-library.js", "window.__TL__?.queryAllByTestId")
return jsExecutor.executeScript(buildString {
append("return window.__TL__.queryAll$by(")
(context as? WebDriver)?.let {
append("document.body, ")
}
(context as? WebElement)?.let {
append("arguments[0], ")
}
append(textMatch.escaped)
options.takeIf { it.isNotEmpty() }
?.escaped
?.let { append(", $it") }
append(")")
}, context as? WebElement) as List
}
override fun toString(): String {
val prefix = if (options.isEmpty()) "" else ", "
return "$by($textMatch$prefix${options.entries.joinToString { "${it.key}: ${it.value}" }})"
}
}
class ByAltText private constructor(text: TextMatch) : TLBy(text) {
constructor(value: String) : this(value.asJsString())
constructor(value: Pattern) : this(value.asJsExpression())
constructor(value: JsFunction) : this(value.asJsExpression())
fun exact(exact: Boolean) = apply { set("exact", exact) }
fun normalizer(normalizer: JsFunction) = apply { set("normalizer", normalizer.asJsExpression()) }
}
class ByDisplayValue private constructor(value: TextMatch) : TLBy(value) {
constructor(value: String) : this(value.asJsString())
constructor(value: Pattern) : this(value.asJsExpression())
constructor(value: JsFunction) : this(value.asJsExpression())
fun exact(exact: Boolean) = apply { set("exact", exact) }
fun normalizer(normalizer: JsFunction) = apply { set("normalizer", normalizer.asJsExpression()) }
}
class ByLabelText private constructor(value: TextMatch) : TLBy(value) {
constructor(value: String) : this(value.asJsString())
constructor(value: Pattern) : this(value.asJsExpression())
constructor(value: JsFunction) : this(value.asJsExpression())
fun exact(exact: Boolean) = apply { set("exact", exact) }
fun selector(selector: String) = apply { set("selector", selector) }
fun normalizer(normalizer: JsFunction) = apply { set("normalizer", normalizer.asJsExpression()) }
}
class ByPlaceholderText private constructor(text: TextMatch) : TLBy(text) {
constructor(text: String) : this(text.asJsString())
constructor(text: Pattern) : this(text.asJsExpression())
constructor(text: JsFunction) : this(text.asJsExpression())
fun exact(exact: Boolean) = apply { set("exact", exact) }
fun normalizer(normalizer: JsFunction) = apply { set("normalizer", normalizer.asJsExpression()) }
}
class ByTestId private constructor(text: TextMatch) : TLBy(text) {
constructor(text: String) : this(text.asJsString())
constructor(text: Pattern) : this(text.asJsExpression())
constructor(text: JsFunction) : this(text.asJsExpression())
fun exact(exact: Boolean) = apply { set("exact", exact) }
fun normalizer(normalizer: JsFunction) = apply { set("normalizer", normalizer.asJsExpression()) }
}
class ByText private constructor(text: TextMatch) : TLBy(text) {
constructor(text: String) : this(text.asJsString())
constructor(text: Pattern) : this(text.asJsExpression())
constructor(text: JsFunction) : this(text.asJsExpression())
fun exact(exact: Boolean) = apply { set("exact", exact) }
fun ignore(ignore: String) = apply { set("ignore", ignore) }
fun ignore(ignore: Boolean) = apply { set("ignore", ignore) }
fun selector(selector: String) = apply { set("selector", selector) }
fun normalizer(normalizer: JsFunction) = apply { set("normalizer", normalizer.asJsExpression()) }
}
class ByTitle private constructor(text: TextMatch) : TLBy(text) {
constructor(text: String) : this(text.asJsString())
constructor(text: Pattern) : this(text.asJsExpression())
constructor(text: JsFunction) : this(text.asJsExpression())
fun exact(exact: Boolean) = apply { set("exact", exact) }
fun normalizer(normalizer: JsFunction) = apply { set("normalizer", normalizer.asJsExpression()) }
}
class ByRole private constructor(role: TextMatch) : TLBy(role) {
constructor(role: Role) : this(role.name.lowercase().asJsString())
fun name(name: String) = apply { set("name", name) }
fun name(name: Pattern) = apply { set("name", name.asJsExpression()) }
fun name(name: JsFunction) = apply { set("name", name.asJsExpression()) }
fun description(description: String) = apply { set("description", description) }
fun description(description: Pattern) = apply { set("description", description.asJsExpression()) }
fun description(description: JsFunction) = apply { set("description", description.asJsExpression()) }
fun hidden(hidden: Boolean) = apply { set("hidden", hidden) }
fun selected(selected: Boolean) = apply { set("selected", selected) }
fun busy(busy: Boolean) = apply { set("busy", busy) }
fun checked(checked: Boolean) = apply { set("checked", checked) }
fun pressed(pressed: Boolean) = apply { set("pressed", pressed) }
fun suggest(suggest: Boolean) = apply { set("suggest", suggest) }
fun current(current: Current) = apply { set("current", current.name.lowercase().asJsString()) }
fun current(current: Boolean) = apply { set("current", current) }
fun expanded(expanded: Boolean) = apply { set("expanded", expanded) }
fun level(level: Int) = apply { set("level", level) }
fun value(value: Value) = apply { set("value", value.toMap()) }
fun queryFallbacks(queryFallbacks: Boolean) = apply { set("queryFallbacks", queryFallbacks) }
fun normalizer(normalizer: JsFunction) = apply { set("normalizer", normalizer.asJsExpression()) }
}
class JsFunction(val value: String) {
internal fun asJsExpression() = JsExpression(value)
}
internal sealed class TextMatch(open val value: String) {
class JsString(override val value: String) : TextMatch(value)
class JsExpression(override val value: String) : TextMatch(value)
override fun toString() = value
}
private fun String.asJsString() = JsString(this)
private fun Pattern.asJsExpression(): JsExpression {
val jsFlags = buildString {
if (flags() and Pattern.CASE_INSENSITIVE != 0) append('i')
if (flags() and RegexOption.MULTILINE.value != 0) append('m')
if (flags() and Pattern.DOTALL != 0) append('s')
if (flags() and RegexOption.COMMENTS.value != 0) append('x')
if (flags() and Pattern.UNICODE_CASE != 0) append('u')
}
return JsExpression("/${pattern()}/$jsFlags")
}
private val String.quoted get() = "'${replace("'", "\\'")}'"
private val Any?.escaped: Any?
get() = when (this) {
is JsString -> value.quoted
is JsExpression -> value
is String -> quoted
is Map<*, *> -> entries.joinToString(prefix = "{ ", postfix = " }") {
"${it.key}: ${it.value?.escaped}"
}
else -> this
}
class Value(
private val min: Int? = null,
private val max: Int? = null,
private val now: Int? = null,
private val text: String? = null,
private val textAsRegex: Pattern? = null,
private val textAsFunction: JsFunction? = null,
) {
init {
require(listOfNotNull(text, textAsRegex, textAsFunction).size <= 1) { "Please provide text just once." }
}
internal fun toMap() =
mapOf(
"min" to min,
"max" to max,
"now" to now,
"text" to (text ?: textAsRegex?.asJsExpression() ?: textAsFunction?.asJsExpression())
).filterValues { it != null }
}
/*
* https://www.w3.org/TR/wai-aria-1.2/#aria-current
*/
@Suppress("UNUSED")
enum class Current {
Page, Step, Location, Date, Time
}
/*
* https://www.w3.org/TR/wai-aria-1.2/#role_definitions
*/
@Suppress("UNUSED")
enum class Role {
Alert, AlertDialog, Application, Article, Banner, Button, Cell, CheckBox, ColumnHeader, ComboBox, Command, Comment,
Complementary, Composite, ContentInfo, Definition, Dialog, Directory, Document, Feed, Figure, Form, Generic, Grid,
GridCell, Group, Heading, Img, Input, Landmark, Link, List, ListBox, ListItem, Log, Main, Mark, Marquee, Math, Menu,
MenuBar, MenuItem, MenuItemCheckBox, MenuItemRadio, Meter, Navigation, None, Note, Option, Presentation,
ProgressBar, Radio, RadioGroup, Range, Region, RoleType, Row, RowGroup, RowHeader, ScrollBar, Search, SearchBox,
Section, SectionHead, Select, Separator, Slider, SpinButton, Status, Structure, Suggestion, Switch, Tab, Table,
TabList, TabPanel, Term, TextBox, Timer, Toolbar, Tooltip, Tree, TreeGrid, TreeItem, Widget, Window
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy