21.分页测试

21. 分页测试

目录

  1. 分页测试概述
  2. 分页组件的常见类型
  3. 分页元素定位策略
  4. 基础分页测试方法
  5. 高级分页测试技巧
  6. 完整代码示例
  7. 常见问题与解决方案
  8. 最佳实践

分页测试概述

什么是分页测试?

分页测试是指对网页中的分页功能进行自动化测试,确保用户能够正确地在不同页面之间导航,查看数据列表的不同部分。

为什么需要分页测试?

  1. 数据完整性验证:确保所有页面的数据都能正确显示
  2. 导航功能验证:验证前一页、后一页、首页、末页等按钮功能
  3. 页码跳转验证:测试直接输入页码跳转功能
  4. 边界条件测试:测试第一页、最后一页的特殊情况
  5. 用户体验保障:确保分页操作流畅,无异常情况

分页测试的挑战

  • 动态加载内容
  • 异步请求处理
  • 页面状态变化
  • 元素定位的动态性
  • 数据量大时的性能问题

分页组件的常见类型

1. 数字分页

<!-- 典型的数字分页结构 -->
<div class="pagination">
    <a href="#" class="prev">上一页</a>
    <a href="#" class="page-num active">1</a>
    <a href="#" class="page-num">2</a>
    <a href="#" class="page-num">3</a>
    <a href="#" class="next">下一页</a>
</div>

2. 简单分页(仅上一页/下一页)

<div class="simple-pagination">
    <button class="prev-btn">← 上一页</button>
    <span class="page-info">第 1 页,共 10 页</span>
    <button class="next-btn">下一页 →</button>
</div>

3. 输入框跳转分页

<div class="jump-pagination">
    <span>跳转到第</span>
    <input type="text" class="page-input" />
    <span>页</span>
    <button class="jump-btn">确定</button>
</div>

4. 下拉选择分页

<div class="select-pagination">
    <select class="page-select">
        <option value="1">第1页</option>
        <option value="2">第2页</option>
        <option value="3">第3页</option>
    </select>
</div>

分页元素定位策略

1. 常用定位方法

// 通过类名定位分页容器
WebElement paginationContainer = driver.findElement(By.className("pagination"));

// 定位上一页按钮
WebElement prevButton = driver.findElement(By.className("prev"));
// 或者通过文本内容
WebElement prevButton = driver.findElement(By.linkText("上一页"));

// 定位下一页按钮
WebElement nextButton = driver.findElement(By.className("next"));

// 定位当前页码
WebElement currentPage = driver.findElement(By.className("active"));

// 定位所有页码
List<WebElement> pageNumbers = driver.findElements(By.className("page-num"));

// 定位页码输入框
WebElement pageInput = driver.findElement(By.className("page-input"));

// 定位跳转按钮
WebElement jumpButton = driver.findElement(By.className("jump-btn"));

2. XPath定位策略

// 通过XPath定位分页元素
WebElement pagination = driver.findElement(By.xpath("//div[@class='pagination']"));

// 定位包含特定文本的按钮
WebElement prevBtn = driver.findElement(By.xpath("//a[contains(text(),'上一页')]"));
WebElement nextBtn = driver.findElement(By.xpath("//a[contains(text(),'下一页')]"));

// 定位特定页码
WebElement page3 = driver.findElement(By.xpath("//a[@class='page-num' and text()='3']"));

// 定位当前激活的页码
WebElement activePage = driver.findElement(By.xpath("//a[@class='page-num active']"));

// 定位最后一个页码
WebElement lastPage = driver.findElement(By.xpath("//a[@class='page-num'][last()]"));

3. CSS选择器定位

// CSS选择器定位
WebElement pagination = driver.findElement(By.cssSelector(".pagination"));
WebElement prevButton = driver.findElement(By.cssSelector(".pagination .prev"));
WebElement nextButton = driver.findElement(By.cssSelector(".pagination .next"));
WebElement activePage = driver.findElement(By.cssSelector(".page-num.active"));

基础分页测试方法

1. 基础分页测试类

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.chrome.ChromeDriver;
import java.time.Duration;
import java.util.List;

public class PaginationTest {
    private WebDriver driver;
    private WebDriverWait wait;

