Skip to content

Commit

Permalink
Drop implicit JS locators
Browse files Browse the repository at this point in the history
  • Loading branch information
bjuric committed Nov 14, 2024
1 parent fc4ac13 commit e6b54c9
Show file tree
Hide file tree
Showing 10 changed files with 10 additions and 236 deletions.
15 changes: 8 additions & 7 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,14 @@
| `gwen.cli.options.meta` | `gwen.launch.options.meta` |
| `gwen.cli.options.report` | `gwen.launch.options.report` |
| `gwen.cli.options.tags` | `gwen.launch.options.tags` |
| Removed Settings | Use this instead |
| :----------------------------- | :-----------------------------------|
| `gwen.auto.discover.data.csv` | `-i|--input` launch option |
| `gwen.auto.discover.data.json` | `-i|--input` launch option |
| `gwen.web.driver.manager` | N/A - SeleniumManager only used now |
| `gwen.auto.discover.meta` | `-m|--meta` launch option |
| `gwen.associative.meta` | This is now always on |
| Removed Settings | Use this instead |
| :------------------------------ | :-----------------------------------|
| `gwen.auto.discover.data.csv` | `-i|--input` launch option |
| `gwen.auto.discover.data.json` | `-i|--input` launch option |
| `gwen.web.driver.manager` | N/A - SeleniumManager only used now |
| `gwen.auto.discover.meta` | `-m|--meta` launch option |
| `gwen.associative.meta` | This is now always on |
| `gwen.web.implicit.js.locators` | JS locators |
- | Deprecated Implicit values | Use this instead |
| :------------------------------------ | :-------------------------------------------- |
| `gwen.eval.status.keyword` | `gwen.feature.eval.status.keyword` |
Expand Down
3 changes: 0 additions & 3 deletions src/main/resources/gwen.conf
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,6 @@ gwen {
focus = true
moveTo = false
}
js {
locators = false
}
}
locator {
wait {
Expand Down
3 changes: 0 additions & 3 deletions src/main/resources/init/gwen.conf
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,6 @@ gwen {
focus = true
moveTo = false
}
js {
locators = false
}
}
locator {
wait {
Expand Down
10 changes: 0 additions & 10 deletions src/main/scala/gwen/web/eval/WebSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ object WebSettings extends LazyLogging {
`gwen.web.firefox.prefs`
`gwen.web.highlight.style`
`gwen.web.implicit.element.focus`
`gwen.web.implicit.js.locators`
`gwen.web.locator.wait.seconds`
`gwen.web.maximize`
`gwen.web.remote.localFileDetector`
Expand Down Expand Up @@ -414,15 +413,6 @@ object WebSettings extends LazyLogging {
}
}

/**
* Provides access to the `gwen.web.implicit.js.locators` setting used to determine whether or not Gwen should
* implicitly convert all locator bindings to JavaScript equivalents to force all elements to be located by
* executing javascript on the page. Default value is false.
*/
def `gwen.web.implicit.js.locators`: Boolean = {
Settings.getBoolean("gwen.web.implicit.js.locators")
}

/**
* Provides access to the `gwen.web.implicit.element.focus` setting used to determine whether or not Gwen should
* implicitly put the focus on all located web elements. Default value is true.
Expand Down
35 changes: 1 addition & 34 deletions src/main/scala/gwen/web/eval/binding/LocatorBinding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,41 +49,8 @@ class LocatorBinding(val name: String, val selectors: List[Selector], ctx: WebCo
lazy val timeoutSeconds: Long = selectors.map(_.timeoutSeconds).sum

override def resolve(): WebElement = ctx.webElementlocator.locate(this)
def resolveAll(): List[WebElement] = ctx.webElementlocator.locateAll(this)

/** Gets the javascript equivalent of this locator binding (used as fallback on stale element reference). */
def jsEquivalent: LocatorBinding = {
val jsSelectors = selectors.map { loc =>
val isListSelector = name.endsWith("/list")
val jsExpression = loc.selectorType match {
case SelectorType.id =>
if (!isListSelector) s"document.getElementById('${loc.expression}')"
else s"document.querySelectorAll('#${loc.expression}')"
case SelectorType.`css selector` =>
s"document.querySelector${if (isListSelector) "All" else ""}('${StringEscapeUtils.escapeEcmaScript(loc.expression)}')"
case SelectorType.xpath =>
s"document.evaluate('${StringEscapeUtils.escapeEcmaScript(loc.expression)}', document, null, XPathResult.${if (isListSelector) "ORDERED_NODE_ITERATOR_TYPE" else "FIRST_ORDERED_NODE_TYPE"}, null)${if (isListSelector) "" else ".singleNodeValue"}"
case SelectorType.name =>
s"document.getElementsByName('${loc.expression}')${if (isListSelector) "" else "[0]"}"
case SelectorType.`class name` =>
s"document.getElementsByClassName('${loc.expression}')${if (isListSelector) "" else "[0]"}"
case SelectorType.`tag name` =>
s"document.getElementsByTagName('${loc.expression}')${if (isListSelector) "" else "[0]"}"
case SelectorType.`link text` =>
s"""document.evaluate('//a[text()="${StringEscapeUtils.escapeEcmaScript(loc.expression)}"]', document, null, XPathResult.${if (isListSelector) "ORDERED_NODE_ITERATOR_TYPE" else "FIRST_ORDERED_NODE_TYPE"}, null)${if (isListSelector) "" else ".singleNodeValue"}"""
case SelectorType.`partial link text` =>
s"""document.evaluate('//a[contains(text(), "${StringEscapeUtils.escapeEcmaScript(loc.expression)}")]', document, null, XPathResult.${if (isListSelector) "ORDERED_NODE_ITERATOR_TYPE" else "FIRST_ORDERED_NODE_TYPE"}, null)${if (isListSelector) "" else ".singleNodeValue"}"""
case _ => loc.expression
}
Selector(SelectorType.javascript, jsExpression, loc.relative, Some(loc.timeout), loc.index)
}
new LocatorBinding(name, jsSelectors, ctx)
}

def withJSEquivalent = new LocatorBinding(name, selectors ++ List(jsEquivalent.selectors.head), ctx)

def resolveAll(): List[WebElement] = ctx.webElementlocator.locateAll(this)
def withFastTimeout: LocatorBinding = withTimeout(Duration(200, TimeUnit.MILLISECONDS))

def withTimeout(timeout: Option[Duration]): LocatorBinding = timeout.map(withTimeout).getOrElse(this)
private def withTimeout(timeout: Duration): LocatorBinding = {
val newSelectors = selectors map { s =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,7 @@ class LocatorBindingResolver(ctx: WebContext) extends LazyLogging {
}
}
if (selectors.nonEmpty) {
val binding = new LocatorBinding(name, selectors.toList, ctx)
if (WebSettings.`gwen.web.implicit.js.locators`) {
Some(binding.jsEquivalent)
} else {
Some(binding)
}
Some(new LocatorBinding(name, selectors.toList, ctx))
}
else None
case None => if (optional) None else locatorBindingError(s"Undefined selector for: $name")
Expand Down
157 changes: 0 additions & 157 deletions src/test/scala/gwen/web/eval/WebElementLocatorTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ import org.scalatest.matchers.should.Matchers

class WebElementLocatorTest extends BaseTest with Matchers with MockitoSugar with BeforeAndAfterEach {

// disable implicit js locators for unit test
sys.props += (("gwen.web.implicit.js.locators", "false"))

private var mockWebElement: WebElement = uninitialized
private var mockWebElements: List[WebElement] = uninitialized
private var mockContainerElement: WebElement = uninitialized
Expand Down Expand Up @@ -942,160 +939,6 @@ class WebElementLocatorTest extends BaseTest with Matchers with MockitoSugar wit

}

"Single element locator bindings with JS equivalents" should "resolve" in {

val mockWebDriver: FirefoxDriver = mock[FirefoxDriver]
val ctx = newCtx(None, mockWebDriver)

val container = Some((RelativeSelectorType.in, new LocatorBinding("container", List(Selector(SelectorType.id, "container")), ctx), None))

LocatorBinding("user name", SelectorType.id, "username", None, None, ctx).jsEquivalent.toString should be (
"""user name [locator: javascript=document.getElementById('username')]"""
)

LocatorBinding("user name", SelectorType.id, "username", container, None, ctx).jsEquivalent.toString should be (
"""user name [locator: javascript=document.getElementById('username') in container [locator: id=container]]"""
)

LocatorBinding("user name", SelectorType.name, "username", None, None, ctx).jsEquivalent.toString should be (
"""user name [locator: javascript=document.getElementsByName('username')[0]]"""
)

LocatorBinding("user name", SelectorType.name, "username", container, None, ctx).jsEquivalent.toString should be (
"""user name [locator: javascript=document.getElementsByName('username')[0] in container [locator: id=container]]"""
)

LocatorBinding("user name", SelectorType.`class name`, "username", None, None, ctx).jsEquivalent.toString should be (
"""user name [locator: javascript=document.getElementsByClassName('username')[0]]"""
)

LocatorBinding("user name", SelectorType.`class name`, "username", container, None, ctx).jsEquivalent.toString should be (
"""user name [locator: javascript=document.getElementsByClassName('username')[0] in container [locator: id=container]]"""
)

LocatorBinding("user name", SelectorType.`css selector`, ".username", None, None, ctx).jsEquivalent.toString should be (
"user name [locator: javascript=document.querySelector('.username')]"
)

LocatorBinding("user name", SelectorType.`css selector`, ".username", container, None, ctx).jsEquivalent.toString should be (
"""user name [locator: javascript=document.querySelector('.username') in container [locator: id=container]]"""
)

LocatorBinding("user name", SelectorType.`tag name`, "input", None, None, ctx).jsEquivalent.toString should be (
"""user name [locator: javascript=document.getElementsByTagName('input')[0]]"""
)

LocatorBinding("user name", SelectorType.`tag name`, "input", container, None, ctx).jsEquivalent.toString should be (
"""user name [locator: javascript=document.getElementsByTagName('input')[0] in container [locator: id=container]]"""
)

LocatorBinding("user name", SelectorType.`xpath`, "//html/body/div/input", None, None, ctx).jsEquivalent.toString should be (
"""user name [locator: javascript=document.evaluate('\/\/html\/body\/div\/input', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue]"""
)

LocatorBinding("user link", SelectorType.`link text`, "user", None, None, ctx).jsEquivalent.toString should be (
"""user link [locator: javascript=document.evaluate('//a[text()="user"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue]"""
)

LocatorBinding("user link", SelectorType.`link text`, "user", container, None, ctx).jsEquivalent.toString should be (
"""user link [locator: javascript=document.evaluate('//a[text()="user"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue in container [locator: id=container]]"""
)

LocatorBinding("user link", SelectorType.`partial link text`, "user", None, None, ctx).jsEquivalent.toString should be (
"""user link [locator: javascript=document.evaluate('//a[contains(text(), "user")]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue]"""
)

LocatorBinding("user link", SelectorType.`partial link text`, "user", container, None, ctx).jsEquivalent.toString should be (
"""user link [locator: javascript=document.evaluate('//a[contains(text(), "user")]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue in container [locator: id=container]]"""
)

LocatorBinding("user name", SelectorType.javascript, "document.getElementById('user')", None, None, ctx).jsEquivalent.toString should be (
"""user name [locator: javascript=document.getElementById('user')]"""
)

LocatorBinding("user name", SelectorType.javascript, "document.getElementById('user')", container, None, ctx).jsEquivalent.toString should be (
"""user name [locator: javascript=document.getElementById('user') in container [locator: id=container]]"""
)

}

"Multi element locator bindings with JS equivalents" should "resolve" in {

val mockWebDriver: FirefoxDriver = mock[FirefoxDriver]
val ctx = newCtx(None, mockWebDriver)

val container = Some((RelativeSelectorType.in, new LocatorBinding("container", List(Selector(SelectorType.id, "container")), ctx), None))

LocatorBinding("user name/list", SelectorType.id, "username", None, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.querySelectorAll('#username')]"""
)

LocatorBinding("user name/list", SelectorType.id, "username", container, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.querySelectorAll('#username') in container [locator: id=container]]"""
)

LocatorBinding("user name/list", SelectorType.name, "username", None, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.getElementsByName('username')]"""
)

LocatorBinding("user name/list", SelectorType.name, "username", container, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.getElementsByName('username') in container [locator: id=container]]"""
)

LocatorBinding("user name/list", SelectorType.`class name`, "username", None, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.getElementsByClassName('username')]"""
)

LocatorBinding("user name/list", SelectorType.`class name`, "username", container, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.getElementsByClassName('username') in container [locator: id=container]]"""
)

LocatorBinding("user name/list", SelectorType.`css selector`, ".username", None, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.querySelectorAll('.username')]"""
)

LocatorBinding("user name/list", SelectorType.`css selector`, ".username", container, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.querySelectorAll('.username') in container [locator: id=container]]"""
)

LocatorBinding("user name/list", SelectorType.`tag name`, "input", None, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.getElementsByTagName('input')]"""
)

LocatorBinding("user name/list", SelectorType.`tag name`, "input", container, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.getElementsByTagName('input') in container [locator: id=container]]"""
)

