You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中Serenity自定义Widget遇成员冲突,求正确实现方案

How to Properly Implement a Custom Serenity Widget in Kotlin?

Let's break down your issue and walk through solutions to get your Kotlin-based Serenity widget working smoothly.

First, let's recap your scenario: your Java implementation of a custom Serenity Widget works perfectly, but the Kotlin version throws inheritance conflict errors related to methods from WebElementFacade/WebElementFacadeImpl.

Your Working Java Code

@ImplementedBy(MyWidgetJImpl.class)
public interface MyWidgetJ extends WidgetObject {
    public void aaa();
}
public class MyWidgetJImpl extends WidgetObjectImpl implements MyWidgetJ {
    public MyWidgetJImpl(final PageObject page, final ElementLocator locator, final WebElement webElement, final long timeoutInMilliseconds) {
        super(page, locator, webElement, timeoutInMilliseconds);
    }
    @FindBy(xpath = "//*[@id='button_text_title']")
    private WebElementFacade title;
    @Override
    public void aaa() {
        System.out.println("*********************************" + title.getText());
    }
}

Your Failing Kotlin Code

@ImplementedBy(MyWidgetKImpl::class)
interface MyWidgetK : WidgetObject {
    fun aaa()
}
class MyWidgetKImpl(page: PageObject, locator: ElementLocator, webElement: WebElement, timeOut: Long) : WidgetObjectImpl(page, locator, webElement, timeOut), MyWidgetK {
    @FindBy(xpath = "//*[@id='button_text_title']")
    private lateinit var title: WebElementFacade
    override fun aaa() {
        println("*********************************" + title.text)
    }
}

The Root Cause

Kotlin's type system is stricter than Java's when resolving inherited generic methods. Here's what's happening:

  • WidgetObject extends WebElementFacade, which defines generic methods like waitUntilEnabled(): T
  • WidgetObjectImpl implements these methods with a concrete return type (WebElementFacade)
  • Java automatically bridges this gap, but Kotlin throws a conflict error because it can't reconcile the generic interface signature with the concrete implementation's signature.

Solution 1: Explicitly Override Conflicting Methods

You can resolve the conflict by explicitly overriding the problematic methods in your Kotlin implementation class, matching the concrete return type from WidgetObjectImpl:

@ImplementedBy(MyWidgetKImpl::class)
interface MyWidgetK : WidgetObject {
    fun aaa()
}

class MyWidgetKImpl(
    page: PageObject,
    locator: ElementLocator,
    webElement: WebElement,
    timeoutInMilliseconds: Long
) : WidgetObjectImpl(page, locator, webElement, timeoutInMilliseconds), MyWidgetK {

    @FindBy(xpath = "//*[@id='button_text_title']")
    private lateinit var title: WebElementFacade

    override fun aaa() {
        println("*********************************" + title.text)
    }

    // Resolve conflict by explicitly overriding with concrete return type
    override fun waitUntilEnabled(): WebElementFacade = super.waitUntilEnabled()
    override fun waitUntilClickable(): WebElementFacade = super.waitUntilClickable()
    override fun waitUntilVisible(): WebElementFacade = super.waitUntilVisible()
    override fun waitUntilHidden(): WebElementFacade = super.waitUntilHidden()
    
    // Repeat this pattern for all other conflicting methods listed in your error log
}

This is straightforward but can be tedious if there are many conflicting methods. For a cleaner approach, try Solution 2.

Solution 2: Create a Base Widget Interface with Default Methods

If you plan to build multiple custom widgets, create a base interface that resolves the conflicts once using @JvmDefault (requires Kotlin 1.4+ and enabling JVM defaults in your build):

// Base interface to handle all generic method conflicts
interface BaseWidget : WidgetObject {
    @JvmDefault
    override fun waitUntilEnabled(): WebElementFacade = (this as WidgetObjectImpl).waitUntilEnabled()
    
    @JvmDefault
    override fun waitUntilClickable(): WebElementFacade = (this as WidgetObjectImpl).waitUntilClickable()
    
    @JvmDefault
    override fun waitUntilVisible(): WebElementFacade = (this as WidgetObjectImpl).waitUntilVisible()
    
    // Add all other conflicting methods here with default implementations
}

// Your custom widget interface now extends the base interface
@ImplementedBy(MyWidgetKImpl::class)
interface MyWidgetK : BaseWidget {
    fun aaa()
}

class MyWidgetKImpl(
    page: PageObject,
    locator: ElementLocator,
    webElement: WebElement,
    timeoutInMilliseconds: Long
) : WidgetObjectImpl(page, locator, webElement, timeoutInMilliseconds), MyWidgetK {

    @FindBy(xpath = "//*[@id='button_text_title']")
    private lateinit var title: WebElementFacade

    override fun aaa() {
        println("*********************************" + title.text)
    }
}

Don't forget to enable JVM defaults in your build configuration:

// build.gradle.kts
tasks.withType<KotlinCompile> {
    kotlinOptions {
        jvmTarget = "1.8" // Match your project's Java version
        freeCompilerArgs += "-Xjvm-default=all"
    }
}

Solution 3: Alternative - Use Extension Functions Instead

If widgets feel overly complex, you can encapsulate custom functionality using Kotlin extension functions, avoiding inheritance issues entirely:

// Extension function to add custom behavior to WebElementFacade
fun WebElementFacade.performCustomAction() {
    println("*********************************" + this.text)
}

// Use it in your Page Object
class MyPage : PageObject() {
    @FindBy(xpath = "//*[@id='button_text_title']")
    private lateinit var myElement: WebElementFacade

    fun executeCustomLogic() {
        myElement.performCustomAction()
    }
}

This approach keeps your code simple while still letting you reuse custom logic across elements.


Quick Notes

  • Ensure your Kotlin version is compatible with your Serenity dependencies (check Serenity's docs for supported versions).
  • When using lateinit with @FindBy, Serenity should handle element injection automatically—just make sure you don't access the element before the page/widget is initialized.

内容的提问来源于stack exchange,提问作者matthew6870

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 09:20:00