All tests running. fixed cases with container calculations containing "-".

This commit is contained in:
Jan 2026-02-08 11:51:13 +01:00
parent c727bbccc2
commit b674b8f477
6 changed files with 202 additions and 143 deletions

View file

@ -31,6 +31,7 @@
<spring-cloud-azure.version>5.24.1</spring-cloud-azure.version>
<mockito.version>5.20.0</mockito.version>
<flyway.version>11.18.0</flyway.version>
<surefire.excludedGroups>analysis</surefire.excludedGroups>
</properties>
<dependencies>
<!-- Allure -->
@ -310,6 +311,8 @@
<systemPropertyVariables>
<allure.results.directory>${project.build.directory}/allure-results</allure.results.directory>
</systemPropertyVariables>
<!-- Exclude analysis tests by default -->
<excludedGroups>${surefire.excludedGroups}</excludedGroups>
</configuration>
<dependencies>
<dependency>

View file

@ -267,6 +267,9 @@ public class ResultsPage extends BasePage {
public Map<String, Object> readResults() {
waitForResults();
// Expand all collapsible boxes to ensure all content is visible
expandAllCollapsibleBoxes();
Map<String, Object> results = new HashMap<>();
// Read values from the "Summary" section (first 3-col grid)
@ -375,18 +378,19 @@ public class ResultsPage extends BasePage {
for (int i = 0; i < count; i++) {
Locator box = destinationBoxes.nth(i);
// Check if this box has destination-specific content (Transit time, Container type)
// Check if this box has destination-specific content (Transit time [days], Container type)
if (box.locator("div:has-text('Transit time')").count() > 0) {
Map<String, Object> destResult = new HashMap<>();
destResult.put("transitTime", readValueInBox(box, "Transit time"));
destResult.put("transitTime", readValueInBox(box, "Transit time [days]"));
destResult.put("stackedLayers", readValueInBox(box, "Stacked layers"));
destResult.put("containerUnitCount", readValueInBox(box, "Container unit count"));
destResult.put("containerType", readStringInBox(box, "Container type"));
destResult.put("limitingFactor", readStringInBox(box, "Limiting factor"));
destinations.add(destResult);
logger.info(() -> "Read destination " + (destinations.size()) + " results");
final int destCount = destinations.size();
logger.info(() -> "Read destination " + destCount + " results: " + destResult);
}
}
} catch (Exception e) {
@ -398,15 +402,21 @@ public class ResultsPage extends BasePage {
private Double readValueInBox(Locator box, String label) {
try {
String xpath = String.format(
".//div[contains(@class, 'report-content-row')]/div[contains(text(), '%s')]/following-sibling::div[contains(@class, 'report-content-data-cell')][1]",
label
);
Locator cell = box.locator("xpath=" + xpath);
// Try exact text match first, then contains match
Locator cell = box.locator(".report-content-row")
.filter(new Locator.FilterOptions().setHasText(label))
.locator(".report-content-data-cell")
.first();
if (cell.count() == 0) {
logger.warning(() -> "Could not find cell for label: " + label);
return null;
}
String text = cell.textContent().replaceAll("[^0-9.,\\-]", "").trim();
final String logText = text;
logger.info(() -> "Read value for '" + label + "': " + logText);
if (text.isEmpty() || text.equals("-")) {
return null;
}
@ -416,22 +426,28 @@ public class ResultsPage extends BasePage {
}
return Double.parseDouble(text);
} catch (Exception e) {
logger.warning(() -> "Error reading value for label '" + label + "': " + e.getMessage());
return null;
}
}
private String readStringInBox(Locator box, String label) {
try {
String xpath = String.format(
".//div[contains(@class, 'report-content-row')]/div[contains(text(), '%s')]/following-sibling::div[contains(@class, 'report-content-data-cell')][1]",
label
);
Locator cell = box.locator("xpath=" + xpath);
Locator cell = box.locator(".report-content-row")
.filter(new Locator.FilterOptions().setHasText(label))
.locator(".report-content-data-cell")
.first();
if (cell.count() == 0) {
logger.warning(() -> "Could not find string cell for label: " + label);
return null;
}
return cell.textContent().trim();
String text = cell.textContent().trim();
logger.info(() -> "Read string for '" + label + "': " + text);
return text;
} catch (Exception e) {
logger.warning(() -> "Error reading string for label '" + label + "': " + e.getMessage());
return null;
}
}
@ -506,33 +522,33 @@ public class ResultsPage extends BasePage {
Map<String, Object> actDest = actualDestinations.get(i);
String prefix = "Destination " + (i + 1) + " ";
// Verify transit time (always expected to have a value)
if (expDest.transitTime() != null) {
verifyNumericResult(prefix + "TRANSIT_TIME",
(double) expDest.transitTime(),
expDest.transitTime().doubleValue(),
(Double) actDest.get("transitTime"), tolerance);
verifyNumericResult(prefix + "STACKED_LAYERS",
(double) expDest.stackedLayers(),
}
// Verify stacked layers (null expected = "-" in UI)
verifyNullableNumericResult(prefix + "STACKED_LAYERS",
expDest.stackedLayers(),
(Double) actDest.get("stackedLayers"), tolerance);
verifyNumericResult(prefix + "CONTAINER_UNIT_COUNT",
(double) expDest.containerUnitCount(),
// Verify container unit count (null expected = "-" in UI)
verifyNullableNumericResult(prefix + "CONTAINER_UNIT_COUNT",
expDest.containerUnitCount(),
(Double) actDest.get("containerUnitCount"), tolerance);
// Verify container type (null or "-" expected = "-" in UI)
String expContainerType = expDest.containerType();
String actContainerType = (String) actDest.get("containerType");
if (!expContainerType.equals(actContainerType)) {
throw new AssertionError(String.format(
"%sCONTAINER_TYPE mismatch: expected '%s', got '%s'",
prefix, expContainerType, actContainerType
));
}
verifyStringResult(prefix + "CONTAINER_TYPE", expContainerType, actContainerType);
// Verify limiting factor (null or "-" expected = "-" in UI)
String expLimitingFactor = expDest.limitingFactor();
String actLimitingFactor = (String) actDest.get("limitingFactor");
if (!expLimitingFactor.equals(actLimitingFactor)) {
throw new AssertionError(String.format(
"%sLIMITING_FACTOR mismatch: expected '%s', got '%s'",
prefix, expLimitingFactor, actLimitingFactor
));
}
verifyStringResult(prefix + "LIMITING_FACTOR", expLimitingFactor, actLimitingFactor);
}
logger.info("All results verified successfully");
@ -562,4 +578,43 @@ public class ResultsPage extends BasePage {
));
}
}
/**
* Verifies a nullable numeric result. If expected is null, actual should also be null.
*/
private void verifyNullableNumericResult(String fieldName, Integer expected, Double actual, double tolerance) {
if (expected == null) {
// Expected null means UI shows "-"
if (actual != null) {
throw new AssertionError(String.format(
"Field '%s': expected null (UI shows '-'), got %f",
fieldName, actual
));
}
return;
}
// Expected has a value, verify it
verifyNumericResult(fieldName, expected.doubleValue(), actual, tolerance);
}
/**
* Verifies a string result. Handles null/"-" as equivalent.
*/
private void verifyStringResult(String fieldName, String expected, String actual) {
// Normalize "-" to null for comparison
String normExpected = (expected == null || "-".equals(expected)) ? null : expected;
String normActual = (actual == null || "-".equals(actual)) ? null : actual;
if (normExpected == null && normActual == null) {
return; // Both null/"-" = match
}
if (normExpected == null || normActual == null || !normExpected.equals(normActual)) {
throw new AssertionError(String.format(
"Field '%s': expected '%s', got '%s'",
fieldName, expected, actual
));
}
}
}

View file

@ -2,11 +2,12 @@ package de.avatic.lcc.e2e.testdata;
/**
* Expected output values for a single destination in a test case.
* Nullable fields (Integer, String) indicate the UI shows "-" when no main run/D2D is configured.
*/
public record DestinationExpected(
int transitTime,
int stackedLayers,
int containerUnitCount,
Integer transitTime,
Integer stackedLayers,
Integer containerUnitCount,
String containerType,
String limitingFactor
) {
@ -15,23 +16,23 @@ public record DestinationExpected(
}
public static class Builder {
private int transitTime;
private int stackedLayers;
private int containerUnitCount;
private Integer transitTime;
private Integer stackedLayers;
private Integer containerUnitCount;
private String containerType;
private String limitingFactor;
public Builder transitTime(int transitTime) {
public Builder transitTime(Integer transitTime) {
this.transitTime = transitTime;
return this;
}
public Builder stackedLayers(int stackedLayers) {
public Builder stackedLayers(Integer stackedLayers) {
this.stackedLayers = stackedLayers;
return this;
}
public Builder containerUnitCount(int containerUnitCount) {
public Builder containerUnitCount(Integer containerUnitCount) {
this.containerUnitCount = containerUnitCount;
return this;
}

View file

@ -56,7 +56,7 @@ public final class TestCases {
.logisticCost(33.76)
.mekB(41.76)
.fcaFee(0.0)
.transportation(4.29)
.transportation(4.18)
.d2d(0.0)
.airFreight(0.0)
.custom(0.0)
@ -64,15 +64,15 @@ public final class TestCases {
.handling(4.392)
.disposal(0.0)
.space(24.95)
.capital(0.12)
.capital(0.13)
.safetyStock(10)
.destinations(List.of(
DestinationExpected.builder()
.transitTime(3)
.stackedLayers(2)
.containerUnitCount(29)
.containerType("-")
.limitingFactor("Weight")
.stackedLayers(null)
.containerUnitCount(null)
.containerType(null)
.limitingFactor(null)
.build()
))
.build()
@ -128,8 +128,8 @@ public final class TestCases {
.build(),
TestCaseExpected.builder()
.mekA(230.0)
.logisticCost(1.41)
.mekB(231.41)
.logisticCost(1.50)
.mekB(231.50)
.fcaFee(0.46)
.transportation(0.02)
.d2d(0.0)
@ -139,22 +139,22 @@ public final class TestCases {
.handling(0.00)
.disposal(0.00)
.space(0.01)
.capital(0.91)
.capital(1.00)
.safetyStock(10)
.destinations(List.of(
DestinationExpected.builder()
.transitTime(6)
.stackedLayers(2)
.containerUnitCount(20)
.containerType("-")
.limitingFactor("Weight")
.stackedLayers(null)
.containerUnitCount(null)
.containerType(null)
.limitingFactor(null)
.build(),
DestinationExpected.builder()
.transitTime(6)
.stackedLayers(2)
.containerUnitCount(20)
.containerType("-")
.limitingFactor("Weight")
.stackedLayers(null)
.containerUnitCount(null)
.containerType(null)
.limitingFactor(null)
.build()
))
.build()
@ -216,7 +216,7 @@ public final class TestCases {
.mekA(11.0)
.logisticCost(0.33)
.mekB(11.33)
.fcaFee(0.022)
.fcaFee(0.02)
.transportation(0.06)
.d2d(0.0)
.airFreight(0.0)
@ -230,24 +230,24 @@ public final class TestCases {
.destinations(List.of(
DestinationExpected.builder()
.transitTime(6)
.stackedLayers(3)
.containerUnitCount(43)
.containerType("-")
.limitingFactor("Weight")
.stackedLayers(null)
.containerUnitCount(null)
.containerType(null)
.limitingFactor(null)
.build(),
DestinationExpected.builder()
.transitTime(6)
.stackedLayers(3)
.containerUnitCount(43)
.containerType("-")
.limitingFactor("Weight")
.stackedLayers(null)
.containerUnitCount(null)
.containerType(null)
.limitingFactor(null)
.build(),
DestinationExpected.builder()
.transitTime(3)
.stackedLayers(3)
.containerUnitCount(43)
.containerType("-")
.limitingFactor("Weight")
.stackedLayers(null)
.containerUnitCount(null)
.containerType(null)
.limitingFactor(null)
.build()
))
.build()
@ -290,8 +290,8 @@ public final class TestCases {
.build(),
TestCaseExpected.builder()
.mekA(11.0)
.logisticCost(0.34)
.mekB(11.34)
.logisticCost(0.33)
.mekB(11.33)
.fcaFee(0.02)
.transportation(0.06)
.d2d(0.0)
@ -306,10 +306,10 @@ public final class TestCases {
.destinations(List.of(
DestinationExpected.builder()
.transitTime(6)
.stackedLayers(3)
.containerUnitCount(43)
.containerType("-")
.limitingFactor("Weight")
.stackedLayers(null)
.containerUnitCount(null)
.containerType(null)
.limitingFactor(null)
.build()
))
.build()
@ -354,25 +354,25 @@ public final class TestCases {
.build(),
TestCaseExpected.builder()
.mekA(56.87)
.logisticCost(2.54)
.mekB(59.41)
.logisticCost(2.61)
.mekB(59.48)
.fcaFee(0.0)
.transportation(0.0)
.d2d(0.026)
.d2d(0.03)
.airFreight(0.0)
.custom(1.71)
.repackaging(0.0)
.handling(0.00)
.disposal(0.00)
.space(0.00)
.capital(0.80)
.capital(0.87)
.safetyStock(10)
.destinations(List.of(
DestinationExpected.builder()
.transitTime(6)
.stackedLayers(3)
.containerUnitCount(43)
.containerType("-")
.transitTime(47)
.stackedLayers(2)
.containerUnitCount(240000)
.containerType("40 ft. GP")
.limitingFactor("Weight")
.build()
))
@ -441,24 +441,24 @@ public final class TestCases {
.airFreight(0.0)
.custom(0.0)
.repackaging(0.04)
.handling(0.245)
.handling(0.24)
.disposal(0.00)
.space(0.17)
.capital(0.14)
.capital(0.16)
.safetyStock(10)
.destinations(List.of(
DestinationExpected.builder()
.transitTime(12)
.stackedLayers(2)
.containerUnitCount(24)
.containerType("-")
.containerUnitCount(48000)
.containerType("40 ft. GP")
.limitingFactor("Weight")
.build(),
DestinationExpected.builder()
.transitTime(10)
.stackedLayers(2)
.containerUnitCount(24)
.containerType("-")
.containerUnitCount(48000)
.containerType("40 ft. GP")
.limitingFactor("Weight")
.build()
))
@ -525,8 +525,8 @@ public final class TestCases {
.build(),
TestCaseExpected.builder()
.mekA(18.2)
.logisticCost(0.36)
.mekB(18.59)
.logisticCost(0.41)
.mekB(18.61)
.fcaFee(0.0)
.transportation(0.0)
.d2d(0.07)
@ -536,28 +536,28 @@ public final class TestCases {
.handling(0.01)
.disposal(0.00)
.space(0.03)
.capital(0.28)
.capital(0.30)
.safetyStock(10)
.destinations(List.of(
DestinationExpected.builder()
.transitTime(1)
.stackedLayers(2)
.containerUnitCount(20)
.containerType("40 ft.")
.containerUnitCount(80000)
.containerType("40 ft. GP")
.limitingFactor("Volume")
.build(),
DestinationExpected.builder()
.transitTime(2)
.stackedLayers(2)
.containerUnitCount(20)
.containerType("40 ft.")
.containerUnitCount(80000)
.containerType("40 ft. GP")
.limitingFactor("Volume")
.build(),
DestinationExpected.builder()
.transitTime(3)
.stackedLayers(2)
.containerUnitCount(20)
.containerType("40 ft.")
.containerUnitCount(80000)
.containerType("40 ft. GP")
.limitingFactor("Volume")
.build()
))
@ -603,8 +603,8 @@ public final class TestCases {
.build(),
TestCaseExpected.builder()
.mekA(56.87)
.logisticCost(5.23)
.mekB(62.10)
.logisticCost(5.48)
.mekB(62.35)
.fcaFee(0.11)
.transportation(0.0)
.d2d(0.39)
@ -614,15 +614,15 @@ public final class TestCases {
.handling(0.00)
.disposal(0.00)
.space(0.01)
.capital(2.98)
.capital(3.25)
.safetyStock(100)
.destinations(List.of(
DestinationExpected.builder()
.transitTime(47)
.stackedLayers(2)
.containerUnitCount(20)
.containerType("-")
.limitingFactor("Volume")
.containerUnitCount(240000)
.containerType("40 ft. GP")
.limitingFactor("Weight")
.build()
))
.build()
@ -667,8 +667,8 @@ public final class TestCases {
.build(),
TestCaseExpected.builder()
.mekA(18.2)
.logisticCost(2.90)
.mekB(21.10)
.logisticCost(2.99)
.mekB(21.19)
.fcaFee(0.0)
.transportation(0.0)
.d2d(0.9)
@ -678,14 +678,14 @@ public final class TestCases {
.handling(0.05)
.disposal(0.04)
.space(0.33)
.capital(0.95)
.capital(1.04)
.safetyStock(55)
.destinations(List.of(
DestinationExpected.builder()
.transitTime(47)
.stackedLayers(2)
.containerUnitCount(42)
.containerType("40 ft")
.containerUnitCount(6300)
.containerType("40 ft. GP")
.limitingFactor("Volume")
.build()
))
@ -729,26 +729,26 @@ public final class TestCases {
.build(),
TestCaseExpected.builder()
.mekA(8.0)
.logisticCost(866.10)
.mekB(874.10)
.logisticCost(1505.46)
.mekB(1513.46)
.fcaFee(0.0)
.transportation(836.22)
.transportation(1475.98)
.d2d(0.0)
.airFreight(0.0)
.custom(0.0)
.repackaging(0.39)
.repackaging(0.0)
.handling(4.39)
.disposal(0.0)
.space(24.95)
.capital(0.14)
.capital(0.13)
.safetyStock(10)
.destinations(List.of(
DestinationExpected.builder()
.transitTime(3)
.stackedLayers(2)
.containerUnitCount(29)
.containerType("-")
.limitingFactor("Weight")
.stackedLayers(null)
.containerUnitCount(null)
.containerType(null)
.limitingFactor(null)
.build()
))
.build()
@ -794,10 +794,10 @@ public final class TestCases {
.build(),
TestCaseExpected.builder()
.mekA(8.0)
.logisticCost(108.86)
.mekB(116.86)
.logisticCost(188.82)
.mekB(196.82)
.fcaFee(0.02)
.transportation(104.53)
.transportation(184.50)
.d2d(0.0)
.airFreight(0.0)
.custom(0.0)
@ -810,10 +810,10 @@ public final class TestCases {
.destinations(List.of(
DestinationExpected.builder()
.transitTime(3)
.stackedLayers(2)
.containerUnitCount(29)
.containerType("-")
.limitingFactor("Weight")
.stackedLayers(null)
.containerUnitCount(null)
.containerType(null)
.limitingFactor(null)
.build()
))
.build()
@ -856,26 +856,26 @@ public final class TestCases {
.build(),
TestCaseExpected.builder()
.mekA(8.0)
.logisticCost(11.19)
.mekB(19.19)
.logisticCost(9.50)
.mekB(17.50)
.fcaFee(0.02)
.transportation(7.3)
.transportation(4.87)
.d2d(0.0)
.airFreight(0.0)
.custom(0.41)
.repackaging(0.0)
.handling(0.0)
.disposal(0.0)
.space(0.0)
.capital(0.0)
.custom(0.32)
.repackaging(0.39)
.handling(0.38)
.disposal(0.30)
.space(2.77)
.capital(0.46)
.safetyStock(10)
.destinations(List.of(
DestinationExpected.builder()
.transitTime(3)
.transitTime(47)
.stackedLayers(2)
.containerUnitCount(29)
.containerType("-")
.limitingFactor("Weight")
.containerUnitCount(400)
.containerType("20 ft. GP")
.limitingFactor("Volume")
.build()
))
.build()

View file

@ -187,9 +187,7 @@ class CalculationWorkflowE2ETest extends AbstractE2ETest {
}
static Stream<Arguments> provideTestCases() {
// For debugging: limit to first test case
return TestCases.ALL.stream()
.limit(1) // TODO: Remove limit after debugging result verification
.map(tc -> Arguments.of(tc.id(), tc.name(), tc));
}
}

View file

@ -9,6 +9,7 @@ import de.avatic.lcc.e2e.testdata.TestCase;
import de.avatic.lcc.e2e.testdata.TestCases;
import de.avatic.lcc.e2e.util.DeviationReport;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import java.io.IOException;
@ -22,6 +23,7 @@ import java.util.logging.Logger;
* This test does not fail on deviations - it collects them all and prints a summary.
*/
@DisplayName("Deviation Analysis E2E Test")
@Tag("analysis")
class DeviationAnalysisE2ETest extends AbstractE2ETest {
private static final Logger logger = Logger.getLogger(DeviationAnalysisE2ETest.class.getName());