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

[Feature] Kotlin support / extensions #1293

Open
KotlinIsland opened this issue May 28, 2023 · 6 comments
Open

[Feature] Kotlin support / extensions #1293

KotlinIsland opened this issue May 28, 2023 · 6 comments

Comments

@KotlinIsland
Copy link

KotlinIsland commented May 28, 2023

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 for or and and:

infix fun Locator.or(other: Locator) = or(other)
infix fun Locator.and(other: Locator) = and(other)

// usage like
println(page.locator("div") or page.locator("span"))

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).

@yury-s
Copy link
Member

yury-s commented May 30, 2023

Can it be achieved without providing full-fledged Kotlin API?

@KotlinIsland
Copy link
Author

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");
    }
}

@yury-s
Copy link
Member

yury-s commented Jun 6, 2023

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?

@helpermethod
Copy link

helpermethod commented Aug 14, 2023

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.

@KotlinIsland
Copy link
Author

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.

@twadzins
Copy link

twadzins commented Aug 17, 2023

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:

Usage of "options() builder"

val browser = playwright.chromium().launch(options {
    // note that command completion of option variables works here
    headless = false 
    args = listOf("--allow-file-access-from-files")
})

OptionsBuilder.kt

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()    
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants