Let us put everything together. This lesson shows a complete, copy-paste-ready project structure with Allure integrated into a Selenium + TestNG framework using the Banking Portal as the test target. This is what a real framework looks like in production.
selenium-framework/
├── pom.xml
├── testng.xml
├── src/
│ └── test/
│ ├── java/
│ │ └── com/practice/
│ │ ├── base/
│ │ │ └── BaseTest.java
│ │ ├── pages/
│ │ │ ├── BasePage.java
│ │ │ ├── LoginPage.java
│ │ │ ├── DashboardPage.java
│ │ │ └── FundTransferPage.java
│ │ ├── listeners/
│ │ │ └── AllureScreenshotListener.java
│ │ ├── utils/
│ │ │ ├── DriverFactory.java
│ │ │ └── AllureUtils.java
│ │ └── tests/
│ │ ├── LoginTests.java
│ │ └── FundTransferTests.java
│ └── resources/
│ └── allure.properties
└── target/
├── allure-results/ (generated after mvn test)
└── allure-report/ (generated after allure generate)package com.practice.utils;
import io.qameta.allure.Allure;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import java.io.ByteArrayInputStream;
public class AllureUtils {
public static void attachScreenshot(WebDriver driver, String name) {
byte[] screenshot = ((TakesScreenshot) driver)
.getScreenshotAs(OutputType.BYTES);
Allure.addAttachment(
name, "image/png",
new ByteArrayInputStream(screenshot), ".png"
);
}
public static void attachText(String name, String content) {
Allure.addAttachment(name, "text/plain", content);
}
public static void attachJson(String name, String json) {
Allure.addAttachment(name, "application/json", json);
}
public static void attachHtml(String name, String html) {
Allure.addAttachment(name, "text/html", html);
}
public static void logStep(String description) {
Allure.step(description);
}
}package com.practice.pages;
import io.qameta.allure.Step;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public abstract class BasePage {
protected WebDriver driver;
protected WebDriverWait wait;
public BasePage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
protected void type(By locator, String text) {
WebElement element = wait.until(
ExpectedConditions.visibilityOfElementLocated(locator));
element.clear();
element.sendKeys(text);
}
protected void click(By locator) {
wait.until(ExpectedConditions.elementToBeClickable(locator)).click();
}
protected String getText(By locator) {
return wait.until(
ExpectedConditions.visibilityOfElementLocated(locator)).getText();
}
protected boolean isDisplayed(By locator) {
try {
return wait.until(
ExpectedConditions.visibilityOfElementLocated(locator))
.isDisplayed();
} catch (Exception e) {
return false;
}
}
protected void selectByVisibleText(By locator, String text) {
WebElement element = wait.until(
ExpectedConditions.visibilityOfElementLocated(locator));
new Select(element).selectByVisibleText(text);
}
}package com.practice.tests;
import com.practice.base.BaseTest;
import com.practice.pages.DashboardPage;
import com.practice.pages.FundTransferPage;
import com.practice.pages.LoginPage;
import com.practice.utils.AllureUtils;
import io.qameta.allure.*;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@Epic("Banking Portal")
@Feature("Fund Transfer")
public class FundTransferTests extends BaseTest {
private FundTransferPage transferPage;
@BeforeMethod
public void navigateToTransferPage() {
LoginPage loginPage = new LoginPage(driver);
DashboardPage dashboard = loginPage.loginAs("testuser", "Pass@123");
transferPage = dashboard.goToFundTransfer();
}
@Test
@Story("Valid Transfer")
@Description("Verify that a user can transfer $500 from Savings "
+ "to another account and see a confirmation message")
@Severity(SeverityLevel.BLOCKER)
@Owner("shivam")
public void testSuccessfulFundTransfer() {
transferPage
.selectFromAccount("Savings")
.enterBeneficiaryAccount("1234567890")
.enterAmount("500")
.enterRemark("Rent payment");
var confirmation = transferPage.submitTransfer();
Assert.assertTrue(confirmation.isTransferSuccessful(),
"Transfer confirmation should be displayed");
Assert.assertEquals(confirmation.getTransferredAmount(),
"$500.00", "Transferred amount should match");
AllureUtils.attachText("Transfer Details",
"From: Savings | To: 1234567890 | Amount: $500");
}
@Test
@Story("Insufficient Balance")
@Description("Verify that transferring more than available balance "
+ "shows an insufficient funds error")
@Severity(SeverityLevel.CRITICAL)
public void testInsufficientBalance() {
transferPage
.selectFromAccount("Savings")
.enterBeneficiaryAccount("1234567890")
.enterAmount("999999999")
.enterRemark("Large transfer");
transferPage.submitTransfer();
Assert.assertTrue(transferPage.isInsufficientFundsErrorDisplayed(),
"Insufficient funds error should be displayed");
}
@Test
@Story("Empty Fields Validation")
@Description("Verify that submitting the transfer form with empty "
+ "fields shows validation messages")
@Severity(SeverityLevel.NORMAL)
public void testEmptyFieldsValidation() {
transferPage.submitTransfer();
Assert.assertTrue(
transferPage.isBeneficiaryValidationShown(),
"Beneficiary account validation should appear");
Assert.assertTrue(
transferPage.isAmountValidationShown(),
"Amount validation should appear");
}
@Test
@Story("Invalid Account")
@Description("Verify that an invalid beneficiary account number "
+ "shows an appropriate error")
@Severity(SeverityLevel.NORMAL)
@Issue("BUG-2045")
public void testInvalidBeneficiaryAccount() {
transferPage
.selectFromAccount("Savings")
.enterBeneficiaryAccount("INVALID")
.enterAmount("100")
.enterRemark("Test");
transferPage.submitTransfer();
Assert.assertTrue(
transferPage.isInvalidAccountErrorDisplayed(),
"Invalid account error should be displayed");
}
}In real projects, the @BeforeMethod login logic would be in BaseTest or a helper method. It is shown here in the test class for clarity. Always keep test setup DRY — if 10 test classes need to log in first, that login code should be in one place.
Key Point: A complete framework ties together annotations, @Step on page objects, screenshot listeners, and AllureUtils — all working together to produce a report that tells the full story of every test.