    public PaginationTest() {
        driver = new ChromeDriver();
        wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    // 获取当前页码
    public int getCurrentPageNumber() {
        WebElement currentPage = wait.until(
            ExpectedConditions.presenceOfElementLocated(
                By.cssSelector(".pagination .page-num.active")
            )
        );
        return Integer.parseInt(currentPage.getText());
    }

    // 获取总页数
    public int getTotalPages() {
        List<WebElement> pageNumbers = driver.findElements(
            By.cssSelector(".pagination .page-num")
        );
        if (pageNumbers.isEmpty()) {
            return 1;
        }

        // 获取最后一个页码元素的文本
        WebElement lastPageElement = pageNumbers.get(pageNumbers.size() - 1);
        return Integer.parseInt(lastPageElement.getText());
    }

    // 点击下一页
    public boolean clickNextPage() {
        try {
            WebElement nextButton = driver.findElement(By.cssSelector(".pagination .next"));

            // 检查下一页按钮是否可用
            if (nextButton.getAttribute("class").contains("disabled")) {
                System.out.println("已经是最后一页,无法继续下一页");
                return false;
            }

            nextButton.click();

            // 等待页面加载完成
            wait.until(ExpectedConditions.stalenessOf(nextButton));
            Thread.sleep(1000); // 额外等待确保页面完全加载

            return true;
        } catch (Exception e) {
            System.out.println("点击下一页失败: " + e.getMessage());
            return false;
        }
    }

    // 点击上一页
    public boolean clickPreviousPage() {
        try {
            WebElement prevButton = driver.findElement(By.cssSelector(".pagination .prev"));

            // 检查上一页按钮是否可用
            if (prevButton.getAttribute("class").contains("disabled")) {
                System.out.println("已经是第一页,无法继续上一页");
                return false;
            }

            prevButton.click();

            // 等待页面加载完成
            wait.until(ExpectedConditions.stalenessOf(prevButton));
            Thread.sleep(1000);

            return true;
        } catch (Exception e) {
            System.out.println("点击上一页失败: " + e.getMessage());
            return false;
        }
    }

    // 跳转到指定页码
    public boolean goToPage(int pageNumber) {
        try {
            // 方法1:直接点击页码链接
            WebElement pageLink = driver.findElement(
                By.xpath("//a[@class='page-num' and text()='" + pageNumber + "']")
            );
            pageLink.click();

            // 等待页面加载
            wait.until(ExpectedConditions.textToBe(
                By.cssSelector(".pagination .page-num.active"), 
                String.valueOf(pageNumber)
            ));

            return true;
        } catch (Exception e) {
            // 方法2:使用输入框跳转
            return goToPageByInput(pageNumber);
        }
    }

    // 通过输入框跳转到指定页码
    public boolean goToPageByInput(int pageNumber) {
        try {
            WebElement pageInput = driver.findElement(By.cssSelector(".page-input"));
            WebElement jumpButton = driver.findElement(By.cssSelector(".jump-btn"));

            // 清空输入框并输入页码
            pageInput.clear();
            pageInput.sendKeys(String.valueOf(pageNumber));

            // 点击跳转按钮
            jumpButton.click();

            // 等待页面加载完成
            wait.until(ExpectedConditions.textToBe(
                By.cssSelector(".pagination .page-num.active"), 
                String.valueOf(pageNumber)
            ));

            return true;
        } catch (Exception e) {
            System.out.println("跳转到第" + pageNumber + "页失败: " + e.getMessage());
            return false;
        }
    }
}

2. 基础测试用例

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.*;

public class BasicPaginationTests {
    private PaginationTest paginationTest;

    @BeforeEach
    public void setUp() {
        paginationTest = new PaginationTest();
        // 导航到测试页面
        paginationTest.driver.get("https://example.com/pagination-test");
    }

    @AfterEach
    public void tearDown() {
        if (paginationTest.driver != null) {
            paginationTest.driver.quit();
        }
    }

    @Test
    public void testInitialPageLoad() {
        // 测试初始页面加载
        int currentPage = paginationTest.getCurrentPageNumber();
        assertEquals(1, currentPage, "初始页面应该是第1页");

        int totalPages = paginationTest.getTotalPages();
        assertTrue(totalPages > 0, "总页数应该大于0");

        System.out.println("当前页: " + currentPage + ", 总页数: " + totalPages);
    }

    @Test
    public void testNextPageNavigation() {
        // 测试下一页导航
        int initialPage = paginationTest.getCurrentPageNumber();
        boolean success = paginationTest.clickNextPage();

        if (success) {
            int newPage = paginationTest.getCurrentPageNumber();
            assertEquals(initialPage + 1, newPage, "点击下一页后页码应该增加1");
        }
    }

    @Test
    public void testPreviousPageNavigation() {
        // 先跳转到第2页
        paginationTest.goToPage(2);

        int initialPage = paginationTest.getCurrentPageNumber();
        boolean success = paginationTest.clickPreviousPage();

        if (success) {
            int newPage = paginationTest.getCurrentPageNumber();
            assertEquals(initialPage - 1, newPage, "点击上一页后页码应该减少1");
        }
    }

    @Test
    public void testDirectPageJump() {
        // 测试直接跳转到指定页码
        int targetPage = 3;
        boolean success = paginationTest.goToPage(targetPage);

        if (success) {
            int currentPage = paginationTest.getCurrentPageNumber();
            assertEquals(targetPage, currentPage, "应该成功跳转到第" + targetPage + "页");
        }
    }
}

高级分页测试技巧

1. 遍历所有页面

public class AdvancedPaginationTest extends PaginationTest {

    // 遍历所有页面并收集数据
    public List<String> traverseAllPages() {
        List<String> allData = new ArrayList<>();

        // 回到第一页
        goToPage(1);

        int totalPages = getTotalPages();
        System.out.println("开始遍历所有 " + totalPages + " 页");

        for (int i = 1; i <= totalPages; i++) {
            System.out.println("正在处理第 " + i + " 页");

            // 收集当前页面的数据
            List<String> pageData = collectCurrentPageData();
            allData.addAll(pageData);

            // 如果不是最后一页,点击下一页
            if (i < totalPages) {
                if (!clickNextPage()) {
                    System.out.println("无法跳转到下一页,停止遍历");
                    break;
                }
            }
        }

        System.out.println("遍历完成,共收集 " + allData.size() + " 条数据");
        return allData;
    }

    // 收集当前页面的数据
    private List<String> collectCurrentPageData() {
        List<String> pageData = new ArrayList<>();

        try {
            // 等待数据加载完成
            wait.until(ExpectedConditions.presenceOfElementLocated(
                By.cssSelector(".data-table tbody tr")
            ));

            // 获取当前页面的所有数据行
            List<WebElement> dataRows = driver.findElements(
                By.cssSelector(".data-table tbody tr")
            );

            for (WebElement row : dataRows) {
                // 提取行数据(根据实际页面结构调整)
                String rowData = row.getText();
                pageData.add(rowData);
            }

        } catch (Exception e) {
            System.out.println("收集页面数据时出错: " + e.getMessage());
        }

        return pageData;
    }
}

2. 分页性能测试

public class PaginationPerformanceTest extends PaginationTest {

    // 测试分页加载性能
    public void testPaginationPerformance() {
        int totalPages = getTotalPages();
        List<Long> loadTimes = new ArrayList<>();

        for (int i = 1; i <= Math.min(totalPages, 10); i++) { // 测试前10页
            long startTime = System.currentTimeMillis();

            goToPage(i);

            // 等待页面完全加载
            wait.until(ExpectedConditions.presenceOfElementLocated(
                By.cssSelector(".data-table tbody tr")
            ));

            long endTime = System.currentTimeMillis();
            long loadTime = endTime - startTime;
            loadTimes.add(loadTime);

            System.out.println("第 " + i + " 页加载时间: " + loadTime + "ms");
        }

        // 计算平均加载时间
        double averageTime = loadTimes.stream()
            .mapToLong(Long::longValue)
            .average()
            .orElse(0.0);

        System.out.println("平均页面加载时间: " + averageTime + "ms");

        // 性能断言
        assertTrue(averageTime < 3000, "页面平均加载时间应该小于3秒");
    }
}

3. 分页边界测试

public class PaginationBoundaryTest extends PaginationTest {

    @Test
    public void testFirstPageBoundary() {
        // 测试第一页的边界情况
        goToPage(1);

        // 验证上一页按钮是否被禁用
        WebElement prevButton = driver.findElement(By.cssSelector(".pagination .prev"));
        assertTrue(prevButton.getAttribute("class").contains("disabled"), 
                  "第一页的上一页按钮应该被禁用");

        // 尝试点击上一页(应该无效)
        boolean result = clickPreviousPage();
        assertFalse(result, "在第一页点击上一页应该返回false");

        // 验证仍然在第一页
        assertEquals(1, getCurrentPageNumber(), "点击禁用的上一页按钮后应该仍在第一页");
    }

    @Test
    public void testLastPageBoundary() {
        // 测试最后一页的边界情况
        int totalPages = getTotalPages();
        goToPage(totalPages);

        // 验证下一页按钮是否被禁用
        WebElement nextButton = driver.findElement(By.cssSelector(".pagination .next"));
        assertTrue(nextButton.getAttribute("class").contains("disabled"), 
                  "最后一页的下一页按钮应该被禁用");

        // 尝试点击下一页(应该无效)
        boolean result = clickNextPage();
        assertFalse(result, "在最后一页点击下一页应该返回false");

        // 验证仍然在最后一页
        assertEquals(totalPages, getCurrentPageNumber(), 
                    "点击禁用的下一页按钮后应该仍在最后一页");
    }

    @Test
    public void testInvalidPageJump() {
        // 测试无效页码跳转
        int totalPages = getTotalPages();

        // 测试跳转到0页
        boolean result1 = goToPageByInput(0);
        assertFalse(result1, "跳转到第0页应该失败");

        // 测试跳转到负数页
        boolean result2 = goToPageByInput(-1);
        assertFalse(result2, "跳转到负数页应该失败");

        // 测试跳转到超出范围的页码
        boolean result3 = goToPageByInput(totalPages + 1);
        assertFalse(result3, "跳转到超出范围的页码应该失败");
    }
}

完整代码示例

1. 完整的分页测试框架

package com.example.pagination;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.JavascriptExecutor;
import java.time.Duration;
import java.util.List;
import java.util.ArrayList;

public class CompletePaginationTestFramework {
    private WebDriver driver;
    private WebDriverWait wait;
    private JavascriptExecutor jsExecutor;

    // 分页元素选择器配置
    private String paginationContainerSelector = ".pagination";
    private String prevButtonSelector = ".pagination .prev";
    private String nextButtonSelector = ".pagination .next";
    private String currentPageSelector = ".pagination .page-num.active";
    private String pageNumberSelector = ".pagination .page-num";
    private String pageInputSelector = ".page-input";
    private String jumpButtonSelector = ".jump-btn";
    private String dataRowSelector = ".data-table tbody tr";

