Open KotlinIsland opened 1 year ago
Can it be achieved without providing full-fledged Kotlin API?
Can it be achieved without providing full-fledged Kotlin API?
Yes, because Kotlin is forward and backward compatible with Java, you can include only a set of Kotlin modules within a Java project.
Although, because Kotlin is designed to be so compatible, any Kotlin API can be consumed by Java/Scala/Groovy code, eg:
Here for demonstration purposes we use Kotlin features such as declaration site variance, inheritance via delegation, and extension functions. Then this code is easily interfaced with from Java without any friction at all.
// utils.kotlin
// unmodifiable collection types, null safe types, declaration site variance, inheritance via delegation
class Foo<T>(val data: MutableList<out T>): List<T> by data
// extension function
fun Locator.doSomething(value: String) { }
// Bar.java
public class Bar {
List<Integer> myList;
Locator myLocator;
void something() {
Foo<Integer> foo = new Foo<Integer>(myList);
System.out.println(foo.get(1));
doSomething(myLocator, "hello");
}
}
Here for demonstration purposes we use Kotlin features such as declaration site variance, inheritance via delegation, and extension functions. Then this code is easily interfaced with from Java without any friction at all.
This code has to be compiled with Kotlin going forward and trying to feed it into javac will fail, which essentially means switching Playwright to Kotlin or am I missing something?
I wonder if it wouldn't be better to publish a bunch of Kotlin extension functions as a separate module. Where I would see most benefit by providing options as extension function literals, e.g.
instead of
playwright.chromium().launch(BrowserType.LaunchOptions().setHeadless(false).setSlowMo(500.0))
you could write
playwright.chromium().launch {
headless = false
slowMo = 500.0
}
I guess with a little bit of classpath scanning and something like KotlinPoet you may even be able to generate such extensions functions from the existing API.
This code has to be compiled with Kotlin going forward and trying to feed it into javac will fail, which essentially means switching Playwright to Kotlin or am I missing something?
Well, in a gradle project, you can take an existing java project, remove the java
plugin and add the kotlin
plugin, and build
would generate the same jvm outputs.
I guess with a little bit of classpath scanning and something like KotlinPoet you may even be able to generate such extensions functions from the existing API.
Something like KotlinPoet would be nicest, but here's a "typed builder" way to get most of what it sounds like you are looking for. This uses is a new function "options()" which works for any playwright Option argument:
val browser = playwright.chromium().launch(options {
// note that command completion of option variables works here
headless = false
args = listOf("--allow-file-access-from-files")
})
inline fun <reified T> options(noinline init: T.() -> Unit): T? {
if (init == NOOP) {
return null
}
val options = T::class.java.getDeclaredConstructor().newInstance()
options.init()
return options
}
object NOOP : (Any) -> Unit {
override fun invoke(p1: Any) {
throw RuntimeException("This line should never be reached under normal use")
}
}
I use the NOOP for creating extension functions with an optional argument for the "Options" object builder) like the following (though there might be a cleaner way to deal with the no-op situation):
fun Locator.shouldBeVisible(init: IsVisibleOptions.() -> Unit = NOOP) = assertThat(this).isVisible(options(init))
//Usage:
@Test
fun `show shouldBeVisible example`() {
//given
val page = ...
//when
page.navigate("https://www.google.com")
//then
// (with no options passed in to shouldBeVisible() )
page.getByRole(BUTTON, options { name = "I'm Feeling Lucky" }).shouldBeVisible()
// or (with options passed in to shouldBeVisible() )
page.getByRole(BUTTON, options { name = "I'm Feeling Lucky" }).shouldBeVisible { timeout = 2000.0 }
// or (introducing another extension function, which would likely live in some supporting file)
fun Page.getButtonByName(buttonName: String) = getByRole(BUTTON, options { name = buttonName })
page.getButtonByName("I'm Feeling Lucky").shouldBeVisible()
}
Kotlin is a very popular JVM language, and playwright could expose some Kotlin extensions that augment the existing API.
A small example would be
infix
/operator
functions foror
andand
:Because Kotlin is backwards compatible with Java, it's a possibility that this entire library could be converted to Kotlin, and there would be no disruption to end users using Java (or Scala, or Groovy etc).