Skip to content

Commit

Permalink
Add integration test for electron (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
ptrdom authored Jan 25, 2024
1 parent 6a83986 commit 1ef7558
Show file tree
Hide file tree
Showing 18 changed files with 197 additions and 68 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ jobs:
- name: Setup pnpm
run: corepack prepare [email protected] --activate
- name: Run tests
run: sbt scalafmtSbtCheck scalafmtCheckAll test scriptedSequentialPerModule
uses: coactions/setup-xvfb@v1
with:
run: sbt scalafmtSbtCheck scalafmtCheckAll test scriptedSequentialPerModule
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"electron": "28.2.0",
"electron-builder": "24.9.1",
"vite-plugin-electron": "0.15.6",
"vite": "5.0.12"
"vite": "5.0.12",
"playwright": "1.41.1"
}
}
Original file line number Diff line number Diff line change
@@ -1,66 +1,104 @@
import org.scalajs.jsenv.nodejs.NodeJSEnv
import org.scalajs.linker.interface.ModuleInitializer
import org.scalajs.sbtplugin.Stage
import scala.sys.process._

enablePlugins(ScalaJSVitePlugin)
lazy val root = (project in file("."))
.aggregate(app, `integration-test`)

scalaVersion := "2.13.8"

scalaJSModuleInitializers := Seq(
ModuleInitializer
.mainMethodWithArgs("example.Main", "main")
.withModuleID("main"),
ModuleInitializer
.mainMethodWithArgs("example.Preload", "main")
.withModuleID("preload"),
ModuleInitializer
.mainMethodWithArgs("example.Renderer", "main")
.withModuleID("renderer")
)

// Suppress meaningless 'multiple main classes detected' warning
Compile / mainClass := None

libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.2.0"
ThisBuild / scalaVersion := "2.13.8"

val viteElectronBuildPackage =
taskKey[Unit]("Generate package directory with electron-builder")
val viteElectronBuildDistributable =
taskKey[Unit]("Package in distributable format with electron-builder")