    public CompletePaginationTestFramework() {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--headless"); // 可选:无头模式
        options.addArguments("--no-sandbox");
        options.addArguments("--disable-dev-shm-usage");

        driver = new ChromeDriver(options);
        wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        jsExecutor = (JavascriptExecutor) driver;

        // 设置隐式等待
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));
    }

    // 配置选择器
    public void configureSelectors(String paginationContainer, String prevButton, 
                                 String nextButton, String currentPage, 
                                 String pageNumber, String dataRow) {
        this.paginationContainerSelector = paginationContainer;
        this.prevButtonSelector = prevButton;
        this.nextButtonSelector = nextButton;
        this.currentPageSelector = currentPage;
        this.pageNumberSelector = pageNumber;
        this.dataRowSelector = dataRow;
    }

    // 导航到测试页面
    public void navigateToPage(String url) {
        driver.get(url);
        waitForPaginationToLoad();
    }

    // 等待分页组件加载完成
    public void waitForPaginationToLoad() {
        wait.until(ExpectedConditions.presenceOfElementLocated(
            By.cssSelector(paginationContainerSelector)
        ));
    }

    // 获取当前页码
    public int getCurrentPageNumber() {
        try {
            WebElement currentPage = wait.until(
                ExpectedConditions.presenceOfElementLocated(
                    By.cssSelector(currentPageSelector)
                )
            );
            return Integer.parseInt(currentPage.getText().trim());
        } catch (Exception e) {
            System.out.println("获取当前页码失败: " + e.getMessage());
            return -1;
        }
    }

    // 获取总页数
    public int getTotalPages() {
        try {
            List<WebElement> pageNumbers = driver.findElements(
                By.cssSelector(pageNumberSelector)
            );

            if (pageNumbers.isEmpty()) {
                return 1;
            }

            // 获取最后一个页码
            String lastPageText = pageNumbers.get(pageNumbers.size() - 1).getText().trim();
            return Integer.parseInt(lastPageText);
        } catch (Exception e) {
            System.out.println("获取总页数失败: " + e.getMessage());
            return 1;
        }
    }

    // 检查按钮是否可用
    private boolean isButtonEnabled(WebElement button) {
        String classAttribute = button.getAttribute("class");
        return !classAttribute.contains("disabled") && button.isEnabled();
    }

    // 点击下一页
    public boolean clickNextPage() {
        try {
            WebElement nextButton = driver.findElement(By.cssSelector(nextButtonSelector));

            if (!isButtonEnabled(nextButton)) {
                System.out.println("下一页按钮不可用");
                return false;
            }

            int currentPage = getCurrentPageNumber();

            // 使用JavaScript点击以避免元素被遮挡的问题
            jsExecutor.executeScript("arguments[0].click();", nextButton);

            // 等待页码变化
            wait.until(ExpectedConditions.not(
                ExpectedConditions.textToBe(By.cssSelector(currentPageSelector), 
                                          String.valueOf(currentPage))
            ));

            // 等待数据加载完成
            waitForDataToLoad();

            return true;
        } catch (Exception e) {
            System.out.println("点击下一页失败: " + e.getMessage());
            return false;
        }
    }

    // 点击上一页
    public boolean clickPreviousPage() {
        try {
            WebElement prevButton = driver.findElement(By.cssSelector(prevButtonSelector));

            if (!isButtonEnabled(prevButton)) {
                System.out.println("上一页按钮不可用");
                return false;
            }

            int currentPage = getCurrentPageNumber();

            jsExecutor.executeScript("arguments[0].click();", prevButton);

            // 等待页码变化
            wait.until(ExpectedConditions.not(
                ExpectedConditions.textToBe(By.cssSelector(currentPageSelector), 
                                          String.valueOf(currentPage))
            ));

            waitForDataToLoad();

            return true;
        } catch (Exception e) {
            System.out.println("点击上一页失败: " + e.getMessage());
            return false;
        }
    }

    // 跳转到指定页码
    public boolean goToPage(int pageNumber) {
        try {
            // 验证页码有效性
            int totalPages = getTotalPages();
            if (pageNumber < 1 || pageNumber > totalPages) {
                System.out.println("无效的页码: " + pageNumber);
                return false;
            }

            // 如果已经在目标页面,直接返回成功
            if (getCurrentPageNumber() == pageNumber) {
                return true;
            }

            // 尝试直接点击页码链接
            try {
                WebElement pageLink = driver.findElement(
                    By.xpath("//a[@class='page-num' and text()='" + pageNumber + "']")
                );
                jsExecutor.executeScript("arguments[0].click();", pageLink);

                // 等待跳转完成
                wait.until(ExpectedConditions.textToBe(
                    By.cssSelector(currentPageSelector), 
                    String.valueOf(pageNumber)
                ));

                waitForDataToLoad();
                return true;
            } catch (Exception e) {
                // 如果直接点击失败,尝试使用输入框跳转
                return goToPageByInput(pageNumber);
            }
        } catch (Exception e) {
            System.out.println("跳转到第" + pageNumber + "页失败: " + e.getMessage());
            return false;
        }
    }

    // 通过输入框跳转
    public boolean goToPageByInput(int pageNumber) {
        try {
            WebElement pageInput = driver.findElement(By.cssSelector(pageInputSelector));
            WebElement jumpButton = driver.findElement(By.cssSelector(jumpButtonSelector));

            // 清空并输入页码
            pageInput.clear();
            pageInput.sendKeys(String.valueOf(pageNumber));

            jsExecutor.executeScript("arguments[0].click();", jumpButton);

            // 等待跳转完成
            wait.until(ExpectedConditions.textToBe(
                By.cssSelector(currentPageSelector), 
                String.valueOf(pageNumber)
            ));

            waitForDataToLoad();
            return true;
        } catch (Exception e) {
            System.out.println("通过输入框跳转失败: " + e.getMessage());
            return false;
        }
    }

    // 等待数据加载完成
    private void waitForDataToLoad() {
        try {
            // 等待数据行出现
            wait.until(ExpectedConditions.presenceOfElementLocated(
                By.cssSelector(dataRowSelector)
            ));

            // 额外等待确保数据完全加载
            Thread.sleep(500);
        } catch (Exception e) {
            System.out.println("等待数据加载时出错: " + e.getMessage());
        }
    }

    // 获取当前页面的数据
    public List<String> getCurrentPageData() {
        List<String> data = new ArrayList<>();

        try {
            waitForDataToLoad();

            List<WebElement> dataRows = driver.findElements(
                By.cssSelector(dataRowSelector)
            );

            for (WebElement row : dataRows) {
                data.add(row.getText().trim());
            }
        } catch (Exception e) {
            System.out.println("获取页面数据失败: " + e.getMessage());
        }

        return data;
    }

    // 遍历所有页面并收集数据
    public List<String> getAllPagesData() {
        List<String> allData = new ArrayList<>();

        // 回到第一页
        goToPage(1);

        int totalPages = getTotalPages();
        System.out.println("开始遍历所有 " + totalPages + " 页");

        for (int i = 1; i <= totalPages; i++) {
            System.out.println("正在处理第 " + i + " 页");

            List<String> pageData = getCurrentPageData();
            allData.addAll(pageData);

            System.out.println("第 " + i + " 页收集到 " + pageData.size() + " 条数据");

            // 如果不是最后一页,跳转到下一页
            if (i < totalPages) {
                if (!clickNextPage()) {
                    System.out.println("无法跳转到下一页,停止遍历");
                    break;
                }
            }
        }

        System.out.println("遍历完成,共收集 " + allData.size() + " 条数据");
        return allData;
    }

    // 验证分页功能完整性
    public boolean validatePaginationIntegrity() {
        try {
            int totalPages = getTotalPages();
            System.out.println("开始验证分页完整性,总页数: " + totalPages);

            // 测试每一页是否可以正常访问
            for (int i = 1; i <= totalPages; i++) {
                if (!goToPage(i)) {
                    System.out.println("无法访问第 " + i + " 页");
                    return false;
                }

                int currentPage = getCurrentPageNumber();
                if (currentPage != i) {
                    System.out.println("页码不匹配,期望: " + i + ", 实际: " + currentPage);
                    return false;
                }

                // 验证页面是否有数据
                List<String> data = getCurrentPageData();
                if (data.isEmpty()) {
                    System.out.println("第 " + i + " 页没有数据");
                    return false;
                }
            }

            System.out.println("分页完整性验证通过");
            return true;
        } catch (Exception e) {
            System.out.println("分页完整性验证失败: " + e.getMessage());
            return false;
        }
    }

    // 关闭浏览器
    public void close() {
        if (driver != null) {
            driver.quit();
        }
    }
}

2. 完整的测试用例

package com.example.pagination;

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;

public class CompletePaginationTests {
    private CompletePaginationTestFramework paginationFramework;
    private static final String TEST_URL = "https://example.com/pagination-test";

    @BeforeEach
    public void setUp() {
        paginationFramework = new CompletePaginationTestFramework();
        paginationFramework.navigateToPage(TEST_URL);
    }

    @AfterEach
    public void tearDown() {
        if (paginationFramework != null) {
            paginationFramework.close();
        }
    }

    @Test
    @DisplayName("测试分页基本功能")
    public void testBasicPaginationFunctionality() {
        // 验证初始状态
        assertEquals(1, paginationFramework.getCurrentPageNumber(), 
                    "初始页面应该是第1页");

        int totalPages = paginationFramework.getTotalPages();
        assertTrue(totalPages > 0, "总页数应该大于0");

        System.out.println("总页数: " + totalPages);
    }

    @Test
    @DisplayName("测试下一页功能")
    public void testNextPageFunctionality() {
        int initialPage = paginationFramework.getCurrentPageNumber();
        boolean success = paginationFramework.clickNextPage();

        if (success) {
            int newPage = paginationFramework.getCurrentPageNumber();
            assertEquals(initialPage + 1, newPage, 
                        "点击下一页后页码应该增加1");
        } else {
            // 如果是最后一页,验证确实无法继续
            int totalPages = paginationFramework.getTotalPages();
            assertEquals(totalPages, initialPage, 
                        "无法点击下一页时应该已经在最后一页");
        }
    }

    @Test
    @DisplayName("测试上一页功能")
    public void testPreviousPageFunctionality() {
        // 先跳转到第2页
        paginationFramework.goToPage(2);

        int initialPage = paginationFramework.getCurrentPageNumber();
        boolean success = paginationFramework.clickPreviousPage();

        if (success) {
            int newPage = paginationFramework.getCurrentPageNumber();
            assertEquals(initialPage - 1, newPage, 
                        "点击上一页后页码应该减少1");
        }
    }

