Skip to content

Commit a40d1f1

Browse files
Merge pull request #44 from iamcharankumar/fb_smart_wait
Reduced README content and introduced smart-waits.
2 parents 4cd2fd2 + e5a4be4 commit a40d1f1

File tree

9 files changed

+127
-2518
lines changed

9 files changed

+127
-2518
lines changed

README.md

Lines changed: 0 additions & 2477 deletions
Large diffs are not rendered by default.

src/main/java/io/swaglabs/portal/qa/commons/WebBasePage.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import io.swaglabs.portal.qa.screenshotsmanager.ElementScreenshotStrategy;
99
import io.swaglabs.portal.qa.screenshotsmanager.ScreenshotContext;
1010

11+
import java.util.Objects;
12+
1113
public abstract class WebBasePage {
1214

1315
private static final String ELEMENT_SCREENSHOT_FILE_LOCATION = WebPortalConstants.SCREENSHOT_FILE_LOCATION + "/elements/"
@@ -48,9 +50,8 @@ protected String getTextContent(Locator locator) {
4850
}
4951

5052
private String extractText(Locator locator, String errorMessage) {
51-
String textContent = locator.textContent().trim();
52-
validateNonEmptyText(textContent, errorMessage);
53-
return textContent;
53+
Objects.requireNonNull(locator, errorMessage);
54+
return locator.textContent().trim();
5455
}
5556

5657
protected void takeElementScreenshot(Locator locator, String fileName) {
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.swaglabs.portal.qa.pages;
22

3-
import com.microsoft.playwright.Locator;
43
import com.microsoft.playwright.Page;
54
import com.microsoft.playwright.options.AriaRole;
65

@@ -11,8 +10,7 @@ public SwagLabsCartPage(Page basePage) {
1110
}
1211

1312
public boolean isCheckoutButtonClicked() {
14-
Locator checkoutButton = locators.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Checkout"));
15-
clickElement(checkoutButton);
13+
clickElement(locators.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Checkout")));
1614
return true;
1715
}
1816
}

src/main/java/io/swaglabs/portal/qa/pages/SwagLabsCheckoutCompletePage.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.swaglabs.portal.qa.pages;
22

3-
import com.microsoft.playwright.Locator;
43
import com.microsoft.playwright.Page;
54
import com.microsoft.playwright.options.AriaRole;
65

@@ -19,8 +18,7 @@ public String getOrderCompleteText() {
1918
}
2019

2120
public boolean isBackHomeButtonClicked() {
22-
Locator backHomeButton = locators.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Back Home"));
23-
clickElement(backHomeButton);
21+
clickElement(locators.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Back Home")));
2422
return true;
2523
}
2624
}
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.swaglabs.portal.qa.pages;
22

3-
import com.microsoft.playwright.Locator;
43
import com.microsoft.playwright.Page;
54
import com.microsoft.playwright.options.AriaRole;
65

@@ -11,8 +10,7 @@ public SwagLabsCheckoutOverviewPage(Page basePage) {
1110
}
1211

1312
public boolean isFinishButtonClicked() {
14-
Locator finishButton = locators.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Finish"));
15-
clickElement(finishButton);
13+
clickElement(locators.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Finish")));
1614
return true;
1715
}
1816
}

src/main/java/io/swaglabs/portal/qa/pages/SwagLabsCheckoutPage.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.swaglabs.portal.qa.pages;
22

3-
import com.microsoft.playwright.Locator;
43
import com.microsoft.playwright.Page;
54
import io.swaglabs.portal.qa.constants.KeyboardEvents;
65

@@ -11,26 +10,22 @@ public SwagLabsCheckoutPage(Page basePage) {
1110
}
1211

1312
public boolean isFirstNameEntered(String firstName) {
14-
Locator firstNameInputBox = locators.getByPlaceholder("First Name");
15-
fillText(firstNameInputBox, firstName);
13+
fillText(locators.getByPlaceholder("First Name"), firstName);
1614
return true;
1715
}
1816

1917
public boolean isLastNameEntered(String lastName) {
20-
Locator lastNameInputBox = locators.getByPlaceholder("Last Name");
21-
fillText(lastNameInputBox, lastName);
18+
fillText(locators.getByPlaceholder("Last Name"), lastName);
2219
return true;
2320
}
2421

2522
public boolean isPostalCodeEntered(String postalCode) {
26-
Locator postalCodeInputBox = locators.getByPlaceholder("Zip/Postal Code");
27-
fillText(postalCodeInputBox, postalCode);
23+
fillText(locators.getByPlaceholder("Zip/Postal Code"), postalCode);
2824
return true;
2925
}
3026

3127
public boolean isContinueButtonClicked() {
32-
Locator continueButton = locators.getByText("Continue");
33-
continueButton.press(KeyboardEvents.ENTER.getDescription());
28+
locators.getByText("Continue").press(KeyboardEvents.ENTER.getDescription());
3429
return true;
3530
}
3631

src/main/java/io/swaglabs/portal/qa/pages/SwagLabsHomePage.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.microsoft.playwright.Page;
55
import com.microsoft.playwright.options.AriaRole;
66
import io.swaglabs.portal.qa.exceptions.WebPageException;
7+
import io.swaglabs.portal.qa.utils.WebPageUtils;
78

89
import java.util.List;
910

@@ -28,7 +29,9 @@ public boolean isHamburgerButtonEnabled() {
2829
public String getAllItemsText() {
2930
Locator openMenuButton = locators.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Open Menu"));
3031
clickElement(openMenuButton);
31-
String hamburgerMenuList = String.join("", locators.getPageLocator(".bm-menu").allTextContents());
32+
Locator hamburgerMenu = locators.getPageLocator(".bm-menu");
33+
WebPageUtils.clickUntilCondition(openMenuButton, hamburgerMenu::isVisible, 5);
34+
String hamburgerMenuList = String.join("", hamburgerMenu.allTextContents());
3235
validateNonEmptyText(hamburgerMenuList, "Hamburger Menu List is empty!");
3336
return hamburgerMenuList;
3437
}
@@ -38,12 +41,8 @@ public int addAllItemsToShoppingCart() {
3841
"#add-to-cart-sauce-labs-bike-light", "#add-to-cart-sauce-labs-bolt-t-shirt",
3942
"#add-to-cart-sauce-labs-fleece-jacket", "#add-to-cart-sauce-labs-onesie",
4043
"button[id='add-to-cart-test.allthethings()-t-shirt-(red)']");
41-
itemLocators.forEach(item -> {
42-
Locator shoppingItem = locators.getPageLocator(item);
43-
clickElement(shoppingItem);
44-
});
45-
Locator shoppingCartBadge = locators.getPageLocator(".shopping_cart_badge");
46-
return validateAndParseShoppingCartBadge(shoppingCartBadge);
44+
itemLocators.forEach(item -> clickElement(locators.getPageLocator(item)));
45+
return validateAndParseShoppingCartBadge(locators.getPageLocator(".shopping_cart_badge"));
4746
}
4847

4948
private int validateAndParseShoppingCartBadge(Locator shoppingCartBadge) {
@@ -53,19 +52,16 @@ private int validateAndParseShoppingCartBadge(Locator shoppingCartBadge) {
5352
}
5453

5554
public boolean isProductSelected(String productName) {
56-
List<Locator> productNameList = locators.getPageLocator(".inventory_item_name").all();
57-
Locator product = productNameList.stream()
55+
Locator product = locators.getPageLocator(".inventory_item_name").all().stream()
5856
.filter(productItem -> getTextContent(productItem).equalsIgnoreCase(productName))
5957
.findFirst().orElseThrow(() -> new WebPageException("Product Name not found!"));
6058
clickElement(product);
6159
return true;
6260
}
6361

6462
public boolean isLogoutSuccess() {
65-
Locator openMenuButton = locators.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Open Menu"));
66-
clickElement(openMenuButton);
67-
Locator logoutButton = locators.getPageLocator("#logout_sidebar_link");
68-
clickElement(logoutButton);
63+
clickElement(locators.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Open Menu")));
64+
clickElement(locators.getPageLocator("#logout_sidebar_link"));
6965
return basePage.title().equalsIgnoreCase("Swag Labs");
7066
}
7167
}

src/main/java/io/swaglabs/portal/qa/pages/SwagLabsLoginPage.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.swaglabs.portal.qa.pages;
22

3-
import com.microsoft.playwright.Locator;
43
import com.microsoft.playwright.Page;
54
import io.swaglabs.portal.qa.constants.KeyboardEvents;
65
import io.swaglabs.portal.qa.constants.WebPortalConstants;
@@ -34,14 +33,12 @@ public String getAllUserPasswords() {
3433
}
3534

3635
public boolean isUserNameEntered(String userName) {
37-
Locator userNameInputBox = locators.getByPlaceholder("Username");
38-
fillText(userNameInputBox, userName);
36+
fillText(locators.getByPlaceholder("Username"), userName);
3937
return true;
4038
}
4139

4240
public boolean isPasswordEntered(String password) {
43-
Locator passwordInputBox = locators.getByPlaceholder("Password");
44-
fillText(passwordInputBox, password);
41+
fillText(locators.getByPlaceholder("Password"), password);
4542
return true;
4643
}
4744

@@ -52,8 +49,7 @@ public boolean isHomePageLanded() {
5249
public boolean isLoginSuccess() {
5350
validateAction(isUserNameEntered(WebPortalConstants.USERNAME), "User Name not entered!");
5451
validateAction(isPasswordEntered(WebPortalConstants.PASSWORD), "Password not entered!");
55-
Locator loginButton = locators.getByText("Login");
56-
loginButton.press(KeyboardEvents.ENTER.getDescription());
52+
locators.getByText("Login").press(KeyboardEvents.ENTER.getDescription());
5753
validateAction(isHomePageLanded(), "Login Failed for the username: " + WebPortalConstants.USERNAME);
5854
return true;
5955
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package io.swaglabs.portal.qa.utils;
2+
3+
import com.microsoft.playwright.Locator;
4+
import com.microsoft.playwright.PlaywrightException;
5+
import io.swaglabs.portal.qa.exceptions.WebPageException;
6+
import lombok.AccessLevel;
7+
import lombok.NoArgsConstructor;
8+
import lombok.extern.slf4j.Slf4j;
9+
10+
import java.util.Objects;
11+
import java.util.concurrent.TimeUnit;
12+
import java.util.function.Supplier;
13+
14+
@Slf4j
15+
@NoArgsConstructor(access = AccessLevel.PRIVATE)
16+
public class WebPageUtils {
17+
18+
private static final long BASE_DELAY_MS = 1000L;
19+
private static final long MAX_DELAY_MS = 5000L;
20+
private static final long CLICK_TIMEOUT_MS = 2000L;
21+
private static final double BACKOFF_FACTOR = 1.5;
22+
23+
public static void clickUntilCondition(Locator clickTarget, Supplier<Boolean> condition, int maxRetries) {
24+
validateInputs(clickTarget, condition, maxRetries);
25+
int attempt = 0;
26+
while (attempt < maxRetries) {
27+
attempt++;
28+
try {
29+
if (checkCondition(condition)) {
30+
return;
31+
}
32+
logAttempt(clickTarget, attempt, maxRetries);
33+
performClick(clickTarget);
34+
sleepWithBackoff(attempt);
35+
} catch (PlaywrightException e) {
36+
handleRetryFailure(e, attempt, maxRetries);
37+
} catch (InterruptedException e) {
38+
handleInterruption(e);
39+
return; // Stop execution on interruption
40+
}
41+
}
42+
verifyFinalCondition(condition, maxRetries);
43+
}
44+
45+
private static void validateInputs(Locator clickTarget, Supplier<Boolean> condition, int maxRetries) {
46+
Objects.requireNonNull(clickTarget, "Click target locator cannot be null");
47+
Objects.requireNonNull(condition, "Condition supplier cannot be null");
48+
if (maxRetries <= 0) {
49+
throw new IllegalArgumentException("Max retries must be positive");
50+
}
51+
}
52+
53+
private static boolean checkCondition(Supplier<Boolean> condition) {
54+
try {
55+
return Boolean.TRUE.equals(condition.get());
56+
} catch (Exception e) {
57+
log.error("Condition check failed: {}", e.getMessage());
58+
return false;
59+
}
60+
}
61+
62+
private static void logAttempt(Locator target, int attempt, int maxRetries) {
63+
log.debug("Attempt {}/{}: Clicking element {}", attempt, maxRetries, target);
64+
}
65+
66+
private static void performClick(Locator target) {
67+
try {
68+
target.click(new Locator.ClickOptions().setTimeout(CLICK_TIMEOUT_MS));
69+
} catch (PlaywrightException e) {
70+
throw new PlaywrightException("Click failed: " + e.getMessage(), e);
71+
}
72+
}
73+
74+
private static void sleepWithBackoff(int attempt) throws InterruptedException {
75+
long delayMs = (long) Math.min(BASE_DELAY_MS * Math.pow(BACKOFF_FACTOR, (double) attempt - 1), MAX_DELAY_MS);
76+
TimeUnit.MILLISECONDS.sleep(delayMs);
77+
}
78+
79+
private static void handleRetryFailure(PlaywrightException e, int attempt, int maxRetries) {
80+
log.warn("Click attempt {} failed: {}", attempt, e.getMessage());
81+
if (attempt == maxRetries) {
82+
log.error("Failed after {} attempts. Last error: {}", maxRetries, e.getMessage());
83+
throw new WebPageException(String.format("Failed after %d attempts. Last error: %s", maxRetries, e.getMessage()));
84+
}
85+
}
86+
87+
private static void handleInterruption(InterruptedException e) {
88+
Thread.currentThread().interrupt();
89+
log.error("Operation interrupted during click retries: {}", e.getMessage());
90+
throw new WebPageException(String.format("Operation interrupted during click retries: %s", e.getMessage()));
91+
}
92+
93+
private static void verifyFinalCondition(Supplier<Boolean> condition, int maxRetries) {
94+
try {
95+
if (!Boolean.TRUE.equals(condition.get())) {
96+
log.error("Condition not met after {} click attempts", maxRetries);
97+
throw new WebPageException(String.format("Condition not met after %d click attempts", maxRetries));
98+
}
99+
} catch (Exception e) {
100+
log.error("Failed to verify final condition: {}", e.getMessage());
101+
throw new WebPageException(String.format("Failed to verify final condition: %s", e.getMessage()));
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)