Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to Selenium 4, add Scala 3 #131

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
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
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,27 @@ jobs:
strategy:
fail-fast: false
matrix:
scalaversion: ["2.11.12", "2.12.10", "2.13.1"]
scalaversion: ["2.12.20", "2.13.15"]
browser: ["chrome"]
include:
- scalaversion: "2.12.10"
- scalaversion: "2.12.20"
browser: "firefox"
env:
SJS_TEST_BROWSER: ${{ matrix.browser }}
steps:
- 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: >
sbt "++${{ matrix.scalaversion }}"
seleniumJSEnv/scalastyle
seleniumJSEnv/test:scalastyle
seleniumJSEnv/Test/scalastyle
seleniumJSEnvTest/scalastyle
seleniumJSHttpEnvTest/test:scalastyle
seleniumJSEnvTest/test:scalastyle
seleniumJSHttpEnvTest/Test/scalastyle
seleniumJSEnvTest/Test/scalastyle
- name: MiMa
run: sbt "++${{ matrix.scalaversion }}" seleniumJSEnv/mimaReportBinaryIssues
- name: Unit tests
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/
23 changes: 10 additions & 13 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@ 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()

val commonSettings: Seq[Setting[_]] = Seq(
version := "1.1.2-SNAPSHOT",
version := "2.0.0-SNAPSHOT",
organization := "org.scala-js",
scalaVersion := "2.11.12",
crossScalaVersions := Seq("2.11.12", "2.12.10", "2.13.1"),
scalaVersion := "2.12.20",
crossScalaVersions := Seq("2.12.20", "2.13.15"),
scalacOptions ++= Seq("-deprecation", "-feature", "-Xfatal-warnings"),

homepage := Some(url("http://scala-js.org/")),
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 @@ -117,7 +114,7 @@ lazy val seleniumJSEnv: Project = project.
pomIncludeRepository := { _ => false },

// The chrome driver seems to not deal with parallelism very well (#47).
parallelExecution in Test := false
(Test / parallelExecution) := false
)

lazy val seleniumJSEnvTest: Project = project.
Expand All @@ -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
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.2.8
sbt.version=1.10.2
12 changes: 6 additions & 6 deletions project/build.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.2.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.17.0")

addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.18")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.4")

addSbtPlugin("org.scalastyle" % "scalastyle-sbt-plugin" % "1.0.0")

Expand All @@ -9,14 +9,14 @@ 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" +: _)

unmanagedSourceDirectories in Compile ++= {
Compile / unmanagedSourceDirectories ++= {
val root = baseDirectory.value.getParentFile
Seq(root / "seleniumJSEnv/src/main/scala")
}

sources in Compile += {
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))
)
Loading