Skip to content

Commit efc6939

Browse files
committed
Added custom assertions.
1 parent 5b4528f commit efc6939

File tree

5 files changed

+431
-47
lines changed

5 files changed

+431
-47
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package qa.autotest.framework.assertions;
2+
3+
import org.assertj.core.api.AbstractAssert;
4+
import qa.autotest.framework.pages.CartPage;
5+
6+
/**
7+
* Custom AssertJ assertion for the state of {@link CartPage}.
8+
*
9+
* <h3>Problem solved</h3>
10+
* Cart-state checks are the most frequently repeated assertion pattern in the
11+
* suite. Three to four {@code assertThat} calls verifying badge count, item
12+
* count, and emptiness appear across {@code CartOperationsTests},
13+
* {@code CheckoutFlowTests}, and {@code NavigationTests}.
14+
*
15+
* <p>This class collapses those chains into named, self-describing methods:
16+
* <pre>
17+
* // Before
18+
* assertThat(cartPage.getCartItemsCount()).as("Cart should have 2 items").isEqualTo(2);
19+
* assertThat(cartPage.isPageLoaded()).as("Cart page should be loaded").isTrue();
20+
*
21+
* // After
22+
* CartAssert.assertThat(cartPage)
23+
* .isLoaded()
24+
* .hasItemCount(2);
25+
* </pre>
26+
*
27+
* <h3>Design note</h3>
28+
* Accepts {@link CartPage} directly rather than a DTO — the cart page exposes
29+
* all state needed for assertions without requiring a separate extraction step.
30+
* If a {@code CartDto} is later introduced as an intermediate value object,
31+
* a companion {@code CartDtoAssert} can be added following the same pattern.
32+
*/
33+
public class CartAssert extends AbstractAssert<CartAssert, CartPage> {
34+
35+
private CartAssert(CartPage actual) {
36+
super(actual, CartAssert.class);
37+
}
38+
39+
public static CartAssert assertThat(CartPage actual) {
40+
return new CartAssert(actual);
41+
}
42+
43+
public CartAssert isLoaded() {
44+
isNotNull();
45+
if (!actual.isPageLoaded()) {
46+
failWithMessage("Expected cart page to be loaded but isPageLoaded() returned false");
47+
}
48+
return this;
49+
}
50+
51+
public CartAssert hasItemCount(int expectedCount) {
52+
isNotNull();
53+
int actual = this.actual.getCartItemsCount();
54+
if (actual != expectedCount) {
55+
failWithMessage(
56+
"Expected cart to contain <%d> item(s) but found <%d>",
57+
expectedCount, actual);
58+
}
59+
return this;
60+
}
61+
62+
public CartAssert isEmpty() {
63+
isNotNull();
64+
if (!actual.isCartEmpty()) {
65+
failWithMessage(
66+
"Expected cart to be empty but found <%d> item(s)",
67+
actual.getCartItemsCount());
68+
}
69+
return this;
70+
}
71+
72+
public CartAssert isNotEmpty() {
73+
isNotNull();
74+
if (actual.isCartEmpty()) {
75+
failWithMessage("Expected cart to be non-empty but found 0 items");
76+
}
77+
return this;
78+
}
79+
80+
public CartAssert containsProduct(String productName) {
81+
isNotNull();
82+
if (!actual.isProductInCart(productName)) {
83+
failWithMessage(
84+
"Expected cart to contain product <%s> but it was not found. "
85+
+ "Cart contents: <%s>",
86+
productName, actual.getCartItemNames());
87+
}
88+
return this;
89+
}
90+
91+
public CartAssert doesNotContainProduct(String productName) {
92+
isNotNull();
93+
if (actual.isProductInCart(productName)) {
94+
failWithMessage(
95+
"Expected cart NOT to contain product <%s> but it was found",
96+
productName);
97+
}
98+
return this;
99+
}
100+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package qa.autotest.framework.assertions;
2+
3+
import org.assertj.core.api.AbstractAssert;
4+
import qa.autotest.framework.pages.CheckoutCompletePage;
5+
import qa.autotest.framework.pages.CheckoutStepOnePage;
6+
import qa.autotest.framework.pages.CheckoutStepTwoPage;
7+
8+
/**
9+
* Custom AssertJ assertions for the checkout funnel pages.
10+
*
11+
* <h3>Problem solved</h3>
12+
* Three patterns repeat across {@code CheckoutFlowTests}:
13+
* <ul>
14+
* <li>Error message checks on {@link CheckoutStepOnePage} — three identical
15+
* {@code assertThat(page.getErrorMessage()).contains(...)} calls</li>
16+
* <li>Total calculation check on {@link CheckoutStepTwoPage} — multi-variable
17+
* subtotal + tax == total comparison</li>
18+
* <li>Order completion check on {@link CheckoutCompletePage} — two assertions
19+
* (isOrderComplete, cart badge = 0) always appear together</li>
20+
* </ul>
21+
*
22+
* <h3>Structure</h3>
23+
* Three static factory methods, one per checkout page, each returning a typed
24+
* assert. All share this outer class as a namespace rather than three separate
25+
* top-level files — the checkout funnel is a single bounded context and keeping
26+
* its assertions together makes discovery easier.
27+
*
28+
* <h3>Usage</h3>
29+
* <pre>
30+
* // Step one — validation errors
31+
* CheckoutAssert.assertThat(checkoutPage)
32+
* .hasValidationError("First Name is required");
33+
*
34+
* // Step two — price totals
35+
* CheckoutAssert.assertThat(overviewPage)
36+
* .hasTotalEqualToSubtotalPlusTax()
37+
* .hasItemCount(2);
38+
*
39+
* // Complete — order confirmed
40+
* CheckoutAssert.assertThat(completePage)
41+
* .isOrderSuccessful();
42+
* </pre>
43+
*/
44+
public class CheckoutAssert {
45+
46+
private CheckoutAssert() {
47+
// namespace only — no instances
48+
}
49+
50+
public static CheckoutStepOneAssert assertThat(CheckoutStepOnePage page) {
51+
return new CheckoutStepOneAssert(page);
52+
}
53+
54+
public static class CheckoutStepOneAssert
55+
extends AbstractAssert<CheckoutStepOneAssert, CheckoutStepOnePage> {
56+
57+
private CheckoutStepOneAssert(CheckoutStepOnePage actual) {
58+
super(actual, CheckoutStepOneAssert.class);
59+
}
60+
61+
/**
62+
* Verifies the validation error message contains the expected substring.
63+
* Fetches the message once and performs the check — avoids calling
64+
* {@code getErrorMessage()} twice in the test.
65+
*/
66+
public CheckoutStepOneAssert hasValidationError(String expectedFragment) {
67+
isNotNull();
68+
if (!actual.isErrorMessageDisplayed()) {
69+
failWithMessage(
70+
"Expected a validation error containing <%s> but no error message was displayed",
71+
expectedFragment);
72+
}
73+
String errorMessage = actual.getErrorMessage();
74+
if (!errorMessage.contains(expectedFragment)) {
75+
failWithMessage(
76+
"Expected error message to contain <%s> but was <%s>",
77+
expectedFragment, errorMessage);
78+
}
79+
return this;
80+
}
81+
82+
public CheckoutStepOneAssert hasNoValidationError() {
83+
isNotNull();
84+
if (actual.isErrorMessageDisplayed()) {
85+
failWithMessage(
86+
"Expected no validation error but error message was displayed: <%s>",
87+
actual.getErrorMessage());
88+
}
89+
return this;
90+
}
91+
}
92+
93+
public static CheckoutStepTwoAssert assertThat(CheckoutStepTwoPage page) {
94+
return new CheckoutStepTwoAssert(page);
95+
}
96+
97+
public static class CheckoutStepTwoAssert
98+
extends AbstractAssert<CheckoutStepTwoAssert, CheckoutStepTwoPage> {
99+
100+
private CheckoutStepTwoAssert(CheckoutStepTwoPage actual) {
101+
super(actual, CheckoutStepTwoAssert.class);
102+
}
103+
104+
public CheckoutStepTwoAssert hasItemCount(int expectedCount) {
105+
isNotNull();
106+
int count = actual.getCartItemsCount();
107+
if (count != expectedCount) {
108+
failWithMessage(
109+
"Expected checkout overview to list <%d> item(s) but found <%d>",
110+
expectedCount, count);
111+
}
112+
return this;
113+
}
114+
115+
/**
116+
* Verifies that {@code total == subtotal + tax} to the precision of
117+
* double arithmetic. Reads each value once and compares with a small
118+
* epsilon to avoid floating-point rounding failures.
119+
*/
120+
public CheckoutStepTwoAssert hasTotalEqualToSubtotalPlusTax() {
121+
isNotNull();
122+
double subtotal = actual.getSubtotal();
123+
double tax = actual.getTax();
124+
double total = actual.getTotal();
125+
double expected = subtotal + tax;
126+
// 0.001 epsilon covers IEEE 754 rounding at two decimal places
127+
if (Math.abs(total - expected) > 0.001) {
128+
failWithMessage(
129+
"Expected total <%s> to equal subtotal <%s> + tax <%s> = <%s>",
130+
total, subtotal, tax, expected);
131+
}
132+
return this;
133+
}
134+
}
135+
136+
public static CheckoutCompleteAssert assertThat(CheckoutCompletePage page) {
137+
return new CheckoutCompleteAssert(page);
138+
}
139+
140+
public static class CheckoutCompleteAssert
141+
extends AbstractAssert<CheckoutCompleteAssert, CheckoutCompletePage> {
142+
143+
private CheckoutCompleteAssert(CheckoutCompletePage actual) {
144+
super(actual, CheckoutCompleteAssert.class);
145+
}
146+
147+
/**
148+
* Compound assertion: verifies the confirmation header is shown AND the
149+
* cart badge count is zero. These two conditions always need to hold
150+
* together after a successful checkout — a single method call makes the
151+
* intent unambiguous.
152+
*/
153+
public CheckoutCompleteAssert isOrderSuccessful() {
154+
isNotNull();
155+
if (!actual.isOrderComplete()) {
156+
failWithMessage(
157+
"Expected order to be complete (confirmation header visible) "
158+
+ "but isOrderComplete() returned false");
159+
}
160+
int badgeCount = actual.getCartBadgeCount();
161+
if (badgeCount != 0) {
162+
failWithMessage(
163+
"Expected cart to be empty after checkout but badge count was <%d>",
164+
badgeCount);
165+
}
166+
return this;
167+
}
168+
169+
public CheckoutCompleteAssert hasCompleteHeaderContaining(String expectedFragment) {
170+
isNotNull();
171+
String header = actual.getCompleteHeaderText();
172+
if (!header.contains(expectedFragment)) {
173+
failWithMessage(
174+
"Expected complete header to contain <%s> but was <%s>",
175+
expectedFragment, header);
176+
}
177+
return this;
178+
}
179+
}
180+
}

0 commit comments

Comments
 (0)