def viteElectronBuildTask(): Seq[Setting[_]] = {
Seq(Compile, Test)
.flatMap { config =>
inConfig(config)(
Seq(Stage.FastOpt, Stage.FullOpt).flatMap { stage =>
val stageTask = stage match {
case Stage.FastOpt => fastLinkJS
case Stage.FullOpt => fullLinkJS
}
lazy val app = (project in file("app"))
.enablePlugins(ScalaJSVitePlugin)
.settings(
Test / test := {},
// Suppress meaningless 'multiple main classes detected' warning
Compile / mainClass := None,
scalaJSModuleInitializers := Seq(
ModuleInitializer
.mainMethodWithArgs("example.Main", "main")
.withModuleID("main"),
ModuleInitializer
.mainMethodWithArgs("example.Preload", "main")
.withModuleID("preload"),
ModuleInitializer
.mainMethodWithArgs("example.Renderer", "main")
.withModuleID("renderer")
),
libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.2.0",
Seq(Compile, Test)
.flatMap { config =>
inConfig(config)(
Seq(Stage.FastOpt, Stage.FullOpt).flatMap { stage =>
val stageTask = stage match {
case Stage.FastOpt => fastLinkJS
case Stage.FullOpt => fullLinkJS
}

def fn(args: List[String] = Nil) = Def.task {
val log = streams.value.log
def fn(args: List[String] = Nil) = Def.task {
val log = streams.value.log

(stageTask / viteBuild).value
(stageTask / viteBuild).value

val targetDir = (viteInstall / crossTarget).value
val targetDir = (viteInstall / crossTarget).value

val exitValue = Process(
"node" :: "node_modules/electron-builder/cli" :: Nil ::: args,
targetDir
).run(log).exitValue()
if (exitValue != 0) {
sys.error(s"Nonzero exit value: $exitValue")
} else ()
val exitValue = Process(
"node" :: "node_modules/electron-builder/cli" :: Nil ::: args,
targetDir
).run(log).exitValue()
if (exitValue != 0) {
sys.error(s"Nonzero exit value: $exitValue")
} else ()
}

Seq(
stageTask / viteElectronBuildPackage := fn("--dir" :: Nil).value,
stageTask / viteElectronBuildDistributable := fn().value
)
}
)
}
)

lazy val `integration-test` = (project in file("integration-test"))
.enablePlugins(ScalaJSPlugin)
.settings(
scalaJSLinkerConfig ~= {
_.withModuleKind(ModuleKind.CommonJSModule)
},
Test / jsEnv := Def.taskDyn {
val stageTask = (app / Compile / scalaJSStage).value match {
case Stage.FastOpt => fastLinkJS
case Stage.FullOpt => fullLinkJS
}

Def.task {
(((app / Compile) / stageTask) / viteBuild).value

val sourcesDirectory =
(((app / Compile) / viteInstall) / crossTarget).value
val mainModuleID = "main"

Seq(
stageTask / viteElectronBuildPackage := fn("--dir" :: Nil).value,
stageTask / viteElectronBuildDistributable := fn().value
)
}
)
}
}
val nodePath = (sourcesDirectory / "node_modules").absolutePath
val mainPath =
(sourcesDirectory / "dist-electron" / s"$mainModuleID.js").absolutePath

viteElectronBuildTask()
new NodeJSEnv(
NodeJSEnv
.Config()
.withEnv(
Map(
"NODE_PATH" -> nodePath,
"MAIN_PATH" -> mainPath
)
)
)
}
}.value,
libraryDependencies += "org.scalatest" %%% "scalatest" % "3.2.15" % "test"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package example

import example.facade.node._
import example.facade.playwright._
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec

import scala.scalajs.concurrent.JSExecutionContext
import scala.scalajs.js
import scala.scalajs.js.Thenable.Implicits._

// https://playwright.dev/docs/api/class-electron rewritten in Scala.js
class PlaywrightSpec extends AsyncWordSpec with Matchers {

implicit override def executionContext =
JSExecutionContext.queue

"Main" should {
"work" in {
for {
electronApp <- Electron
.launch(new LaunchConfig {
override val args =
js.Array(NodeGlobals.process.env.MAIN_PATH.asInstanceOf[String])
})
window <- electronApp.firstWindow()
title <- window.title()
_ = println(s"$title")
_ <- window.screenshot(new ScreenshotConfig {
override val path = "intro.png"
})
_ <- electronApp.close()
} yield succeed
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package example.facade.node

import scala.scalajs.js
import scala.scalajs.js.annotation.JSGlobalScope

@js.native
@JSGlobalScope
object NodeGlobals extends js.Object {
val process: Process = js.native
}

@js.native
trait Process extends js.Object {
val env: js.Dynamic = js.native
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package example.facade.playwright

import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport

@js.native
@JSImport("playwright", "_electron")
object Electron extends js.Object {
def launch(config: LaunchConfig): js.Promise[ElectronApplication] = js.native
}

trait LaunchConfig extends js.Object {
val args: js.Array[String]
}

@js.native
trait ElectronApplication extends js.Object {

def firstWindow(): js.Promise[Window] = js.native

def close(): js.Promise[Unit] = js.native
}

@js.native
trait Window extends js.Object {
def title(): js.Promise[String] = js.native

def screenshot(config: ScreenshotConfig): js.Promise[js.Dynamic]
}

trait ScreenshotConfig extends js.Object {
val path: String
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
$ absent vite/package-lock.json
$ absent app/vite/package-lock.json
> clean
> fastLinkJS/startVite
# TODO html test with selenium and electron-chromedriver
> fastLinkJS/stopVite
$ exists vite/package-lock.json
> set app/Compile/scalaJSStage := org.scalajs.sbtplugin.Stage.FastOpt
> integration-test/test
> app/fastLinkJS/startVite
# TODO figure out how to test if dev mode works
> app/fastLinkJS/stopVite
$ exists app/vite/package-lock.json

$ delete vite/package-lock.json
$ delete app/vite/package-lock.json
> clean
> fullLinkJS/startVite
# TODO html test with selenium and electron-chromedriver
> fullLinkJS/stopVite
$ exists vite/package-lock.json
> set app/Compile/scalaJSStage := org.scalajs.sbtplugin.Stage.FullOpt
> integration-test/test
> app/fullLinkJS/startVite
# TODO figure out how to test if dev mode works
> app/fullLinkJS/stopVite
$ exists app/vite/package-lock.json

$ delete vite/package-lock.json
$ delete app/vite/package-lock.json
> clean
> fastLinkJS/viteElectronBuildPackage
> fastLinkJS/viteElectronBuildDistributable
$ exists vite/package-lock.json
> app/fastLinkJS/viteElectronBuildPackage
> app/fastLinkJS/viteElectronBuildDistributable
$ exists app/vite/package-lock.json

$ delete vite/package-lock.json
$ delete app/vite/package-lock.json
> clean
> fullLinkJS/viteElectronBuildPackage
> fullLinkJS/viteElectronBuildDistributable
$ exists vite/package-lock.json
> app/fullLinkJS/viteElectronBuildPackage
> app/fullLinkJS/viteElectronBuildDistributable
$ exists app/vite/package-lock.json

0 comments on commit 1ef7558

Please sign in to comment.