Kotlin中Serenity自定义Widget遇成员冲突,求正确实现方案
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:
WidgetObjectextendsWebElementFacade, which defines generic methods likewaitUntilEnabled(): TWidgetObjectImplimplements 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
lateinitwith@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

