├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.sbt ├── project ├── BinaryIncompatibilities.scala ├── build.properties └── build.sbt ├── scalastyle-config.xml ├── seleniumJSEnv └── src │ ├── main │ └── scala │ │ └── org │ │ └── scalajs │ │ └── jsenv │ │ └── selenium │ │ ├── FileMaterializers.scala │ │ ├── JSSetup.scala │ │ ├── OutputStreams.scala │ │ ├── SeleniumJSEnv.scala │ │ └── SeleniumRun.scala │ └── test │ └── scala │ └── org │ └── scalajs │ └── jsenv │ └── selenium │ ├── KeepAliveTest.scala │ ├── SeleniumJSEnvSuite.scala │ └── TestCapabilities.scala ├── seleniumJSEnvTest └── src │ ├── main │ └── scala │ │ └── org │ │ └── scalajs │ │ └── jsenv │ │ └── selenium │ │ ├── CanvasCreator.scala │ │ └── ElementCreator.scala │ └── test │ └── scala │ └── org │ └── scalajs │ └── jsenv │ └── selenium │ ├── CanvasTest.scala │ ├── DocumentTest.scala │ ├── ElementCreatorTest.scala │ ├── StructureTest.scala │ └── WindowTest.scala └── seleniumJSHttpEnvTest └── src ├── main └── scala │ └── org │ └── scalajs │ └── jsenv │ └── selenium │ └── CamelCase.scala └── test └── scala └── org └── scalajs └── jsenv └── selenium ├── LocationTest.scala └── ModuleSupportTest.scala /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | scalaversion: ["2.11.12", "2.12.10", "2.13.1"] 16 | browser: ["chrome"] 17 | include: 18 | - scalaversion: "2.12.10" 19 | browser: "firefox" 20 | env: 21 | SJS_TEST_BROWSER: ${{ matrix.browser }} 22 | steps: 23 | - uses: actions/checkout@v2 24 | - uses: olafurpg/setup-scala@v10 25 | with: 26 | java-version: "adopt@1.8" 27 | - uses: coursier/cache-action@v5 28 | - name: Scalastyle 29 | run: > 30 | sbt "++${{ matrix.scalaversion }}" 31 | seleniumJSEnv/scalastyle 32 | seleniumJSEnv/test:scalastyle 33 | seleniumJSEnvTest/scalastyle 34 | seleniumJSHttpEnvTest/test:scalastyle 35 | seleniumJSEnvTest/test:scalastyle 36 | - name: MiMa 37 | run: sbt "++${{ matrix.scalaversion }}" seleniumJSEnv/mimaReportBinaryIssues 38 | - name: Unit tests 39 | run: sbt "++${{ matrix.scalaversion }}" seleniumJSEnv/test 40 | - name: Integration tests 41 | run: > 42 | sbt "++${{ matrix.scalaversion }}" 43 | seleniumJSEnvTest/test 44 | 'set scalaJSStage in Global := FullOptStage' 45 | seleniumJSEnvTest/test 46 | - name: Start HTTP server 47 | run: "python3 -m http.server 8080 &" 48 | - name: Integration tests with HTTP server 49 | run: > 50 | sbt "++${{ matrix.scalaversion }}" 51 | seleniumJSHttpEnvTest/test 52 | 'set scalaJSStage in Global := FullOptStage' 53 | seleniumJSHttpEnvTest/test 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache 6 | .history 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | 15 | # Scala-IDE specific 16 | .scala_dependencies 17 | .worksheet 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | See the [contributing guidelines of Scala.js core](https://github.com/scala-js/scala-js/blob/main/CONTRIBUTING.md). 4 | The same guidelines apply to this repository. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2014 EPFL 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | * Neither the name of the EPFL nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 21 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scalajs-env-selenium 2 | 3 | [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-0.6.17.svg)](https://www.scala-js.org/) 4 | 5 | ## Usage 6 | Simply add the following line to your `project/plugins.sbt` (note that this line must be placed before `addSbtPlugin("org.scala-js" % "sbt-scalajs" % )`; otherwise you may get errors such as `java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkState` when you run tests): 7 | ```scala 8 | // For Scala.js 0.6.x 9 | libraryDependencies += "org.scala-js" %% "scalajs-env-selenium" % "0.3.0" 10 | 11 | // For Scala.js 1.x 12 | libraryDependencies += "org.scala-js" %% "scalajs-env-selenium" % "1.1.1" 13 | ``` 14 | and the following line to your sbt settings: 15 | ```scala 16 | // Apply to the 'run' command 17 | jsEnv := new org.scalajs.jsenv.selenium.SeleniumJSEnv(capabilities) 18 | 19 | // Apply to tests 20 | jsEnv in Test := new org.scalajs.jsenv.selenium.SeleniumJSEnv(capabilities) 21 | ``` 22 | where `capabilities` is one of the members of 23 | [`org.openqa.selenium.remote.DesiredCapabilities`]( 24 | https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/remote/DesiredCapabilities.html). 25 | 26 | For example for Firefox: 27 | 28 | ```scala 29 | jsEnv := new org.scalajs.jsenv.selenium.SeleniumJSEnv( 30 | new org.openqa.selenium.firefox.FirefoxOptions()) 31 | ``` 32 | 33 | You are responsible for installing the [drivers]( 34 | http://docs.seleniumhq.org/download/#thirdPartyDrivers) needed for the browsers 35 | you request. 36 | 37 | When executing the program with `run` a new browser window will be created, 38 | the code will be executed in it and finally the browser will close itself. 39 | All the console outputs will appear in SBT as usual. Executing `test` will open 40 | several browser windows and close them all before the end of the tests. 41 | 42 | ### In browser debugging 43 | If you wish to keep the browser window opened after the execution has terminated simply 44 | add the option `withKeepAlive` on the environment: 45 | 46 | ``` scala 47 | new SeleniumJSEnv(capabilities, SeleniumJSEnv.Config().withKeepAlive(true)) 48 | ``` 49 | 50 | It is recommend to use this with a `run` and not `test` because the latter tends 51 | to leave too many browser windows open. 52 | 53 | #### Debugging tests on a single window 54 | By default tests are executed in their own window for parallelism. 55 | When debugging tests with `withKeepAlive` it is possible to disable this option 56 | using the `sbt` setting `parallelExecution in Test := false`. 57 | 58 | ### Headless Usage 59 | It is often desirable to run Selenium headlessly. 60 | This could be to run tests on a server without graphics, or to just prevent browser 61 | windows popping up when running locally. 62 | 63 | ##### xvfb 64 | A common approach on Linux and Mac OSX, is to use `xvfb`, "X Virtual FrameBuffer". 65 | It starts an X server headlessly, without the need for a graphics driver. 66 | 67 | Once you have `xvfb` installed, usage here with SBT is as simple as: 68 | ```sh 69 | Xvfb :1 & 70 | DISPLAY=:1 sbt 71 | ``` 72 | 73 | The `:1` indicates the X display-number, which is a means to uniquely identify an 74 | X server on a host. The `1` is completely arbitrary—you can choose any number so 75 | long as there isn't another X server running that's already associated with it. 76 | 77 | ## License 78 | 79 | `scalajs-env-selenium` is distributed under the 80 | [BSD 3-Clause license](./LICENSE). 81 | 82 | ## Contributing 83 | 84 | Follow the [contributing guide](./CONTRIBUTING.md). 85 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbt.Keys._ 2 | 3 | import org.scalajs.sbtplugin.ScalaJSCrossVersion 4 | 5 | import org.openqa.selenium.Capabilities 6 | 7 | import org.scalajs.jsenv.selenium.SeleniumJSEnv 8 | import org.scalajs.jsenv.selenium.TestCapabilities 9 | 10 | val previousVersion: Option[String] = Some("1.1.1") 11 | 12 | val newScalaBinaryVersionsInThisRelease: Set[String] = 13 | Set() 14 | 15 | val commonSettings: Seq[Setting[_]] = Seq( 16 | version := "1.1.2-SNAPSHOT", 17 | organization := "org.scala-js", 18 | scalaVersion := "2.11.12", 19 | crossScalaVersions := Seq("2.11.12", "2.12.10", "2.13.1"), 20 | scalacOptions ++= Seq("-deprecation", "-feature", "-Xfatal-warnings"), 21 | 22 | homepage := Some(url("http://scala-js.org/")), 23 | licenses += ("BSD New", 24 | url("https://github.com/scala-js/scala-js-env-selenium/blob/main/LICENSE")), 25 | scmInfo := Some(ScmInfo( 26 | url("https://github.com/scala-js/scala-js-env-selenium"), 27 | "scm:git:git@github.com:scala-js/scala-js-env-selenium.git", 28 | Some("scm:git:git@github.com:scala-js/scala-js-env-selenium.git"))), 29 | testOptions += Tests.Argument(TestFramework("com.novocode.junit.JUnitFramework"), "-v", "-a") 30 | ) 31 | 32 | val previousArtifactSetting = Def.settings( 33 | mimaPreviousArtifacts ++= { 34 | val scalaV = scalaVersion.value 35 | val scalaBinaryV = scalaBinaryVersion.value 36 | val thisProjectID = projectID.value 37 | previousVersion match { 38 | case None => 39 | Set.empty 40 | case _ if newScalaBinaryVersionsInThisRelease.contains(scalaBinaryV) => 41 | // New in this release, no binary compatibility to comply to 42 | Set.empty 43 | case Some(prevVersion) => 44 | /* Filter out e:info.apiURL as it expects 0.6.7-SNAPSHOT, whereas the 45 | * artifact we're looking for has 0.6.6 (for example). 46 | */ 47 | val prevExtraAttributes = 48 | thisProjectID.extraAttributes.filterKeys(_ != "e:info.apiURL") 49 | val prevProjectID = 50 | (thisProjectID.organization % thisProjectID.name % prevVersion) 51 | .cross(thisProjectID.crossVersion) 52 | .extra(prevExtraAttributes.toSeq: _*) 53 | Set(prevProjectID) 54 | } 55 | } 56 | ) 57 | 58 | val jsEnvCapabilities = settingKey[org.openqa.selenium.Capabilities]( 59 | "Capabilities of the SeleniumJSEnv") 60 | 61 | val testSettings: Seq[Setting[_]] = commonSettings ++ Seq( 62 | jsEnvCapabilities := TestCapabilities.fromEnv, 63 | jsEnv := new SeleniumJSEnv(jsEnvCapabilities.value), 64 | scalaJSUseMainModuleInitializer := true 65 | ) 66 | 67 | // We'll need the name scalajs-env-selenium for the `seleniumJSEnv` project 68 | name := "root" 69 | 70 | lazy val seleniumJSEnv: Project = project. 71 | settings(commonSettings). 72 | settings( 73 | name := "scalajs-env-selenium", 74 | 75 | libraryDependencies ++= Seq( 76 | /* Make sure selenium is before scalajs-envs-test-kit: 77 | * It pulls in "closure-compiler-java-6" which in turn bundles some old 78 | * guava stuff which in turn makes selenium fail. 79 | */ 80 | "org.seleniumhq.selenium" % "selenium-server" % "3.141.59", 81 | "org.scala-js" %% "scalajs-js-envs" % "1.1.1", 82 | "com.google.jimfs" % "jimfs" % "1.1", 83 | "org.scala-js" %% "scalajs-js-envs-test-kit" % "1.1.1" % "test", 84 | "com.novocode" % "junit-interface" % "0.11" % "test" 85 | ), 86 | 87 | previousArtifactSetting, 88 | mimaBinaryIssueFilters ++= BinaryIncompatibilities.SeleniumJSEnv, 89 | 90 | publishMavenStyle := true, 91 | publishTo := { 92 | val nexus = "https://oss.sonatype.org/" 93 | if (isSnapshot.value) 94 | Some("snapshots" at nexus + "content/repositories/snapshots") 95 | else 96 | Some("releases" at nexus + "service/local/staging/deploy/maven2") 97 | }, 98 | pomExtra := ( 99 | 100 | 101 | nicolasstucki 102 | Nicolas Stucki 103 | https://github.com/nicolasstucki/ 104 | 105 | 106 | sjrd 107 | Sébastien Doeraene 108 | https://github.com/sjrd/ 109 | 110 | 111 | gzm0 112 | Tobias Schlatter 113 | https://github.com/gzm0/ 114 | 115 | 116 | ), 117 | pomIncludeRepository := { _ => false }, 118 | 119 | // The chrome driver seems to not deal with parallelism very well (#47). 120 | parallelExecution in Test := false 121 | ) 122 | 123 | lazy val seleniumJSEnvTest: Project = project. 124 | enablePlugins(ScalaJSPlugin). 125 | enablePlugins(ScalaJSJUnitPlugin). 126 | settings(testSettings) 127 | 128 | lazy val seleniumJSHttpEnvTest: Project = project. 129 | enablePlugins(ScalaJSPlugin). 130 | enablePlugins(ScalaJSJUnitPlugin). 131 | settings(testSettings). 132 | settings( 133 | jsEnv := { 134 | new SeleniumJSEnv( 135 | jsEnvCapabilities.value, 136 | SeleniumJSEnv.Config() 137 | .withMaterializeInServer("tmp", "http://localhost:8080/tmp/") 138 | ) 139 | }, 140 | scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) } 141 | ) 142 | -------------------------------------------------------------------------------- /project/BinaryIncompatibilities.scala: -------------------------------------------------------------------------------- 1 | import com.typesafe.tools.mima.core._ 2 | import com.typesafe.tools.mima.core.ProblemFilters._ 3 | 4 | object BinaryIncompatibilities { 5 | val SeleniumJSEnv = Seq( 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /project/build.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.2.0") 2 | 3 | addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.18") 4 | 5 | addSbtPlugin("org.scalastyle" % "scalastyle-sbt-plugin" % "1.0.0") 6 | 7 | /* Make sure selenium is before scalajs-envs: 8 | * It pulls in "closure-compiler-java-6" which in turn bundles some old 9 | * guava stuff which in turn makes selenium fail. 10 | */ 11 | libraryDependencies ~= 12 | ("org.seleniumhq.selenium" % "selenium-server" % "3.141.59" +: _) 13 | 14 | unmanagedSourceDirectories in Compile ++= { 15 | val root = baseDirectory.value.getParentFile 16 | Seq(root / "seleniumJSEnv/src/main/scala") 17 | } 18 | 19 | sources in Compile += { 20 | val root = baseDirectory.value.getParentFile 21 | root / "seleniumJSEnv/src/test/scala/org/scalajs/jsenv/selenium/TestCapabilities.scala" 22 | } 23 | -------------------------------------------------------------------------------- /scalastyle-config.xml: -------------------------------------------------------------------------------- 1 | 2 | Scalastyle configuration for Scala.js 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | COLON, COMMA, RPAREN 69 | 70 | 71 | 72 | 73 | LPAREN 74 | 75 | 76 | 77 | 78 | IF, FOR, WHILE, DO, TRY 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /seleniumJSEnv/src/main/scala/org/scalajs/jsenv/selenium/FileMaterializers.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import java.io._ 4 | import java.nio.file._ 5 | import java.net._ 6 | import java.util.Arrays 7 | 8 | private[selenium] sealed abstract class FileMaterializer { 9 | private val tmpSuffixRE = """[a-zA-Z0-9-_.]*$""".r 10 | 11 | private[this] var tmpFiles: List[Path] = Nil 12 | 13 | def materialize(path: Path): URL = { 14 | val tmp = newTmp(path.toString) 15 | Files.copy(path, tmp, StandardCopyOption.REPLACE_EXISTING) 16 | toURL(tmp) 17 | } 18 | 19 | final def materialize(name: String, content: String): URL = { 20 | val tmp = newTmp(name) 21 | Files.write(tmp, Arrays.asList(content)) 22 | toURL(tmp) 23 | } 24 | 25 | final def close(): Unit = { 26 | tmpFiles.foreach(Files.delete) 27 | tmpFiles = Nil 28 | } 29 | 30 | private def newTmp(path: String): Path = { 31 | val suffix = tmpSuffixRE.findFirstIn(path).orNull 32 | val p = createTmp(suffix) 33 | tmpFiles ::= p 34 | p 35 | } 36 | 37 | protected def createTmp(suffix: String): Path 38 | protected def toURL(file: Path): URL 39 | } 40 | 41 | object FileMaterializer { 42 | import SeleniumJSEnv.Config.Materialization 43 | def apply(m: Materialization): FileMaterializer = m match { 44 | case Materialization.Temp => 45 | new TempDirFileMaterializer 46 | 47 | case Materialization.Server(contentDir, webRoot) => 48 | new ServerDirFileMaterializer(contentDir, webRoot) 49 | } 50 | } 51 | 52 | /** materializes virtual files in a temp directory (uses file:// schema). */ 53 | private class TempDirFileMaterializer extends FileMaterializer { 54 | override def materialize(path: Path): URL = { 55 | try { 56 | path.toFile.toURI.toURL 57 | } catch { 58 | case _: UnsupportedOperationException => 59 | super.materialize(path) 60 | } 61 | } 62 | 63 | protected def createTmp(suffix: String) = Files.createTempFile(null, suffix) 64 | protected def toURL(file: Path): URL = file.toUri.toURL 65 | } 66 | 67 | private class ServerDirFileMaterializer(contentDir: Path, webRoot: URL) 68 | extends FileMaterializer { 69 | Files.createDirectories(contentDir) 70 | 71 | protected def createTmp(suffix: String) = 72 | Files.createTempFile(contentDir, null, suffix) 73 | 74 | protected def toURL(file: Path): URL = { 75 | val rel = contentDir.relativize(file) 76 | assert(!rel.isAbsolute) 77 | val nameURI = new URI(null, null, rel.toString, null) 78 | webRoot.toURI.resolve(nameURI).toURL 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /seleniumJSEnv/src/main/scala/org/scalajs/jsenv/selenium/JSSetup.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import java.nio.charset.StandardCharsets 4 | import java.nio.file.{Files, Path} 5 | 6 | import com.google.common.jimfs.Jimfs 7 | 8 | private[selenium] object JSSetup { 9 | def setupFile(enableCom: Boolean): Path = { 10 | val path = Jimfs.newFileSystem().getPath("setup.js") 11 | val contents = setupCode(enableCom).getBytes(StandardCharsets.UTF_8) 12 | Files.write(path, contents) 13 | } 14 | 15 | private def setupCode(enableCom: Boolean): String = { 16 | s""" 17 | |(function() { 18 | | // Buffers for console.log / console.error 19 | | var consoleLog = []; 20 | | var consoleError = []; 21 | | 22 | | // Buffer for errors. 23 | | var errors = []; 24 | | 25 | | // Buffer for outgoing messages. 26 | | var outMessages = []; 27 | | 28 | | // Buffer for incoming messages (used if onMessage not initalized). 29 | | var inMessages = []; 30 | | 31 | | // Callback for incoming messages. 32 | | var onMessage = null; 33 | | 34 | | function captureConsole(fun, buf) { 35 | | if (!fun) return fun; 36 | | return function() { 37 | | var strs = [] 38 | | for (var i = 0; i < arguments.length; ++i) 39 | | strs.push(String(arguments[i])); 40 | | 41 | | buf.push(strs.join(" ")); 42 | | return fun.apply(this, arguments); 43 | | } 44 | | } 45 | | 46 | | console.log = captureConsole(console.log, consoleLog); 47 | | console.error = captureConsole(console.error, consoleError); 48 | | 49 | | window.addEventListener('error', function(e) { 50 | | errors.push(e.message) 51 | | }); 52 | | 53 | | if ($enableCom) { 54 | | this.scalajsCom = { 55 | | init: function(onMsg) { 56 | | onMessage = onMsg; 57 | | window.setTimeout(function() { 58 | | for (var m in inMessages) 59 | | onMessage(inMessages[m]); 60 | | inMessages = null; 61 | | }); 62 | | }, 63 | | send: function(msg) { outMessages.push(msg); } 64 | | } 65 | | } 66 | | 67 | | this.scalajsSeleniumInternalInterface = { 68 | | fetch: function() { 69 | | var res = { 70 | | consoleLog: consoleLog.slice(), 71 | | consoleError: consoleError.slice(), 72 | | errors: errors.slice(), 73 | | msgs: outMessages.slice() 74 | | } 75 | | 76 | | consoleLog.length = 0; 77 | | consoleError.length = 0; 78 | | errors.length = 0; 79 | | outMessages.length = 0; 80 | | 81 | | return res; 82 | | }, 83 | | send: function(msg) { 84 | | if (inMessages !== null) inMessages.push(msg); 85 | | else onMessage(msg); 86 | | } 87 | | }; 88 | |}).call(this) 89 | """.stripMargin 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /seleniumJSEnv/src/main/scala/org/scalajs/jsenv/selenium/OutputStreams.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import org.scalajs.jsenv.RunConfig 4 | 5 | import java.io._ 6 | 7 | private[selenium] object OutputStreams { 8 | final class Streams(val out: PrintStream, val err: PrintStream) { 9 | def close(): Unit = { 10 | out.close() 11 | err.close() 12 | } 13 | } 14 | 15 | def prepare(config: RunConfig): Streams = { 16 | val outp = optPipe(!config.inheritOutput) 17 | val errp = optPipe(!config.inheritError) 18 | 19 | config.onOutputStream.foreach(f => f(outp.map(_._1), errp.map(_._1))) 20 | 21 | val out = outp.fold[OutputStream](new UnownedOutputStream(System.out))(_._2) 22 | val err = errp.fold[OutputStream](new UnownedOutputStream(System.err))(_._2) 23 | 24 | new Streams(new PrintStream(out), new PrintStream(err)) 25 | } 26 | 27 | private def optPipe(want: Boolean) = { 28 | if (want) { 29 | val i = new PipedInputStream() 30 | val o = new PipedOutputStream(i) 31 | Some((i, o)) 32 | } else { 33 | None 34 | } 35 | } 36 | 37 | private class UnownedOutputStream(out: OutputStream) extends FilterOutputStream(out) { 38 | override def close(): Unit = flush() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /seleniumJSEnv/src/main/scala/org/scalajs/jsenv/selenium/SeleniumJSEnv.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import org.openqa.selenium._ 4 | import org.openqa.selenium.remote.DesiredCapabilities 5 | import org.openqa.selenium.remote.server._ 6 | 7 | import org.scalajs.jsenv._ 8 | 9 | import java.net.URL 10 | import java.nio.file.{Path, Paths} 11 | 12 | final class SeleniumJSEnv(capabilities: Capabilities, config: SeleniumJSEnv.Config) extends JSEnv { 13 | def this(capabilities: Capabilities) = 14 | this(capabilities, SeleniumJSEnv.Config()) 15 | 16 | private val augmentedCapabilities = { 17 | val x = new DesiredCapabilities(capabilities) 18 | x.setJavascriptEnabled(true) 19 | x 20 | } 21 | 22 | val name: String = s"SeleniumJSEnv ($capabilities)" 23 | 24 | def start(input: Seq[Input], runConfig: RunConfig): JSRun = 25 | SeleniumRun.start(newDriver _, input, config, runConfig) 26 | 27 | def startWithCom(input: Seq[Input], runConfig: RunConfig, onMessage: String => Unit): JSComRun = 28 | SeleniumRun.startWithCom(newDriver _, input, config, runConfig, onMessage) 29 | 30 | private def newDriver() = { 31 | val driver: WebDriver = 32 | config.driverFactory.newInstance(augmentedCapabilities) 33 | 34 | /* The first `asInstanceOf`s are a fail-fast for the second one, which 35 | * scalac partially erases, so that we're sure right now that the last 36 | * cast is correct, as opposed to crashing when we call a method of 37 | * JavascriptExecutor on the driver. 38 | * 39 | * We are "allowed" to cast since we explicitly request JavascriptEnabled in 40 | * the capabilities. 41 | */ 42 | driver.asInstanceOf[JavascriptExecutor] 43 | 44 | driver.asInstanceOf[WebDriver with JavascriptExecutor] 45 | } 46 | } 47 | 48 | object SeleniumJSEnv { 49 | final class Config private ( 50 | val driverFactory: DriverFactory, 51 | val keepAlive: Boolean, 52 | val materialization: Config.Materialization 53 | ) { 54 | import Config.Materialization 55 | 56 | private def this() = this( 57 | keepAlive = false, 58 | materialization = Config.Materialization.Temp, 59 | driverFactory = new DefaultDriverFactory(Platform.getCurrent())) 60 | 61 | /** Materializes purely virtual files into a temp directory. 62 | * 63 | * Materialization is necessary so that virtual files can be referred to by 64 | * name. If you do not know/care how your files are referred to, this is a 65 | * good default choice. It is also the default of [[SeleniumJSEnv.Config]]. 66 | */ 67 | def withMaterializeInTemp: Config = 68 | copy(materialization = Materialization.Temp) 69 | 70 | /** Materializes files in a static directory of a user configured server. 71 | * 72 | * This can be used to bypass cross origin access policies. 73 | * 74 | * @param contentDir Static content directory of the server. The files will 75 | * be put here. Will get created if it doesn't exist. 76 | * @param webRoot URL making `contentDir` accessible thorugh the server. 77 | * This must have a trailing slash to be interpreted as a directory. 78 | * 79 | * @example 80 | * 81 | * The following will make the browser fetch files using the http:// schema 82 | * instead of the file:// schema. The example assumes a local webserver is 83 | * running and serving the ".tmp" directory at http://localhost:8080. 84 | * 85 | * {{{ 86 | * jsSettings( 87 | * jsEnv := new SeleniumJSEnv( 88 | * new org.openqa.selenium.firefox.FirefoxOptions(), 89 | * SeleniumJSEnv.Config() 90 | * .withMaterializeInServer(".tmp", "http://localhost:8080/") 91 | * ) 92 | * ) 93 | * }}} 94 | */ 95 | def withMaterializeInServer(contentDir: String, webRoot: String): Config = 96 | withMaterializeInServer(Paths.get(contentDir), new URL(webRoot)) 97 | 98 | /** Materializes files in a static directory of a user configured server. 99 | * 100 | * Version of `withMaterializeInServer` with stronger typing. 101 | * 102 | * @param contentDir Static content directory of the server. The files will 103 | * be put here. Will get created if it doesn't exist. 104 | * @param webRoot URL making `contentDir` accessible thorugh the server. 105 | * This must have a trailing slash to be interpreted as a directory. 106 | */ 107 | def withMaterializeInServer(contentDir: Path, webRoot: URL): Config = 108 | copy(materialization = Materialization.Server(contentDir, webRoot)) 109 | 110 | def withMaterialization(materialization: Materialization): Config = 111 | copy(materialization = materialization) 112 | 113 | def withKeepAlive(keepAlive: Boolean): Config = 114 | copy(keepAlive = keepAlive) 115 | 116 | def withDriverFactory(driverFactory: DriverFactory): Config = 117 | copy(driverFactory = driverFactory) 118 | 119 | private def copy(keepAlive: Boolean = keepAlive, 120 | materialization: Config.Materialization = materialization, 121 | driverFactory: DriverFactory = driverFactory) = { 122 | new Config(driverFactory, keepAlive, materialization) 123 | } 124 | } 125 | 126 | object Config { 127 | def apply(): Config = new Config() 128 | 129 | abstract class Materialization private () 130 | object Materialization { 131 | final case object Temp extends Materialization 132 | final case class Server(contentDir: Path, webRoot: URL) extends Materialization { 133 | require(webRoot.getPath().endsWith("/"), "webRoot must end with a slash (/)") 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /seleniumJSEnv/src/main/scala/org/scalajs/jsenv/selenium/SeleniumRun.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import org.openqa.selenium._ 4 | 5 | import org.scalajs.jsenv._ 6 | 7 | import scala.annotation.tailrec 8 | import scala.concurrent._ 9 | import scala.util.control.NonFatal 10 | 11 | import java.util.concurrent.{ConcurrentLinkedQueue, Executors} 12 | import java.util.function.Consumer 13 | import java.nio.file.Path 14 | import java.net.URL 15 | 16 | private sealed class SeleniumRun( 17 | driver: WebDriver with JavascriptExecutor, 18 | config: SeleniumJSEnv.Config, 19 | streams: OutputStreams.Streams, 20 | materializer: FileMaterializer) extends JSRun { 21 | 22 | @volatile 23 | private[this] var wantClose = false 24 | 25 | protected val intf = "this.scalajsSeleniumInternalInterface" 26 | 27 | private[this] implicit val ec = 28 | ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor()) 29 | 30 | private val handler = Future { 31 | while (!isInterfaceUp() && !wantClose) { 32 | Thread.sleep(100) 33 | } 34 | 35 | while (!wantClose) { 36 | sendAll() 37 | fetchAndProcess() 38 | Thread.sleep(100) 39 | } 40 | } 41 | 42 | val future: Future[Unit] = handler.andThen { case _ => 43 | SeleniumRun.maybeCleanupDriver(driver, config) 44 | streams.close() 45 | materializer.close() 46 | } 47 | 48 | def close(): Unit = wantClose = true 49 | 50 | private final def fetchAndProcess(): Unit = { 51 | import SeleniumRun.consumer 52 | 53 | val data = driver 54 | .executeScript(s"return $intf.fetch();") 55 | .asInstanceOf[java.util.Map[String, java.util.List[String]]] 56 | 57 | data.get("consoleLog").forEach(consumer(streams.out.println _)) 58 | data.get("consoleError").forEach(consumer(streams.err.println _)) 59 | data.get("msgs").forEach(consumer(receivedMessage _)) 60 | 61 | val errs = data.get("errors") 62 | if (!errs.isEmpty()) { 63 | // Convoluted way of writing errs.toList without JavaConverters. 64 | val errList = errs.toArray(Array[String]()).toList 65 | throw new SeleniumRun.WindowOnErrorException(errList) 66 | } 67 | } 68 | 69 | private final def isInterfaceUp() = 70 | driver.executeScript(s"return !!$intf;").asInstanceOf[Boolean] 71 | 72 | // Hooks for SeleniumComRun. 73 | 74 | protected def sendAll(): Unit = () 75 | 76 | protected def receivedMessage(msg: String): Unit = 77 | throw new AssertionError(s"received message in non-com run: $msg") 78 | } 79 | 80 | private final class SeleniumComRun( 81 | driver: WebDriver with JavascriptExecutor, 82 | config: SeleniumJSEnv.Config, 83 | streams: OutputStreams.Streams, 84 | materializer: FileMaterializer, 85 | onMessage: String => Unit) 86 | extends SeleniumRun(driver, config, streams, materializer) with JSComRun { 87 | private[this] val sendQueue = new ConcurrentLinkedQueue[String] 88 | 89 | def send(msg: String): Unit = sendQueue.offer(msg) 90 | 91 | override protected def receivedMessage(msg: String) = onMessage(msg) 92 | 93 | @tailrec 94 | override protected final def sendAll(): Unit = { 95 | val msg = sendQueue.poll() 96 | if (msg != null) { 97 | driver.executeScript(s"$intf.send(arguments[0]);", msg) 98 | sendAll() 99 | } 100 | } 101 | } 102 | 103 | private[selenium] object SeleniumRun { 104 | import SeleniumJSEnv.Config 105 | import OutputStreams.Streams 106 | 107 | type JSDriver = WebDriver with JavascriptExecutor 108 | 109 | private lazy val validator = { 110 | RunConfig.Validator() 111 | .supportsInheritIO() 112 | .supportsOnOutputStream() 113 | } 114 | 115 | def start(newDriver: () => JSDriver, input: Seq[Input], config: Config, 116 | runConfig: RunConfig): JSRun = { 117 | startInternal(newDriver, input, config, runConfig, enableCom = false)( 118 | new SeleniumRun(_, _, _, _), JSRun.failed _) 119 | } 120 | 121 | def startWithCom(newDriver: () => JSDriver, input: Seq[Input], config: Config, 122 | runConfig: RunConfig, onMessage: String => Unit): JSComRun = { 123 | startInternal(newDriver, input, config, runConfig, enableCom = true)( 124 | new SeleniumComRun(_, _, _, _, onMessage), JSComRun.failed _) 125 | } 126 | 127 | private type Ctor[T] = (JSDriver, Config, Streams, FileMaterializer) => T 128 | 129 | private def startInternal[T](newDriver: () => JSDriver, input: Seq[Input], 130 | config: Config, runConfig: RunConfig, enableCom: Boolean)( 131 | newRun: Ctor[T], failed: Throwable => T): T = { 132 | validator.validate(runConfig) 133 | 134 | try { 135 | withCleanup(FileMaterializer(config.materialization))(_.close()) { m => 136 | val setupJsScript = Input.Script(JSSetup.setupFile(enableCom)) 137 | val fullInput = setupJsScript +: input 138 | val page = m.materialize("scalajsRun.html", htmlPage(fullInput, m)) 139 | withCleanup(newDriver())(maybeCleanupDriver(_, config)) { driver => 140 | driver.navigate().to(page) 141 | 142 | withCleanup(OutputStreams.prepare(runConfig))(_.close()) { streams => 143 | newRun(driver, config, streams, m) 144 | } 145 | } 146 | } 147 | } catch { 148 | case NonFatal(t) => 149 | failed(t) 150 | } 151 | } 152 | 153 | private def withCleanup[V, R](mk: => V)(cleanup: V => Unit)(body: V => R): R = { 154 | val v = mk 155 | try { 156 | body(v) 157 | } catch { 158 | case t: Throwable => 159 | cleanup(v) 160 | throw t 161 | } 162 | } 163 | 164 | private def maybeCleanupDriver(d: WebDriver, config: SeleniumJSEnv.Config) = 165 | if (!config.keepAlive) d.quit() 166 | 167 | private def htmlPage(fullInput: Seq[Input], materializer: FileMaterializer): String = { 168 | val tags = fullInput.map { 169 | case Input.Script(path) => makeTag(path, "text/javascript", materializer) 170 | case Input.ESModule(path) => makeTag(path, "module", materializer) 171 | case _ => throw new UnsupportedInputException(fullInput) 172 | } 173 | 174 | s""" 175 | | 176 | | 177 | | ${tags.mkString("\n ")} 178 | | 179 | | 180 | """.stripMargin 181 | } 182 | 183 | private def makeTag(path: Path, tpe: String, materializer: FileMaterializer): String = { 184 | val url = materializer.materialize(path) 185 | s"" 186 | } 187 | 188 | private class WindowOnErrorException(errs: List[String]) extends Exception(s"JS error: $errs") 189 | 190 | private def consumer[A](f: A => Unit): Consumer[A] = new Consumer[A] { 191 | override def accept(v: A): Unit = f(v) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /seleniumJSEnv/src/test/scala/org/scalajs/jsenv/selenium/KeepAliveTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import scala.concurrent.Await 4 | import scala.concurrent.duration._ 5 | 6 | import java.net.URL 7 | 8 | import org.openqa.selenium._ 9 | import org.openqa.selenium.remote.DesiredCapabilities 10 | import org.openqa.selenium.remote.server._ 11 | 12 | import org.junit._ 13 | import org.junit.Assert._ 14 | 15 | import org.scalajs.jsenv._ 16 | 17 | class KeepAliveTest { 18 | private final class MockWebDriver extends WebDriver with JavascriptExecutor { 19 | var closed = false 20 | 21 | def quit(): Unit = closed = true 22 | 23 | def executeScript(code: String, args: Object*): Object = 24 | new java.util.HashMap[String, java.util.List[String]]() 25 | 26 | def get(url: String): Unit = () 27 | 28 | def navigate(): WebDriver.Navigation = new WebDriver.Navigation { 29 | def back(): Unit = () 30 | def forward(): Unit = () 31 | def refresh(): Unit = () 32 | def to(url: String): Unit = () 33 | def to(url: URL): Unit = () 34 | } 35 | 36 | // Stuff we won't need. 37 | 38 | def close(): Unit = ??? 39 | 40 | def executeAsyncScript(code: String, args: Object*): Object = ??? 41 | 42 | def findElement(x1: By): WebElement = ??? 43 | def findElements(x1: By): java.util.List[WebElement] = ??? 44 | 45 | def getTitle(): String = ??? 46 | def getWindowHandle(): String = ??? 47 | def getWindowHandles(): java.util.Set[String] = ??? 48 | def manage(): WebDriver.Options = ??? 49 | 50 | def getCurrentUrl(): String = ??? 51 | def getPageSource(): String = ??? 52 | 53 | def switchTo(): WebDriver.TargetLocator = ??? 54 | } 55 | 56 | private final class MockInjector(driver: WebDriver) extends DriverFactory { 57 | var used = false 58 | 59 | def newInstance(caps: Capabilities): WebDriver = { 60 | require(!used) 61 | used = true 62 | driver 63 | } 64 | 65 | def hasMappingFor(caps: Capabilities): Boolean = true 66 | def registerDriverProvider(p: DriverProvider): Unit = ??? 67 | } 68 | 69 | private def setup(keepAlive: Boolean) = { 70 | val driver = new MockWebDriver 71 | val factory = new MockInjector(driver) 72 | val config = SeleniumJSEnv.Config() 73 | .withDriverFactory(factory) 74 | .withKeepAlive(keepAlive) 75 | val env = new SeleniumJSEnv(new DesiredCapabilities, config) 76 | 77 | (driver, factory, env) 78 | } 79 | 80 | private def runNoCom(env: JSEnv) = { 81 | val run = env.start(Nil, RunConfig()) 82 | run.close() 83 | Await.ready(run.future, 1.minute) 84 | } 85 | 86 | private def runWithCom(env: JSEnv) = { 87 | val run = env.startWithCom(Nil, RunConfig(), _ => ()) 88 | run.close() 89 | Await.ready(run.future, 1.minute) 90 | } 91 | 92 | @Test 93 | def runClosesWithoutKeepAlive: Unit = { 94 | val (driver, factory, env) = setup(keepAlive = false) 95 | runNoCom(env) 96 | assertTrue(factory.used) 97 | assertTrue(driver.closed) 98 | } 99 | 100 | @Test 101 | def runNoCloseWithKeepAlive: Unit = { 102 | val (driver, factory, env) = setup(keepAlive = true) 103 | runNoCom(env) 104 | assertTrue(factory.used) 105 | assertFalse(driver.closed) 106 | } 107 | 108 | @Test 109 | def comRunClosesWithoutKeepAlive: Unit = { 110 | val (driver, factory, env) = setup(keepAlive = false) 111 | runWithCom(env) 112 | assertTrue(factory.used) 113 | assertTrue(driver.closed) 114 | } 115 | 116 | @Test 117 | def comRunNoCloseWithKeepAlive: Unit = { 118 | val (driver, factory, env) = setup(keepAlive = true) 119 | runWithCom(env) 120 | assertTrue(factory.used) 121 | assertFalse(driver.closed) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /seleniumJSEnv/src/test/scala/org/scalajs/jsenv/selenium/SeleniumJSEnvSuite.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import java.util.Arrays 4 | 5 | import org.scalajs.jsenv.test._ 6 | 7 | import org.junit.runner.RunWith 8 | import org.junit.runner.Runner 9 | import org.junit.runners.Suite 10 | import org.junit.runner.manipulation.Filter 11 | import org.junit.runner.Description 12 | 13 | @RunWith(classOf[JSEnvSuiteRunner]) 14 | class SeleniumJSSuite extends JSEnvSuite( 15 | JSEnvSuiteConfig(new SeleniumJSEnv(TestCapabilities.fromEnv)) 16 | ) 17 | -------------------------------------------------------------------------------- /seleniumJSEnv/src/test/scala/org/scalajs/jsenv/selenium/TestCapabilities.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import org.openqa.selenium.Capabilities 4 | import org.openqa.selenium.firefox.{FirefoxOptions, FirefoxDriverLogLevel} 5 | import org.openqa.selenium.chrome.ChromeOptions 6 | 7 | import java.util.logging.{Logger, Level} 8 | 9 | object TestCapabilities { 10 | // Lower the logging level for Selenium to avoid spam. 11 | Logger.getLogger("org.openqa.selenium").setLevel(Level.WARNING) 12 | 13 | def fromEnv: Capabilities = nameFromEnv match { 14 | case "firefox" => 15 | new FirefoxOptions() 16 | .setHeadless(true) 17 | .setLogLevel(FirefoxDriverLogLevel.ERROR) 18 | 19 | case "chrome" => 20 | new ChromeOptions() 21 | .setHeadless(true) 22 | 23 | case name => 24 | throw new IllegalArgumentException(s"Unknown browser $name") 25 | } 26 | 27 | def nameFromEnv: String = sys.env.getOrElse("SJS_TEST_BROWSER", "firefox") 28 | } 29 | -------------------------------------------------------------------------------- /seleniumJSEnvTest/src/main/scala/org/scalajs/jsenv/selenium/CanvasCreator.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import scala.scalajs.js.Dynamic._ 4 | 5 | // Canvas example from: http://www.w3schools.com/html/html5_canvas.asp 6 | object CanvasCreator { 7 | 8 | def create(id: String): Unit = { 9 | ElementCreator.create("canvas", id = id) 10 | } 11 | 12 | def paint(id: String): Unit = { 13 | val canvas = global.document.getElementById(id) 14 | val ctx = canvas.getContext("2d") 15 | 16 | val grd = ctx.createLinearGradient(0, 0, 200, 0) 17 | grd.addColorStop(0, "red") 18 | grd.addColorStop(1, "white") 19 | 20 | ctx.fillStyle = grd 21 | ctx.fillRect(10, 10, 150, 80) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /seleniumJSEnvTest/src/main/scala/org/scalajs/jsenv/selenium/ElementCreator.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.Dynamic.global 5 | 6 | object ElementCreator { 7 | def create(element: String, id: String = "", text: String = ""): js.Dynamic = { 8 | val el = global.document.createElement(element) 9 | 10 | if (id != "") { 11 | el.id = id 12 | } 13 | 14 | if (text != "") { 15 | el.textContent = text 16 | } 17 | 18 | global.document.body.appendChild(el) 19 | el 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /seleniumJSEnvTest/src/test/scala/org/scalajs/jsenv/selenium/CanvasTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import org.junit.Assert._ 4 | import org.junit.Test 5 | 6 | import scala.scalajs.js 7 | import scala.scalajs.js.Dynamic.global 8 | 9 | class CanvasTest { 10 | 11 | @Test def testCanvas(): Unit = { 12 | // create the element 13 | val canvasId = "testCanvas" 14 | CanvasCreator.create(canvasId) 15 | CanvasCreator.paint(canvasId) 16 | 17 | val body = global.document.getElementsByTagName("body") 18 | .asInstanceOf[js.Array[js.Dynamic]].head 19 | 20 | assertEquals("canvas", body.lastChild.tagName.toString.toLowerCase) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /seleniumJSEnvTest/src/test/scala/org/scalajs/jsenv/selenium/DocumentTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import org.junit.Assert._ 4 | import org.junit.Test 5 | 6 | import scala.scalajs.js.Dynamic.global 7 | import scala.scalajs.js.isUndefined 8 | 9 | class DocumentTest { 10 | 11 | @Test def document(): Unit = { 12 | assertFalse(isUndefined(global.document)) 13 | assertEquals("#document", global.document.nodeName) 14 | } 15 | 16 | @Test def documentBody(): Unit = { 17 | assertFalse(isUndefined(global.document.body)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /seleniumJSEnvTest/src/test/scala/org/scalajs/jsenv/selenium/ElementCreatorTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import org.junit.Assert._ 4 | import org.junit.Test 5 | 6 | import scala.scalajs.js 7 | import scala.scalajs.js.Dynamic.global 8 | 9 | class ElementCreatorTest { 10 | 11 | @Test def should_be_able_to_create_an_element_in_the_body(): Unit = { 12 | // create the element 13 | val h1 = ElementCreator.create("h1", text = "Testing DOM.") 14 | 15 | // jquery would make this easier, but I wanted to 16 | // only use pure html in the test itself 17 | val body = global.document.getElementsByTagName("body") 18 | .asInstanceOf[js.Array[js.Dynamic]].head 19 | 20 | // the Scala.js DOM API would make this easier 21 | assertEquals("H1", body.lastChild.tagName.toString) 22 | assertEquals("Testing DOM.", body.lastChild.innerHTML.toString) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /seleniumJSEnvTest/src/test/scala/org/scalajs/jsenv/selenium/StructureTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import org.junit.Assert._ 4 | import org.junit.Test 5 | 6 | import scala.scalajs.js.Dynamic.global 7 | 8 | class StructureTest { 9 | private val canvasId = "testCanvas" 10 | 11 | @Test 12 | def testStructure(): Unit = { 13 | ElementCreator.create("h1", text = "SeleniumJSEnv Test") 14 | CanvasCreator.create(canvasId) 15 | CanvasCreator.paint(canvasId) 16 | 17 | assertStructure() 18 | 19 | ElementCreator.create("h1", text = "'run' finished.") 20 | println("'run' finished.") 21 | } 22 | 23 | def assertStructure(): Unit = { 24 | val nodes = global.document.body.childNodes 25 | val len = nodes.length.asInstanceOf[Int] 26 | 27 | // Take last two nodes. 28 | 29 | val h1 = nodes.item(len - 2) 30 | assertEquals("H1", h1.tagName.asInstanceOf[String]) 31 | assertEquals("SeleniumJSEnv Test", h1.textContent.asInstanceOf[String]) 32 | 33 | val canvas = nodes.item(len - 1) 34 | assertEquals("CANVAS", canvas.tagName.asInstanceOf[String]) 35 | assertEquals(canvasId, canvas.id.asInstanceOf[String]) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /seleniumJSEnvTest/src/test/scala/org/scalajs/jsenv/selenium/WindowTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import org.junit.Assert._ 4 | import org.junit.Test 5 | 6 | import scala.scalajs.js.Dynamic.global 7 | import scala.scalajs.js.isUndefined 8 | 9 | class WindowTest { 10 | 11 | @Test def WindowTest(): Unit = { 12 | assertFalse(isUndefined(global.window)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /seleniumJSHttpEnvTest/src/main/scala/org/scalajs/jsenv/selenium/CamelCase.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation.JSImport 5 | 6 | object CamelCase { 7 | def hello(input: String): String = s"Hello ${camelCase(input)}!" 8 | 9 | @JSImport("https://cdn.skypack.dev/camelcase@^6.0.0", JSImport.Default) 10 | @js.native 11 | def camelCase(input: String): String = js.native 12 | } 13 | -------------------------------------------------------------------------------- /seleniumJSHttpEnvTest/src/test/scala/org/scalajs/jsenv/selenium/LocationTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import org.junit.Assert._ 4 | import org.junit.Test 5 | 6 | import scala.scalajs.js.Dynamic.global 7 | 8 | class LocationTest { 9 | 10 | @Test def LocationTest(): Unit = { 11 | assertEquals("http:", global.window.location.protocol.toString()) 12 | assertEquals("localhost:8080", global.window.location.host.toString()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /seleniumJSHttpEnvTest/src/test/scala/org/scalajs/jsenv/selenium/ModuleSupportTest.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.jsenv.selenium 2 | 3 | import org.junit.Assert._ 4 | import org.junit.Test 5 | 6 | class ModuleSupportTest { 7 | @Test def testBasicImport(): Unit = { 8 | assertEquals("Hello scalaJsSelenium!", CamelCase.hello("scala js selenium")) 9 | } 10 | } 11 | --------------------------------------------------------------------------------