Selenium Tests

Good & Evil

🖊️ Work in progress

Topics

  • Verständlichkeit der Tests
    • Testablauf
    • Im Fehlerfall
  • Test Aufbau
    • Hands On
  • Good to Know

Verständlichkeit

Des Tests

Testschritte benennen

resellerApplication.action().createNewCartInPortal("New Cart");

CartRow newCart = portal.getCartTable().getCartByName("New Cart");
newCart.action().addProduct(appleiPadAir);
newCart.action().shareFirstLineItem();
newCart.action().addNegotiationPartner(distributorUser.getUserName());

SearchPage<?> searchPage = openPage().globalSearchNegotiationPage();
SellerResponsePage<?> sellerResponsePage = 
    searchPage.openFirstSellerResponseFromCompany(getCompany());
sellerResponsePage.getFirstRow().setPrice("1.00");

CartColumnTableRow supplierCard = 
    newCart.getCartColumn().getCartTable().getFirstSupplierCard();
supplierCard = selectNegotiationPosition(1, newCart, supplierCard);

priceRequestTest()

Testschritte benennen

// #1 (Buyer) create new cart and open it
resellerApplication.action().createNewCartInPortal("New Cart");

// #2 (Buyer) add Product and start negotiation with distri Also
CartRow newCart = portal.getCartTable().getCartByName("New Cart");
newCart.action().addProduct(appleiPadAir);
newCart.action().shareFirstLineItem();
newCart.action().addNegotiationPartner(distributorUser.getUserName());

// #3 (distri) set a price
SearchPage<?> searchPage = openPage().globalSearchNegotiationPage();
SellerResponsePage<?> sellerResponsePage = 
    searchPage.openFirstSellerResponseFromCompany(getCompany());
sellerResponsePage.getFirstRow().setPrice("1.00");

// #4 Select negotiation price
CartColumnTableRow supplierCard = 
    newCart.getCartColumn().getCartTable().getFirstSupplierCard();
supplierCard = selectNegotiationPosition(1, newCart, supplierCard);

priceRequestTest()

Componenten statt statische IDs

waitForElementVisibile(getDriver(), By.id(RedgiantNotificationWindow.ID));

Componenten statt statische IDs

waitForElementVisibile(getDriver(), By.id(RedgiantNotificationWindow.ID));

// vs

assertThat(window, is(visible));

Componenten statt getWebelement

window.getWebElement().findElement(
        By.id("-RedgiantNotificationWindow-TextField")).clear();

showErrorTest()

Componenten statt getWebelement

window.getWebElement().findElement(
        By.id("-RedgiantNotificationWindow-TextField")).clear();

// vs

notificationWindow().getWarningTextField().clear();

showErrorTest()

Componenten statt getWebelement

actions().dragAndDrop(
    card.getDraggableComponent().getWebElement(), 
    ((MultiCartPortalDealColumn) sendDealColumn).getWebElement()).
    build().perform();

dragDropCartCardTest()

Componenten statt getWebelement

actions().dragAndDrop(
    card.getDraggableComponent().getWebElement(), 
    ((MultiCartPortalDealColumn) sendDealColumn).getWebElement()).
    build().perform();

// vs

// HasDragHandler  // HasDropHandler
card.dragAndDropTo(sendDealColumn);

dragDropCartCardTest()

Konstruktion von Primitive Komponenten

assertThat(new Label(getDriver(), By.id(ID_ERROR_LABEL)).getText(), 
    equalToIgnoringCase(CHECK_ERROR_LABEL_PERMISSION));

showErrorTest()

Konstruktion von Primitive Komponenten

assertThat(new Label(getDriver(), By.id(ID_ERROR_LABEL)).getText(), 
    equalToIgnoringCase(CHECK_ERROR_LABEL_PERMISSION));

// vs

assertThat(errorLabel, hasText("not enough acces rights"));

showErrorTest()

Adressierung per ID

dialog.getSalutation().selectComboItem(2);
dialog.getContactTitle().selectComboItem(5);

showErrorTest()

Adressierung per ID

dialog.getSalutation().selectComboItem(2);
dialog.getContactTitle().selectComboItem(5);

