Skip to content

Faster setText in Selenium #29

@japgolly

Description

@japgolly

This is much faster

  private def asJsString(text: String): String = {
    val sb = new StringBuilder
    sb.append("'")
    text.foreach {
      case '\'' => sb.append("\\'")
      case '\\' => sb.append("\\\\")
      case '\n' => sb.append("\\n")
      case '\r' => sb.append("\\r")
      case '\t' => sb.append("\\t")
      case c    => sb append c
    }
    sb.append("'")
    sb.toString()
  }

  implicit class WebElementLocalExt(private val self: WebElement) extends AnyVal {
    private def currentValue(): String =
      self.getAttribute("value")

    def quickSetTextIfNeeded(text: String)(implicit d: WebDriver): Unit =
      if (text != currentValue())
        quickSetText(text)

    def quickSetText(text0: String)(implicit d: WebDriver): Unit =
      if (text0.isEmpty)
        self.clear()
      else {
        // React will only ingest the new value when it receives an event - just modifying the dom via JS will go
        // ignored by React. Therefore we set all but the last character using JS then use a real event for the last
        // char. This also has the nice effect of supporting a carriage-return at the end of the text argument to trigger
        // form submission.
        val text                  = text0.replace("\r", "")
        val n                     = text.reverseIterator.takeWhile(_ == '\n').size + 1
        val loc                   = self.getLocation
        val x                     = s"${loc.x}-window.scrollX+1"
        val y                     = s"${loc.y}-window.scrollY+1"
        val init                  = text.dropRight(n)
        val last                  = text.takeRight(n)
        val (lastText, lastOther) = if (last.endsWith("\n")) (last.dropRight(1), "\n") else (last, "")
        val cmd                   = s"document.elementFromPoint($x,$y).value=${asJsString(init)}"
        // println(s"${asJsString(init)} | ${asJsString(lastText)} | ${asJsString(lastOther)}")
        // println(cmd)

        // Step 1: Fast!
        if (currentValue() !=* init) {
          d.executeJsOrThrow(cmd)
          assert(currentValue() ==* init) // Without this, the value here can be overwritten by the steps below
        }

        // Step 2: Send 1 textual char without \n so that the app processes the expected event
        self.sendKeys(Keys.END + lastText)
        assert(currentValue() ==* (init + lastText))

        // Step 3: Send the non-text key
        // This needs to be separate from the previous step else ~2% of the time, the update is missed
        if (lastOther.nonEmpty)
          self.sendKeys(lastOther)
      }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions