├── .gitignore
├── src
├── main
│ ├── resources
│ │ └── log4j.properties
│ ├── webapp
│ │ ├── index.jsp
│ │ ├── style
│ │ │ └── style.css
│ │ ├── WEB-INF
│ │ │ ├── jsp
│ │ │ │ ├── taglibs.jspf
│ │ │ │ ├── helloPage.jsp
│ │ │ │ ├── customer
│ │ │ │ │ ├── customer-new.jsp
│ │ │ │ │ ├── customer-edit.jsp
│ │ │ │ │ └── customer-view.jsp
│ │ │ │ └── home.jsp
│ │ │ ├── tags
│ │ │ │ └── head.tag
│ │ │ ├── spring-context-web.xml
│ │ │ ├── web.xml
│ │ │ └── spring-context-data.xml
│ │ └── static.htm
│ └── scala
│ │ └── com
│ │ └── example
│ │ └── scalawebapp
│ │ ├── data
│ │ ├── AbstractEntity.scala
│ │ └── Customer.scala
│ │ ├── controller
│ │ ├── ControllerTools.scala
│ │ ├── HelloWorldController.scala
│ │ ├── HomePageController.scala
│ │ └── CustomerController.scala
│ │ └── repository
│ │ └── CustomerRepository.scala
└── test
│ └── scala
│ └── com
│ └── example
│ └── scalawebapp
│ └── webtest
│ ├── page
│ ├── StaticFilePage.scala
│ ├── ServerNamePage.scala
│ ├── customer
│ │ ├── ViewCustomerPage.scala
│ │ ├── AddCustomerPage.scala
│ │ └── EditCustomerPage.scala
│ ├── HomePage.scala
│ └── Page.scala
│ ├── ServerNamePageWebTest.scala
│ ├── StaticFileWebTest.scala
│ ├── WebDriverAccess.scala
│ └── CustomersWebTest.scala
├── LICENSE
├── README.md
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | .classpath
3 | .idea
4 | out
5 | .project
6 | .settings
7 | .settings/*
8 | target
9 | *.iml
10 |
--------------------------------------------------------------------------------
/src/main/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.rootCategory=INFO,Console
2 | log4j.logger.org.hibernate=WARN
3 | log4j.logger.org.springframework=WARN
4 | log4j.logger.org.springframework.web.servlet.DispatcherServlet=INFO
5 |
6 | log4j.appender.Console=org.apache.log4j.ConsoleAppender
7 | log4j.appender.Console.layout=org.apache.log4j.PatternLayout
8 | log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This code is in the public domain and may be used in any way you see fit, with the following conditions:
2 |
3 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
4 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
5 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
6 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
7 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
8 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 |
--------------------------------------------------------------------------------
/src/main/webapp/index.jsp:
--------------------------------------------------------------------------------
1 | <%--
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | --%>
12 | <%@page contentType="text/html;charset=utf-8"%>
13 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
14 |
15 |
--------------------------------------------------------------------------------
/src/main/webapp/style/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | html {
14 | border: 0;
15 | }
16 |
17 | body {
18 | font-family: Helvetica, Arial, sans-serif;
19 | background-color: #eeeeee;
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/jsp/taglibs.jspf:
--------------------------------------------------------------------------------
1 | <%--
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | --%>
12 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
13 | <%@taglib prefix="tags" tagdir="/WEB-INF/tags"%>
14 | <%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
15 |
--------------------------------------------------------------------------------
/src/test/scala/com/example/scalawebapp/webtest/page/StaticFilePage.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.webtest.page
14 |
15 | import org.openqa.selenium.WebDriver
16 |
17 | class StaticFilePage(driver: WebDriver) extends Page[StaticFilePage]("static-file", driver)
18 |
--------------------------------------------------------------------------------
/src/test/scala/com/example/scalawebapp/webtest/page/ServerNamePage.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.webtest.page
14 |
15 | import org.openqa.selenium.WebDriver
16 |
17 | class ServerNamePage(driver: WebDriver) extends Page[ServerNamePage]("server-name", driver) {
18 | def serverName: String = textOf(id("serverName"))
19 | }
--------------------------------------------------------------------------------
/src/main/scala/com/example/scalawebapp/data/AbstractEntity.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.data
14 |
15 | import javax.persistence.{ MappedSuperclass, GeneratedValue, Id }
16 |
17 | @MappedSuperclass
18 | abstract class AbstractEntity {
19 | @Id
20 | @GeneratedValue
21 | var id: Long = 0
22 |
23 | def getId: Long = id
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/scala/com/example/scalawebapp/controller/ControllerTools.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.controller
14 |
15 | import org.springframework.ui.ModelMap
16 |
17 | object ControllerTools {
18 | implicit def map2ModelMap(m: Map[String, Any]): ModelMap = {
19 | val mm = new ModelMap
20 | m.foreach((kv) => mm.addAttribute(kv._1, kv._2))
21 | mm
22 | }
23 | }
--------------------------------------------------------------------------------
/src/main/scala/com/example/scalawebapp/controller/HelloWorldController.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.controller
14 |
15 | import org.springframework.stereotype.Controller
16 | import org.springframework.web.bind.annotation.RequestMapping
17 |
18 | @Controller
19 | class HelloWorldController {
20 | @RequestMapping(Array("/hello"))
21 | def showHello = "helloPage"
22 | }
--------------------------------------------------------------------------------
/src/main/scala/com/example/scalawebapp/data/Customer.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.data
14 |
15 | import beans.BeanProperty
16 | import javax.persistence.Entity
17 |
18 | @Entity
19 | class Customer extends AbstractEntity {
20 | @BeanProperty
21 | var name: String = null
22 |
23 | override def toString = "[Customer: id = " + id + ", name = " + name + "]"
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/tags/head.tag:
--------------------------------------------------------------------------------
1 | <%--
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | --%>
12 | <%@tag body-content="tagdependent" %>
13 | <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
14 | <%@attribute name="title" required="true" rtexprvalue="true" type="java.lang.String" %>
15 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/jsp/helloPage.jsp:
--------------------------------------------------------------------------------
1 | <%--
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | --%>
12 | <%@page contentType="text/html;charset=utf-8"%>
13 | <%@include file="taglibs.jspf"%>
14 |
15 |
16 |
17 |
18 |
Hello from ${pageContext.request.serverName}
19 |
20 | " class="home-link">Home
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/test/scala/com/example/scalawebapp/webtest/page/customer/ViewCustomerPage.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.webtest.page.customer
14 |
15 | import org.openqa.selenium.WebDriver
16 | import com.example.scalawebapp.webtest.page.{HomePage, Page}
17 |
18 | class ViewCustomerPage(driver: WebDriver) extends Page[ViewCustomerPage]("customer-view", driver) {
19 | def deleteCustomer() = click(linkText("Delete the Customer")).expecting[HomePage]
20 |
21 | def edit() = click(linkText("Edit the Customer")).expecting[EditCustomerPage]
22 | }
--------------------------------------------------------------------------------
/src/test/scala/com/example/scalawebapp/webtest/ServerNamePageWebTest.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.webtest
14 |
15 | import com.example.scalawebapp.webtest.page._
16 | import org.junit.Test
17 | import org.junit.Assert._
18 | import org.hamcrest.CoreMatchers._
19 |
20 | class ServerNamePageWebTest extends WebDriverAccess {
21 | @Test
22 | def shouldShowTheServerName {
23 | val homePage = HomePage.open()
24 | val serverNamePage = homePage.showServerName()
25 | assertThat(serverNamePage.serverName, is("localhost"))
26 | serverNamePage.home()
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/src/test/scala/com/example/scalawebapp/webtest/StaticFileWebTest.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.webtest
14 |
15 | import com.example.scalawebapp.webtest.page._
16 | import org.junit.Test
17 | import org.junit.Assert._
18 | import org.hamcrest.CoreMatchers._
19 |
20 | class StaticFileWebTest extends WebDriverAccess {
21 | @Test
22 | def shouldReturnTheContentsOfTheFile {
23 | val homePage = HomePage.open()
24 | val staticFilePage = homePage.showStaticFile()
25 | assertThat(staticFilePage.text, containsString("This is a static HTML file."))
26 | staticFilePage.home()
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/src/test/scala/com/example/scalawebapp/webtest/page/customer/AddCustomerPage.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.webtest.page.customer
14 |
15 | import org.openqa.selenium.WebDriver
16 | import com.example.scalawebapp.webtest.page.Page
17 |
18 | class AddCustomerPage(driver: WebDriver) extends Page[AddCustomerPage]("customer-new", driver) {
19 | def createCustomer(customerName: String) = {
20 | set(input("name"), customerName)
21 | submitExpecting[ViewCustomerPage]
22 | }
23 |
24 | def createCustomerExpectingFailure(customerName: String) = {
25 | set(input("name"), customerName)
26 | submitExpecting[AddCustomerPage]
27 | }
28 | }
--------------------------------------------------------------------------------
/src/test/scala/com/example/scalawebapp/webtest/page/customer/EditCustomerPage.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.webtest.page.customer
14 |
15 | import org.openqa.selenium.WebDriver
16 | import com.example.scalawebapp.webtest.page.Page
17 |
18 | class EditCustomerPage(driver: WebDriver) extends Page[EditCustomerPage]("customer-edit", driver) {
19 | def customerName = valueOf(input("name"))
20 |
21 | def updateCustomer(customerName: String) = {
22 | set(input("name"), customerName)
23 | submitExpecting[ViewCustomerPage]
24 | }
25 |
26 | def updateCustomerExpectingFailure(customerName: String) = {
27 | set(input("name"), customerName)
28 | submitExpecting[EditCustomerPage]
29 | }
30 | }
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/jsp/customer/customer-new.jsp:
--------------------------------------------------------------------------------
1 | <%--
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | --%>
12 | <%@page contentType="text/html;charset=utf-8"%>
13 | <%@include file="../taglibs.jspf"%>
14 |
15 |
16 |
17 |
18 |
New Customer
19 |
20 | Name
21 |
22 |
23 |
24 |
25 |
26 |
27 | ">Home
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/jsp/customer/customer-edit.jsp:
--------------------------------------------------------------------------------
1 | <%--
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | --%>
12 | <%@page contentType="text/html;charset=utf-8"%>
13 | <%@include file="../taglibs.jspf"%>
14 |
15 |
16 |
17 |
18 |
Edit Customer #:
19 |
20 | Name
21 |
22 |
23 |
24 |
25 |
26 |
27 | ">Home
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/main/scala/com/example/scalawebapp/controller/HomePageController.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.controller
14 |
15 | import org.springframework.stereotype.Controller
16 | import org.springframework.beans.factory.annotation.Autowired
17 | import org.hibernate.SessionFactory
18 | import org.springframework.web.bind.annotation.RequestMapping
19 | import org.springframework.web.bind.annotation.RequestMethod._
20 | import org.springframework.web.servlet.ModelAndView
21 | import com.example.scalawebapp.repository.CustomerRepository
22 |
23 | @Controller
24 | class HomePageController {
25 | implicit def sessionFactory2Session(sf: SessionFactory) = sf.getCurrentSession
26 |
27 | @Autowired
28 | val customerRepository: CustomerRepository = null
29 |
30 | @RequestMapping(value = Array("/home"), method = Array(GET))
31 | def loadCustomers() =
32 | new ModelAndView("home", "customers", customerRepository.getAll)
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/scala/com/example/scalawebapp/webtest/WebDriverAccess.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.webtest
14 |
15 | import org.openqa.selenium.WebDriver
16 | import org.openqa.selenium.firefox.FirefoxDriver
17 |
18 | trait WebDriverAccess {
19 | implicit val driver = WebDriverAccess.defaultDriver
20 | }
21 |
22 | object WebDriverAccess {
23 |
24 | val closeBrowserAfterTests = true
25 |
26 | val defaultDriver: WebDriver = {
27 | print("Creating WebDriver... ")
28 | try {
29 | new FirefoxDriver()
30 | } finally {
31 | println("Done.")
32 | }
33 | }
34 |
35 | if (!isRunningInIde || closeBrowserAfterTests) {
36 | Runtime.getRuntime.addShutdownHook(new Thread(new Runnable { def run() {defaultDriver.quit()} } ))
37 | }
38 |
39 | private def isRunningInIde: Boolean = {
40 | Thread.currentThread.getStackTrace.find{_.getClassName.startsWith("com.intellij")} != None
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/jsp/customer/customer-view.jsp:
--------------------------------------------------------------------------------
1 | <%--
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | --%>
12 | <%@page contentType="text/html;charset=utf-8"%>
13 | <%@include file="../taglibs.jspf"%>
14 |
15 |
16 |
17 |
18 |
19 | Customer #:
20 |
21 |
Might I suggest enhancing the system to support more than just a customer's name?
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/test/scala/com/example/scalawebapp/webtest/page/HomePage.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.webtest.page
14 |
15 | import customer.{ViewCustomerPage, AddCustomerPage}
16 | import org.openqa.selenium.{By, WebDriver}
17 | import java.lang.String
18 |
19 | object HomePage {
20 | private val DEFAULT_PORT = 9090
21 |
22 | def open(server: String = "localhost", port: Int = DEFAULT_PORT)(implicit driver: WebDriver) = {
23 | driver.navigate.to("http://" + server + ":" + port + "/")
24 | new HomePage(driver)
25 | }
26 | }
27 |
28 | class HomePage(driver: WebDriver) extends Page[HomePage]("home", driver) {
29 |
30 | def showServerName() = click(linkText("Server Name")).expecting[ServerNamePage]
31 | def showStaticFile() = click(linkText("Static File")).expecting[StaticFilePage]
32 | def addCustomer() = click(linkText("Add a Customer")).expecting[AddCustomerPage]
33 |
34 | def viewCustomer(customerName: String) = click(By.partialLinkText(customerName)).expecting[ViewCustomerPage]
35 |
36 | def customerNames: Seq[String] =
37 | driver.findElement(By.id("customer-list")).findElements(By.tagName("a")).map(_.getText.split(": ")(1))
38 |
39 | def deleteAllCustomers() = click(linkText("Delete All Customers")).expecting[HomePage]
40 | }
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/jsp/home.jsp:
--------------------------------------------------------------------------------
1 | <%--
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | --%>
12 | <%@page contentType="text/html;charset=utf-8"%>
13 | <%@include file="taglibs.jspf"%>
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | ">Server Name
23 |
24 |
25 | ">Static File
26 |
27 |
Customers
28 |
29 |
30 |
">#:
31 |
32 |
33 |
34 |
35 |
36 | ">Add a Customer
37 |
38 |
39 |
" method="POST">
40 |
41 |
42 | Delete All Customers
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | :warning: **NOTE:** This repo hasn't been updated in 6 years and is now archived. :warning:
2 |
3 | Scala + Spring + Hibernate + Maven + Selenium WebDriver
4 | =======================================================
5 |
6 | This project contains source code for kickstarting a webapp project using the latest versions of
7 | Scala, Spring, Hibernate and Maven, including Selenium WebDriver PageObject-based web tests.
8 |
9 | Versions currently used in the project are:
10 |
11 | * [Scala](http://www.scala-lang.org/): 2.11.2
12 | * [Spring](http://www.springsource.org/about): 4.0.6.RELEASE
13 | * [Hibernate](http://www.hibernate.org/): 4.3.6.Final
14 |
15 | The code also makes use of [HSQLDB](http://hsqldb.org/), but you'll probably want to replace that with the JDBC driver
16 | for whatever database you're using.
17 |
18 |
19 | Building and Running
20 | --------------------
21 |
22 | Assuming you already have Maven installed, the webapp can be built by running:
23 |
24 | mvn clean install
25 |
26 | The webapp can be run inside Jetty using the Maven plugin:
27 |
28 | mvn jetty:run
29 |
30 | An Eclipse project will be generated with all the correct natures in it by running:
31 |
32 | mvn eclipse:eclipse
33 |
34 |
35 | Attribution
36 | -----------
37 |
38 | This source code was originally created by Graham Lea for the blog http://grahamhackingscala.blogspot.com/
39 | (though Graham now blogs at http://www.grahamlea.com/)
40 |
41 | The Eclipse plugin configuration was submitted by Trevor Lalish-Menagh (http://www.trevmex.com/)
42 |
43 |
44 | Conditions of Usage
45 | -------------------
46 |
47 | This code is in the public domain and may be used in any way you see fit, with the following conditions:
48 |
49 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
50 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
51 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
52 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
53 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
54 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
55 | > THE SOFTWARE.
56 |
--------------------------------------------------------------------------------
/src/main/scala/com/example/scalawebapp/repository/CustomerRepository.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.repository
14 |
15 | import com.example.scalawebapp.data.Customer
16 | import org.springframework.stereotype.Repository
17 | import org.springframework.transaction.annotation.Transactional
18 | import org.springframework.beans.factory.annotation.Autowired
19 | import org.hibernate.SessionFactory
20 |
21 | trait CustomerRepository {
22 | def getAll: java.util.List[Customer]
23 | def save(customer: Customer): Long
24 | def update(customer: Customer)
25 | def get(customerId: Long): Customer
26 | def delete(customerId: Long)
27 | }
28 |
29 | @Repository
30 | class CustomerRepositoryImpl @Autowired() (
31 | val sessionFactory: SessionFactory
32 | ) extends CustomerRepository {
33 |
34 | private def currentSession = sessionFactory.getCurrentSession
35 |
36 | @Transactional
37 | def save(customer: Customer): Long = Long.unbox(currentSession.save(customer).asInstanceOf[Object])
38 |
39 | @Transactional
40 | def update(customer: Customer): Unit = currentSession.saveOrUpdate(customer)
41 |
42 | @Transactional
43 | def delete(customerId: Long): Unit = currentSession.delete(get(customerId))
44 |
45 | @Transactional(readOnly = true)
46 | def get(customerId: Long): Customer = currentSession.get(classOf[Customer], Long.box(customerId)).asInstanceOf[Customer]
47 |
48 | @Transactional(readOnly = true)
49 | def getAll: java.util.List[Customer] = currentSession.createCriteria(classOf[Customer]).list().asInstanceOf[java.util.List[Customer]]
50 | }
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/spring-context-web.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
34 |
35 |
36 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
18 |
19 |
20 | contextConfigLocation
21 | /WEB-INF/spring-context-data.xml
22 |
23 |
24 |
25 | org.springframework.web.context.ContextLoaderListener
26 |
27 |
28 |
29 | dispatcher
30 | org.springframework.web.servlet.DispatcherServlet
31 |
32 | contextConfigLocation
33 | /WEB-INF/spring-context-web.xml
34 |
35 | 1
36 |
37 |
38 |
39 | dispatcher
40 | *.html
41 |
42 |
43 |
44 |
45 | methodFilter
46 | org.springframework.web.filter.HiddenHttpMethodFilter
47 |
48 |
49 |
50 | methodFilter
51 | *.html
52 |
53 |
54 |
55 | index.jsp
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/spring-context-data.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | com.example.scalawebapp.data.Customer
42 |
43 |
44 |
45 |
46 | org.hibernate.dialect.HSQLDialect
47 | false
48 | create
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/test/scala/com/example/scalawebapp/webtest/page/Page.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.webtest.page
14 |
15 | import org.openqa.selenium.{By, WebDriver}
16 | import org.hamcrest.CoreMatchers._
17 | import org.junit.Assert
18 | import java.lang.reflect.InvocationTargetException
19 | import collection.JavaConversions
20 | import org.openqa.selenium.By.ByName
21 | import org.openqa.selenium.support.ui.{ExpectedCondition, WebDriverWait}
22 |
23 | abstract class Page[T <: Page[T]](expectedBodyId: String, val driver: WebDriver) {
24 |
25 | self: T =>
26 |
27 | waitUntil { !driver.findElements(By.tagName("body")).isEmpty }
28 | Assert.assertThat(driver.findElement(By.tagName("body")).getAttribute("id"), is(expectedBodyId))
29 |
30 | final protected def id = By.id _
31 | final protected def className = By.className _
32 | final protected def linkText = By.linkText _
33 | final protected def input = new ByName(_)
34 | final protected def xpath = By.xpath _
35 | final protected def xpathFormat(text: String, arguments: Any*) = xpath(text.format(arguments))
36 | final protected def spanWithId(id: String) = xpathFormat("//span[@id='%s']", id)
37 |
38 | final private val submitButton = xpath("//input[@type='submit']")
39 |
40 | lazy val text = driver.getPageSource.replaceAll("<[^>]+>", " ").replaceAll("[ \t]+", " ").replaceAll("\n( ?\n)+", "\n")
41 |
42 | protected def textOf(element: By) : String = driver.findElement(element).getText
43 | protected def valueOf(element: ByName) : String = driver.findElement(element).getAttribute("value")
44 |
45 | def submitButtonText : String = driver.findElement(submitButton).getAttribute("value")
46 |
47 | def home() = click(linkText("Home")).expecting[HomePage]
48 |
49 | protected def waitUntil(f: => Boolean): Unit = {
50 | new WebDriverWait(driver, 10, 50) until
51 | (new ExpectedCondition[Boolean] { def apply(input: WebDriver) = f })
52 | }
53 |
54 | protected def set(inputBy: By, value: String) : T = {
55 | val inputElement = driver.findElement(inputBy)
56 | inputElement.clear()
57 | inputElement.sendKeys(value)
58 | this
59 | }
60 |
61 | protected def click(by: By) : T = {
62 | driver.findElement(by).click()
63 | this
64 | }
65 |
66 | protected def expecting[P <: Page[P]](implicit m: Manifest[P]): P = {
67 | try {
68 | m.runtimeClass.getConstructor(classOf[WebDriver]).newInstance(driver).asInstanceOf[P]
69 | } catch {
70 | case e: InvocationTargetException => throw e.getCause
71 | case e: Throwable => throw e
72 | }
73 | }
74 |
75 | protected implicit def javaList2Seq[A](list: java.util.List[A]): Seq[A] = JavaConversions.asScalaBuffer(list)
76 |
77 | def submitExpecting[P <: Page[P]](implicit m: Manifest[P]): P = click(submitButton).expecting[P]
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/test/scala/com/example/scalawebapp/webtest/CustomersWebTest.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.webtest
14 |
15 | import com.example.scalawebapp.webtest.page._
16 | import org.junit.Assert._
17 | import org.hamcrest.CoreMatchers._
18 | import org.junit.{After, Test}
19 |
20 | class CustomersWebTest extends WebDriverAccess {
21 | @After
22 | def deleteAllCustomers() {
23 | HomePage.open().deleteAllCustomers()
24 | }
25 |
26 | @Test
27 | def shouldBeAbleToBeAdded {
28 | val viewCustomerPage = createCustomerNamed("Adrian")
29 | assertThat(viewCustomerPage.text, containsString("Adrian"))
30 | }
31 |
32 | @Test
33 | def shouldNotBeAbleToBeAddedWithABlankName {
34 | val homePage = HomePage.open()
35 | var addCustomerPage = homePage.addCustomer()
36 | addCustomerPage = addCustomerPage.createCustomerExpectingFailure(customerName = "")
37 | assertThat(addCustomerPage.text, containsString("may not be empty"))
38 | }
39 |
40 | @Test
41 | def shouldBeAccessibleFromTheHomePageAfterBeingAdded {
42 | var viewCustomerPage = createCustomerNamed("Bruce")
43 | val homePage = viewCustomerPage.home()
44 | assertThat(homePage.customerNames.contains("Bruce"), is(true))
45 | viewCustomerPage = homePage.viewCustomer("Bruce")
46 | assertThat(viewCustomerPage.text, containsString("Bruce"))
47 | }
48 |
49 | @Test
50 | def shouldBeAbleToBeEdited {
51 | val viewCustomerPage = createCustomerNamed("Carl")
52 | val editCustomerPage = viewCustomerPage.edit()
53 | assertThat(editCustomerPage.customerName, is("Carl"))
54 | editCustomerPage.updateCustomer(customerName = "Chris")
55 | assertThat(viewCustomerPage.text, containsString("Chris"))
56 | val homePage = viewCustomerPage.home()
57 | assertThat(homePage.customerNames.contains("Chris"), is(true))
58 | assertThat(homePage.customerNames.contains("Carl"), is(false))
59 | }
60 |
61 | @Test
62 | def shouldNotBeAbleToBeEditedToHaveABlankName {
63 | val viewCustomerPage = createCustomerNamed("David")
64 | var editCustomerPage = viewCustomerPage.edit()
65 | assertThat(editCustomerPage.customerName, is("David"))
66 | editCustomerPage = editCustomerPage.updateCustomerExpectingFailure(customerName = "")
67 | assertThat(editCustomerPage.text, containsString("may not be empty"))
68 | val homePage = editCustomerPage.home()
69 | assertThat(homePage.customerNames.contains("David"), is(true))
70 | }
71 |
72 | @Test
73 | def shouldBeAbleToBeDeleted {
74 | val viewCustomerPage = createCustomerNamed("Elijah")
75 | val homePage = viewCustomerPage.deleteCustomer()
76 | assertThat(homePage.customerNames.contains("Elijah"), is(false))
77 | }
78 |
79 | @Test
80 | def shouldBeAbleToBeDeletedAllAtOnce {
81 | createCustomerNamed("Frank")
82 | createCustomerNamed("George")
83 | val homePage = HomePage.open()
84 | assertThat(homePage.customerNames.contains("Frank"), is(true))
85 | assertThat(homePage.customerNames.contains("George"), is(true))
86 | homePage.deleteAllCustomers()
87 | assertThat(homePage.customerNames.contains("Frank"), is(false))
88 | assertThat(homePage.customerNames.contains("George"), is(false))
89 | }
90 |
91 | private def createCustomerNamed(name: String) = {
92 | val homePage = HomePage.open()
93 | val addCustomerPage = homePage.addCustomer()
94 | addCustomerPage.createCustomer(customerName = name)
95 | }
96 |
97 | }
98 |
99 |
--------------------------------------------------------------------------------
/src/main/scala/com/example/scalawebapp/controller/CustomerController.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * This code is in the public domain and may be used in any way you see fit, with the following conditions:
3 | *
4 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10 | * THE SOFTWARE.
11 | */
12 |
13 | package com.example.scalawebapp.controller
14 |
15 | import org.springframework.stereotype.Controller
16 | import org.springframework.beans.factory.annotation.Autowired
17 | import org.springframework.web.bind.annotation.{ PathVariable, ModelAttribute, RequestMapping, RequestParam }
18 | import org.springframework.web.bind.annotation.RequestMethod._
19 | import org.springframework.web.servlet.ModelAndView
20 |
21 | import com.example.scalawebapp.data.Customer
22 | import com.example.scalawebapp.repository.CustomerRepository
23 | import beans.BeanProperty
24 | import javax.validation.constraints.NotNull
25 | import org.hibernate.validator.constraints.NotEmpty
26 | import javax.validation.Valid
27 | import org.springframework.validation.BindingResult
28 | import collection.JavaConversions
29 |
30 | @Controller
31 | class CustomerController @Autowired() (
32 | val customerRepository: CustomerRepository
33 | ) {
34 |
35 | import ControllerTools._
36 |
37 | @RequestMapping(value = Array("/customers/new"), method = Array(GET))
38 | def showNewCustomerForm() = new ModelAndView("customer/customer-new", "customerData", new CustomerPageData)
39 |
40 | @RequestMapping(value = Array("/customers/new"), method = Array(POST))
41 | def createNewCustomer(
42 | @Valid @ModelAttribute("customerData") customerData: CustomerPageData,
43 | bindingResult: BindingResult): String = {
44 | if (bindingResult.hasErrors) {
45 | "customer/customer-new"
46 | } else {
47 | val newCustomer = new Customer
48 | customerData.copyTo(newCustomer)
49 | "redirect:/customers/" + customerRepository.save(newCustomer) + ".html"
50 | }
51 | }
52 |
53 | @RequestMapping(value = Array("/customers/{customerId}"), method = Array(GET))
54 | def viewCustomer(
55 | @PathVariable customerId: Long,
56 | @RequestParam(required = false) edit: String) = {
57 | val customer: Customer = customerRepository.get(customerId)
58 | if (edit == null) {
59 | new ModelAndView("customer/customer-view", "customer", customer)
60 | }
61 | else {
62 | new ModelAndView("customer/customer-edit", Map("customer" -> customer, "customerData" -> CustomerPageData(customer)))
63 | }
64 | }
65 |
66 | @RequestMapping(value = Array("/customers/{customerId}"), method = Array(POST))
67 | def editCustomer(
68 | @PathVariable customerId: Long,
69 | @Valid @ModelAttribute("customerData") customerData: CustomerPageData,
70 | bindingResult: BindingResult): ModelAndView = {
71 | val customer = customerRepository.get(customerId)
72 | if (bindingResult.hasErrors) {
73 | new ModelAndView("customer/customer-edit", "customer", customer)
74 | } else {
75 | customerData.copyTo(customer)
76 | customerRepository.update(customer)
77 | new ModelAndView("redirect:/customers/{customerId}.html")
78 | }
79 | }
80 |
81 | @RequestMapping(value = Array("/customers/{customerId}"), method = Array(DELETE))
82 | def deleteCustomer(@PathVariable customerId: Long) = {
83 | customerRepository.delete(customerId)
84 | "redirect:/"
85 | }
86 |
87 | @RequestMapping(value = Array("/customers"), method = Array(DELETE))
88 | def deleteAllCustomers() = {
89 | for (c: Customer <- JavaConversions.asScalaBuffer(customerRepository.getAll)) {
90 | customerRepository.delete(c.id)
91 | }
92 | "redirect:/"
93 | }
94 | }
95 |
96 | class CustomerPageData {
97 | @BeanProperty @NotNull @NotEmpty
98 | var name: String = null
99 |
100 | override def toString = "[CustomerPageData: name = " + name + "]"
101 |
102 | def copyTo(customer: Customer): Unit = {
103 | // TODO: Use Dozer to do this automatically
104 | customer.name = name
105 | }
106 |
107 | def copyFrom(customer: Customer): Unit = {
108 | // TODO: Use Dozer to do this automatically
109 | name = customer.name
110 | }
111 | }
112 |
113 | object CustomerPageData {
114 | def apply(c: Customer): CustomerPageData = {
115 | val data = new CustomerPageData
116 | data.copyFrom(c)
117 | data
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | 4.0.0
5 | com.example.scalawebapp
6 | scala-spring-hibernate-maven-webapp
7 | 1.0-SNAPSHOT
8 | war
9 |
10 |
11 |
12 | Public Domain
13 |
14 | This code is in the public domain and may be used in any way you see fit, with the following conditions:
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
17 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 |
24 |
25 |
26 |
27 |
28 | Graham Lea
29 | http://www.grahamlea.com
30 | graham.lea@belmonttechnology.com.au
31 | Australia/Sydney
32 | Belmont Technology Pty Ltd
33 |
34 |
35 |
36 |
37 | UTF-8
38 | 2.11.2
39 | 4.0.6.RELEASE
40 | 1.7.7
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | org.scala-lang
54 | scala-library
55 | ${scala.version}
56 | compile
57 |
58 |
59 |
60 | xerces
61 | xercesImpl
62 | 2.9.1
63 | runtime
64 |
65 |
66 |
67 | junit
68 | junit
69 | 4.11
70 | test
71 |
72 |
73 |
74 | org.hibernate
75 | hibernate-validator
76 | 5.1.2.Final
77 | compile
78 |
79 |
80 |
81 | org.springframework
82 | spring-context
83 | ${org.springframework.version}
84 | compile
85 |
86 |
87 |
88 | org.springframework
89 | spring-orm
90 | ${org.springframework.version}
91 | compile
92 |
93 |
94 |
95 | org.springframework
96 | spring-web
97 | ${org.springframework.version}
98 | compile
99 |
100 |
101 |
102 | org.springframework
103 | spring-webmvc
104 | ${org.springframework.version}
105 | compile
106 |
107 |
108 |
109 | javax.servlet
110 | javax.servlet-api
111 | 3.0.1
112 | provided
113 |
114 |
115 |
116 | javax.servlet.jsp
117 | javax.servlet.jsp-api
118 | 2.2.1
119 | provided
120 |
121 |
122 |
123 | javax.servlet.jsp.jstl
124 | javax.servlet.jsp.jstl-api
125 | 1.2.1
126 | provided
127 |
128 |
129 |
130 | commons-logging
131 | commons-logging
132 | 1.2
133 | runtime
134 |
135 |
136 |
137 | org.slf4j
138 | slf4j-api
139 | ${slf4j.version}
140 | runtime
141 |
142 |
143 |
144 | org.slf4j
145 | slf4j-log4j12
146 | ${slf4j.version}
147 | runtime
148 |
149 |
150 |
151 | org.hibernate
152 | hibernate-core
153 | 4.3.6.Final
154 | compile
155 |
156 |
157 |
158 | org.javassist
159 | javassist
160 | 3.18.2-GA
161 | runtime
162 |
163 |
164 |
165 | c3p0
166 | c3p0
167 | 0.9.1.2
168 |
169 |
170 |
171 | org.hsqldb
172 | hsqldb
173 | 2.3.2
174 | runtime
175 |
176 |
177 |
178 | org.seleniumhq.selenium
179 | selenium-java
180 | 2.42.2
181 | test
182 |
183 |
184 |
185 | com.google.code.findbugs
186 | jsr305
187 | 1.3.9
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 | net.alchim31.maven
196 | scala-maven-plugin
197 | 3.2.0
198 |
199 |
200 | compile
201 |
202 | compile
203 |
204 | process-resources
205 |
206 |
207 | test-compile
208 |
209 | testCompile
210 |
211 | process-test-resources
212 |
213 |
214 |
215 | ${scala.version}
216 |
217 |
218 |
219 | org.apache.maven.plugins
220 | maven-compiler-plugin
221 | 3.1
222 |
223 | 1.7
224 | 1.7
225 |
226 |
227 |
228 | org.apache.maven.plugins
229 | maven-resources-plugin
230 | 2.6
231 |
232 |
233 | org.apache.maven.plugins
234 | maven-surefire-plugin
235 | 2.17
236 |
237 | true
238 |
239 |
240 |
241 | unit-tests
242 | test
243 |
244 | test
245 |
246 |
247 | false
248 |
249 | **/*Test.class
250 | **/*Spec.class
251 |
252 |
253 | **/webtest/**/*Test.class
254 |
255 |
256 |
257 |
258 | web-tests
259 | integration-test
260 |
261 | test
262 |
263 |
264 | false
265 |
266 | **/webtest/**/*Test.class
267 |
268 |
269 |
270 |
271 |
272 |
273 | org.eclipse.jetty
274 | jetty-maven-plugin
275 | 9.2.2.v20140723
276 |
277 |
278 |
279 | 9090
280 |
281 | stopIt
282 | 9191
283 | manual
284 |
285 |
286 |
287 | org.apache.jasper.compiler.disablejsr199
288 | true
289 |
290 |
291 |
292 |
293 |
294 | start-jetty-before-integration-tests
295 | pre-integration-test
296 |
297 | run
298 |
299 |
300 | 0
301 | true
302 |
303 |
304 |
305 | stop-jetty-after-integration-tests
306 | post-integration-test
307 |
308 | stop
309 |
310 |
311 |
312 |
313 |
314 | org.apache.maven.plugins
315 | maven-eclipse-plugin
316 | 2.9
317 |
318 |
319 | org.springframework.ide.eclipse.core.springbuilder
320 | org.eclipse.m2e.core.maven2Builder
321 |
322 |
323 | org.springframework.ide.eclipse.core.springnature
324 | org.eclipse.m2e.core.maven2Nature
325 |
326 |
327 | org.scala-ide.sdt.core.scalabuilder
328 |
329 |
330 | org.scala-ide.sdt.launching.SCALA_CONTAINER
331 | org.eclipse.jdt.launching.JRE_CONTAINER
332 |
333 | true
334 | true
335 |
336 | org.scala-lang:scala-library
337 | org.scala-lang:scala-compiler
338 |
339 |
340 | org.scala-ide.sdt.core.scalanature
341 | org.eclipse.jdt.core.javanature
342 |
343 |
344 | **/*.scala
345 |
346 | 2.0
347 |
348 |
349 |
350 |
351 |
352 |
--------------------------------------------------------------------------------