//vs

dialog.getSalutation().selectComboItem("Mrs");
dialog.getContactTitle().selectComboItem("Prof.");

showErrorTest()

Adressierung per ID

dialog.getSalutation().selectComboItem(2);
dialog.getContactTitle().selectComboItem(5);

//vs

dialog.getSalutation().selectComboItem("Mrs");
dialog.getContactTitle().selectComboItem("Prof.");

//vs

dialog.getSalutation().selectMr();
dialog.getContactTitle().selectDr();

showErrorTest()

Produkt abhängiges in TestProduct kapseln

DLinkDes1005DSwitch.getMarketingText()

productPreviewTest()

Component basierte Matcher verwenden

assertThat(quotePage.getLineItemTable().size(), is(1));

// vs

assertThat(quotePage.getLineItemTable(), hasSize(1));

quotePageTest()

Component basierte Matcher verwenden

assertThat(quotePage.getLineItemTable().size(), is(1));

// vs

assertThat(quotePage.getLineItemTable(), hasSize(1));

quotePageTest()

  • containsText("text")
  • hasCaption("text")
  • hasDimension(100, 200);
  • hasError()
  • hasPartnerStatusTag()
  • hasSize(200)
  • hasTag("tag")
  • hasText("text")
  • hasTrackingEvents()
  • isActive()
  • isCollapsed()
  • isEmpty()
  • isEnabled()
  • isFolded()
  • isInFocus()
  • isMarked()
  • isNotInFocus()
  • isNotVisible()
  • isSelected()
  • isVisible()

Component basierte Matcher verwenden

assertThat(contactPage.getLineItemTable().visible(), is(true));

// vs

assertThat(contactPage.getLineItemTable(), isVisible(1));

contactPageTest()

Component basierte Matcher verwenden

assertThat(contactPage.getLineItemTable().visible(), is(true));

// vs

assertThat(contactPage.getLineItemTable(), is(visible()));

contactPageTest()

Expected true but was false.
Expected component is visible but component (id:Application.Seach.Button) was not visible.

Component basierte Matcher verwenden

assertThat(sendButton, is(not(visible())));

//  not the same !

assertThat(sendButton, is(notVisible())));

sendMailTest()

⚠️

Keine langen Imports

de.itscope.redgiant.testbench.element.application.frontend.
                LabsFrontendApplication.loginWithCookieAndOpenPage(
                    getDriver(), email,	password, getTestCaseName());

loginWithCookiesTest()

Keine langen Imports

de.itscope.redgiant.testbench.element.application.frontend.
                LabsFrontendApplication.loginWithCookieAndOpenPage(
                    getDriver(), email,	password, getTestCaseName());

\\ vs

loginWithCookieAndOpenPage(getDriver(), email, password, getTestCaseName());

loginWithCookiesTest()

Keine lange Selektoren

MenuItem printPDFMenuItem = cart.getSendDealColumn()
	.getDealTable()
	.getFirstRow()
	.getHeader()
	.getRowActions()
	.getMenuItem("Download")
	.openSubMenu()
	.getMenuItem("Download PDF");

printPdfTest()

Keine lange Selektoren

MenuItem printPDFMenuItem = cart.getSendDealColumn()
	.getDealTable()
	.getFirstRow()
	.getHeader()
	.getRowActions()
	.getMenuItem("Download")
	.openSubMenu()
	.getMenuItem("Download PDF");

\\ vs

MenuItem printPDFMenuItem = firstDealcard.getDownloadPDFMenu();

printPdfTest()

Keine uncaught Exceptions

return getRows().get(2);

customerTableTest()

⚡ NullpointerException

⚡ IndexOutOfBoundsException

Test Aufbau

Gerneric Typen verwenden

public class QuoteGuestPageTable extends CustomLayoutTable {

quoteGuestPageTableTest()

Gerneric Typen verwenden

public class QuoteGuestPageTable extends CustomLayoutTable {

// vs

public class QuoteGuestPageTable extends 
    CustomLayoutTable<QuoteGuestPageTable.QuoteGuestPageRow> {

quoteGuestPageTableTest()

Konkrete Widgets nutzen

personTabShee.getTab("Alle")

createCustomContactTest()

Konkrete Widgets nutzen

personTabShee.getTab("Alle")

\\ vs

personTabShee.getTabAll();

createCustomContactTest()

Konkrete Widgets nutzen

personTabShee.getTab("Alle")

\\ vs

personTabShee.getTabAll();

createCustomContactTest()

public static class ContactTabSheet extends TabSheet<TabSheet.Tab> {

	private ContactTabSheet(WebDriver driver, Component parent) { ... }

	public TabSheet.Tab getTabAll() {
		return getTab("ALLE");
	}

}

Keine IDs konkatenieren

static String SUFFIX_EMPLOYEE_SINGLE = "-EmployeeSingleView";
static String SUFFIX_EMPLOYEE_LAYOUT = "_employeeLayout";
static String SUFFIX_HEADING = SUFFIX_EMPLOYEE_LAYOUT + "_headingLayout";
static String SUFFIX_AVATAR = SUFFIX_HEADING + "_image";
static String SUFFIX_CONTACT = SUFFIX_HEADING + "-ContactEditView";
static String SUFFIX_TABSHEET = SUFFIX_EMPLOYEE_LAYOUT + "_verticalTabSheet";
static String SUFFIX_ACTIVITY = SUFFIX_EMPLOYEE_LAYOUT + "_activityIndexBar";
static String SUFFIX_PROFILE = "-ProfileLayout";
static String SUFFIX_SETTINGS = "-SettingsLayout";

EmployeeSingleView

Keine IDs konkatenieren


static class EmployeeLayout extends Component {

  public EmployeeLayout(WebDriver driver, Component parent) {...}

  public Label getActivity() {
    return new Label(getDriver(), By.id(getId() + "_activityIndexBar"));
  }
  
  public HeadingLayout getHeadingLayout() {
    return new HeadingLayout(getDriver(), this);
  }
  
  static class HeadingLayout extends Component {
  
    public Image getAvatar() {
      return new Image(getDriver(), By.id(getId() + "_image"));
    }
}

EmployeeSingleView

Referenzierung über Text gegenüber ID bevorzugen

public ComboBox getDepartment() {
     return new ComboBox(driver, By.id(getId() + "-ComboBox3"));
}

public TextField getEmail() {
     return new TextField(driver, By.id(getId() + "-TextField4"));
}

public TextField getFax() {
     return new TextField(driver, By.id(getId() + "-TextField8"));
}

public TextField getMobile() {
     return new TextField(driver, By.id(getId() + "-TextField7"));
}

public TextField getPhone() {
     return new TextField(driver, By.id(getId() + "-TextField6"));
}

EmployeeSingleView

Referenzierung über Text gegenüber ID bevorzugen

public ComboBox getDepartment() {
     return getFormLayoutRow(ComboBox::new, "Abteilung").getContent();
}

public TextField getEmail() {
     return getFormLayoutRow(TextField::new, "E-Mail").getContent();
}

public TextField getFax() {
     return getFormLayoutRow(TextField::new, "Fax").getContent();
}

public TextField getMobile() {
     return getFormLayoutRow(TextField::new, "Telefon (Mobil)").getContent();
}

public TextField getPhone() {
     return getFormLayoutRow(TextField::new, "Telefon (Büro)").getContent();
}

EmployeeSingleView

Statische Klassen
verwenden

public class QuotePageRow extends CustomLayoutTable.Row {\\...

priceRequestTest()

Statische Klassen
verwenden

public class QuotePageRow extends CustomLayoutTable.Row {\\...

\\ vs 

public staic class QuotePageRow extends CustomLayoutTable.Row {\\...

priceRequestTest()

Asserts im Test, nicht bei getComponent()

public SwitchButton getToogleVisibility() {
        assertThat(getSwitchButton(), is(visible()));
        return getSwitchButton().getText();
}

priceRequestTest()

Stateless Mocks

Lazy Mocks

private Button buttonCancel;

public Button getButtonCancel() {
    if (buttonCancel == null) {
        buttonCancel = new Button(driver, By.id(getId() + "-Button"));
    }
    return buttonCancel;
}

\\ vs

public Button getButtonCancel() {
    return new Button(driver, By.id(getId() + "-Button"));
}

EmailChangeDialog

👨🏼‍💻

Keine Sleeps im Test

ToolTip toolTip = new ToolTip(driver, 
    By.xpath("//div[contains(@class, 'v-tooltip')]"));
try {
	Thread.sleep(1000L);
} catch (InterruptedException ignored) {
}
return toolTip;

priceRequestTest()

Keine Bedingungen im Test

if (datasheetSelection.isSelected()) {
    datasheetSelection.click();
}

showDatasheetTest()

Good To Know

CLT immer mit custom row Style bauen

public static class CartTable extends CustomLayoutTable<CartRow> {	

	@Override
	protected String getRowCssClass() {
		return super.getRowCssClass() + "-cartcolumn";
}

CartTable 

Default Setup mit RedgiantFrontendTest

private LabsFrontendApplication application;

public Employee_ManageCompanyRights() {
	super();
}

@Override
@Before
public void setUp() throws Exception {
	super.initializeDefaultDriver();
	String email = getDriver().getCompany().getUserEmail();
	String password = getDriver().getCompany().getUserPassword();
	application = LabsFrontendApplication.loginWithCookieAndOpenPage(
            getDriver(), email,	password, getTestCaseName());
}
Employee_ManageCompanyRights 

Default Setup mit RedgiantFrontendTest

private LabsFrontendApplication application;

public Employee_ManageCompanyRights() {
	super();
}

@Override
@Before
public void setUp() throws Exception {
	super.initializeDefaultDriver();
	String email = getDriver().getCompany().getUserEmail();
	String password = getDriver().getCompany().getUserPassword();
	application = LabsFrontendApplication.loginWithCookieAndOpenPage(
            getDriver(), email,	password, getTestCaseName());
}
Employee_ManageCompanyRights 
public class Employee_ManageCompanyRights​ extends RedgiantFrontendTest 

vs

URL auf Erreichbarkeit in JUnit testen

@WikiTest(property = "exportsAndAPI.authentication")
@Test
public void testGetWikiPageApiCredentials() throws Exception {
	assertThatUrlIsAReachableWikiTopic(
            links.getExportsAndAPI().getAuthentication(), 
            "Authentifizierung über API Zugangsdaten");
}
WikiLinksAvailabilityTest

URL auf Erreichbarkeit in JUnit testen

@WikiTest(property = "exportsAndAPI.authentication")
@Test
public void testGetWikiPageApiCredentials() throws Exception {
	assertThatUrlIsAReachableWikiTopic(
            links.getExportsAndAPI().getAuthentication(), 
            "Authentifizierung über API Zugangsdaten");
}
WikiLinksAvailabilityTest
@Test
public void ensureAllWikiLinksAreCheckedInTest() {...}
WikiLinksTestCoverageCheck

Emails im Unit Test

@Test
public void testInviteITscope() {
    EmailDef emailDef = new EmailDefMock(EmailTemplate.Invitation.Itscope,
                                         EmailTemplate.Base.FooterLong);
    final TeaserDTO teaserDTO = new TeaserDTOMock(Locale.GERMANY);
    InviteDTO inviteDTO = new InviteDTOMock( teaserDTO);
    inviteDTO.setRegisterLink("https://www.itscope.com/red/register/abc132");
    EmailConfiguration mailConfig = new EmailConfigurationMock(dto, GERMANY);
    String mail = mailConfig.createEmail(emailConfig).getBody();
    assertThat(mail, containsString("über 3 Mio. ITK-Artikeln"));
    assertThat(mail, containsString(
          "<a href=\"https://www.itscope.com/red/register/abc132\" 
              class=button>Jetzt kostenlos registrieren »</a>"));
    assertThatNoEmtpyTemplateVarsIn(mail);
    Approvals.verifyHtml(mail);
}
WikiLinksAvailabilityTest

Emails im Unit Test

public class TeaserDTOMock extends TeaserDTO {
  public TeaserDTOMock(Locale locale) {
    super(locale);

    // Redgiant links
    setProductBoardUrl("https://www.itscope.com/red/app#feedback/");

    // Wiki links
    setUrlSupport("https://support.itscope.com/hc/de/");
    setUrlSupportSearch("https://support.itscope.com/hc/de/articles/206134861");
    setUrlSupportAddSupplier("https://support.itscope.com/hc/de/articles/360000028039");
    setUrlSupportOptimization("https://support.itscope.com/hc/de/articles/206174701");
    setUrlSupportQueryDistributors("https://support.itscope.com/hc/de/articles/360000028039");
    setUrlSupportOnlineQuotes("https://support.itscope.com/hc/de/articles/206033202");
    setUrlSupportOnlineQuotes("https://support.itscope.com/hc/de/articles/206033202");
    setUrlSupportPriceCalculation("https://support.itscope.com/hc/de/articles/206129589");
    setUrlSupportCollections("https://support.itscope.com/hc/de/articles/206031722");
    setUrlSupportAboutYourCompany("https://support.itscope.com/hc/de/articles/209561185");
  }
}
WikiLinksAvailabilityTest

Golden Master Testing
mit Approvals

@Test
public void testInviteITscope() {
      // ...
    String mail = mailConfig.createEmail(emailConfig).getBody();
      // ...    
    Approvals.verifyHtml(mail);
}
WikiLinksAvailabilityTest

Golden Master Testing
mit Approvals

@Test
public void testInviteITscope() {
      // ...
    String mail = mailConfig.createEmail(emailConfig).getBody();
      // ...    
    Approvals.verifyHtml(mail);
}
WikiLinksAvailabilityTest
🗎 InviteMailTest.testActivateUser.approved.html
🗎 InviteMailTest.testActivateUser.received.html

👨🏼‍💻

Componenten in ID-Generierung beeinflussen

@IgnoreInDebugId
public class PersonTableAndBarView extends
   VerticalLayout implements ICombinedPersonTableAndSearchBarView {
   //...
}
PersonTableAndBarView
@DebugId("CustomLayoutTable")
public class RedgiantCustomLayoutTable<Slot extends Enum<?>> 
                                            extends CustomLayoutTable {
  // ...
}
RedgiantCustomLayoutTable

Basis Setup mit RedgiantFrontendTest

@RunWith(DelegatingRunnerFactory.class)
public class RedgiantFrontendTest extends RedgiantTest {

    protected LabsFrontendApplication application;

    @Override
    public void setUp() throws Exception {
        // loginWithCookieAndOpenPage()
    }

}
RedgiantFrontendTest

Basis Setup mit RedgiantFrontendTest

@Override
@Before
public void setUp() throws Exception {
   super.initializeDefaultDriver(
   		TestSetups.defaultCompany(
                    createRandomCompanyName(this)).
                    withTestUserPermission(TestSetup.TestUserPermissionDef.ORDER).
                    build());
   String email = getDriver().getCompany().getUserEmail();
   String password = getDriver().getCompany().getUserPassword();
   application = login(getDriver(), email, password, getTestCaseName());
   searchPage = application.openPage().companySearchPage();
}
Organisation_ContactNavigation

Basis Setup mit RedgiantFrontendTest

@Override
@Before
public void setUp() throws Exception {
   super.initializeDefaultDriver(
   		TestSetups.defaultCompany(
                    createRandomCompanyName(this)).
                    withTestUserPermission(TestSetup.TestUserPermissionDef.ORDER).
                    build());
   String email = getDriver().getCompany().getUserEmail();
   String password = getDriver().getCompany().getUserPassword();
   application = login(getDriver(), email, password, getTestCaseName());
   searchPage = application.openPage().companySearchPage();
}
Organisation_ContactNavigation

Basis Setup mit RedgiantFrontendTest

@Override
@Before
public void setUp() throws Exception {
   super.initializeDefaultDriver(tesSetup().defaultCompany().
                withTestUserPermission(TestUserPermissionDef.ORDER));
   super.setUp();
   searchPage = application.openPage().companySearchPage();
}
Organisation_ContactNavigation

Selenium Tests - Good & Evil

By Tobse Fritz

Selenium Tests - Good & Evil

Was wir an unseren Testworkflow gelern haben

  • 532