Adding a Wasm target to Kotest, I'm observing stra...
# webassembly
o
Adding a Wasm target to Kotest, I'm observing strange differences between Kotlin/JS and Kotlin/WasmJS regarding the test setup. While
jsNodeTest
correctly supplies Mocha test framework functions like
describe
,
wasmJsNodeTest
does not. This reproduces with a simple example setup. Details in 🧵
1
`commonTest/kotlin/Main.kt`:
Copy code
fun main() {
    println("commonTest main invoked")
    describe("describe invocation") { println("inside describe") }
}

private external fun describe(description: String, specDefinitions: () -> Unit)
`build.gradle.kts`:
Copy code
plugins {
    kotlin("multiplatform")
}

kotlin {
    jvmToolchain(11)

    js {
        nodejs()
    }

    wasmJs {
        nodejs()
    }
}

// Use a Node.js version current enough to support Kotlin/Wasm

rootProject.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin> {
    rootProject.extensions.configure<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension> {
        nodeVersion = "22.0.0-v8-canary20231213fc7703246e"
        nodeDownloadBaseUrl = "<https://nodejs.org/download/v8-canary>"
    }
}

rootProject.tasks.withType<org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask>().configureEach {
    args.add("--ignore-engines") // Prevent Yarn from complaining about newer Node.js versions.
}
gradlew cleanAllTests jsNodeTest
produces:
Copy code
> Task :jsNodeTest
commonTest main invoked
inside describe
  0 passing (1ms)
commonTest main invoked
inside describe
BUILD SUCCESSFUL in 1s
gradlew cleanAllTests wasmJsNodeTest
produces:
Copy code
> Task :wasmJsNodeTest FAILED
commonTest main invoked
file:///home/oliver/Repositories/experimental/Kotlin/Issues/kotlin-multiplatform-arena/build/js/packages/test-project-wasm-js-test/kotlin/test-project-wasm-js-test.uninstantiated.mjs:116
        'describe_$external_fun' : (p0, p1) => describe(p0, p1),
                                               ^
ReferenceError: describe is not defined
    at describe_$external_fun (file:///home/oliver/Repositories/experimental/Kotlin/Issues/kotlin-multiplatform-arena/build/js/packages/test-project-wasm-js-test/kotlin/test-project-wasm-js-test.uninstantiated.mjs:116:48)
    at <test-project_test>.describe_$external_fun__externalAdapter (<wasm://wasm/><test-project_test>-001c96d6:wasm-function[2706]:0x27e79)
    at <test-project_test>.main (<wasm://wasm/><test-project_test>-001c96d6:wasm-function[2702]:0x27dfe)
    at <test-project_test>.kotlin.wasm.internal.mainCallsWrapper (<wasm://wasm/><test-project_test>-001c96d6:wasm-function[2709]:0x2800e)
    at <test-project_test>._initialize (<wasm://wasm/><test-project_test>-001c96d6:wasm-function[2711]:0x2803f)
    at instantiate (file:///home/oliver/Repositories/experimental/Kotlin/Issues/kotlin-multiplatform-arena/build/js/packages/test-project-wasm-js-test/kotlin/test-project-wasm-js-test.uninstantiated.mjs:187:21)
    at async file:///home/oliver/Repositories/experimental/Kotlin/Issues/kotlin-multiplatform-arena/build/js/packages/test-project-wasm-js-test/kotlin/test-project-wasm-js-test.mjs:3:17
Node.js v22.0.0-v8-canary20231213fc7703246e
FAILURE: Build failed with an exception.
Why is this? And how does the Kotlin test framework ensure that Mocha functions are available with
wasmJsNodeTest
?
e
Looks like it's using different functions for JS and Wasm? For JS I think it's bridging to mocha here.
o
I'll be looking into the possibility that WasmJs might not be using a Jasmine-like test framework at all. Would be a bit strange, as the KMP Gradle plugin still seems to rely on Karma/Mocha for
wasmJs
targets. Kotlin test always maps external test frameworks to
suite
and
test
functions of
FrameworkAdapter
. For JS/Jasmine-like frameworks like Mocha it is here.
Kotlin test's
wasmJs
Jasmine-like adapter is here. It resides in a separate module, as
wasmWasi
does not provide one (it does not rely on a JS runtime to host it).
@Svyatoslav Kuzmich [JB] any idea on how to enable Mocha access?
s
I see that gradle plugin is hardcoded to not run mocha in wasm. There is one extension point I see: we can add arguments to the node command.
Copy code
nodejs {
  testTask {
     nodeJsArgs.add("arg")
  }
}
After a quick look, I can’t figure it out how to make a workaround for your case other than adding custom Gradle tasks to run mocha, bypassing the built-in testing infra. Could you report an issue, please?
o
Yes, I'll do. Since
wasmJs
tests have a Jasmine-like adapter (for Mocha) and these work with
kotlin-test
, I was wondering what the difference could be. Maybe that adapter is not used after all. Thanks for looking into it!
s
The adapter is used in browser karma tests. I’m guessing node support wasn’t included due to lack of use cases, but there could be other reasons.
Does Kotest need/want to use mocha, or could there be some other abstractions we could instead provide in
kotlin-test
?
o
No, Kotest does not care and provides multiple adapters for other targets (like TeamCity, which seems to be used when running
wasmJsNodeTest
from IntelliJ IDEA with
kotlin-test
). I could probably enable this easily for Kotest/Wasm. The Kotest test structure would play well with what
kotlin-test
thinks (suites and tests). If we could access one test adapter abstraction like
FrameworkAdapter
that would be even easier since we could get rid of duplicating adapters which are already present in the Kotlin code base. I'll investigate a bit more and file an issue.
👍 1
Made
wasmJsNodeTest
work via the existing Kotest TeamCity adapter. 🎉 Now exploring 30 second timeout with
wasmJsBrowserTest
("Test eventy were not received").
Made
wasmJsBrowser
tests work in Kotest via commit 8b8fb94. The 30-second timeout was caused by the KGP-generated JS launcher calling
startUnitTests()
in addition to
main()
.
startUnitTests()
was not there, of course, and this made Karma hang and timeout. The rest was learning-by-doing about K/Wasm-JS interop. I had to be way more strict with types. Mirroring K/JS and treating a function as
JsAny
(like
dynamic
in K/JS) did not work. Problematic was that these errors did only surface in the (headless) browser started by Karma. Normally, if tests fail, their output is transferred back to the invocation side. In this case, where the test engine fails, there is nothing logged visibly and even if a regular (not headless) browser is used, it gets closed immediately. The error with the missing
startUnitTests()
actually helped in this case as the Karma server was at least available for 30 seconds and errors could be inspected via http://localhost:9876/ opened in a regular browser (in addition to the one started by KGP/Karma). For future relief: Is there a more convenient way to keep a KGP/Karma-started browser open for inspection or make KGP/Karma wait for http://localhost:9876/ access?