    @Test
    @DisplayName("测试页码跳转功能")
    public void testPageJumpFunctionality() {
        int totalPages = paginationFramework.getTotalPages();

        if (totalPages >= 3) {
            // 测试跳转到第3页
            boolean success = paginationFramework.goToPage(3);
            assertTrue(success, "应该能够跳转到第3页");
            assertEquals(3, paginationFramework.getCurrentPageNumber(), 
                        "跳转后应该在第3页");
        }

        // 测试跳转到最后一页
        boolean success = paginationFramework.goToPage(totalPages);
        assertTrue(success, "应该能够跳转到最后一页");
        assertEquals(totalPages, paginationFramework.getCurrentPageNumber(), 
                    "跳转后应该在最后一页");
    }

    @Test
    @DisplayName("测试边界条件")
    public void testBoundaryConditions() {
        // 测试第一页边界
        paginationFramework.goToPage(1);
        boolean prevResult = paginationFramework.clickPreviousPage();
        assertFalse(prevResult, "在第一页点击上一页应该失败");
        assertEquals(1, paginationFramework.getCurrentPageNumber(), 
                    "点击失败后应该仍在第一页");

        // 测试最后一页边界
        int totalPages = paginationFramework.getTotalPages();
        paginationFramework.goToPage(totalPages);
        boolean nextResult = paginationFramework.clickNextPage();
        assertFalse(nextResult, "在最后一页点击下一页应该失败");
        assertEquals(totalPages, paginationFramework.getCurrentPageNumber(), 
                    "点击失败后应该仍在最后一页");
    }

    @Test
    @DisplayName("测试无效页码跳转")
    public void testInvalidPageJump() {
        int totalPages = paginationFramework.getTotalPages();

        // 测试跳转到0页
        boolean result1 = paginationFramework.goToPage(0);
        assertFalse(result1, "跳转到第0页应该失败");

        // 测试跳转到负数页
        boolean result2 = paginationFramework.goToPage(-1);
        assertFalse(result2, "跳转到负数页应该失败");

        // 测试跳转到超出范围的页码
        boolean result3 = paginationFramework.goToPage(totalPages + 1);
        assertFalse(result3, "跳转到超出范围的页码应该失败");
    }

    @Test
    @DisplayName("测试数据完整性")
    public void testDataIntegrity() {
        // 验证每一页都有数据
        int totalPages = paginationFramework.getTotalPages();

        for (int i = 1; i <= Math.min(totalPages, 5); i++) { // 测试前5页
            paginationFramework.goToPage(i);
            List<String> data = paginationFramework.getCurrentPageData();

            assertFalse(data.isEmpty(), "第 " + i + " 页应该有数据");
            System.out.println("第 " + i + " 页有 " + data.size() + " 条数据");
        }
    }

    @Test
    @DisplayName("测试分页完整性")
    public void testPaginationIntegrity() {
        boolean isValid = paginationFramework.validatePaginationIntegrity();
        assertTrue(isValid, "分页功能应该完整可用");
    }

    @Test
    @DisplayName("测试全页面数据收集")
    public void testAllPagesDataCollection() {
        List<String> allData = paginationFramework.getAllPagesData();

        assertFalse(allData.isEmpty(), "应该能够收集到数据");
        System.out.println("总共收集到 " + allData.size() + " 条数据");

        // 验证数据的唯一性(如果适用)
        long uniqueCount = allData.stream().distinct().count();
        System.out.println("唯一数据条数: " + uniqueCount);
    }
}

常见问题与解决方案

1. 元素定位问题

问题: 分页元素定位不稳定

// 解决方案:使用多种定位策略
public WebElement findPaginationElement(String primarySelector, String... fallbackSelectors) {
    try {
        return driver.findElement(By.cssSelector(primarySelector));
    } catch (Exception e) {
        for (String selector : fallbackSelectors) {
            try {
                return driver.findElement(By.cssSelector(selector));
            } catch (Exception ignored) {}
        }
        throw new RuntimeException("无法找到分页元素");
    }
}

// 使用示例
WebElement nextButton = findPaginationElement(
    ".pagination .next",
    ".pager .next",
    "[data-testid='next-page']",
    "//a[contains(text(),'下一页')]"
);

2. 异步加载问题

问题: 页面数据异步加载导致测试不稳定

// 解决方案:智能等待策略
public void waitForPageDataLoad() {
    // 等待加载指示器消失
    try {
        wait.until(ExpectedConditions.invisibilityOfElementLocated(
            By.cssSelector(".loading-spinner")
        ));
    } catch (Exception ignored) {}

    // 等待数据行出现
    wait.until(ExpectedConditions.presenceOfElementLocated(
        By.cssSelector(dataRowSelector)
    ));

    // 等待数据稳定(检查数据行数量不再变化)
    int previousCount = -1;
    int stableCount = 0;

    while (stableCount < 3) { // 连续3次检查数量相同
        List<WebElement> rows = driver.findElements(By.cssSelector(dataRowSelector));
        int currentCount = rows.size();

        if (currentCount == previousCount && currentCount > 0) {
            stableCount++;
        } else {
            stableCount = 0;
        }

        previousCount = currentCount;

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            break;
        }
    }
}

3. 动态分页问题

问题: 分页结构动态变化

