How to Test Real Estate Search and Listing Flows
Short answer
Property portals fail when map pins and list results desync, stale listings remain searchable after sold, saved searches leak across users, and geo filters return wrong bounds—not when a search box accepts text. Seed listings with known coordinates, assert listing id sets match between map and list probes, and prioritize high-traffic filter combos via TrueCoverage search_filters dimension.
Part of Testing Guides by industry.
Who this is for
Teams shipping MLS-powered portals, rental marketplaces, commercial property search, or map-first listing UIs with price/beds/baths filters, draw-on-map search, saved searches, and lead forms.
Why testing real estate listings matters
Search bugs directly affect broker revenue and consumer trust:
- Map/list desync — pin visible, listing missing from list; users miss properties or click ghosts.
- Stale sold listings — status sold but still in search index; wasted tours and MLS compliance issues.
- Wrong geo filter — draw polygon excludes interior pins; fair housing scrutiny if certain neighborhoods systematically dropped.
- Saved search alerts — cross-user leak of saved criteria; privacy incident.
- Lead routing — inquiry attached to wrong agent or listing id; commission disputes.
Probe listing id sets and listing_status—map marker count alone is unreliable with clustering.
Complexity map
| Scenario | Edge case | Why tests break | Approach |
|---|---|---|---|
| Map/list sync | Cluster hides count | Mismatch | Probe same id set |
| Sold stale | Index lag | Ghost listing | Probe status + search |
| Price+beds combo | AND logic wrong | False empty | Seed tagged listings |
| Draw polygon | Edge pins | Boundary bug | Probe geo contains |
| Saved search | User A sees B | ACL | Probe owner user_id |
| Pagination map move | New bounds | Duplicate fetch | Assert unique ids |
| Sort price desc | Wrong order | Bad comparables | Probe sorted ids |
| New listing alert | Email/in-app | Missing notify | Inject listing probe |
| Photo carousel | CDN 404 | Broken detail | HEAD probe optional |
| MLS sync delay | Pending status | Premature public | Probe visibility flag |
| School district filter | Boundary data | Wrong inclusion | Fixture district ids |
| Rental vs sale | Wrong mode | Mixed results | Probe listing_type |
Geo listing seed
await request.post('/api/test/seed-listings', {
data: {
runId,
listings: [
{ id: 'L1', lat: 37.77, lng: -122.42, price: 500000, beds: 2, status: 'active' },
{ id: 'L2', lat: 37.78, lng: -122.43, price: 800000, beds: 3, status: 'active' },
{ id: 'L3', lat: 37.77, lng: -122.42, price: 600000, beds: 2, status: 'sold' },
],
},
});
Map/list sync Assert
await page.goto('/search/san-francisco');
await page.getByLabel('Min beds').fill('2');
await page.getByRole('button', { name: 'Search' }).click();
const probe = await request.get(`/api/test/search?runId=${runId}&beds=2&status=active`).then(r => r.json());
expect(probe.ids).toEqual(expect.arrayContaining(['L1', 'L2']));
expect(probe.ids).not.toContain('L3');
const listIds = await page.getByTestId('listing-card').evaluateAll(nodes =>
nodes.map(n => n.getAttribute('data-listing-id'))
);
expect(new Set(listIds)).toEqual(new Set(probe.ids));
// Map: click pin for L1
await page.getByTestId('map-pin-L1').click();
await expect(page.getByTestId('listing-card').filter({ has: page.locator('[data-listing-id="L1"]') })).toBeVisible();
For Google Maps embeds see Google Maps integration—may require probe-first strategy when pins are canvas-rendered.
Saved search
await page.getByRole('button', { name: 'Save search' }).click();
const saved = await request.get(`/api/test/saved-searches?runId=${runId}`).then(r => r.json());
expect(saved).toHaveLength(1);
expect(saved[0].criteria.beds).toBe(2);
// Negative: other user
const other = await request.get(`/api/test/saved-searches?runId=${runId}-other`);
expect((await other.json())).toHaveLength(0);
Requirement slices to cover
search_filters— normalized combo e.g.beds:2|price:500k-800k|type:sale
Pair with search filters pattern for facet explosion strategy.
CI checklist
- Listings with lat/lng + status in seed
- Map/list id set equality via probe
- Sold listing excluded from active search probe
- Saved search ACL negative
- Avoid real MLS keys in CI—fixture index only
- ExploreChimp mobile map/list split view
Anti-patterns
| Anti-pattern | Why it fails | Better approach |
|---|---|---|
| Count map pins visually | Clustering | Probe ids |
| Prod MLS in CI | Rate/legal | Seed API |
| List text only | Map desync | Id set match |
| Skip sold stale | Ghost tours | status probe |
| Hard-coded zoom | Flaky pins | Fixed seed bbox |
| No saved search ACL | Privacy | User scope probe |
Example scenario
Situation: Buyer filters 2+ beds under $700k; map shows pin for listing sold yesterday.
Expected outcome: Sold listing excluded from list and map; probe search ids omit sold id.
Why UI-only automation breaks: List correct but map pin remains—user tours unavailable home.
- Arrange: Seed L3 sold with coords overlapping active listings.
- Act: Apply price and beds filters matching L1 only.
- Assert: Probe ids exclude L3; map pin L3 absent; detail URL for L3 returns gone or redirect.
TestChimp workflow: Instrument search_filters in prod; evolve price+beds combo if top traffic untested.
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).
Related scenarios
- Search and filters — facet patterns
- Google Maps — map embed testing
- Form validation — lead inquiry forms
- Infinite scroll — long result lists
External references
Frequently asked questions
How do I test map and list view stay in sync?
Apply filters, GET search probe for listing ids, compare to list card data-listing-id attributes, click map pin and assert list highlights same id.
How do sold listings leave search?
Seed sold status, assert probe omits from active search and map pin test id absent—test index lag with poll if async MLS sync.
Map clustering breaks pin counts?
Do not assert pin DOM count—use probe id set as source of truth; optional cluster expand interaction if product supports.
How do draw-on-map searches work in E2E?
Simulate polygon via test API setting bounds, or evaluate map library setBounds in test env, probe geo query returns expected ids inside polygon.
Saved search privacy?
Create saved search as user A, probe user B cannot fetch A saved search id—403 or empty.
Which search_filters matter most?
TrueCoverage search_filters distribution—evolve top combos like price range + beds + property type.
Google Maps vs custom map?
Custom maps allow data-testid pins; Google embed may need probe-first with limited pin click—see maps integration guide.
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.