LocatorBinding("user name/list", SelectorType.`xpath`, "//html/body/div/input", None, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.evaluate('\/\/html\/body\/div\/input', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null)]"""
)

LocatorBinding("user link/list", SelectorType.`link text`, "user", None, None, ctx).jsEquivalent.toString should be (
"""user link/list [locator: javascript=document.evaluate('//a[text()="user"]', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null)]"""
)

LocatorBinding("user link/list", SelectorType.`link text`, "user", container, None, ctx).jsEquivalent.toString should be (
"""user link/list [locator: javascript=document.evaluate('//a[text()="user"]', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null) in container [locator: id=container]]"""
)

LocatorBinding("user link/list", SelectorType.`partial link text`, "user", None, None, ctx).jsEquivalent.toString should be (
"""user link/list [locator: javascript=document.evaluate('//a[contains(text(), "user")]', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null)]"""
)

LocatorBinding("user link/list", SelectorType.`partial link text`, "user", container, None, ctx).jsEquivalent.toString should be (
"""user link/list [locator: javascript=document.evaluate('//a[contains(text(), "user")]', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null) in container [locator: id=container]]"""
)

LocatorBinding("user name/list", SelectorType.javascript, "document.getElementsByName('user')", None, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.getElementsByName('user')]"""
)

LocatorBinding("user name/list", SelectorType.javascript, "document.getElementsByName('user')", container, None, ctx).jsEquivalent.toString should be (
"""user name/list [locator: javascript=document.getElementsByName('user') in container [locator: id=container]]"""
)

}

