Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- uses: actions/checkout@v2
- uses: olafurpg/setup-scala@v10
with:
java-version: "adopt@1.8"
java-version: "adopt@1.11"
- uses: coursier/cache-action@v5
- name: Scalastyle
run: >
Expand Down
56 changes: 39 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,30 @@
## Usage
Simply add the following line to your `project/plugins.sbt` (note that this line must be placed before `addSbtPlugin("org.scala-js" % "sbt-scalajs" % <scalajs-version>)`; otherwise you may get errors such as `java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkState` when you run tests):
```scala
// For Scala.js 0.6.x
libraryDependencies += "org.scala-js" %% "scalajs-env-selenium" % "0.3.0"

// For Scala.js 1.x
libraryDependencies += "org.scala-js" %% "scalajs-env-selenium" % "1.1.1"
libraryDependencies += "org.scala-js" %% "scalajs-env-selenium" % "2.0.0"
```
and the following line to your sbt settings:
```scala
// Apply to the 'run' command
jsEnv := new org.scalajs.jsenv.selenium.SeleniumJSEnv(capabilities)
jsEnv := new org.scalajs.jsenv.selenium.SeleniumJSEnv(driverFactory)

// Apply to tests
jsEnv in Test := new org.scalajs.jsenv.selenium.SeleniumJSEnv(capabilities)
(Test / jsEnv) := new org.scalajs.jsenv.selenium.SeleniumJSEnv(driverFactory)
```
where `driverFactory` is an implementation of the `org.scalajs.jsenv.selenium.DriverFactory` function type:

```scala
type DriverFactory = () => WebDriver
```
where `capabilities` is one of the members of
[`org.openqa.selenium.remote.DesiredCapabilities`](
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/remote/DesiredCapabilities.html).

For example for Firefox:

```scala
jsEnv := new org.scalajs.jsenv.selenium.SeleniumJSEnv(
new org.openqa.selenium.firefox.FirefoxOptions())
() => new org.openqa.selenium.firefox.FirefoxDriver())
```

You are responsible for installing the [drivers](
http://docs.seleniumhq.org/download/#thirdPartyDrivers) needed for the browsers
you request.
Selenium will download the appropriate binaries for you based on the driver your factory creates.

When executing the program with `run` a new browser window will be created,
the code will be executed in it and finally the browser will close itself.
Expand All @@ -44,7 +40,7 @@ If you wish to keep the browser window opened after the execution has terminated
add the option `withKeepAlive` on the environment:

``` scala
new SeleniumJSEnv(capabilities, SeleniumJSEnv.Config().withKeepAlive(true))
new SeleniumJSEnv(driverFactory, SeleniumJSEnv.Config().withKeepAlive(true))
```

It is recommend to use this with a `run` and not `test` because the latter tends
Expand All @@ -53,14 +49,38 @@ to leave too many browser windows open.
#### Debugging tests on a single window
By default tests are executed in their own window for parallelism.
When debugging tests with `withKeepAlive` it is possible to disable this option
using the `sbt` setting `parallelExecution in Test := false`.
using the `sbt` setting `(Test / parallelExecution) := false`.

### Headless Usage
It is often desirable to run Selenium headlessly.
This could be to run tests on a server without graphics, or to just prevent browser
windows popping up when running locally.

##### xvfb
#### Driver options
**Note:** the availability and behavior of this configuration depends on the driver you choose. See the [noteworthy post from Selenium developers themselves][selenium-headless-going-away].

When creating a driver in your `DriverFactory`, you can provide additional options to specify behavior
such as headless mode.

For example, when using Firefox:

```scala
new FirefoxDriver(
new FirefoxOptions()
.addArguments("--headless")
)
```

For Chrome:

```scala
new ChromeDriver(
new ChromeOptions()
.addArguments("--headless")
)
```

#### xvfb
A common approach on Linux and Mac OSX, is to use `xvfb`, "X Virtual FrameBuffer".
It starts an X server headlessly, without the need for a graphics driver.

Expand All @@ -82,3 +102,5 @@ long as there isn't another X server running that's already associated with it.
## Contributing

Follow the [contributing guide](./CONTRIBUTING.md).

[selenium-headless-going-away]: https://www.selenium.dev/blog/2023/headless-is-going-away/
15 changes: 6 additions & 9 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import org.scalajs.sbtplugin.ScalaJSCrossVersion
import org.openqa.selenium.Capabilities

import org.scalajs.jsenv.selenium.SeleniumJSEnv
import org.scalajs.jsenv.selenium.TestCapabilities
import org.scalajs.jsenv.selenium.TestDrivers

val previousVersion: Option[String] = Some("1.1.1")
// we're breaking bincompat with the bump to selenium 4
val previousVersion: Option[String] = None

val newScalaBinaryVersionsInThisRelease: Set[String] =
Set()
Expand Down Expand Up @@ -55,12 +56,8 @@ val previousArtifactSetting = Def.settings(
}
)