// 解决方案:动态适应分页结构
public class DynamicPaginationHandler {

    public boolean isInfiniteScroll() {
        // 检查是否是无限滚动分页
        return driver.findElements(By.cssSelector(".infinite-scroll")).size() > 0;
    }

    public boolean isLoadMoreButton() {
        // 检查是否是"加载更多"按钮分页
        return driver.findElements(By.cssSelector(".load-more")).size() > 0;
    }

    public void handleDynamicPagination() {
        if (isInfiniteScroll()) {
            handleInfiniteScroll();
        } else if (isLoadMoreButton()) {
            handleLoadMoreButton();
        } else {
            handleTraditionalPagination();
        }
    }

    private void handleInfiniteScroll() {
        JavascriptExecutor js = (JavascriptExecutor) driver;

        // 滚动到页面底部触发加载
        js.executeScript("window.scrollTo(0, document.body.scrollHeight);");

        // 等待新内容加载
        waitForNewContentLoad();
    }

    private void handleLoadMoreButton() {
        WebElement loadMoreBtn = driver.findElement(By.cssSelector(".load-more"));

        while (loadMoreBtn.isDisplayed() && loadMoreBtn.isEnabled()) {
            loadMoreBtn.click();
            waitForNewContentLoad();

            // 重新获取按钮引用(可能已经更新)
            try {
                loadMoreBtn = driver.findElement(By.cssSelector(".load-more"));
            } catch (Exception e) {
                break; // 按钮消失,说明已经加载完所有内容
            }
        }
    }
}

4. 性能优化问题

问题: 大量页面遍历导致测试缓慢

// 解决方案:并行处理和采样测试
public class OptimizedPaginationTest {

    // 采样测试:只测试部分页面
    public void samplePaginationTest(int sampleSize) {
        int totalPages = getTotalPages();
        int step = Math.max(1, totalPages / sampleSize);

        System.out.println("采样测试:总页数 " + totalPages + ",采样间隔 " + step);

        for (int i = 1; i <= totalPages; i += step) {
            System.out.println("测试第 " + i + " 页");
            goToPage(i);
            validateCurrentPage();
        }

        // 确保测试最后一页
        if (totalPages % step != 1) {
            goToPage(totalPages);
            validateCurrentPage();
        }
    }

    // 快速验证:只检查关键信息
    public boolean quickValidation() {
        try {
            // 检查分页组件是否存在
            driver.findElement(By.cssSelector(paginationContainerSelector));

            // 检查是否有数据
            List<WebElement> dataRows = driver.findElements(By.cssSelector(dataRowSelector));
            if (dataRows.isEmpty()) {
                return false;
            }

            // 检查页码是否正确
            int currentPage = getCurrentPageNumber();
            return currentPage > 0;

        } catch (Exception e) {
            return false;
        }
    }
}

最佳实践

1. 测试设计原则

// 1. 使用页面对象模式
public class PaginationPageObject {
    private WebDriver driver;
    private WebDriverWait wait;

    // 封装分页操作
    @FindBy(css = ".pagination .next")
    private WebElement nextButton;

    @FindBy(css = ".pagination .prev")
    private WebElement prevButton;

    @FindBy(css = ".pagination .page-num.active")
    private WebElement currentPageElement;

    public PaginationPageObject(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        PageFactory.initElements(driver, this);
    }

    public boolean goToNextPage() {
        if (!nextButton.isEnabled()) {
            return false;
        }

        String currentPage = currentPageElement.getText();
        nextButton.click();

        // 等待页码变化
        wait.until(ExpectedConditions.not(
            ExpectedConditions.textToBe(By.cssSelector(".pagination .page-num.active"), currentPage)
        ));

        return true;
    }
}

2. 配置化测试

// 2. 使用配置文件管理测试参数
public class PaginationTestConfig {
    private Properties config;

    public PaginationTestConfig() {
        config = new Properties();
        try {
            config.load(getClass().getResourceAsStream("/pagination-test.properties"));
        } catch (Exception e) {
            throw new RuntimeException("无法加载配置文件", e);
        }
    }

    public String getPaginationSelector() {
        return config.getProperty("pagination.container.selector", ".pagination");
    }

    public int getMaxTestPages() {
        return Integer.parseInt(config.getProperty("test.max.pages", "10"));
    }

    public int getPageLoadTimeout() {
        return Integer.parseInt(config.getProperty("page.load.timeout", "10"));
    }
}

// pagination-test.properties 文件内容:
/*
pagination.container.selector=.pagination
pagination.next.selector=.pagination .next
pagination.prev.selector=.pagination .prev
pagination.current.selector=.pagination .page-num.active
data.row.selector=.data-table tbody tr
test.max.pages=10
page.load.timeout=10
*/

3. 日志和报告

// 3. 详细的日志记录
public class PaginationTestLogger {
    private static final Logger logger = LoggerFactory.getLogger(PaginationTestLogger.class);

    public void logPaginationInfo(int currentPage, int totalPages) {
        logger.info("分页信息 - 当前页: {}, 总页数: {}", currentPage, totalPages);
    }

