Writing E2E Tests for AG Grid React Tables with Playwright

  |   React

This tutorial demonstrates how to write e2e (end-to-end) tests for your AG Grid React Table components with Playwright.

You can find the complete code for this tutorial in the following GitHub.

Contents

Project Setup

Create a new react project by running the following command:

npm create vite@latest testing-ag-grid -- --template react-ts

Change the directory to your newly created app and install the necessary packages with the following command:

cd testing-ag-grid
npm install
npm run dev

Create a new AG Grid Component

Next, go ahead and create a new AG Grid component in your React application. Run the following command to install AG Grid npm package.

npm install ag-grid-react

Next, open the index.css file and delete all the styles and replace all the code in your App.tsx file with the following code:

import { useState, useMemo } from "react";
import { AllCommunityModule, ModuleRegistry, ColDef } from "ag-grid-community";
import { AgGridReact } from "ag-grid-react";

export type ICar = {
  make: string;
  model: string;
  price: number;
  electric: boolean;
};

ModuleRegistry.registerModules([AllCommunityModule]);

function App() {
  // Row Data: The data to be displayed.
  const [rowData, setRowData] = useState<ICar[]>([
    { make: "Tesla", model: "Model Y", price: 64950, electric: true },
    { make: "Ford", model: "F-Series", price: 33850, electric: false },
    { make: "Toyota", model: "Corolla", price: 29600, electric: false },
    { make: "Mercedes", model: "EQA", price: 48890, electric: true },
    { make: "Fiat", model: "500", price: 15774, electric: false },
    { make: "Nissan", model: "Juke", price: 20675, electric: false },
  ]);

  // Column Definitions: Defines & controls grid columns.
  const [colDefs, setColDefs] = useState<ColDef<ICar>[]>([
    { field: "make", editable: true, filter: true },
    { field: "model" },
    { field: "price", editable: true },
    { field: "electric" },
  ]);

  const defaultColDef = useMemo(() => { 
    return {
      flex: 1
    };
  }, []););

  // Container: Defines the grid's theme & dimensions.
  return (
    <div style={{ width: "100%", height: '100vh' }}>
      <AgGridReact
        rowData={rowData}
        columnDefs={colDefs}
        defaultColDef={defaultColDef}
      />
    </div>
  );
}

export default App

Configure Playwright

To configure Playwright, first install the necessary dependencies by running the following command:

npm install --save-dev @playwright/test

Next, run the following script in the terminal to setup Playwright for component testing:

npm init playwright@latest -- --ct

Follow the prompts in your terminal and select the option based on your project. For simplicity, we are choosing the default options for this tutorial:

Playwright end-to-end test setup for AG Grid React Table

Once the Playwright script finishes running, open up the playwright-ct.config.js file.

This configuration file sets up Playwright to run tests on the specified browsers, with parallel execution, timeouts, retries, and other settings to control the testing process.

The auto-generated file may be using require for importing modules. If that is the case, update the file to use import keyword to keep everything consistent. You can use this playwright-ct.config.js file as a reference.

Testing Rendering

To test that the grid renders correctly, create a new file, App.spec.tsx, and add the following code:

import { test, expect } from '@playwright/experimental-ct-react';
import App from './App';

test('should render component with AG Grid', async ({ mount }) => {
	// Mount the component
	const component = await mount(<App />);

	// Verify that the AG Grid is rendered by checking for a specific element
	await expect(component.locator('.ag-root')).toBeVisible();

	// Optionally, verify that specific columns are rendered
	await expect(component.locator('.ag-header-cell-text').nth(0)).toHaveText('Make');
	await expect(component.locator('.ag-header-cell-text').nth(1)).toHaveText('Model');

	// Optionally, verify that a specific row is rendered
	await expect(component.locator('.ag-cell').nth(0)).toHaveText('Tesla');
	await expect(component.locator('.ag-cell').nth(1)).toHaveText('Model Y');
});

This test case ensures that the App component renders AG Grid correctly by checking for key elements, including column headers and row data. It confirms that the column headers "Make," "Model," and "Price" are displayed in the correct order. Additionally, it validates that a specific row contains the expected data: "Tesla," "Model Y," and "64950." Together, these checks verify that the grid’s structure and data are rendered as intended.

Run your test with the following command:

npm run test-ct
Output of the rendering test

When the test passes, it prints an output similar to the one above.

You can run the following command to open the HTML report of the test cases which will open up the test report in your browser.

npx playwright show-report
Test report in the browser

Asynchronous data loading

Most uses of AG Grid will involve fetching data from an API, so let's simulate this by asynchronously loading our rowData with a setTimeout:

const [rowData, setRowData] = useState(null);

useEffect(() => {
  // Simulate an asynchronous data fetch (e.g., from an API)
  const timer = setTimeout(() => {
    setRowData([
      { make: "Tesla", model: "Model Y", price: 64950, electric: true },
      { make: "Ford", model: "F-Series", price: 33850, electric: false },
      { make: "Toyota", model: "Corolla", price: 29600, electric: false },
      { make: "Mercedes", model: "EQA", price: 48890, electric: true },
      { make: "Fiat", model: "500", price: 15774, electric: false },
      { make: "Nissan", model: "Juke", price: 20675, electric: false },
    ]);
  }, 500);
  
  return () => clearTimeout(timer);
}, []);

Next, let's write a test to verify the table is rendered correctly when async data is involved:

test("should load data asynchronously and display it in the grid",
  async ({ mount, page }) => {
    // 1. Mount the component
    const component = await mount(<App />);
    
    // 2. Check that the default AG Grid loading overlay is visible.
    //    By default, it has the class 'ag-overlay-loading-center'.
    await expect(
        component.locator(".ag-overlay-loading-center")
    ).toBeVisible();
    
    // 3. Check if the first cell contains "Tesla"
    //    This confirms that row data has been rendered.
    const firstCell = component.locator(".ag-cell").first();
    await expect(firstCell).toHaveText("Tesla");
    
    // 4. Check that the loading overlay is no longer visible
    await expect(
        component.locator(".ag-overlay-loading-center")
    ).not.toBeVisible();
  }
);

This test verifies that AG Grid's loading overlay initially appears when loading async data. It then checks that the Row Data has been rendered by confirming the first cell contains "Tesla,", and ensures the loading overlay disappears after the data has loaded.

User interaction testing

Playwright can also be used to simulate user interactions. In this section, we will explore several code examples to demonstrate how you can use Playwright to simulate user actions and thoroughly test your grid's behaviour.

Sorting

Let’s create a test to verify the grid’s sorting functionality. Specifically, this test will check that clicking the price column header correctly sorts the data in both ascending and descending order. To implement this, we’ll expand the App.spec.tsx file with the following code:

test('should sort "Make" column ascending and then descending', 
	async ({ mount }) => {
		// 1. Mount the component
		const component = await mount(<App />);

		// 2. Locate the header cell with the text "Make"
		const priceHeader = 
			component.locator('.ag-header-cell-text', { hasText: 'Price' });

		// 3. Click once to sort ascending
		await priceHeader.click();

		// 4. Verify the first cell in the grid is now 'Fiat' 
        // (sorted by ascending "price")
		const firstItem = await component
			.locator(`.ag-row[row-index="0"] [col-id="make"]`)
			.innerText();
		await expect(firstItem).toBe('Fiat');

		// 5. Click again to sort descending
		await priceHeader.click();

		// 6. Verify the first cell in the grid is now 'Tesla' 
		// (descending by "price")
		const firstItemDesc = await component
				.locator(`.ag-row[row-index="0"] [col-id="make"]`)
				.innerText();
		await expect(firstItemDesc).toBe('Tesla');
	}
);

In the above code, we use the rowIndex (which reflects the row's current position in the grid's display) to verify the sort order. In this example, we've hardcoded the values we expect to see; a more robust test may loop through the values of each row, and use the Playwright expect(value).toBeLessThanOrEqual() API to validate the sorting functionality.

It may also be useful to consider using sourceRowIndex to compare values before and after sorting as this represents the position of a row in the original rowData array and remains constant regardless of sorting, filtering, grouping, or other UI operations.

Edit cell value

To test cell editing, modify the column definition in your App.tsx file to make the Make column and Price column editable by adding the editable property:

// Column Definitions: Defines & controls grid columns.
const [colDefs, setColDefs] = useState([
  { field: "make", editable: true },
  { field: "model" },
  { field: "price", editable: true },
  { field: "electric" },
]);

And add the following test to App.spec.tsx:

test('should allow editing of editable cells and update the data correctly',
	async ({ mount }) => {
		// 1. Mount the component
		const component = await mount(<App />);

		// 2. Locate the editable cell for "Price" in the first row
		const priceCell = 
			component.locator('.ag-row[row-index="0"] [col-id="price"]');

		// 3. Double-click the cell to activate edit mode
		await priceCell.dblclick();
		// Wait for the input field to appear (AG Grid renders it dynamically)
		const priceInput = priceCell.locator('input');
		await expect(priceInput).toBeVisible();
		
		// 4. Enter a new value into the input field
		const newPrice = '70000';
		await priceInput.fill(newPrice);
		// Simulate pressing Enter to save the change
		await priceInput.press('Enter');

		// 5. Verify the cell displays the updated value
		await expect(priceCell).toHaveText(newPrice);
	}
);

This test verifies the cell editing functionality in an AG Grid by simulating the following user interactions:

    1. Double clicks on a cell in the first row of the price column
    2. Inputs "70000"
    3. Presses “Enter”
    4. Verifies that the edited cell displays new value correctly

Filtering

Finally, let's test the filtering functionality. Modify the Column Definition again, this time adding the filter option to the Make column:

// Column Definitions: Defines & controls grid columns.
const [colDefs, setColDefs] = useState([
  { field: "make", editable: true, filter: true },
  { field: "model" },
  { field: "price", editable: true },
  { field: "electric" },
]);

Next, add the following test to your App.spec.tsx file:

test('should filter data by "Make" using the column filter menu', 
	async ({ mount, page }) => {
		// 1. Mount the component
		const component = await mount(<App />);

		// 2. Wait for AG Grid to be fully rendered
		await page.waitForSelector('.ag-root');

		// 3. Locate and click the filter menu icon for the "Make" column
		const makeColumnFilterIcon = 
				component.locator('.ag-header-cell[col-id="make"] .ag-header-icon');
		await expect(makeColumnFilterIcon).toBeVisible();
		await makeColumnFilterIcon.click();

		// 4. Use the placeholder locator to fill in "Tesla"
		const filterInput = 
				component.locator('.ag-filter-body input[placeholder="Filter..."]');
		await filterInput.fill('Tesla');

		// 5. Verify only one row is visible
		const rows = component.locator('.ag-center-cols-viewport .ag-row');
		await expect(rows).toHaveCount(1);

		// 6. Check the cell text
		const makeCell = rows.first().locator('.ag-cell[col-id="make"]');
		await expect(makeCell).toHaveText('Tesla');
	}
);

The test above simulates user interaction by clicking the filter icon in the "Make" column and entering "Tesla" as the filter value. It then confirms the filtering worked correctly by checking that only one row remains visible and contains "Tesla" in the make column.

Accessibility testing

AG Grid provides amongst the best support for accessibility compared to other grids available on the market today. The final part of this blog post demonstrates how to test this by validating things like ARIA labels and Keyboard navigation, etc...

Accessibility (a11y) checks

Install the axe-playwright package using the following command:

npm install --save-dev axe-playwright

This library integrates Deque’s axe-core into Playwright tests.

Create a new file App.a11y.spec.tsx and add the following code:

import { test, expect } from '@playwright/experimental-ct-react';
import { injectAxe, getViolations } from 'axe-playwright';
import App from './App';

test('check for accessibility violations', 
  async ({ mount, page }) => {
    // 1. Mount your React component
    await mount(<App />);

    // 2. Wait for AG Grid to load
    await page.waitForSelector('.ag-root');
    await page.waitForSelector(
      '.ag-overlay-loading-center', 
      { state: 'detached' }
    );

    // 3. Inject Axe on the page
    await injectAxe(page);

    // 4. Run the accessibility check on the grid
    const violations = await getViolations(page, '.ag-root', {
      detailedReport: true,
      detailedReportOptions: { html: true },
    });

    // 5. Check for violations
    expect(violations.length).toBe(0);
  }
);

This automated test ensures your AG Grid implementation follows accessibility standards by automatically catching common issues like missing ARIA labels, contrast problems, or improper HTML structure.

By integrating axe scan in your Playwright test suite, you can catch many common accessibility pitfalls early.

Keyboard navigation and focus

Many users rely on the keyboard for navigation. To ensure a truly accessible experience, it's not enough to check only for automated accessibility violations, keyboard navigation and focus management are equally critical. Let's explore how to test keyboard accessibility in your AG Grid implementation.

The following code tests keyboard accessibility in a data grid by checking several key interactions:

test('should allow keyboard navigation and focus within the grid', 
	async ({ mount, page }) => {
		// 1. Mount the component
		const component = await mount(<App />);
		await page.waitForSelector('.ag-root');
		await component.locator('.ag-root').click();

		// 2. Press Tab to move focus onto the Make column header
		await page.keyboard.press('Tab');
		const makeHeader =
				component.locator('.ag-header-cell[col-id="make"]');
		// 3. Open the filter pop-up by clicking the filter icon, then close it
		const makeHeaderFilterIcon =
				makeHeader.locator('.ag-header-icon');
		await makeHeaderFilterIcon.click();
		const filterPopup = component.locator('.ag-filter-wrapper');
		await expect(filterPopup).toBeVisible();

		// Press Escape to close the filter pop-up
		await page.keyboard.press('Escape');
		await expect(filterPopup).toBeHidden();

		// 4. Press ArrowDown to move focus from the header to 
        // the first row, "make" cell.
		await page.keyboard.press('ArrowDown');
		const firstRowFirstCell =
				component.locator('.ag-row[row-index="0"] [col-id="make"]');
		await expect(firstRowFirstCell).toBeFocused();
	}
);

Here’s the breakdown of the code:

  • Initially, the code tests if pressing Tab moves focus to the first interactive element, which is the "Make" column header.
  • Checks if clicking the filter icon opens a filter popup menu.
  • Verifies that pressing Escape properly closes this popup menu.
  • Tests if pressing the Down Arrow key moves focus from the header to the first cell in the "Make" column.
  • Ensures the proper cell gets focused after this keyboard navigation.

Conclusion

In this tutorial, we covered essential testing scenarios for testing AG Grid components using Playwright. You can build upon these examples to create more specialized tests for their specific use cases, whether that involves complex data manipulations, custom cell renderers, or advanced filtering scenarios.

If you’re an enterprise user and need further assistance with testing strategies, please contact us through Zendesk.

Read more posts about...