val jsEnvCapabilities = settingKey[org.openqa.selenium.Capabilities](
"Capabilities of the SeleniumJSEnv")

val testSettings: Seq[Setting[_]] = commonSettings ++ Seq(
jsEnvCapabilities := TestCapabilities.fromEnv,
jsEnv := new SeleniumJSEnv(jsEnvCapabilities.value),
jsEnv := new SeleniumJSEnv(TestDrivers.fromEnv),
scalaJSUseMainModuleInitializer := true
)

Expand All @@ -77,7 +74,7 @@ lazy val seleniumJSEnv: Project = project.
* It pulls in "closure-compiler-java-6" which in turn bundles some old
* guava stuff which in turn makes selenium fail.
*/
"org.seleniumhq.selenium" % "selenium-server" % "3.141.59",
"org.seleniumhq.selenium" % "selenium-java" % "4.25.0",
"org.scala-js" %% "scalajs-js-envs" % "1.1.1",
"com.google.jimfs" % "jimfs" % "1.1",
"org.scala-js" %% "scalajs-js-envs-test-kit" % "1.1.1" % "test",
Expand Down Expand Up @@ -132,7 +129,7 @@ lazy val seleniumJSHttpEnvTest: Project = project.
settings(
jsEnv := {
new SeleniumJSEnv(
jsEnvCapabilities.value,
TestDrivers.fromEnv,
SeleniumJSEnv.Config()
.withMaterializeInServer("tmp", "http://localhost:8080/tmp/")
)
Expand Down
4 changes: 2 additions & 2 deletions project/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ addSbtPlugin("org.scalastyle" % "scalastyle-sbt-plugin" % "1.0.0")
* guava stuff which in turn makes selenium fail.
*/
libraryDependencies ~=
("org.seleniumhq.selenium" % "selenium-server" % "3.141.59" +: _)
("org.seleniumhq.selenium" % "selenium-java" % "4.25.0" +: _)

Compile / unmanagedSourceDirectories ++= {
val root = baseDirectory.value.getParentFile
Expand All @@ -18,5 +18,5 @@ Compile / unmanagedSourceDirectories ++= {

Compile / sources += {
val root = baseDirectory.value.getParentFile
root / "seleniumJSEnv/src/test/scala/org/scalajs/jsenv/selenium/TestCapabilities.scala"
root / "seleniumJSEnv/src/test/scala/org/scalajs/jsenv/selenium/TestDrivers.scala"
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
package org.scalajs.jsenv.selenium

import org.openqa.selenium._
import org.openqa.selenium.remote.DesiredCapabilities
import org.openqa.selenium.remote.server._

import org.scalajs.jsenv._

import java.net.URL
import java.nio.file.{Path, Paths}
import org.openqa.selenium.Capabilities
import org.openqa.selenium.remote.DesiredCapabilities
import org.openqa.selenium.WebDriver
import org.openqa.selenium.JavascriptExecutor
import org.openqa.selenium.Platform
import SeleniumJSEnv.DriverFactory

final class SeleniumJSEnv(capabilities: Capabilities, config: SeleniumJSEnv.Config) extends JSEnv {
def this(capabilities: Capabilities) =
this(capabilities, SeleniumJSEnv.Config())

private val augmentedCapabilities = {
val x = new DesiredCapabilities(capabilities)
x.setJavascriptEnabled(true)
x
}
final class SeleniumJSEnv(driverFactory: DriverFactory, config: SeleniumJSEnv.Config) extends JSEnv {

val name: String = s"SeleniumJSEnv ($capabilities)"
val name: String = s"SeleniumJSEnv ($config)"

def start(input: Seq[Input], runConfig: RunConfig): JSRun =
SeleniumRun.start(newDriver _, input, config, runConfig)
Expand All @@ -29,7 +24,7 @@ final class SeleniumJSEnv(capabilities: Capabilities, config: SeleniumJSEnv.Conf

private def newDriver() = {
val driver: WebDriver =
config.driverFactory.newInstance(augmentedCapabilities)
driverFactory()

/* The first `asInstanceOf`s are a fail-fast for the second one, which
* scalac partially erases, so that we're sure right now that the last
Expand All @@ -43,20 +38,22 @@ final class SeleniumJSEnv(capabilities: Capabilities, config: SeleniumJSEnv.Conf

driver.asInstanceOf[WebDriver with JavascriptExecutor]
}

def this(driverFactory: DriverFactory) = this(driverFactory, SeleniumJSEnv.Config())
}

object SeleniumJSEnv {
type DriverFactory = () => WebDriver

final class Config private (
val driverFactory: DriverFactory,
val keepAlive: Boolean,
val materialization: Config.Materialization
) {
import Config.Materialization

private def this() = this(
keepAlive = false,
materialization = Config.Materialization.Temp,
driverFactory = new DefaultDriverFactory(Platform.getCurrent()))
materialization = Config.Materialization.Temp)

/** Materializes purely virtual files into a temp directory.
*
Expand Down Expand Up @@ -113,13 +110,9 @@ object SeleniumJSEnv {
def withKeepAlive(keepAlive: Boolean): Config =
copy(keepAlive = keepAlive)

def withDriverFactory(driverFactory: DriverFactory): Config =
copy(driverFactory = driverFactory)

private def copy(keepAlive: Boolean = keepAlive,
materialization: Config.Materialization = materialization,
driverFactory: DriverFactory = driverFactory) = {
new Config(driverFactory, keepAlive, materialization)
materialization: Config.Materialization = materialization) = {
new Config(keepAlive, materialization)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import java.net.URL

import org.openqa.selenium._
import org.openqa.selenium.remote.DesiredCapabilities
import org.openqa.selenium.remote.server._

import org.junit._
import org.junit.Assert._

import org.scalajs.jsenv._
import org.scalajs.jsenv.selenium.SeleniumJSEnv.DriverFactory

class KeepAliveTest {
private final class MockWebDriver extends WebDriver with JavascriptExecutor {
Expand Down Expand Up @@ -56,23 +56,19 @@ class KeepAliveTest {
private final class MockInjector(driver: WebDriver) extends DriverFactory {
var used = false

def newInstance(caps: Capabilities): WebDriver = {
def apply(): WebDriver = {
require(!used)
used = true
driver
}

def hasMappingFor(caps: Capabilities): Boolean = true
def registerDriverProvider(p: DriverProvider): Unit = ???
}

private def setup(keepAlive: Boolean) = {
val driver = new MockWebDriver
val factory = new MockInjector(driver)
val config = SeleniumJSEnv.Config()
.withDriverFactory(factory)
.withKeepAlive(keepAlive)
val env = new SeleniumJSEnv(new DesiredCapabilities, config)
val env = new SeleniumJSEnv(factory, config)

(driver, factory, env)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import org.junit.runner.Runner
import org.junit.runners.Suite
import org.junit.runner.manipulation.Filter
import org.junit.runner.Description
import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.firefox.FirefoxOptions

@RunWith(classOf[JSEnvSuiteRunner])
class SeleniumJSSuite extends JSEnvSuite(
JSEnvSuiteConfig(new SeleniumJSEnv(TestCapabilities.fromEnv))
JSEnvSuiteConfig(new SeleniumJSEnv(TestDrivers.fromEnv))
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,28 @@ import org.openqa.selenium.firefox.{FirefoxOptions, FirefoxDriverLogLevel}
import org.openqa.selenium.chrome.ChromeOptions

import java.util.logging.{Logger, Level}
import org.openqa.selenium.WebDriver
import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.chrome.ChromeDriver
import org.scalajs.jsenv.selenium.SeleniumJSEnv.DriverFactory

object TestCapabilities {
object TestDrivers {
// Lower the logging level for Selenium to avoid spam.
Logger.getLogger("org.openqa.selenium").setLevel(Level.WARNING)

def fromEnv: Capabilities = nameFromEnv match {
val fromEnv: DriverFactory = () => nameFromEnv match {
case "firefox" =>
new FirefoxOptions()
.setHeadless(true)
.setLogLevel(FirefoxDriverLogLevel.ERROR)
new FirefoxDriver(
new FirefoxOptions()
.addArguments("--headless")
.setLogLevel(FirefoxDriverLogLevel.ERROR)
)

case "chrome" =>
new ChromeOptions()
.setHeadless(true)
new ChromeDriver(
new ChromeOptions()
.addArguments("--headless")
)

case name =>
throw new IllegalArgumentException(s"Unknown browser $name")
Expand Down