Merge pull request #6252 from MashaKashirina/bug6251

bug6251: selenium test impl
This commit is contained in:
Axel Uhl
2026-06-10 15:13:49 +02:00
committed by GitHub
2 changed files with 174 additions and 5 deletions
@@ -1,7 +1,14 @@
package com.sap.sailing.selenium.pages.adminconsole.connectors;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import com.sap.sailing.selenium.core.BySeleniumId;
import com.sap.sailing.selenium.core.FindBy;
@@ -18,23 +25,30 @@ public class SmartphoneTrackingEventManagementPanelPO extends PageArea {
private static final String ACTION_COMPETITOR_REGISTRATIONS = "ACTION_COMPETITOR_REGISTRATIONS";
private static final String ACTION_MAP_DEVICES = "ACTION_MAP_DEVICES";
private static final String ACTION_DENOTE_FOR_RACELOG_TRACKING = "ACTION_DENOTE_FOR_RACELOG_TRACKING";
private static final String ACTION_START_TRACKING = "ACTION_START_TRACKING";
private static final String ACTION_STOP_TRACKING = "ACTION_STOP_TRACKING";
private static final String ACTION_REMOVE_DENOTATION = "ACTION_REMOVE_DENOTATION";
@FindBy(how = BySeleniumId.class, using = "AvailableLeaderboardsTable")
private WebElement leaderboardTable;
@FindBy(how = BySeleniumId.class, using = "RaceColumnTable")
private WebElement raceColumnTableWrapper;
@FindBy(how = BySeleniumId.class, using = "TrackedRacesListComposite")
private WebElement trackedRacesListComposite;
@FindBy(how = BySeleniumId.class, using = "LeaderboardRefreshButton")
private WebElement leaderboardRefreshButton;
@FindBy(how = BySeleniumId.class, using = "StartTrackingButton")
private WebElement startTrackingButton;
public SmartphoneTrackingEventManagementPanelPO(WebDriver driver, WebElement element) {
super(driver, element);
}
public CellTablePO<DataEntryPO> getLeaderboardTable() {
return new GenericCellTablePO<>(this.driver, this.leaderboardTable, DataEntryPO.class);
}
@@ -61,4 +75,93 @@ public class SmartphoneTrackingEventManagementPanelPO extends PageArea {
aLeaderboard.clickActionImage(ACTION_MAP_DEVICES);
return this.waitForPO(MapDevicesDialogPO::new, "regattaLogTrackingDeviceMappingsDialog");
}
public void denoteLeaderboardForRaceLogTracking(DataEntryPO aLeaderboard) {
aLeaderboard.clickActionImage(ACTION_DENOTE_FOR_RACELOG_TRACKING);
findElementBySeleniumId(this.driver, "OkButton").click();
waitForAjaxRequests();
}
public void startTrackingForRace(DataEntryPO aRaceRow) {
aRaceRow.clickActionImage(ACTION_START_TRACKING);
waitForAjaxRequests();
}
public void stopTrackingForRace(DataEntryPO aRaceRow) {
aRaceRow.clickActionImage(ACTION_STOP_TRACKING);
waitForAjaxRequests();
}
public void clickStartTrackingButton() {
this.startTrackingButton.click();
waitForAjaxRequests();
}
public void removeDenotationForRace(DataEntryPO aRaceRow) {
aRaceRow.clickActionImage(ACTION_REMOVE_DENOTATION);
waitForAjaxRequests();
}
// Waits for a leaderboard row with the given name to be stably present and returns it.
// Retries on stale/transient DOM state caused by GWT re-renders after Ajax.
public DataEntryPO waitForLeaderboardEntry(final String leaderboardName) {
return waitForTableEntry(getLeaderboardTable(), "Name", leaderboardName);
}
// Waits for a race row with the given name to be stably present and returns it.
// Retries on stale/transient DOM state caused by GWT re-renders after Ajax.
public DataEntryPO waitForRaceRow(final String raceName) {
return waitForTableEntry(getRaceColumnTableWrapper().getRaceColumnTable(), "Race", raceName);
}
// Selects exactly the named rows in the race table, deselecting all others.
// Retries one row at a time to handle GWT re-renders between clicks.
public void selectRaceRowsByName(String... raceNames) {
final List<String> namesToSelect = Arrays.asList(raceNames);
boolean changed = true;
while (changed) {
changed = false;
try {
final CellTablePO<DataEntryPO> raceTable = getRaceColumnTableWrapper().getRaceColumnTable();
for (final DataEntryPO entry : raceTable.getEntries()) {
final boolean shouldBeSelected = namesToSelect.contains(entry.getColumnContent("Race"));
final boolean isSelected = entry.isSelected();
if (shouldBeSelected != isSelected) {
final WebElement checkboxDiv = entry.getWebElement().findElement(By.xpath("./td[1]/div"));
scrollToView(checkboxDiv);
new Actions(this.driver).moveToElement(checkboxDiv).click().build().perform();
changed = true;
break;
}
}
} catch (StaleElementReferenceException e) {
changed = true;
}
}
}
private DataEntryPO waitForTableEntry(final CellTablePO<DataEntryPO> table, final String column, final String value) {
final List<DataEntryPO> result = new ArrayList<>();
waitUntil(() -> {
boolean found = false;
try {
for (final DataEntryPO entry : table.getEntries()) {
if (value.equals(entry.getColumnContent(column))) {
result.clear();
result.add(entry);
found = true;
break;
}
}
} catch (StaleElementReferenceException e) {
found = false;
} catch (RuntimeException e) {
// createDataEntry throws RuntimeException when the table is mid-re-render
// (transient row visible that cannot be reflected into a DataEntryPO); retry
found = false;
}
return found;
});
return result.get(0);
}
}
@@ -0,0 +1,66 @@
package com.sap.sailing.selenium.test.adminconsole;
import org.junit.jupiter.api.BeforeEach;
import com.sap.sailing.selenium.core.SeleniumTestCase;
import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage;
import com.sap.sailing.selenium.pages.adminconsole.connectors.SmartphoneTrackingEventManagementPanelPO;
import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaListCompositePO.RegattaDescriptor;
import com.sap.sailing.selenium.pages.adminconsole.regatta.RegattaStructureManagementPanelPO;
import com.sap.sailing.selenium.test.AbstractSeleniumTest;
/**
* Selenium test for bug6113 + bug6251: verifies the start/stop tracking cycle for Race Log /
* Smartphone Tracking. Runs two full denote → start → stop → undenote cycles to ensure that
* button states reset correctly after the first cycle (bug6251). Tests both global and local start/stop tracking buttons.
*/
public class TestStartStopTrackingCycleForRaceLog extends AbstractSeleniumTest {
private static final String REGATTA_NAME = "TestRegatta6113";
private static final String BOAT_CLASS = "Laser";
private static final String R1 = "R1";
private static final String R2 = "R2";
private static final String R3 = "R3";
private RegattaDescriptor regatta;
@Override
@BeforeEach
public void setUp() {
this.regatta = new RegattaDescriptor(REGATTA_NAME, BOAT_CLASS);
clearState(getContextRoot());
super.setUp();
configureRegattaAndLeaderboard();
}
@SeleniumTestCase
public void testStartStopTrackingCycle() {
final AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot());
final SmartphoneTrackingEventManagementPanelPO smartphonePanel = adminConsole.goToSmartphoneTrackingPanel();
smartphonePanel.getLeaderboardTable().selectEntry(smartphonePanel.waitForLeaderboardEntry(this.regatta.toString()));
performTrackingCycle(smartphonePanel);
performTrackingCycle(smartphonePanel);
}
private void configureRegattaAndLeaderboard() {
final AdminConsolePage adminConsole = AdminConsolePage.goToPage(getWebDriver(), getContextRoot());
final RegattaStructureManagementPanelPO regattaStructure = adminConsole.goToRegattaStructure();
regattaStructure.createRegatta(this.regatta, /* withDefaultLeaderboard */ true);
}
private void performTrackingCycle(final SmartphoneTrackingEventManagementPanelPO smartphonePanel) {
// denote all races for tracking, re-fetching the leaderboard entry each cycle
smartphonePanel.denoteLeaderboardForRaceLogTracking(smartphonePanel.waitForLeaderboardEntry(this.regatta.toString()));
// start R1 via local button, R2+R3 via global toggle button
smartphonePanel.startTrackingForRace(smartphonePanel.waitForRaceRow(R1));
smartphonePanel.selectRaceRowsByName(R2, R3);
smartphonePanel.clickStartTrackingButton();
// stop R3 via local button, R1+R2 via global toggle button
smartphonePanel.stopTrackingForRace(smartphonePanel.waitForRaceRow(R3));
smartphonePanel.selectRaceRowsByName(R1, R2);
smartphonePanel.clickStartTrackingButton();
// undenote all races to reset state for next cycle
smartphonePanel.removeDenotationForRace(smartphonePanel.waitForRaceRow(R1));
smartphonePanel.removeDenotationForRace(smartphonePanel.waitForRaceRow(R2));
smartphonePanel.removeDenotationForRace(smartphonePanel.waitForRaceRow(R3));
}
}