private def newLocator(ctx: WebContext): WebElementLocator = {
new WebElementLocator(ctx)
}
Expand Down
2 changes: 0 additions & 2 deletions src/test/scala/gwen/web/eval/WebEngineTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,6 @@ class WebEngineTest extends BaseTest with Matchers with MockitoSugar with Before
"I wait until <element> is <state>" should "evaluate" in {
val mockBinding = mock[LocatorBinding]
doReturn(mockBinding).when(ctx).getLocatorBinding("<element>")
doReturn(mockBinding).when(mockBinding).jsEquivalent
elemStates.foreach { state =>
doReturn(None).when(mockTopScope).getOpt(s"<element> is $state/javascript")
doNothing().when(ctx).waitForElementState(mockBinding, state, false)
Expand All @@ -628,7 +627,6 @@ class WebEngineTest extends BaseTest with Matchers with MockitoSugar with Before
"I wait until <element> is not <state>" should "evaluate" in {
val mockBinding = mock[LocatorBinding]
doReturn(mockBinding).when(ctx).getLocatorBinding("<element>")
doReturn(mockBinding).when(mockBinding).jsEquivalent
elemStates.foreach { state =>
doReturn(None).when(mockTopScope).getOpt(s"<element> is not $state/javascript")
doNothing().when(ctx).waitForElementState(mockBinding, state, true)
Expand Down
3 changes: 0 additions & 3 deletions src/test/scala/gwen/web/eval/WebSettingsTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ class WebSettingsTest extends BaseTest with Matchers with MockitoSugar {
WebSettings.`gwen.web.highlight.style` should be ("background: yellow; border: 2px solid gold;")
WebSettings.`gwen.web.implicit.element.focus` should be (true)
WebSettings.`gwen.web.implicit.element.moveTo` should be (false)
WebSettings.`gwen.web.implicit.js.locators` should be (false)
WebSettings.`gwen.web.locator.wait.seconds` should be (10L)
WebSettings.`gwen.web.maximize` should be (false)
WebSettings.`gwen.web.remote.localFileDetector` should be (false)
Expand Down Expand Up @@ -211,7 +210,6 @@ class WebSettingsTest extends BaseTest with Matchers with MockitoSugar {
WebSettings.`gwen.web.highlight.style` should be ("background: yellow; border: 2px solid gold;")
WebSettings.`gwen.web.implicit.element.focus` should be (true)
WebSettings.`gwen.web.implicit.element.moveTo` should be (false)
WebSettings.`gwen.web.implicit.js.locators` should be (false)
WebSettings.`gwen.web.locator.wait.seconds` should be (10L)
WebSettings.`gwen.web.maximize` should be (false)
WebSettings.`gwen.web.remote.localFileDetector` should be (false)
Expand Down Expand Up @@ -305,7 +303,6 @@ class WebSettingsTest extends BaseTest with Matchers with MockitoSugar {
WebSettings.`gwen.web.highlight.style` should be ("background: yellow; border: 2px solid gold;")
WebSettings.`gwen.web.implicit.element.focus` should be (true)
WebSettings.`gwen.web.implicit.element.moveTo` should be (false)
WebSettings.`gwen.web.implicit.js.locators` should be (false)
WebSettings.`gwen.web.locator.wait.seconds` should be (10L)
WebSettings.`gwen.web.maximize` should be (false)
WebSettings.`gwen.web.remote.localFileDetector` should be (false)
Expand Down
11 changes: 0 additions & 11 deletions src/test/scala/gwen/web/features/FeaturesTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,5 @@ class FeaturesTest extends BaseFeatureTest {
None)
}
}

"Implicit javascript locators" should "evaluate" in {
withSetting("gwen.web.implicit.js.locators", "true") {
evaluate(
List("src/test/features/samples/se-test"),
parallel = true,
dryRun = false,
"implicit-js-locators",
None)
}
}

}

0 comments on commit e6b54c9

Please sign in to comment.