    public void logPageNavigation(String action, int fromPage, int toPage, boolean success) {
        if (success) {
            logger.info("分页导航成功 - 操作: {}, 从第{}页到第{}页", action, fromPage, toPage);
        } else {
            logger.warn("分页导航失败 - 操作: {}, 从第{}页到第{}页", action, fromPage, toPage);
        }
    }

    public void logDataCollection(int pageNumber, int dataCount) {
        logger.info("数据收集 - 第{}页收集到{}条数据", pageNumber, dataCount);
    }

    public void logTestSummary(int totalPagesVisited, int totalDataCollected, long testDuration) {
        logger.info("测试总结 - 访问页面数: {}, 收集数据条数: {}, 耗时: {}ms", 
                   totalPagesVisited, totalDataCollected, testDuration);
    }
}

4. 错误处理和重试机制

// 4. 健壮的错误处理
public class RobustPaginationTest {
    private static final int MAX_RETRY_ATTEMPTS = 3;
    private static final int RETRY_DELAY_MS = 1000;

    public boolean goToPageWithRetry(int pageNumber) {
        for (int attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
            try {
                boolean success = goToPage(pageNumber);
                if (success) {
                    return true;
                }
            } catch (Exception e) {
                logger.warn("第{}次尝试跳转到第{}页失败: {}", attempt, pageNumber, e.getMessage());

                if (attempt < MAX_RETRY_ATTEMPTS) {
                    try {
                        Thread.sleep(RETRY_DELAY_MS);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        return false;
                    }

                    // 刷新页面重试
                    driver.navigate().refresh();
                    waitForPaginationToLoad();
                }
            }
        }

        logger.error("跳转到第{}页最终失败,已重试{}次", pageNumber, MAX_RETRY_ATTEMPTS);
        return false;
    }

    // 异常恢复机制
    public void recoverFromError() {
        try {
            // 检查页面是否正常
            if (!isPaginationVisible()) {
                driver.navigate().refresh();
                waitForPaginationToLoad();
            }

            // 回到第一页重新开始
            goToPage(1);

        } catch (Exception e) {
            logger.error("错误恢复失败", e);
            throw new RuntimeException("无法从分页测试错误中恢复", e);
        }
    }

    private boolean isPaginationVisible() {
        try {
            WebElement pagination = driver.findElement(By.cssSelector(paginationContainerSelector));
            return pagination.isDisplayed();
        } catch (Exception e) {
            return false;
        }
    }
}

5. 测试数据验证

// 5. 数据一致性验证
public class PaginationDataValidator {

    // 验证分页数据的一致性
    public boolean validateDataConsistency() {
        List<String> allData = new ArrayList<>();
        Set<String> uniqueData = new HashSet<>();

        int totalPages = getTotalPages();

        for (int i = 1; i <= totalPages; i++) {
            goToPage(i);
            List<String> pageData = getCurrentPageData();

            // 检查页面是否有重复数据
            for (String data : pageData) {
                if (uniqueData.contains(data)) {
                    logger.warn("发现重复数据: {} (在第{}页)", data, i);
                    return false;
                }
                uniqueData.add(data);
                allData.add(data);
            }
        }

        logger.info("数据一致性验证通过,共{}条唯一数据", uniqueData.size());
        return true;
    }

    // 验证分页数据的完整性
    public boolean validateDataCompleteness(int expectedTotalCount) {
        List<String> allData = getAllPagesData();

        if (allData.size() != expectedTotalCount) {
            logger.error("数据数量不匹配 - 期望: {}, 实际: {}", expectedTotalCount, allData.size());
            return false;
        }

        return true;
    }

    // 验证分页数据的排序
    public boolean validateDataSorting(String sortField, boolean ascending) {
        List<String> allData = getAllPagesData();

        // 根据排序字段提取值进行比较
        List<String> sortValues = allData.stream()
            .map(data -> extractSortValue(data, sortField))
            .collect(Collectors.toList());

        for (int i = 1; i < sortValues.size(); i++) {
            int comparison = sortValues.get(i-1).compareTo(sortValues.get(i));

            if (ascending && comparison > 0) {
                logger.error("排序错误:第{}条数据应该在第{}条数据之后", i-1, i);
                return false;
            } else if (!ascending && comparison < 0) {
                logger.error("排序错误:第{}条数据应该在第{}条数据之前", i-1, i);
                return false;
            }
        }

        return true;
    }

    private String extractSortValue(String data, String field) {
        // 根据实际数据格式实现字段值提取逻辑
        // 这里只是示例
        return data.split(",")[0]; // 假设第一个字段是排序字段
    }
}

总结

分页测试是Web自动化测试中的重要组成部分,需要考虑以下关键点:

  1. 全面的测试覆盖:包括基础导航、边界条件、数据完整性等
  2. 稳定的元素定位:使用多种定位策略确保测试稳定性
  3. 智能的等待机制:处理异步加载和动态内容
  4. 性能优化:采用采样测试和并行处理提高效率
  5. 错误处理:实现重试机制和异常恢复
  6. 数据验证:确保分页数据的一致性和完整性

通过遵循这些最佳实践,您可以构建出健壮、高效的分页测试自动化框架,确保Web应用的分页功能正常工作。

记住,分页测试不仅仅是点击按钮,更重要的是验证用户体验和数据完整性。在实际项目中,要根据具体的业务需求和页面结构调整测试策略。

发表评论