├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── build.sbt
├── project
├── build.properties
├── headers.sbt
└── release.sbt
├── src
├── main
│ └── scala
│ │ └── com
│ │ └── markatta
│ │ └── scalenium
│ │ ├── Await.scala
│ │ ├── Browser.scala
│ │ ├── Cookies.scala
│ │ ├── Element.scala
│ │ ├── ElementInteractions.scala
│ │ ├── ElementSeq.scala
│ │ ├── FailureHandler.scala
│ │ ├── Forms.scala
│ │ ├── HasDriver.scala
│ │ ├── HasSearchContext.scala
│ │ ├── JqueryStyle.scala
│ │ ├── MarkupSearch.scala
│ │ ├── Navigation.scala
│ │ ├── PageProperties.scala
│ │ ├── ScreenShots.scala
│ │ ├── Scripts.scala
│ │ ├── Specs2Integration.scala
│ │ ├── Time.scala
│ │ └── package.scala
└── test
│ └── scala
│ └── com
│ └── markatta
│ └── scalenium
│ ├── DslSpec.scala
│ ├── HttpTestServer.scala
│ └── ScaleniumSpec.scala
└── version.sbt
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.log
3 |
4 | # sbt specific
5 | dist/*
6 | target/
7 | lib_managed/
8 | src_managed/
9 | project/boot/
10 | project/plugins/project/
11 |
12 | # Scala-IDE specific
13 | .scala_dependencies
14 |
15 | # IDEA
16 | .idea
17 | .idea_modules
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 | jdk:
3 | - oraclejdk8
4 | scala:
5 | - 2.10.6
6 | - 2.11.4
7 | - 2.12.2
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This software is licensed under the Apache 2 license, quoted below.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with
4 | the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
5 |
6 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
7 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
8 | language governing permissions and limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | scalenium
2 | =========
3 | [](https://travis-ci.org/johanandren/scalenium)
4 |
5 | A Scala-ified fluent wrapper for selenium webdriver heavily inspired by FluentLenium but making use
6 | of the scala collection API:s and all the fun DSL:y stuff possible with Scala.
7 |
8 | License: [Apache Licence v2](https://www.apache.org/licenses/LICENSE-2.0.txt).
9 |
10 |
11 |
12 | Using the library
13 | -----------------
14 |
15 | Scalenium is available from maven central, just add it Scalenium to your dependencies:
16 |
17 | ```scala
18 | libraryDependencies += "com.markatta" %% "scalenium" % "1.0.3"
19 | ```
20 |
21 | Latest stable version:
22 |
23 | * Scala 2.12 - [](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22scalenium_2.12%22)
24 | * Scala 2.11 - [](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22scalenium_2.11%22)
25 | * Scala 2.10 - [](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22scalenium_2.10%22)
26 |
27 |
28 |
29 | Setting up base object of library
30 | ---------------------------------
31 |
32 | Everything in the API is available through the class com.markatta.scalenium.Browser
33 |
34 | You can create a Browser instance like this (of course you may use any other selenium driver):
35 | ```scala
36 | import com.markatta.scalenium._
37 | val browser = new Browser(new FirefoxDriver())
38 | ```
39 |
40 | Selecting elements in a page
41 | ----------------------------
42 |
43 | Is done with a css selector, here are some samples of how it would look in specs2 tests
44 |
45 | ```scala
46 | import com.markatta.scalenium._
47 | val b: Browser = ...
48 |
49 | b.find(".cssClass") // returns a Seq[Element]
50 | b.first(".cssClass") // returns an Option[Element]
51 |
52 | // aliases to find, for an additional style, see the JQuery syntax example below
53 | b.all(".cssClass")
54 | b.select(".cssClass")
55 |
56 | // use regular scala collection API ops
57 | b.find(".someClass").isEmpty must beTrue
58 | b.find(".someClass") must haveSize(0)
59 | b.first(".someClass").isEmpty must beTrue
60 | b.find(".someClass").exists(_.id == "someId") must beTrue
61 | b.find(".someClass").size must equalTo(3)
62 |
63 | // Element contains the same ops as browser for searching so
64 | // these two gives the same result
65 | b.first(".someClass").get.first(".someOtherClass")
66 | b.first(".someClass > .someOtherClass")
67 |
68 | // Seq[Element] is implicitly converted to an ElementSeq by
69 | // com.markatta.scalenium.seqOfElements2ElementSeq
70 | // allowing us to:
71 |
72 | b.find(".cssClass").hidden must beTrue // all elements matching .cssClass hidden
73 | b.find(".someClass > .someOtherClass").anySelected must beTrue
74 | b.find("li").texts must contain("Banana", "Pineapple")
75 | b.find("ul").find("li").size must equalTo(4)
76 |
77 | b.first("#it") must beSome
78 | b.first("#it").map(_.visible) must beSome(true)
79 | ```
80 |
81 | JQuery style selection
82 | ----------------------
83 | It is also possible to do selects with a jquery flavored syntax, which will use an implicit browser for searching:
84 | ```scala
85 | import com.markatta.scalenium._
86 | import JqueryStyle._
87 | implicit b: Browser = ...
88 |
89 | $("#someId").visible must beTrue
90 | $("ul > li").exists(li => li.text == "Banana" && li.visible) must beTrue
91 | ```
92 |
93 | Entering data into forms
94 | ------------------------
95 | ```scala
96 | import com.markatta.scalenium._
97 | val b: Browser = ...
98 |
99 | b.write("newPassword").into("input[name='password']")
100 | b.write("happy").intoAll("input[type='text']")
101 |
102 | b.first("input[name='password']").get.write("newPassword")
103 | // if multiple elements are matched this writes into all of them
104 | b.find("input[name='password']").write("newPassword")
105 |
106 |
107 | // fill entire form
108 | // identifying fields with name
109 | b.fillByName("user" -> "johndoe", "password" -> "newPasword")
110 | // id
111 | b.fillById("user" -> "johndoe", "password" -> "newPassword")
112 | // or css selector
113 | b.fill("input#user" -> "johndoe", "input[name='password']" -> "newPassword")
114 | ```
115 |
116 |
117 | Waiting for asynchronous javascript logic
118 | -----------------------------------------
119 | In many cases we want to wait for some asynchronous operation for a while before
120 | failing a test, the default timeout is 5 seconds, polling every 250ms:
121 |
122 | ```scala
123 | import com.markatta.scalenium._
124 | val b: Browser = ...
125 |
126 | // to get specs2 test failures instead of exceptions
127 | import Specs2Integration.specs2FailureHandler
128 |
129 | b.waitFor(".someClass").toBecomeVisible()
130 | b.waitAtMost(5).secondsFor(".someClass").toBecomeVisible()
131 |
132 | b.waitFor(1 == 2).toBecomeTrue()
133 | b.waitAtMost(3000000).secondsFor(1 == 2).toBecomeTrue
134 |
135 | b.waitAtMost(5).secondsFor(b.find("button").allAreDisabled).toBecomeTrue
136 | ```
137 |
138 | The default timeout and polling interval can be provided with implicit values:
139 | ```scala
140 | import com.markatta.scalenium._
141 | import scala.concurrent.duration._
142 |
143 | implicit val timeout = Timeout(3.seconds)
144 | implicit val interval = Interval(100.millis)
145 |
146 | b.waitFor(".someClass").toBecomeVisible()
147 | ```
148 |
149 | Note that you need to mix in NoTimeConversions into your test spec or else the Specs2 time unit classes
150 | will interfere with the scala.concurrent.duration time units.
151 |
152 | Error handling
153 | --------------
154 | TODO not so sure about this, should the error handling be set on the browser object instead, it will probably not need to change in the same browser??
155 |
156 | Error handling is done by implicit implementations of the various XxxFailureHandler traits,
157 | the defaults throw exceptions defined in the library which might not suit any given test framework
158 | or your use case so good. Therefore you can define your own or use another set of predefined
159 | failure handlers by defining them as implicits closer to where you use the framework.
160 |
161 | Example
162 | ```scala
163 | // to get specs2 test failures instead of exceptions
164 | import Specs2Integration.specs2FailureHandler // <- defined as an "implicit object"
165 |
166 | ... use API:s ...
167 | ```
168 |
169 | or
170 | ```scala
171 | implicit val customHandler = new MissingElementFailureHandler { ... }
172 |
173 | ... use API:s ...
174 | ```
175 |
176 |
177 | License
178 | -------
179 | Copyright 2012 Johan Andrén
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | import de.heikoseeberger.sbtheader.license.Apache2_0
2 |
3 | name := "scalenium"
4 | organization := "com.markatta"
5 |
6 | scalaVersion := "2.12.2"
7 | crossScalaVersions := Seq(scalaVersion.value, "2.11.11", "2.10.6")
8 |
9 | scalacOptions ++= Seq("-feature", "-deprecation")
10 |
11 | libraryDependencies ++= Seq(
12 | "org.seleniumhq.selenium" % "selenium-java" % "2.44.0",
13 | // this should probably be an optional dependency somehow, extract specs2 integration to separate module?
14 | "org.specs2" %% "specs2" % "2.4.17"
15 | )
16 |
17 | resolvers ++= Seq(
18 | // required for the scalaz-streams dependency from specs2 :(
19 | "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases"
20 | )
21 |
22 | initialCommands := """
23 | import com.markatta.scalenium._
24 | val browser = new Browser(new org.openqa.selenium.htmlunit.HtmlUnitDriver)
25 | """
26 |
27 | headers := Map(
28 | "scala" -> Apache2_0("2015", "Johan Andrén")
29 | )
30 |
31 | // releasing
32 | sonatypeSettings
33 | licenses := Seq("Apache License, Version 2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0"))
34 | homepage := Some(url("https://github.com/johanandren/scalenium"))
35 | publishMavenStyle := true
36 | publishArtifact in Test := false
37 | pomIncludeRepository := { _ => false }
38 | publishTo := Some {
39 | val nexus = "https://oss.sonatype.org/"
40 | if (isSnapshot.value)
41 | "snapshots" at nexus + "content/repositories/snapshots"
42 | else
43 | "releases" at nexus + "service/local/staging/deploy/maven2"
44 | }
45 |
46 | pomExtra :=
47 |
48 | git@github.com:johanandren/scalenium.git
49 | scm:git:git@github.com:johanandren/scalenium.git
50 |
51 |
52 |
53 | johanandren
54 | Johan Andrén
55 | johan@markatta.com
56 | https://markatta.com/johan/codemonkey
57 |
58 |
59 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.16
2 |
--------------------------------------------------------------------------------
/project/headers.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("de.heikoseeberger" % "sbt-header" % "1.3.1")
--------------------------------------------------------------------------------
/project/release.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "0.2.2")
2 |
3 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.6")
4 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/Await.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | import scala.concurrent.duration._
20 | import scala.annotation.tailrec
21 |
22 | object Await {
23 |
24 | /** Run until check returns true or timeout from now is passed
25 | * @return
26 | */
27 | private def becameTrue(timeout: Timeout, interval: Interval, check: => Boolean): Boolean = {
28 | val end = System.currentTimeMillis + timeout.inner.toMillis
29 |
30 | @tailrec
31 | def loop(until: Long, interval: FiniteDuration, predicate: => Boolean): Boolean = {
32 | if (System.currentTimeMillis() > end) {
33 | false
34 | } else if (predicate) {
35 | true
36 | } else {
37 | Thread.sleep(interval.toMillis)
38 | loop(until, interval, predicate)
39 | }
40 | }
41 |
42 | loop(end, interval.inner, check)
43 | }
44 |
45 | }
46 |
47 | trait Await { this: MarkupSearch =>
48 | import Await._
49 |
50 | implicit val defaultTimeout = Timeout(5.seconds)
51 | implicit val defaultInterval = Interval(250.millis)
52 |
53 |
54 | class UntilCssSelector(cssSelector: String, timeout: Timeout, pollingInterval: Interval) {
55 | def toBecomeVisible[A]()(implicit failureHandler: TimeoutFailureHandler): Unit = {
56 | if (!becameTrue(timeout, pollingInterval, first(cssSelector).isDefined)) {
57 | failureHandler.fail(timeout, "element matching '" + cssSelector + "' never became visible")
58 | }
59 | }
60 | def toDisappear()(implicit failureHandler: TimeoutFailureHandler): Unit = {
61 | if (!becameTrue(timeout, pollingInterval, first(cssSelector).isEmpty)) {
62 | failureHandler.fail(timeout, "element matching '" + cssSelector + "' never disappeared")
63 | }
64 | }
65 | }
66 |
67 | class UntilPredicate(predicate: => Boolean, timeout: Timeout, pollingInterval: Interval) {
68 | def toBecomeTrue()(implicit failureHandler: TimeoutFailureHandler): Unit = {
69 | if (!becameTrue(timeout, pollingInterval, predicate)) {
70 | failureHandler.fail(timeout, "expression never became true")
71 | }
72 | }
73 | def toBecomeFalse()(implicit failureHandler: TimeoutFailureHandler): Unit = {
74 | if (!becameTrue(timeout, pollingInterval, !predicate)) {
75 | failureHandler.fail(timeout, "expression never became false")
76 | }
77 | }
78 | }
79 |
80 | // implicit timeout
81 | def waitFor(predicate: => Boolean)(implicit timeout: Timeout, interval: Interval) =
82 | new UntilPredicate(predicate, timeout, interval)
83 |
84 | def waitFor(cssSelector: String)(implicit timeout: Timeout, interval: Interval) =
85 | new UntilCssSelector(cssSelector, timeout, interval)
86 |
87 | // explicit timeouts
88 | class WaitAtMostBuilder private[Await](n: Long) {
89 | def secondsFor(predicate: => Boolean)(implicit interval: Interval) = new UntilPredicate(predicate, new Timeout(n.seconds), interval)
90 | def secondsFor(cssSelector: String)(implicit interval: Interval) = new UntilCssSelector(cssSelector, new Timeout(n.seconds), interval)
91 | def msFor(predicate: => Boolean)(implicit interval: Interval) = new UntilPredicate(predicate, new Timeout(n.millis), interval)
92 | def msFor(cssSelector: String)(implicit interval: Interval) = new UntilCssSelector(cssSelector, new Timeout(n.millis), interval)
93 |
94 | }
95 | def waitAtMost(n: Long) = new WaitAtMostBuilder(n)
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/Browser.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | import org.openqa.selenium._
20 |
21 | object Browser {
22 | def apply(driver: WebDriver): Browser = new Browser(driver)
23 | }
24 |
25 | /**
26 | * Represents the browser, use it to do stuff you do with your browser, but automated.
27 | * @param driver Selenium WebDriver to use as backend
28 | */
29 | final class Browser(protected val driver: WebDriver)
30 | extends HasDriver
31 | with HasSearchContext
32 | with PageProperties
33 | with Cookies
34 | with ScreenShots
35 | with Navigation
36 | with Scripts
37 | with MarkupSearch
38 | with Await
39 | with Forms {
40 |
41 | override val searchContext = driver
42 |
43 | /** close the current window, quit of no windows left */
44 | def close(): Unit = {
45 | driver.close()
46 | }
47 |
48 | /** shut down the selenium browser */
49 | def quit(): Unit = {
50 | driver.quit()
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/Cookies.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | import scala.collection.breakOut
20 | import scala.collection.JavaConversions._
21 | import java.util.Date
22 |
23 | trait Cookies { this: HasDriver =>
24 |
25 | def cookies: Map[String, Cookie] = driver.manage().getCookies.map(c => (c.getName, Cookie(c)))(breakOut)
26 |
27 | }
28 |
29 | object Cookie {
30 | def apply(c: org.openqa.selenium.Cookie): Cookie =
31 | new Cookie(c.getName, c.getDomain, c.getPath, c.getExpiry, c.getValue, c.isSecure)
32 | }
33 | case class Cookie(name: String, domain: String, path: String, expiry: Date, value: String, secure: Boolean)
34 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/Element.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | import org.openqa.selenium.{Dimension, WebElement, WebDriver}
20 | import org.openqa.selenium.interactions.Actions
21 |
22 | final class Element private[scalenium](
23 | protected val cssSelector: String,
24 | protected val driver: WebDriver,
25 | val webElement: WebElement)
26 | extends HasDriver
27 | with ElementInteractions
28 | with HasSearchContext
29 | with MarkupSearch
30 | with Await {
31 |
32 | override val searchContext = webElement
33 |
34 | /** @return the value of the given attribute name */
35 | def apply(attributeName: String): String = webElement.getAttribute(attributeName)
36 |
37 | def tagName: String = webElement.getTagName
38 | def value: String = this("value")
39 | def id: String = this("id")
40 | def name: String = this("name")
41 |
42 | def text: String = webElement.getText
43 |
44 | def html: String = this("innerHTML")
45 |
46 | def enabled: Boolean = webElement.isEnabled
47 | def disabled: Boolean = !enabled
48 |
49 | /** @return is the element shown onscreen */
50 | def visible: Boolean = webElement.isDisplayed
51 | def hidden: Boolean = !visible
52 |
53 | /** is a checkbox or radio button element selected */
54 | def selected: Boolean = webElement.isSelected
55 |
56 |
57 | /** @return onscreen dimensions of the element */
58 | def size: Dimension = webElement.getSize
59 |
60 |
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/ElementInteractions.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | import org.openqa.selenium.Keys
20 | import org.openqa.selenium.interactions.Actions
21 |
22 | trait ElementInteractions { this: Element =>
23 |
24 |
25 | // keyboard interactions
26 |
27 | /** clears the given text/input element and writes the given text in it */
28 | def write(text: String)(implicit failureHandler: MissingElementFailureHandler): Element = {
29 | operationAsEither {
30 | clear()
31 | webElement.sendKeys(text)
32 | }
33 | }
34 |
35 | /** clear the text content of the element */
36 | def clear()(implicit failureHandler: MissingElementFailureHandler): Element = {
37 | operationAsEither(webElement.clear())
38 | }
39 |
40 | /** trigger a key down for the given key on the element */
41 | def keyDown(key: Keys)(implicit failureHandler: MissingElementFailureHandler): Element =
42 | doAction(_.keyDown(webElement, key))
43 |
44 | /** trigger a key up for the given key on the element */
45 | def keyUp(key: Keys)(implicit failureHandler: MissingElementFailureHandler): Element =
46 | doAction(_.keyUp(webElement, key))
47 |
48 |
49 |
50 | // mouse interactions
51 |
52 |
53 | def click()(implicit failureHandler: MissingElementFailureHandler): Element =
54 | operationAsEither(webElement.click())
55 |
56 | def clickAndHold()(implicit failureHandler: MissingElementFailureHandler): Element =
57 | doAction(_.clickAndHold(webElement))
58 |
59 | def contextClick()(implicit failureHandler: MissingElementFailureHandler): Element =
60 | doAction(_.contextClick(webElement))
61 |
62 | def doubleClick()(implicit failureHandler: MissingElementFailureHandler): Element =
63 | doAction(_.doubleClick(webElement))
64 |
65 | def dragAndDropTo(other: Element)(implicit failureHandler: MissingElementFailureHandler): Element =
66 | doAction(_.dragAndDrop(webElement, other.webElement))
67 |
68 | /** move the mouse cursor to above the center of the element */
69 | def hover()(implicit failureHandler: MissingElementFailureHandler): Element =
70 | doAction(_.moveToElement(webElement))
71 |
72 |
73 | /** submit the form that the element belongs to, if the element is a form element */
74 | @deprecated("This only does .click(), just use that instead")
75 | def submit()(implicit failureHandler: MissingElementFailureHandler): Element = {
76 | operationAsEither {
77 | webElement.click()
78 | }(failureHandler)
79 | }
80 |
81 |
82 | private def doAction(builder: Actions => Actions)(implicit failureHandler: MissingElementFailureHandler): Element = {
83 | operationAsEither {
84 | builder(new Actions(driver)).perform()
85 | }
86 | this
87 | }
88 |
89 | private def operationAsEither(b: => Any)(implicit failureHandler: MissingElementFailureHandler): Element = {
90 | try {
91 | b
92 | this
93 | } catch {
94 | case e: NoSuchElementException =>
95 | failureHandler.noSuchElement(cssSelector)
96 | // we will never get here because all failure handlers will throw exceptions!
97 | this
98 | }
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/ElementSeq.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | /**
20 | * Useful operations you might want to perform on a Seq of Element
21 | *
22 | * @param elements decorated seq
23 | */
24 | final class ElementSeq private[scalenium] (elements: Seq[Element]) {
25 |
26 | /** Click on all elements on the list.
27 | * Only the visible elements are filled, if no elements are visible
28 | * nothing happens.
29 | */
30 | def click()(implicit failureHandler: MissingElementFailureHandler): Seq[Element] = {
31 | elements.foreach(_.click())
32 | this.elements
33 | }
34 |
35 | /** Clear all visible elements in the list,
36 | * if no elements are visible
37 | * nothing happens. */
38 | def clearAll()(implicit failureHandler: MissingElementFailureHandler): Seq[Element] = {
39 | elements.filter(_.visible).foreach(_.clear())
40 | this.elements
41 | }
42 |
43 | /** write the given text into all elements of the list */
44 | def write(text: String)(implicit failureHandler: MissingElementFailureHandler): Seq[Element] = {
45 | elements.foreach(_.write(text))
46 | this.elements
47 | }
48 |
49 | /** @return all element ids */
50 | def ids: Seq[String] = elements.map(_.id)
51 |
52 | /** @return all element text contents */
53 | def texts: Seq[String] = elements.map(_.text)
54 | /** @return the text content of all elements concatenated into one string */
55 | def text: String = elements.map(_.text).mkString
56 | def values: Seq[String] = elements.map(_.value)
57 | def html: String = elements.map(_.html).mkString
58 |
59 | /** @return true if all elements in the seq are enabled */
60 | def enabled = elements.forall(_.enabled)
61 | /** @return true if all elements in the seq are disabled */
62 | def disabled = elements.forall(_.disabled)
63 |
64 | def anyEnabled = elements.exists(_.enabled)
65 | def anyDisabled = elements.exists(_.disabled)
66 |
67 | /** @return true if all elements in the sequence are visible */
68 | def visible: Boolean = elements.forall(_.visible)
69 | /** @return true if all elements in the sequence are hidden */
70 | def hidden: Boolean = elements.forall(!_.hidden)
71 |
72 | def anyVisible: Boolean = elements.exists(_.visible)
73 | def anyHidden: Boolean = elements.exists(!_.visible)
74 |
75 | def anySelected = elements.exists(_.selected)
76 |
77 | def find(cssSelector: String): Seq[Element] = elements.flatMap(_.find(cssSelector))
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/FailureHandler.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 |
20 | /* Allows for customization of how failures are handled to adapt
21 | * the library to a specific testing framework.
22 | */
23 |
24 | final class MissingElementException(message: String) extends RuntimeException(message)
25 | final class AwaitFailedException(msg: String) extends RuntimeException(msg)
26 |
27 | /** default simple failure handler, will throw an [[com.markatta.scalenium.AwaitFailedException]],
28 | * see [[com.markatta.scalenium.Specs2Integration]] for an example of
29 | * testing framework integration */
30 | object SimpleFailureHandler extends MissingElementFailureHandler with TimeoutFailureHandler {
31 |
32 | def fail(timeout: Timeout, msg: String): Unit = {
33 | throw new AwaitFailedException("Waited " + Time.humanText(timeout.inner) + " but " + msg)
34 | }
35 |
36 | /** fail with an explanation */
37 | def noSuchElement(msg: String): Unit = {
38 | throw new MissingElementException(msg)
39 | }
40 | }
41 |
42 |
43 | object MissingElementFailureHandler {
44 | implicit val defaultMissingElementHandler = SimpleFailureHandler
45 | }
46 |
47 | trait MissingElementFailureHandler {
48 | /**
49 | * An element was asked for but not found inside the page
50 | *
51 | * @param elementSelector The css selector that was missing
52 | * @return
53 | */
54 | def noSuchElement(elementSelector: String): Unit
55 | }
56 |
57 | object TimeoutFailureHandler {
58 | implicit val defaultTimeoutFailureHandler = SimpleFailureHandler
59 | }
60 |
61 | trait TimeoutFailureHandler {
62 | /**
63 | * Some kind of async query timed out, an element was not shown onscreen etc.
64 | * @param timeout
65 | * @param message
66 | * @return
67 | */
68 | def fail(timeout: Timeout, message: String): Unit
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/Forms.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | trait Forms { this: MarkupSearch =>
20 |
21 | final class FieldWriter private[Forms](text: String) {
22 | def into(cssSelector: String)(implicit failureHandler: MissingElementFailureHandler): Unit = {
23 | first(cssSelector).map(_.write(text))
24 | }
25 |
26 | def intoAll(cssSelector: String)(implicit failureHandler: MissingElementFailureHandler): Unit = {
27 | find(cssSelector).foreach(_.write(text))
28 | }
29 | }
30 |
31 | final def write(text: String): FieldWriter = new FieldWriter(text)
32 |
33 | /** Fill each field by name with the corresponding value
34 | * @param vals name -> value
35 | */
36 | def fillByName(vals: (String, String)*)(implicit failureHandler: MissingElementFailureHandler): Unit = {
37 | fill(vals, "name['" + _ + "']")(failureHandler)
38 | }
39 |
40 | /** Fill each field by id with the corresponding value
41 | * @param vals id -> value
42 | */
43 | def fillById(vals: (String, String)*)(implicit failureHandler: MissingElementFailureHandler): Unit = {
44 | fill(vals, "#" + _)(failureHandler)
45 | }
46 |
47 | /** Fill each field by css selector with the corresponding value
48 | * @param vals cssSelector -> value
49 | */
50 | def fill(vals: (String, String)*)(implicit failureHandler: MissingElementFailureHandler): Unit = {
51 | fill(vals, key => key)(failureHandler)
52 | }
53 |
54 | private def fill(vals: Seq[(String, String)], f: String => String)(failureHandler: MissingElementFailureHandler): Unit = {
55 | vals.foreach { case (key, value) => find(f(key)).write(value)(failureHandler)}
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/HasDriver.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | import org.openqa.selenium.WebDriver
20 |
21 | trait HasDriver {
22 | protected def driver: WebDriver
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/HasSearchContext.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | import org.openqa.selenium.SearchContext
20 |
21 | trait HasSearchContext {
22 |
23 | def searchContext: SearchContext
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/JqueryStyle.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | object JqueryStyle {
20 | def $(cssSelector: String)(implicit browser: Browser): Seq[Element] = browser.find(cssSelector)
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/MarkupSearch.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | import collection.JavaConversions._
20 | import org.openqa.selenium._
21 | import org.openqa.selenium.interactions.Actions
22 |
23 |
24 | trait MarkupSearch { this: HasDriver with HasSearchContext =>
25 |
26 | /** @return all matching elements or empty sequence if not found or any other type of error */
27 | def find(cssSelector: String): Seq[Element] =
28 | try {
29 | searchContext.findElements(By.cssSelector(cssSelector)).map(e => new Element(cssSelector, driver, e))
30 | } catch {
31 | case e: RuntimeException => Seq()
32 | }
33 |
34 | // aliases for find
35 | def select(cssSelector: String) = find(cssSelector)
36 | def all(cssSelector: String) = find(cssSelector)
37 |
38 | // TODO: figure out a way to keep the selector in the option so that we can say what selection failed on None.get
39 | def first(cssSelector: String): Option[Element] = find(cssSelector).headOption
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/Navigation.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | trait Navigation { this: HasDriver =>
20 |
21 | def goTo(url: String): this.type = {
22 | require(url != "", "URL must not be empty")
23 | driver.get(url)
24 | this
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/PageProperties.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | trait PageProperties { this: HasDriver =>
20 |
21 | /** @return the title of the current page */
22 | def pageTitle: String = driver.getTitle
23 |
24 | def currentUrl: String = driver.getCurrentUrl
25 |
26 | def pageSource: String = driver.getPageSource
27 |
28 | def focusedElement: Element = new Element("", driver, driver.switchTo.activeElement())
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/ScreenShots.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | import org.openqa.selenium.{OutputType, TakesScreenshot}
20 | import java.io.File
21 | import org.apache.commons.io.FileUtils
22 |
23 | trait ScreenShots { this: HasDriver =>
24 |
25 | private def defaultScreenShotName = System.currentTimeMillis + ".png"
26 |
27 | def takeScreenShot(fileName: String = defaultScreenShotName): this.type = {
28 | require(fileName != "", "Filename of screenshot cannot be empty")
29 |
30 | // TODO default directory?
31 | val scrFile = driver.asInstanceOf[TakesScreenshot].getScreenshotAs(OutputType.FILE)
32 | try {
33 | val destFile = new File(fileName)
34 | FileUtils.copyFile(scrFile, destFile)
35 | } catch {
36 | case e: RuntimeException => throw new RuntimeException("error when taking the snapshot", e)
37 | }
38 |
39 | this
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/Scripts.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | import org.openqa.selenium.JavascriptExecutor
20 |
21 | trait Scripts { this: HasDriver =>
22 | def executeScript(script: String, args: String*): this.type = {
23 | driver match {
24 | case j: JavascriptExecutor => j.executeScript(script, args: _*)
25 | case x => throw new IllegalStateException("Driver " + x + " is not a javascript executor")
26 | }
27 | this
28 | }
29 |
30 | def executeAsyncScript(script: String, args: String*): this.type = {
31 | driver match {
32 | case j: JavascriptExecutor => j.executeAsyncScript(script, args: _*)
33 | case x => throw new IllegalStateException("Driver " + x + " is not a javascript executor")
34 | }
35 | this
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/Specs2Integration.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | import org.specs2.execute.{Failure, FailureException}
20 |
21 | /** To avoid bringin in hard dependencies on specs2 across the entire library
22 | * all integration should be kept in this module (and possibly broken out into
23 | * a separate library)
24 | */
25 | object Specs2Integration {
26 |
27 | /**
28 | * Throw failures as FailureExceptions that specs2 will treat as failures instead
29 | * of errors.
30 | */
31 | implicit object specs2FailureHandler extends MissingElementFailureHandler with TimeoutFailureHandler {
32 |
33 | def noSuchElement(msg: String): Unit = {
34 | throw new FailureException(Failure(msg))
35 | }
36 |
37 | def fail(timeout: Timeout, msg: String): Unit = {
38 | throw new FailureException(Failure("Waited " + Time.humanText(timeout.inner) + " but " + msg))
39 | }
40 |
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/Time.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta.scalenium
18 |
19 | import scala.concurrent.duration._
20 |
21 | object Timeout {
22 | implicit val defaultTimeout = Timeout(5.seconds)
23 | }
24 | case class Timeout(inner: FiniteDuration)
25 |
26 |
27 | object Interval {
28 | implicit val defaultInterval = Interval(5.millis)
29 | }
30 | case class Interval(inner: FiniteDuration)
31 |
32 | private[scalenium] object Time {
33 |
34 | def humanText(d: FiniteDuration): String =
35 | if (d.toSeconds > 0) d.toSeconds + "s"
36 | else d.toMillis + "ms"
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/scala/com/markatta/scalenium/package.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Johan Andrén
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.markatta
18 |
19 | import scala.language.implicitConversions
20 |
21 | package object scalenium {
22 |
23 | implicit def seqOfElements2ElementSeq(elements: Seq[Element]): ElementSeq = new ElementSeq(elements)
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/scala/com/markatta/scalenium/DslSpec.scala:
--------------------------------------------------------------------------------
1 | package com.markatta.scalenium
2 |
3 | import org.specs2.mutable.Specification
4 | import org.openqa.selenium.firefox.FirefoxDriver
5 | import org.specs2.time.NoTimeConversions
6 | import scala.concurrent.duration._
7 |
8 | class DslSpec extends Specification with NoTimeConversions {
9 |
10 | def mustCompile {
11 | // copy of the examples in the README, must compile
12 | // if anything is changed here, make sure you change the
13 | // readme as well
14 |
15 | // ex 1
16 | val b = new Browser(new FirefoxDriver())
17 |
18 | // ex 2
19 | {
20 | b.find(".cssClass") // returns a Seq[Element]
21 | b.first(".cssClass") // returns an Option[Element]
22 |
23 | // aliases to find, for an additional style, see the JQuery syntax example below
24 | b.all(".cssClass")
25 | b.select(".cssClass")
26 |
27 | b.find(".someClass").isEmpty must beTrue
28 | b.find(".someClass") must haveSize(0)
29 | b.first(".someClass").isEmpty must beTrue
30 | b.find(".someClass").exists(_.id == "someId") must beTrue
31 | b.find(".someClass").size must equalTo(3)
32 |
33 | // Element contains the same ops as browser for searching so
34 | // these two gives the same result
35 | b.first(".someClass").get.first(".someOtherClass")
36 | b.first(".someClass > .someOtherClass")
37 |
38 | // Seq[Element] is implicitly converted to an ElementSeq by
39 | // import com.markatta.scalenium.seqOfElements2ElementSeq
40 | // allowing us to:
41 |
42 | b.find(".cssClass").hidden must beTrue // all elements matching .cssClass hidden
43 | b.find(".someClass > .someOtherClass").anySelected must beTrue
44 | b.find("li").texts must contain("Banana", "Pineapple")
45 | b.find("ul").find("li").size must equalTo(4)
46 |
47 | b.first("#it") must beSome
48 | b.first("#it").map(_.visible) must beSome(true)
49 |
50 | b.first("#doIt").foreach(_.click())
51 | }
52 |
53 | // ex 3
54 | {
55 | implicit val brower = b
56 | import JqueryStyle._
57 | $("#someId").visible must beTrue
58 | $("ul > li").exists(li => li.text == "Banana" && li.visible) must beTrue
59 | }
60 |
61 | // ex 4
62 | {
63 | b.write("newPassword").into("input[name='password']")
64 | b.write("happy").intoAll("input[type='text']")
65 |
66 | b.first("input[name='password']").get.write("newPassword")
67 | b.find("input[name='password']").write("newPassword")
68 |
69 | // fill entire form
70 | // identifying fields with name
71 | b.fillByName("user" -> "johndoe", "password" -> "newPasword")
72 | // id
73 | b.fillById("user" -> "johndoe", "password" -> "newPassword")
74 | // or css selector
75 | b.fill("input#user" -> "johndoe", "input[name='password']" -> "")
76 | }
77 |
78 | // ex 5
79 | {
80 | // to get specs2 failures instead of exceptions
81 | implicit val failureHandler = Specs2Integration.specs2FailureHandler
82 |
83 | b.waitFor(".someClass").toBecomeVisible()
84 | b.waitAtMost(5).secondsFor(".someClass").toBecomeVisible()
85 |
86 | b.waitFor(1 == 2).toBecomeTrue()
87 | b.waitAtMost(3000000).secondsFor(1 == 2).toBecomeTrue
88 |
89 | b.waitAtMost(5).secondsFor(b.find("button").disabled).toBecomeTrue
90 | }
91 |
92 | // ex 6
93 | {
94 | implicit val timeout = Timeout(3.seconds)
95 | implicit val interval = Interval(100.millis)
96 |
97 | b.waitFor(".someClass").toBecomeVisible()
98 | }
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/test/scala/com/markatta/scalenium/HttpTestServer.scala:
--------------------------------------------------------------------------------
1 | package com.markatta.scalenium
2 |
3 | import com.sun.net.httpserver._
4 | import java.net.InetSocketAddress
5 |
6 | /** serve the same static content for any request for test purposes */
7 | class HttpTestServer(port: Int, val contentPath: String, var staticContent: String) {
8 |
9 | val server = HttpServer.create(new InetSocketAddress(port), 0)
10 |
11 | server.createContext(contentPath, new HttpHandler(){
12 | def handle(exchange: HttpExchange) {
13 | val bytes = staticContent.getBytes("UTF-8")
14 | exchange.sendResponseHeaders(200, bytes.length)
15 | val output = exchange.getResponseBody
16 | try {
17 | output.write(bytes)
18 | } finally {
19 | output.close()
20 | }
21 | }
22 | })
23 | server.start()
24 |
25 | def stop() {
26 | server.stop(0)
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/src/test/scala/com/markatta/scalenium/ScaleniumSpec.scala:
--------------------------------------------------------------------------------
1 | package com.markatta.scalenium
2 |
3 | import org.specs2.mutable.Specification
4 | import com.markatta.scalenium._
5 | import org.openqa.selenium.htmlunit.HtmlUnitDriver
6 | import org.specs2.specification.AfterExample
7 |
8 | class ScaleniumSpec extends Specification {
9 |
10 | var httpServer: Option[HttpTestServer] = None
11 |
12 | step{
13 | httpServer = Some(new HttpTestServer(8080, "/test", someSimpleHtml))
14 | }
15 | import Specs2Integration.specs2FailureHandler
16 |
17 | "markup search" should {
18 |
19 | "find elements from css selector" in { withBrowser { browser =>
20 | browser.find("h1") should haveSize (1)
21 | browser.find("ul") should haveSize (1)
22 | browser.find("li") should haveSize (2)
23 | browser.find("li") should haveSize (2)
24 | browser.all("li") should haveSize (2)
25 | browser.select("li") should haveSize (2)
26 | browser.first("li") should beSome
27 | }
28 | }
29 |
30 | "handle missing elements gracefully" in { withBrowser { browser =>
31 | browser.find("a") should beEmpty
32 | browser.first("a") should beNone
33 | }
34 | }
35 | }
36 |
37 | def withBrowser[T](testBlock: Browser => T): T = {
38 | val browser = new Browser(new HtmlUnitDriver())
39 | browser.goTo("http://localhost:8080/test")
40 | testBlock(browser)
41 | }
42 |
43 | step {
44 | httpServer.foreach(_.stop())
45 | }
46 |
47 | def someSimpleHtml =
48 | """
49 | |
the page
50 | |
51 | | The header
52 | |
53 | | - first
54 | | - second
55 | |
56 | |
57 | """.stripMargin
58 |
59 | }
60 |
61 |
62 |
--------------------------------------------------------------------------------
/version.sbt:
--------------------------------------------------------------------------------
1 | version in ThisBuild := "1.0.4-SNAPSHOT"
2 |
--------------------------------------------------------------------------------