In web applications, tables are often the backbone for displaying large datasets, making sorting and pagination essential features for enhancing user experience. However, ensuring data is sorted correctly across multiple pages can be challenging, particularly in complex tables with various data types and sorting conditions.
In this post, we'll explore a practical approach to automating table sorting tests using Playwright. We'll walk through verifying ascending and descending order across pages and handling multi-page data to ensure reliable sorting functionality.
To illustrate sorting tests effectively, we've set up a demo table that mimics real-world scenarios where multi-page data is displayed in sortable columns. This table includes four columns: Name, Age, Joining Date, and Salary, each representing different data types to demonstrate a comprehensive sorting test.
Sample Table for Test Script Development
Each page of the table displays a subset of randomized data that is fetched from an API, ensuring fresh data for each test run. This randomness adds a realistic dynamic, as it allows us to validate that sorting functionality works consistently regardless of the data content. By covering various data types, handling pagination, and working with randomized API-driven data, this table setup provides a solid foundation for implementing and testing reliable sorting functionality across different scenarios.
To automate sorting validation effectively, we rely on two core helper functions: getTableData and verifySorted. These functions work together to capture the table's data across multiple pages and confirm that it is sorted correctly according to the specified column and sort direction.
The getTableData function is responsible for gathering all visible data on the current page of the table. It extracts each row's details and organizes them into an object format that reflects the structure of the table columns (name, age, joining date, and salary). Here's a breakdown of its operation:
async function getTableData(page) {
return await page.$$eval("#tableBody tr", (rows) =>
rows.map((row) => {
const cells = row.querySelectorAll("td");
return {
name: cells[0].textContent.trim(),
age: parseInt(cells[1].textContent.trim(), 10),
joiningDate: new Date(cells[2].textContent.trim()),
salary: parseFloat(cells[3].textContent.replace("$", "").trim()),
};
})
);
}
1. Element Selection: The function targets each row of the table using the selector #tableBody tr .
2. Data Mapping: For each row, it identifies individual cells, extracts their text, and formats the data as follows:
This function returns an array of objects, each representing a row of data, which will be used to verify sorting.
The verifySorted function compares the current table data against an expected order to ensure it is sorted correctly. Here's how it operates:
async function verifySorted(data, column, direction) {
const sorted = [...data].sort((a, b) => {
const valA = a[column];
const valB = b[column];
if (column === "age" || column === "salary") {
return direction === "asc" ? valA - valB : valB - valA;
} else if (column === "joiningDate") {
return direction === "asc" ? valA - valB : valB - valA;
} else {
return direction === "asc"
? valA.localeCompare(valB)
: valB.localeCompare(valA);
}
});
for (let i = 0; i < data.length; i++) {
if (data[i][column] !== sorted[i][column]) return false;
}
return true;
}
1. Cloning and Sorting Data: A copy of the table data is made, which is then sorted based on the specified column and direction (asc for ascending or desc for descending). The sorting logic varies:
2. Comparison with Original Data: The sorted copy is then compared against the original data to ensure they match. If every item is in the correct order, the function returns true; otherwise, it returns false, indicating a sorting discrepancy.
These functions form the core of our sorting test logic. In each test, getTableData captures the data on the current page, while verifySorted verifies if it matches the expected order. This process is repeated across multiple pages to ensure the sorting holds consistently for the entire dataset. By combining data extraction and sorting verification, we achieve a comprehensive automated approach to validate table sorting across both single-page and multi-page views.
This allows us to validate that the sorting feature performs accurately across the complete dataset regardless of pagination.
With our helper functions in place, we can now walk through the main test logic for validating sorting across each column, in both ascending and descending order, and across multiple paginated pages.
The test iterates through each column (name, age, joiningDate, and salary) and validates that the table sorts correctly when sorted by each. This ensures we cover all possible sorting configurations and helps detect any inconsistencies across different types of data (text, numbers, and dates).
The test cycles through both ascending (asc) and descending (desc) sorting for each column. For each column, the test first sets the sort order by clicking the relevant column header. If descending order is needed, it clicks the column header a second time to toggle the sorting direction. This logic ensures that we comprehensively test the full functionality of sorting.
for (const column of columns) {
for (const direction of ["asc", "desc"]) {
test(`should sort table by ${column} in ${direction} order across pages`, async ({ page }) => {
// Default sort is by name in ascending order, so click only if column is not 'name'
if (column !== "name") {
await page.click(`th[data-sort="${column}"]`);
}
if (direction === "desc") {
await page.click(`th[data-sort="${column}"]`); // Toggle to descending order
}
Since the table is paginated, the test collects data across all pages to ensure that sorting is consistent for the entire dataset. Starting from the first page, it loops through each page, extracting the table data with getTableData and appending it to an array (allData) that accumulates data from all pages.
After gathering data from all pages, the test verifies the accumulated data using verifySorted, which checks if the complete dataset matches the expected sorted order. This part of the test ensures that sorting logic holds across paginated views, not just on individual pages.
let allData = [];
let currentPage = 1;
let totalPages;
do {
// Collect data from the current page
const tableData = await getTableData(page);
allData = allData.concat(tableData);
// Determine the total number of pages from pagination info
if (!totalPages) {
const pageInfoText = await page.locator("#pageInfo").textContent();
totalPages = parseInt(pageInfoText.match(/Page \d+ of (\d+)/)[1], 10);
}
// Navigate to the next page if not on the last page
if (currentPage < totalPages) {
await page.click('button:has-text("Next")');
currentPage++;
} else {
break; // Exit loop if on the last page
}
} while (currentPage <= totalPages);
// Verify the entire dataset is sorted correctly
expect(await verifySorted(allData, column, direction)).toBeTruthy();
});
}
When dealing with tables that span multiple pages, ensuring accurate sorting validation involves navigating some unique challenges.
1. Managing State Across Pages
One key challenge with paginated tables is preserving the sorting state across pages. When sorting by a particular column, users expect the sorting order to apply not only to the current page but also to the entire dataset, regardless of which page they're viewing. The test ensures that once a column is sorted, the order remains consistent as it navigates through all pages. To maintain this state, we initialize the sorting by clicking on the column header and, if needed, toggling to descending order. This step is done at the start, ensuring every page we load adheres to the selected sorting direction.
2. Resuming Sorting While Navigating
A common pitfall in paginated tables is that some implementations may reset the sorting order when a user moves from one page to another. The test script counters this by re-checking the sorting on each page, ensuring the selected order remains intact as it gathers data across pages. If the table's underlying code inadvertently resets sorting, this test would fail, alerting us to a potential bug.
3. Gathering Data Across All Pages
To validate sorting, the test must work with data from the entire table, not just from a single page. As each page may display only a subset of the data, we gather all rows across pages by navigating to each one and appending its data to an accumulating array (allData). This ensures that the verifySorted function has access to the complete dataset, making it possible to check the sorting integrity for the entire collection.
4. Verifying Sorting on the Complete Dataset
Once all data has been gathered, it is crucial to check if the cumulative data is sorted correctly. Since we've collected the entire table dataset, we can pass it to the verifySorted function to confirm that the order across all pages matches the intended sorting direction. This ensures that any anomalies, such as rows that may have been mis-sorted on individual pages, are detected.
In this blog post, we walked through building a robust Playwright test for validating sorting functionality across a paginated table. By handling sorting verification for different data types and maintaining state across multiple pages, this approach ensures reliable test coverage for complex tables. For those interested in trying it out, the complete test code and the demo application are available on our GitHub page.
As a challenge, consider adapting this test for parallel execution! Currently, this test will fail if executed in parallel due to its dependency on navigating through pages sequentially to collect and verify the complete dataset. Parallel execution would require rethinking the data collection and verification approach to avoid conflicts. Can you find a solution that enables the test to handle sorting validation across pages while running in parallel? Good luck!
Further Reading: