Skip to main content

How to Test Data Grids, Sorting, and Bulk Actions

Short answer

Data grids fail on virtual scroll hiding rows, bulk actions targeting wrong selection scopes, and sort+filter interactions—not on whether one column header clicks. Seed 100+ rows with stable ids, scroll into view for interactions, probe bulk mutation results, and prioritize grid_action slices via TrueCoverage for admin consoles.

Part of Testing Guides by UI patterns.

Who this is for

Teams shipping admin consoles, ops tables, CRM lists, or AG Grid / MUI DataGrid / TanStack Table views with sort, filter, pagination, row selection, and bulk delete/export.

Why testing data grids matters

Grid bugs cause irreversible ops mistakes:

  • Bulk delete wrong rows — "select all on page" vs "select all matching filter" confusion deletes 10k records.
  • Virtual scroll — automation clicks wrong row; permission change applied to neighbor id.
  • Sort instability — equal keys reorder between pages; reconciliation reports wrong.
  • Export skew — CSV missing filtered subset; compliance export incomplete.
  • Stale selection — filter changes but selection bitmap retains old ids; silent wrong bulk update.

Probe affected row ids after every bulk action—never toast alone.

Complexity map

ScenarioEdge caseWhy tests breakApproach
Virtual scrollRow not mountedClick missscrollIntoView + data-id
Select all pagevs select all resultsWrong scopeProbe count deleted
Sort + filterSort resets filterBad exportCombined probe
Column resize/reorderPersist prefsLow riskOptional snapshot
Inline editTab between cellsSave raceProbe cell value
Bulk action asyncJob queueEarly assertpoll job probe
Row expansionNested gridLocator depthgetByRole row
Keyboard navShift select rangeUntestedkeyboard.select
Empty filterZero rows select allError stateDisabled bulk bar
Permission columnAction hidden but API openSecurityProbe 403
Cross-page selectIDs preservedLeak on page 2Probe selection set
CSV exportBackground jobDownload flakeProbe export job ids

Seed large table

await request.post('/api/test/seed-grid', {
data: { runId, rowCount: 150, pattern: 'status:pending' },
});
await page.goto('/admin/users');

Sort and filter probe

await page.getByRole('columnheader', { name: 'Created' }).click();
await expect(page.getByRole('columnheader', { name: 'Created' })).toHaveAttribute('aria-sort', 'ascending');
const sorted = await request.get(`/api/test/grid?runId=${runId}&sort=created_asc`).then(r => r.json());
expect(sorted.ids).toEqual(sorted.ids.slice().sort(/* match server */));

Pair with search filters guide for facet overlap.

Bulk select scoped action

await page.getByRole('checkbox', { name: 'Filter: Pending' }).check();
await page.getByRole('checkbox', { name: 'Select all rows' }).check(); // know your grid semantics!
await page.getByRole('button', { name: 'Archive' }).click();
await page.getByRole('button', { name: 'Confirm' }).click();

const probe = await request.get(`/api/test/grid-archived?runId=${runId}`).then(r => r.json());
expect(probe.archivedCount).toBe(probe.pendingCount);
expect(probe.archivedIds.every(id => probe.pendingIds.includes(id))).toBe(true);

Critical: assert semantic of "select all" matches product definition.

Virtual row interaction

const row = page.getByRole('row', { name: /user-042/ });
await row.scrollIntoViewIfNeeded();
await row.getByRole('checkbox').check();
await page.getByRole('button', { name: 'Disable' }).click();
const user = await request.get('/api/test/user/user-042').then(r => r.json());
expect(user.disabled).toBe(true);

Requirement slices to cover

  • grid_action — archive, export, disable, tag
  • selection_scope — page, filtered, explicit_ids

When prod shows bulk export on filtered subsets at 30% but tests only single-row delete, run /testchimp evolve—link // @Scenario: for SOC2 controls (audit logs).

CI checklist

  1. Seed 100+ rows for virtual scroll specs
  2. Bulk action specs include selection scope assertion via probe
  3. Sort uses aria-sort or probe ordering
  4. Export: probe job result ids, not only download event
  5. Negative: bulk API without permission returns 403
  6. Parallel-safe runId on all row ids

Anti-patterns

Anti-patternWhy it failsBetter approach
Click row by indexVirtualizationdata-id / name
Trust select-all labelAmbiguous productProbe counts
DOM row count = totalVirtual capProbe total
Skip export contentWrong CSVJob probe ids
One sort direction onlyDesc bugsToggle twice
No async job pollFlaky passexpect.poll job

Example scenario

Situation: Admin filters pending users, selects all matching, bulk archives.

Expected outcome: Only pending rows archived; active users untouched; audit log bulk_archive.

Why UI-only automation breaks: Toast says 50 archived; probe shows 50 random ids including active users.

  1. Arrange: Seed 80 pending + 20 active with distinct ids for runId.
  2. Act: Filter pending, select all matching, archive confirm.
  3. Assert: Probe archivedIds subset of pendingIds; active unchanged; audit probe event bulk_archive.

TestChimp workflow: Compare grid_action × selection_scope prod vs test; evolve filtered bulk paths.

Same Arrange/Act/Assert pattern as expired-coupon checkout.

Connect scenarios to your QA workflow

Capture business rules in markdown test plans and enforce them with seed routes and probe Assert. Link SmartTests with // @Scenario: for requirement traceability. Use /testchimp test on PRs; /testchimp explore on SmartTest paths for non-functional gaps (ExploreChimp).

External references

Frequently asked questions

Which grid actions are used in prod admin?

Compare grid_action × selection_scope in TrueCoverage. Prioritize bulk operations with high prod traffic—run /testchimp evolve when adding new bulk buttons.

How do virtualized grids affect row clicks?

scrollIntoViewIfNeeded on row locator located by stable id or accessible name—never nth-row index on 1000-row virtual grids.

Select all on page vs all results?

Product-specific—probe archived/deleted ids match intended scope. One of the highest severity admin bugs when wrong.

How do I test CSV export?

Trigger export, poll job probe for row ids included, optionally parse downloaded file in test env—probe first for speed.

How do inline edits differ from bulk?

Inline: probe single cell after blur/save. Bulk: probe batch job affecting many ids—async poll required.

Should sort tests use DOM or API?

API probe for order authoritative; aria-sort on columnheader for UX. Combine for sort+filter combos.

How do audit logs tie to grid bulk actions?

After bulk archive, probe audit_event_type bulk_archive with actor and id list—required for compliance verticals.

Apply these patterns in your repo

Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.

Start free on TestChimp · Book a demo