Simple Pagination Testing

Basic pagination testing for offset-based, page-based, and cursor-based API pagination

LoadForge can record your browser, graphically build tests, scan your site with a wizard and more. Sign up now to run your first test.

Sign up now


This guide shows how to test different pagination patterns in APIs. Perfect for validating pagination logic and performance under load.

Use Cases

  • Test pagination performance under load
  • Validate pagination logic and consistency
  • Test different pagination patterns
  • Check boundary conditions and edge cases

Simple Implementation

from locust import task, HttpUser
import random
import json

class PaginationTestUser(HttpUser):
    def on_start(self):
        # API endpoints with different pagination types
        self.endpoints = {
            "/api/users": "offset",     # offset-based: ?limit=20&offset=40
            "/api/products": "page",    # page-based: ?page=2&per_page=25
            "/api/orders": "cursor"     # cursor-based: ?cursor=abc123&limit=30
        }

    @task(4)
    def test_offset_pagination(self):
        """Test offset-based pagination (?limit=20&offset=40)"""
        endpoint = "/api/users"
        limit = 20
        offset = random.choice([0, 20, 40, 60, 100])
        
        params = {"limit": limit, "offset": offset}
        
        with self.client.get(
            endpoint,
            params=params,
            name=f"Offset Pagination - {offset}"
        ) as response:
            if response.status_code == 200:
                try:
                    data = response.json()
                    
                    # Handle different response formats
                    if isinstance(data, dict) and "data" in data:
                        items = data["data"]
                        total = data.get("total", 0)
                        print(f"Offset pagination: {len(items)} items, offset {offset}")
                        
                        # Basic validation
                        if len(items) > limit:
                            response.failure(f"Too many items: {len(items)} > {limit}")
                    else:
                        items = data if isinstance(data, list) else []
                        print(f"Offset pagination: {len(items)} items")
                        
                except json.JSONDecodeError:
                    response.failure("Invalid JSON response")
            else:
                response.failure(f"Pagination failed: {response.status_code}")

    @task(3)
    def test_page_pagination(self):
        """Test page-based pagination (?page=2&per_page=25)"""
        endpoint = "/api/products"
        per_page = 25
        page = random.choice([1, 2, 3, 4, 5])
        
        params = {"per_page": per_page, "page": page}
        
        with self.client.get(
            endpoint,
            params=params,
            name=f"Page Pagination - {page}"
        ) as response:
            if response.status_code == 200:
                try:
                    data = response.json()
                    
                    if isinstance(data, dict):
                        items = data.get("data", data.get("items", []))
                        current_page = data.get("current_page", data.get("page", page))
                        total_pages = data.get("total_pages", 0)
                        
                        print(f"Page pagination: {len(items)} items, page {current_page}")
                        
                        # Validate page logic
                        if len(items) > per_page:
                            response.failure(f"Too many items per page: {len(items)}")
                    else:
                        items = data if isinstance(data, list) else []
                        print(f"Page pagination: {len(items)} items")
                        
                except json.JSONDecodeError:
                    response.failure("Invalid JSON response")
            else:
                response.failure(f"Page pagination failed: {response.status_code}")

    @task(2)
    def test_cursor_pagination(self):
        """Test cursor-based pagination (?cursor=abc123&limit=30)"""
        endpoint = "/api/orders"
        limit = 30
        # Test with different cursors (in real use, get from previous responses)
        cursor = random.choice([None, "start", "abc123", "xyz789"])
        
        params = {"limit": limit}
        if cursor:
            params["cursor"] = cursor
        
        with self.client.get(
            endpoint,
            params=params,
            name=f"Cursor Pagination - {cursor or 'start'}"
        ) as response:
            if response.status_code == 200:
                try:
                    data = response.json()
                    
                    if isinstance(data, dict):
                        items = data.get("data", data.get("items", []))
                        next_cursor = data.get("next_cursor")
                        has_more = data.get("has_more", False)
                        
                        print(f"Cursor pagination: {len(items)} items, next: {next_cursor}")
                        
                        # Basic validation
                        if len(items) > limit:
                            response.failure(f"Too many items: {len(items)} > {limit}")
                    else:
                        items = data if isinstance(data, list) else []
                        print(f"Cursor pagination: {len(items)} items")
                        
                except json.JSONDecodeError:
                    response.failure("Invalid JSON response")
            else:
                response.failure(f"Cursor pagination failed: {response.status_code}")

    @task(1)
    def test_pagination_edge_cases(self):
        """Test pagination edge cases"""
        endpoint = random.choice(list(self.endpoints.keys()))
        pagination_type = self.endpoints[endpoint]
        
        # Test edge cases based on pagination type
        if pagination_type == "offset":
            # Test large offset
            params = {"limit": 10, "offset": 999999}
            edge_case = "large_offset"
        elif pagination_type == "page":
            # Test page 0
            params = {"per_page": 10, "page": 0}
            edge_case = "page_zero"
        else:  # cursor
            # Test with empty cursor
            params = {"limit": 10, "cursor": ""}
            edge_case = "empty_cursor"
        
        with self.client.get(
            endpoint,
            params=params,
            name=f"Edge Case - {edge_case}"
        ) as response:
            if response.status_code in [200, 400, 404]:
                print(f"Edge case {edge_case}: {response.status_code}")
            else:
                print(f"Edge case {edge_case}: unexpected {response.status_code}")

Setup Instructions

  1. Replace endpoint URLs with your actual API endpoints
  2. Adjust pagination parameters to match your API's format
  3. Update field names if your API uses different response structure
  4. Customize limit/per_page values based on your API's defaults

What This Tests

  • Offset Pagination: Tests limit/offset based pagination
  • Page Pagination: Tests page number and per_page based pagination
  • Cursor Pagination: Tests cursor-based pagination for large datasets
  • Edge Cases: Tests boundary conditions and invalid parameters

Best Practices

  • Test different page sizes to find optimal performance
  • Validate response structure consistency across pages
  • Monitor response times for different pagination positions
  • Test edge cases like empty results and invalid parameters

Common Issues

  • Inconsistent Response Format: Ensure all pages return same structure
  • Performance Degradation: Later pages may be slower (especially offset-based)
  • Boundary Conditions: Handle edge cases like page 0 or negative offsets
  • Cursor Validity: Cursor-based pagination requires valid cursor tokens

Ready to run your test?
Launch your locust test at scale.