vaadin / testbench

Vaadin TestBench is a tool for automated user interface testing of Vaadin applications.
https://vaadin.com/testbench
Other
20 stars 22 forks source link

Unhelpfull exception, NullPointerException: text must not be null #1801

Open HerbertsVaadin opened 3 weeks ago

HerbertsVaadin commented 3 weeks ago

I'm trying to query an extended Vaadin Button by attribute. Now, the extended button is a bit special, but the exception is quite unclear. You'll see in my example myButton3 has no text set, and that is what causes the exception. The button itself is working just fine on frontend.

@Test
void testButtonExists() {
    navigate(RichTextButtonView.class);

    assertThat(findCustomerButtonByTranslation("tasty")).isNotNull();
    assertThat(findCustomerButtonByTranslation("xoxo")).isNotNull();
}

protected CustomerButton findCustomerButtonByTranslation(String translationKey) {
    return $(CustomerButton.class).withAttribute(TRANSLATION_ATTR, translationKey).single();
}

Exception:

java.lang.NullPointerException: text must not be null
    at com.vaadin.testbench.unit.internal.ComponentUtilsKt.getCaption(ComponentUtils.kt:382)
    at com.vaadin.testbench.unit.internal.ComponentUtilsKt.getCaption(ComponentUtils.kt:365)
    at com.vaadin.testbench.unit.internal.PrettyPrintTreeKt.toPrettyString(PrettyPrintTree.kt:102)
    at com.vaadin.testbench.unit.internal.PrettyPrintTree$Companion.ofVaadin(PrettyPrintTree.kt:63)
    at com.vaadin.testbench.unit.internal.PrettyPrintTree$Companion.ofVaadin(PrettyPrintTree.kt:65)
    at com.vaadin.testbench.unit.internal.PrettyPrintTree$Companion.ofVaadin(PrettyPrintTree.kt:65)
    at com.vaadin.testbench.unit.internal.PrettyPrintTree$Companion.ofVaadin(PrettyPrintTree.kt:65)
    at com.vaadin.testbench.unit.internal.PrettyPrintTree$Companion.ofVaadin(PrettyPrintTree.kt:65)
    at com.vaadin.testbench.unit.internal.PrettyPrintTreeKt.toPrettyTree(PrettyPrintTree.kt:72)
    at com.vaadin.testbench.unit.internal.LocatorKt._find(Locator.kt:186)
    at com.vaadin.testbench.unit.internal.LocatorKt._get(Locator.kt:144)
    at com.vaadin.testbench.unit.internal.LocatorKt._get(Locator.kt:167)
    at com.vaadin.testbench.unit.ComponentQuery.find(ComponentQuery.java:596)
    at com.vaadin.testbench.unit.ComponentQuery.single(ComponentQuery.java:468)
    at customer.CustomerTest.findButtonByTranslation(CustomerTest.java:67)
        .......

The extended button:

import com.vaadin.flow.component.button.Button;

public class CustomerButton extends Button {

    private RichTextSpan textContainer;

        @Override
    public String getText() {
        return getTextContainer().getText();
    }

    @Override
    public void setText(String text) {
        getTextContainer().setText(text);
        setAriaLabel(getTextContainer().getText());
    }

    private RichTextSpan getTextContainer() {
        if (textContainer == null) {
            textContainer = new RichTextSpan();
            getElement().appendChild(textContainer.getElement());
        }

        return textContainer;
    }
}

RichTextSpan.java:

import com.vaadin.flow.component.html.Span;

public class RichTextSpan extends Span {

    public RichTextSpan() {
    }

    public RichTextSpan(String unsafeText) {
        setText(unsafeText);
    }

    @Override
    public String getText() {
        return getElement().getProperty("innerHTML");
    }

    @Override
    public void setText(String unsafeText) {
        String safeText = sanitize(unsafeText);
        getElement().setProperty("innerHTML", safeText);
        setWhiteSpace(WhiteSpace.BREAK_SPACES);
    }

    private String sanitize(String unsafeText) {
        // sanitizing logic here
        return unsafeText;
    }
}

The view, myButton3 is the one causing the problem:

package com.example.application.views.random;

import com.example.application.component.CustomerButton;
import com.example.application.views.MainLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.PermitAll;

@PageTitle("Rich text button")
@Route(value = "rich-text-button", layout = MainLayout.class)
@PermitAll
public class RichTextButtonView extends VerticalLayout {

    public static final String TRANSLATION_ATTR = "data-translationKey";

    public RichTextButtonView() {
        var myButton = new CustomerButton();
        myButton.setText("I want it \n<b>tasty</b>");
        myButton.getElement().setAttribute(TRANSLATION_ATTR, "tasty");

        var myButton2 = new CustomerButton();
        myButton2.setText("I want it \n<b>super tasty</b>");
        myButton2.getElement().setAttribute(TRANSLATION_ATTR, "super tasty");

        var myButton3 = new CustomerButton();
        myButton3.getElement().setAttribute(TRANSLATION_ATTR, "no text");

        add(myButton, myButton2, myButton3);
    }
}

Test fails as expected, but doesn't doesn't provide useful message, and doesn't show the view structure because of the exception. Seems like pretty print should be able to handle text null cases. It seems to not to fall apart with not extended Button.

In the specific case, a the exception occurs when a button is not found, but there is a button somewhere with no text. Took me a while to find that button, since it was in a parent layout.

mcollovati commented 3 weeks ago

The HasText.getText() Javadoc states that the method does not return null, so probably PrettyPrinter fails because it assumes it will get a non-null value. Anyway, it makes sense that PrettyPrinter should not fail, even if the component does not fulfill Javadocs requirement. It should instead print the representation and possibly highlight unexpected values.