---
name: Selenium Advanced POM Framework
description: Advanced Selenium WebDriver framework with three Page Object Model patterns (Basic POM, Improved POM, Page Factory), retry mechanisms, Allure reporting, Excel data-driven testing, and Selenoid grid support.
version: 1.0.0
author: thetestingacademy
license: MIT
tags: [selenium, java, pom, page-object-model, page-factory, allure, testng, data-driven, selenoid, retry]
testingTypes: [e2e]
frameworks: [selenium]
languages: [java]
domains: [web]
agents: [claude-code, cursor, github-copilot, windsurf, codex, aider, continue, cline, zed, bolt]
---
# Selenium Advanced POM Framework Skill
You are an expert QA automation engineer specializing in advanced Selenium WebDriver frameworks with Java. When the user asks you to build, review, or debug a Selenium test automation framework, follow these detailed instructions covering three Page Object Model patterns, retry mechanisms, data-driven testing, and cloud grid execution.
## Core Principles
1. **Three POM patterns** -- Implement Basic POM, Improved POM (with inheritance), and Page Factory depending on project needs.
2. **CommonToAllPage base class** -- Centralize reusable page actions (click, type, getText) in a single base class that all page objects extend.
3. **DriverManager singleton** -- Manage WebDriver lifecycle through a centralized DriverManager with static getter/setter and multi-browser support.
4. **Listener-driven reporting** -- Use TestNG listeners (ITestListener, IRetryAnalyzer, IAnnotationTransformer) for automatic screenshots, retry logic, and Allure integration.
5. **Data externalization** -- Store test data in properties files and Excel spreadsheets, never hardcoded in test methods.
6. **Wait strategy hierarchy** -- Prefer explicit waits via WebDriverWait, use fluent waits for polling scenarios, avoid Thread.sleep entirely.
7. **Environment-specific suites** -- Maintain separate TestNG XML suite files for QA, staging, and production environments.
8. **Grid-ready architecture** -- Design the framework to run locally or on Selenoid/Docker grid without code changes.
## Project Structure
```
src/
main/java/com/thetestingacademy/
base/
CommonToAllPage.java # Base class for all page objects
driver/
DriverManager.java # WebDriver lifecycle management
pages/
pageFactory/vwo/
LoginPage_PF.java # Page Factory pattern (@FindBy)
DashBoardPage_PF.java
pageObjectModel/
normal_POM/normal_POM/vwo/
LoginPage.java # Basic POM pattern
DashBoardPage.java
ForgetPasswordPage.java
FreeTrial.java
SupportPage.java
normal_POM/imporved_POM/vwo/
LoginPage.java # Improved POM (extends CommonToAllPage)
DashBoardPage.java
utils/
PropertiesReader.java # Config from .properties files
WaitHelpers.java # Explicit, Fluent, Implicit waits
main/resources/
data.properties # Test configuration & credentials
log4j2.xml # Logging configuration
test/java/com/thetestingacademy/
base/
CommonToAllTest.java # Base test class (setUp/tearDown)
listeners/
RetryAnalyzer.java # IRetryAnalyzer implementation
RetryListener.java # IAnnotationTransformer for global retry
ScreenshotListener.java # Screenshot on failure + Allure attach
tests/
sample/
TestCaseBoilerPlate.java # Test template
pageFactoryTests/vwo/
TestVWOLogin_PF.java # Page Factory tests
pageObjectModelTests/vwo/
TestVWOLogin_01_NormalScript_POM.java
TestVWOLogin_02_PropertyReader_DriverManager_POM_CommonToAll.java
TestVWOLogin_03_Retry.java # Tests with retry logic
utilexcel/
UtilExcel.java # Apache POI Excel reader
test/resources/
TestData.xlsx # Excel test data
testng_vwo_normal_s1.xml # Basic test suite
testng_vwo_qa.xml # QA environment suite
testng_vwo_prod.xml # Production suite
testng_vwo_retry.xml # Retry + listeners suite
pom.xml
```
## Maven Dependencies
```xml
org.seleniumhq.selenium
selenium-java
4.31.0
org.testng
testng
7.11.0
test
io.qameta.allure
allure-testng
2.26.0
org.uncommons
reportng
1.1.2
org.assertj
assertj-core
3.25.1
test
org.apache.poi
poi-ooxml
5.2.4
org.apache.logging.log4j
log4j-core
3.0.0-beta2
maven-compiler-plugin
3.11.0
11
11
maven-surefire-plugin
3.2.5
testng.xml
```
## Driver Management
```java
package com.thetestingacademy.driver;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
public class DriverManager {
private static WebDriver driver;
public static WebDriver getDriver() {
return driver;
}
public static void setDriver(WebDriver driver) {
DriverManager.driver = driver;
}
public static void init() {
String browser = PropertiesReader.readKey("browser");
switch (browser.toLowerCase()) {
case "chrome":
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments("--guest");
driver = new ChromeDriver(chromeOptions);
break;
case "edge":
driver = new EdgeDriver();
break;
case "firefox":
driver = new FirefoxDriver();
break;
default:
driver = new ChromeDriver();
}
driver.manage().window().maximize();
}
public static void down() {
if (driver != null) {
driver.quit();
driver = null;
}
}
}
```
## Page Object Model -- Pattern 1: Basic POM
The simplest POM pattern where each page class owns its locators and uses `driver.findElement()` directly.
```java
package com.thetestingacademy.pages.pageObjectModel.normal_POM.normal_POM.vwo;
import com.thetestingacademy.utils.PropertiesReader;
import com.thetestingacademy.utils.WaitHelpers;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class LoginPage {
private WebDriver driver;
// Locators
private By username = By.id("login-username");
private By password = By.id("login-password");
private By signButton = By.id("js-login-btn");
private By error_message = By.cssSelector("[data-qa='error-text']");
public LoginPage(WebDriver driver) {
this.driver = driver;
}
public String loginToVWOLoginInvalidCreds(String user, String pwd) {
driver.get(PropertiesReader.readKey("url"));
driver.findElement(username).sendKeys(user);
driver.findElement(password).sendKeys(pwd);
driver.findElement(signButton).click();
WaitHelpers.checkVisibility(driver, error_message, 3);
return driver.findElement(error_message).getText();
}
public void loginToVWOLoginValidCreds(String user, String pwd) {
driver.get(PropertiesReader.readKey("url"));
driver.findElement(username).sendKeys(user);
driver.findElement(password).sendKeys(pwd);
driver.findElement(signButton).click();
}
}
```
## Page Object Model -- Pattern 2: Improved POM (Inheritance)
Extends `CommonToAllPage` to reuse common actions like `clickElement()`, `enterInput()`, `getText()`. Eliminates repeated `driver.findElement()` calls.
```java
package com.thetestingacademy.pages.pageObjectModel.normal_POM.imporved_POM.vwo;
import com.thetestingacademy.base.CommonToAllPage;
import com.thetestingacademy.utils.WaitHelpers;
import org.openqa.selenium.By;
public class LoginPage extends CommonToAllPage {
private By username = By.id("login-username");
private By password = By.id("login-password");
private By signButton = By.id("js-login-btn");
private By error_message = By.cssSelector("[data-qa='error-text']");
public String loginToVWOLoginInvalidCreds(String user, String pwd) {
openVWOUrl();
enterInput(username, user);
enterInput(password, pwd);
clickElement(signButton);
WaitHelpers.checkVisibility(getDriver(), error_message);
return getText(error_message);
}
}
```
## Page Object Model -- Pattern 3: Page Factory
Uses Selenium's `@FindBy` annotations for declarative element location. Elements are automatically initialized via `PageFactory.initElements()`.
```java
package com.thetestingacademy.pages.pageFactory.vwo;
import com.thetestingacademy.base.CommonToAllPage;
import com.thetestingacademy.utils.PropertiesReader;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class LoginPage_PF extends CommonToAllPage {
@FindBy(id = "login-username")
private WebElement username;
@FindBy(name = "password")
private WebElement password;
@FindBy(id = "js-login-btn")
private WebElement signButton;
@FindBy(css = "[data-qa='error-text']")
private WebElement error_message;
public LoginPage_PF(WebDriver driver) {
PageFactory.initElements(driver, this);
}
public String loginToVWOInvalidCreds() {
openVWOUrl();
enterInput(username, PropertiesReader.readKey("invalid_username"));
enterInput(password, PropertiesReader.readKey("invalid_password"));
clickElement(signButton);
return getText(error_message);
}
}
```
## CommonToAllPage -- Base Page Object
```java
package com.thetestingacademy.base;
import com.thetestingacademy.driver.DriverManager;
import com.thetestingacademy.utils.PropertiesReader;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class CommonToAllPage {
public WebDriver getDriver() {
return DriverManager.getDriver();
}
public void clickElement(By by) {
getDriver().findElement(by).click();
}
public void clickElement(WebElement element) {
element.click();
}
public void enterInput(By by, String text) {
getDriver().findElement(by).sendKeys(text);
}
public void enterInput(WebElement element, String text) {
element.sendKeys(text);
}
public String getText(By by) {
return getDriver().findElement(by).getText();
}
public String getText(WebElement element) {
return element.getText();
}
public void openVWOUrl() {
getDriver().get(PropertiesReader.readKey("url"));
}
public void openOrangeHRMUrl() {
getDriver().get(PropertiesReader.readKey("ohr_url"));
}
}
```
## CommonToAllTest -- Base Test Class
```java
package com.thetestingacademy.base;
import com.thetestingacademy.driver.DriverManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openqa.selenium.WebDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
public class CommonToAllTest {
protected WebDriver driver;
protected Logger logger = LogManager.getLogger(this.getClass());
public WebDriver getDriver() {
return DriverManager.getDriver();
}
@BeforeMethod
public void setUp() {
DriverManager.init();
driver = DriverManager.getDriver();
}
@AfterMethod
public void tearDown() {
DriverManager.down();
}
}
```
## Wait Helpers
```java
package com.thetestingacademy.utils;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
public class WaitHelpers {
// Implicit Wait
public static void waitImplicitWait(WebDriver driver, int timeInSeconds) {
driver.manage().timeouts().implicitlyWait(timeInSeconds, TimeUnit.SECONDS);
}
// Explicit Wait -- Visibility
public static void checkVisibility(WebDriver driver, By locator, int timeInSeconds) {
new WebDriverWait(driver, Duration.ofSeconds(timeInSeconds))
.until(ExpectedConditions.visibilityOfElementLocated(locator));
}
// Explicit Wait -- Visibility (default 10s)
public static void checkVisibility(WebDriver driver, By locator) {
new WebDriverWait(driver, Duration.ofSeconds(10))
.until(ExpectedConditions.visibilityOfElementLocated(locator));
}
// Explicit Wait -- Text Present
public static void checkVisibilityOfAndTextToBePresentInElement(
WebDriver driver, By locator, String text, int timeInSeconds) {
new WebDriverWait(driver, Duration.ofSeconds(timeInSeconds))
.until(ExpectedConditions.textToBePresentInElementLocated(locator, text));
}
// Explicit Wait -- Presence
public static WebElement presenceOfElement(WebDriver driver, By locator, int timeInSeconds) {
return new WebDriverWait(driver, Duration.ofSeconds(timeInSeconds))
.until(ExpectedConditions.presenceOfElementLocated(locator));
}
// Fluent Wait
public static void checkVisibilityByFluentWait(WebDriver driver, By locator) {
new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(30))
.pollingEvery(Duration.ofMillis(500))
.ignoring(NoSuchElementException.class)
.until(ExpectedConditions.visibilityOfElementLocated(locator));
}
// JVM Sleep (use sparingly)
public static void waitJVM(int timeInMillis) {
try {
Thread.sleep(timeInMillis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
```
## Properties Reader
```java
package com.thetestingacademy.utils;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class PropertiesReader {
public static String readKey(String key) {
Properties properties = new Properties();
try {
FileInputStream fis = new FileInputStream("src/main/resources/data.properties");
properties.load(fis);
} catch (IOException e) {
throw new RuntimeException("Failed to read properties file", e);
}
return properties.getProperty(key);
}
}
```
## Configuration -- data.properties
```properties
# Application URLs
url=https://app.vwo.com
ohr_url=https://awesomeqa.com/hr/web/index.php/auth/login
katalon_url=https://katalon-demo-cura.herokuapp.com/
# Credentials
username=user@example.com
password=SecurePass123
invalid_username=admin@admin.com
invalid_password=Test@2024
error_message=Your email, password, IP address or location did not match
# Browser
browser=Chrome
# Expected values
expected_username=Test User
```
## Writing Tests -- Basic POM Test
```java
package com.thetestingacademy.tests.pageObjectModelTests.vwo;
import com.thetestingacademy.base.CommonToAllTest;
import com.thetestingacademy.pages.pageObjectModel.normal_POM.normal_POM.vwo.DashBoardPage;
import com.thetestingacademy.pages.pageObjectModel.normal_POM.normal_POM.vwo.LoginPage;
import com.thetestingacademy.utils.PropertiesReader;
import io.qameta.allure.Description;
import io.qameta.allure.Owner;
import org.testng.Assert;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class TestVWOLogin_01_NormalScript_POM extends CommonToAllTest {
@Description("Verify that with invalid email and password, error message is shown")
@Owner("Promode")
@Test
public void test_negative_vwo_login() {
LoginPage loginPage = new LoginPage(driver);
String error_msg = loginPage.loginToVWOLoginInvalidCreds(
PropertiesReader.readKey("invalid_username"),
PropertiesReader.readKey("invalid_password")
);
assertThat(error_msg).isNotNull().isNotBlank().isNotEmpty();
Assert.assertEquals(error_msg, PropertiesReader.readKey("error_message"));
}
@Test
public void testLoginPositiveVWO() {
LoginPage loginPage = new LoginPage(driver);
loginPage.loginToVWOLoginValidCreds(
PropertiesReader.readKey("username"),
PropertiesReader.readKey("password")
);
DashBoardPage dashBoardPage = new DashBoardPage(driver);
String usernameLoggedIn = dashBoardPage.loggedInUserName();
Assert.assertEquals(usernameLoggedIn, PropertiesReader.readKey("expected_username"));
}
}
```
## Writing Tests -- Page Factory Test
```java
package com.thetestingacademy.tests.pageFactoryTests.vwo;
import com.thetestingacademy.base.CommonToAllTest;
import com.thetestingacademy.pages.pageFactory.vwo.LoginPage_PF;
import com.thetestingacademy.utils.PropertiesReader;
import org.testng.Assert;
import org.testng.annotations.Test;
public class TestVWOLogin_PF extends CommonToAllTest {
@Test
public void testLoginNegativeVWO_PF() {
logger.info("Starting the Page Factory test");
LoginPage_PF loginPage_PF = new LoginPage_PF(driver);
String error_msg = loginPage_PF.loginToVWOInvalidCreds();
logger.info("Error msg: " + error_msg);
Assert.assertEquals(error_msg, PropertiesReader.readKey("error_message"));
}
}
```
## Data-Driven Testing with Excel
```java
package com.thetestingacademy.utilexcel;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
public class UtilExcel {
public static Object[][] getTestDataFromExcel(String sheetName) {
Object[][] data = null;
try {
FileInputStream fis = new FileInputStream("src/test/resources/TestData.xlsx");
Workbook workbook = new XSSFWorkbook(fis);
Sheet sheet = workbook.getSheet(sheetName);
int rowCount = sheet.getPhysicalNumberOfRows();
int colCount = sheet.getRow(0).getPhysicalNumberOfCells();
data = new Object[rowCount - 1][colCount]; // skip header row
for (int i = 1; i < rowCount; i++) {
Row row = sheet.getRow(i);
for (int j = 0; j < colCount; j++) {
Cell cell = row.getCell(j);
data[i - 1][j] = getCellValue(cell);
}
}
workbook.close();
} catch (IOException e) {
throw new RuntimeException("Failed to read Excel file", e);
}
return data;
}
private static Object getCellValue(Cell cell) {
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
return cell.getNumericCellValue();
case BOOLEAN:
return cell.getBooleanCellValue();
default:
return "";
}
}
}
```
### Using Excel Data Provider in Tests
```java
@DataProvider(name = "loginData")
public Object[][] getLoginData() {
return UtilExcel.getTestDataFromExcel("LoginData");
}
@Test(dataProvider = "loginData")
public void testDataDrivenLogin(String email, String password, String expectedResult) {
LoginPage loginPage = new LoginPage(driver);
String result = loginPage.loginToVWOLoginInvalidCreds(email, password);
Assert.assertEquals(result, expectedResult);
}
```
## Retry Mechanism
### RetryAnalyzer
```java
package com.thetestingacademy.listeners;
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
public class RetryAnalyzer implements IRetryAnalyzer {
private int retryCount = 0;
private static final int maxRetryCount = 1;
@Override
public boolean retry(ITestResult iTestResult) {
if (retryCount < maxRetryCount) {
retryCount++;
return true;
}
return false;
}
}
```
### RetryListener (Global Retry)
```java
package com.thetestingacademy.listeners;
import org.testng.IAnnotationTransformer;
import org.testng.annotations.ITestAnnotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class RetryListener implements IAnnotationTransformer {
@Override
public void transform(ITestAnnotation annotation, Class testClass,
Constructor testConstructor, Method testMethod) {
annotation.setRetryAnalyzer(RetryAnalyzer.class);
}
}
```
## Screenshot Listener with Allure
```java
package com.thetestingacademy.listeners;
import com.thetestingacademy.driver.DriverManager;
import io.qameta.allure.Allure;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.testng.ITestListener;
import org.testng.ITestResult;
import org.testng.Reporter;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ScreenshotListener implements ITestListener {
@Override
public void onTestFailure(ITestResult result) {
WebDriver driver = DriverManager.getDriver();
String methodName = result.getName();
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
if (driver != null) {
try {
File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
String screenshotPath = "failure_screenshots/" + methodName + "_" + timestamp + ".png";
FileUtils.copyFile(scrFile, new File(screenshotPath));
Reporter.log(" Screenshot");
Allure.addAttachment("Screenshot on Failure", "image/png",
new java.io.FileInputStream(screenshotPath), "png");
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onTestStart(ITestResult result) {
System.out.println("Starting test: " + result.getName());
}
@Override
public void onTestSuccess(ITestResult result) {
System.out.println("Test passed: " + result.getName());
}
}
```
## TestNG XML Configuration
### Basic Suite
```xml
```
### Retry Suite with Listeners
```xml
```
### Multi-Environment Suite
```xml
```
## Selenoid Docker Grid Integration
```java
// Remote WebDriver configuration for Selenoid
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
public static void initRemote(String browser) {
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setBrowserName(browser);
capabilities.setVersion("latest");
capabilities.setCapability("enableVNC", true);
capabilities.setCapability("enableVideo", true);
try {
driver = new RemoteWebDriver(
new URL("http://localhost:4444/wd/hub"),
capabilities
);
} catch (MalformedURLException e) {
throw new RuntimeException("Invalid Selenoid hub URL", e);
}
driver.manage().window().maximize();
}
```
### Selenoid docker-compose.yml
```yaml
version: '3'
services:
selenoid:
image: aerokube/selenoid:latest
ports:
- "4444:4444"
volumes:
- "./browsers.json:/etc/selenoid/browsers.json"
- "/var/run/docker.sock:/var/run/docker.sock"
selenoid-ui:
image: aerokube/selenoid-ui:latest
ports:
- "8080:8080"
command: ["--selenoid-uri", "http://selenoid:4444"]
```
## Allure Reporting
### Annotations
```java
@Test
@Description("Verify login with invalid credentials shows error")
@Owner("Promode")
@Severity(SeverityLevel.CRITICAL)
@Story("Login Validation")
@Feature("Authentication")
public void testInvalidLogin() {
Allure.step("Navigate to login page");
Allure.step("Enter invalid credentials");
Allure.step("Verify error message");
// test implementation
}
```
### Generate and Open Report
```bash
# Run tests
mvn clean test -Dsurefire.suiteXmlFiles=testng_vwo_retry.xml
# Generate Allure report
mvn allure:report
# Or use Allure CLI
allure generate target/allure-results --clean -o allure-report
allure open allure-report
```
## Log4j2 Configuration
```xml
```
## Best Practices
1. **Choose the right POM pattern** -- Use Basic POM for small projects, Improved POM for medium projects needing code reuse, and Page Factory for large projects with many elements.
2. **Centralize driver management** -- Always use DriverManager to create and destroy WebDriver instances, never instantiate drivers in test classes directly.
3. **Externalize all test data** -- Use `data.properties` for configuration and `TestData.xlsx` for parameterized test data. Never hardcode URLs, credentials, or expected values.
4. **Use explicit waits strategically** -- Place waits in page objects, not test classes. Prefer `WebDriverWait` with `ExpectedConditions` over implicit waits.
5. **Implement retry for flaky tests** -- Use `RetryAnalyzer` with a max retry count of 1-2. Apply globally via `RetryListener` in TestNG XML.
6. **Capture screenshots on failure** -- Use `ScreenshotListener` to automatically capture and attach screenshots to Allure reports on every test failure.
7. **Maintain separate test suites** -- Create environment-specific TestNG XML files (QA, staging, prod) with appropriate test groups and parameters.
8. **Use AssertJ for fluent assertions** -- Combine TestNG's `Assert.assertEquals` with AssertJ's `assertThat` for readable, chainable assertions.
9. **Log meaningfully** -- Use Log4j2 in test classes to log test steps, making debugging easier when tests fail in CI.
10. **Design for grid execution** -- Keep the framework grid-ready by abstracting driver creation so tests run identically on local browsers and Selenoid/Docker.
## Anti-Patterns to Avoid
1. **`Thread.sleep()` for synchronization** -- Always use explicit waits with conditions. Sleep causes brittle, slow tests.
2. **Hardcoded test data in methods** -- Extract to properties files or Excel. Hardcoded data makes maintenance difficult.
3. **Direct `driver.findElement()` in test classes** -- Always go through page objects. Tests should only call page object methods.
4. **Mixing POM patterns in one project** -- Pick one pattern (or deliberately layer them) and be consistent across the framework.
5. **Not quitting the driver in tearDown** -- Always call `DriverManager.down()` in `@AfterMethod` to prevent zombie browser processes.
6. **Global implicit waits** -- They conflict with explicit waits and cause unpredictable timeouts. Use explicit waits only.
7. **Monolithic test methods** -- Break long test scenarios into smaller, focused test methods with clear descriptions.
8. **Ignoring test failure screenshots** -- Always configure `ScreenshotListener` and attach evidence to reports for debugging.
9. **Not using test groups** -- Tag tests with groups (smoke, regression, e2e) for selective execution across environments.
10. **Running tests only locally** -- Set up Selenoid or a cloud grid early. Tests that only run locally miss cross-browser issues.