Search Testing Guide

How to Test Typesense Search UI with Playwright: Complete 2026 Guide

A scenario-by-scenario walkthrough of testing Typesense search interfaces with Playwright. Search-as-you-type debouncing, faceted filtering, geo-search radius queries, synonym resolution, typo tolerance thresholds, and the InstantSearch adapter pitfalls that silently break real search test suites.

50ms

Typesense consistently returns search results in under 50 milliseconds on datasets of millions of documents, making it one of the fastest open-source search engines available.

Typesense documentation

0msMedian query latency
0Search scenarios covered
0+Rich search features tested
0%Fewer lines with Assrt

Typesense Search-as-You-Type Flow

BrowserInstantSearch AdapterTypesense ServerSearch IndexUser types keystrokeDebounce (200-400ms)POST /multi_searchQuery collectionRanked results + facet countsJSON responseRender hits, facets, pagination

1. Why Testing Typesense Search UI Is Harder Than It Looks

Typesense is fast. That speed is what makes it compelling for search-as-you-type experiences, but it is also what makes testing difficult. When results appear in under 50 milliseconds, the browser DOM updates faster than most test frameworks expect. Your Playwright test types a character, the results update before the next keystroke fires, and your assertions race against an intermediate state that flickers into existence for a few frames before the final result set arrives. Debounce logic in the InstantSearch adapter compounds this: the adapter waits 200 to 400 milliseconds after the last keystroke before sending the query, which means your test must account for both the debounce delay and the network round-trip, neither of which is deterministic.

Beyond timing, Typesense has six features that each introduce distinct testing challenges. Faceted filtering renders dynamic checkbox groups whose counts update with every query, and your test must verify both the filter labels and the numeric counts. Typo tolerance means a query for “laptp” returns results for “laptop,” which is correct behavior you need to assert positively rather than treating as a bug. Synonym configuration lives server-side, so your test environment must seed the same synonyms as production or results will differ silently. Geo-search adds latitude/longitude filtering that depends on the test dataset containing realistic coordinates. The InstantSearch adapter translates between Algolia’s InstantSearch.js widget protocol and Typesense’s native API, introducing a compatibility layer that can mask errors or produce unexpected widget states.

Finally, Typesense is self-hosted (or uses Typesense Cloud), which means your CI environment needs a running Typesense instance with seeded data. Unlike SaaS search providers, you control the infrastructure, the schema, the ranking rules, and the data pipeline. That control is powerful but means your test environment must replicate all of those configurations exactly.

Typesense Search Query Lifecycle

🌐

User Types

Keystroke in search box

⚙️

Debounce

200-400ms wait

↪️

Adapter

Translates to Typesense API

⚙️

Typesense

Queries collection

Ranking

Typo tolerance + synonyms

⚙️

Response

Hits + facets + metadata

🌐

Render

DOM updates with results

Faceted Search Filter Flow

🌐

Initial Query

All results loaded

🌐

Click Facet

User selects filter

↪️

Refined Query

filter_by appended

⚙️

Typesense

Filtered results

Update UI

Hits + facet counts refresh

2. Setting Up a Reliable Typesense Test Environment

A reproducible Typesense test environment requires three things: a running Typesense server instance, a seeded collection with known test data, and the correct synonym and curation rules configured before tests run. Docker is the most reliable way to manage the server in CI. Typesense publishes official Docker images that start in under two seconds.

Typesense Test Environment Checklist

  • Install Typesense via Docker or binary for CI
  • Generate a dedicated API key with search-only scope for the frontend
  • Create a collection schema matching your production schema exactly
  • Seed test documents with known, deterministic data (100+ records)
  • Configure synonyms that match production synonym rules
  • Add geo coordinates to test documents if using geo-search
  • Set up the InstantSearch adapter with the test Typesense host
  • Verify the search UI loads and connects before running scenarios

Docker Compose for CI

Typesense Docker Setup

Environment Variables

.env.test

Collection Schema and Seed Script

Create the collection with fields that support all the features you need to test: string fields for full-text search, numeric fields for sorting and range filtering, string arrays for faceting, and a geopoint field for geo-search. Seed at least 100 documents so facet counts are meaningful and pagination can be exercised.

test/helpers/typesense-seed.ts

Playwright Configuration for Typesense Tests

playwright.config.ts

3. Scenario: Search-as-You-Type with Debounce

Search-as-you-type is the core Typesense experience. The user types into a search box, the InstantSearch adapter debounces the input (typically 200 to 400 milliseconds), sends the query to the Typesense /multi_search endpoint, and renders the results in the DOM. The challenge is that intermediate results may flash on screen between keystrokes. Your test must wait for the final stable result set after all keystrokes have been processed and the debounce timer has elapsed.

1

Search-as-You-Type Happy Path

Moderate

Goal

Type a multi-character query into the search box, wait for the debounce to resolve, and verify that the displayed results match the expected documents from the seeded dataset.

Preconditions

  • Typesense server running with 150 seeded products
  • App search page loaded at /search
  • InstantSearch adapter configured with 300ms debounce

Playwright Implementation

search-as-you-type.spec.ts

What to Assert Beyond the UI

  • Intercept the /multi_search API call and verify the request body contains the correct query string
  • Verify the response includes found count matching your seeded data expectations
  • Confirm the debounce prevented excessive API calls (one call per settled keystroke batch, not one per character)

Search-as-You-Type: Playwright vs Assrt

import { test, expect } from '@playwright/test';

test('search-as-you-type returns matching results', async ({ page }) => {
  await page.goto('/search');
  await expect(
    page.locator('[class*="ais-Hits-item"]').first()
  ).toBeVisible();

  const searchBox = page.locator(
    'input[class*="ais-SearchBox-input"]'
  );
  await searchBox.fill('Electronics');

  await expect(
    page.locator('[class*="ais-Hits-item"]').first()
  ).toContainText(/electronics/i, { timeout: 5_000 });

  const hitCount = await page
    .locator('[class*="ais-Hits-item"]')
    .count();
  expect(hitCount).toBeGreaterThan(0);
});
37% fewer lines

4. Scenario: Faceted Filtering and Refinement

Typesense supports faceted search out of the box. Fields marked with facet: true in the collection schema generate dynamic filter groups in the InstantSearch UI. Each facet value displays a count of matching documents. When the user clicks a facet checkbox, the adapter appends a filter_by parameter to the query, and Typesense returns a reduced result set with updated facet counts for the remaining values. Testing this correctly requires verifying both the result reduction and the facet count recalculation.

2

Faceted Category Filtering

Moderate

Goal

Select a category facet, verify the results are filtered to only that category, confirm the facet counts update to reflect the narrowed result set, then remove the filter and verify all results return.

Preconditions

  • Collection schema has category and brand as faceted fields
  • At least 5 distinct categories seeded with 20+ documents each
  • InstantSearch RefinementList widgets rendered for each facet

Playwright Implementation

faceted-filtering.spec.ts

What to Assert Beyond the UI

  • Intercept the /multi_search request and confirm the filter_by parameter includes category:=Electronics
  • Verify that brand facet counts recalculate when category is filtered (cross-facet dependency)
  • Confirm the URL query parameters update to reflect the active facet (for bookmarkable filtered views)

Try Assrt for free

Open-source AI testing framework. No signup required.

Get Started

5. Scenario: Typo Tolerance and Fuzzy Matching

Typesense has built-in typo tolerance controlled by the num_typosparameter (defaulting to 2). When a user types “laptp” instead of “laptop,” Typesense identifies the edit distance and returns results for the corrected term. Testing this requires asserting that misspelled queries return the same results as correctly spelled ones, while also verifying that the typo tolerance does not produce false positives for completely unrelated terms.

3

Typo Tolerance Fuzzy Matching

Straightforward

Goal

Search for a deliberately misspelled term, verify that results still match the intended query, and confirm that the result set is consistent with the correctly spelled version.

Playwright Implementation

typo-tolerance.spec.ts

What to Assert Beyond the UI

  • Intercept the API response and check that the text_match score is lower for typo results, confirming Typesense applied edit distance
  • Verify the num_typos parameter in the request matches your application configuration

6. Scenario: Synonym Resolution

Typesense synonyms are configured at the collection level using the synonyms API. When you define ["laptop", "notebook", "portable computer"] as a synonym group, searching for any of those terms returns results containing any of the others. This is powerful for user experience but introduces a testing dependency: your test environment must seed the same synonym rules as production, or your assertions about which results appear for a given query will be wrong.

4

Synonym Search Resolution

Moderate

Goal

Search for a synonym term and verify that results include documents containing any term in the synonym group. Confirm that the result sets for all synonym variants overlap significantly.

Preconditions

  • Synonym group seeded: ["phone", "smartphone", "mobile", "cell phone"]
  • Test documents include products with all synonym variants in their names

Playwright Implementation

synonym-resolution.spec.ts

Synonym Testing: Playwright vs Assrt

import { test, expect } from '@playwright/test';

test('synonyms return equivalent results', async ({ page }) => {
  await page.goto('/search');
  const searchBox = page.locator(
    'input[class*="ais-SearchBox-input"]'
  );

  await searchBox.fill('phone');
  await expect(
    page.locator('[class*="ais-Hits-item"]').first()
  ).toBeVisible({ timeout: 5_000 });
  const phoneCount = await page
    .locator('[class*="ais-Stats-text"]')
    .textContent();

  await searchBox.clear();
  await searchBox.fill('mobile');
  await expect(
    page.locator('[class*="ais-Hits-item"]').first()
  ).toBeVisible({ timeout: 5_000 });
  const mobileCount = await page
    .locator('[class*="ais-Stats-text"]')
    .textContent();

  expect(phoneCount).toBe(mobileCount);
});
50% fewer lines

8. Scenario: InstantSearch Adapter Integration

Most Typesense search UIs use the typesense-instantsearch-adapterpackage, which translates between Algolia’s InstantSearch.js widget protocol and Typesense’s native multi-search API. This adapter is a compatibility layer that makes it possible to use Algolia’s rich widget ecosystem (SearchBox, Hits, RefinementList, Pagination, and dozens more) with a Typesense backend. However, the adapter introduces its own set of failure modes. Version mismatches between the adapter, InstantSearch, and Typesense Server can cause silent data transformation bugs where the adapter sends malformed queries or misinterprets response fields.

6

InstantSearch Adapter Pagination and Sorting

Complex

Goal

Verify that pagination, sorting, and the hits-per-page widget all function correctly through the adapter. This covers the most common adapter integration failure points.

Playwright Implementation

instantsearch-adapter.spec.ts

What to Assert Beyond the UI

  • Verify the adapter sends the correct collection name in each /multi_search request body
  • Check that the page parameter increments correctly when navigating pagination
  • Confirm the adapter does not silently drop facet parameters when sorting changes (a known issue in older adapter versions)

Pagination Testing: Playwright vs Assrt

import { test, expect } from '@playwright/test';

test('pagination navigates pages', async ({ page }) => {
  await page.goto('/search');
  await expect(
    page.locator('[class*="ais-Hits-item"]').first()
  ).toBeVisible();

  const firstResult = await page
    .locator('[class*="ais-Hits-item"]')
    .first()
    .textContent();

  await page.locator('[class*="ais-Pagination-link"]')
    .filter({ hasText: '2' })
    .click();

  await expect(
    page.locator('[class*="ais-Hits-item"]').first()
  ).not.toHaveText(firstResult || '', { timeout: 5_000 });

  const activePage = page.locator(
    '[class*="ais-Pagination-item--selected"]'
  );
  await expect(activePage).toHaveText('2');
});
48% fewer lines

9. Common Pitfalls That Break Typesense Test Suites

Debounce Timing Causes Flaky Assertions

The most common source of flakiness in Typesense search tests is asserting on results before the debounce timer has elapsed. If your InstantSearch adapter uses a 300ms debounce and your test types a query then immediately checks the result DOM, it will see stale or empty results. Never use a fixed waitForTimeoutto work around this. Instead, use Playwright’s built-in retry assertions like toContainText or toHaveCount with a generous timeout. These assertions poll the DOM automatically until the condition is met or the timeout expires.

Stale Index Data Between Test Runs

Typesense indexes documents synchronously on write, but if your global setup script does not wait for the import operation to complete before starting tests, you may query an empty or partially populated collection. Always await the documents().import() call and verify the document count matches expectations before proceeding. A single missing document can cause a facet count assertion to fail intermittently.

Synonym Rules Not Seeded in Test Environment

Synonyms are stored at the collection level and are not included when you export and re-import documents. If your CI setup creates a fresh collection and imports documents but forgets to seed synonym rules, every synonym-dependent test will fail with zero results for the variant terms. Include synonym seeding as an explicit step in your global setup script, right after document import.

InstantSearch Adapter Version Mismatches

The typesense-instantsearch-adapter has strict version compatibility requirements with both instantsearch.js and the Typesense server. Adapter version 2.x works with InstantSearch 4.x and Typesense Server 0.25+. Using mismatched versions can cause subtle bugs: facet counts that are off by one, pagination that skips pages, or sorting that silently falls back to the default. Pin all three versions in your package.json and test with the exact same versions in CI as in production.

Geo-Search Coordinate Precision

Typesense expects geopoint fields as [lat, lng]arrays with decimal degrees. A common mistake is swapping latitude and longitude in the test seed data, which places every document on the wrong side of the planet. Your geo-search tests will return zero results and the error is not obvious from the Typesense response. Always validate a sample document’s coordinates against a map before trusting your seed data.

Typesense Test Anti-Patterns to Avoid

  • Using fixed waitForTimeout instead of retry assertions for debounce
  • Forgetting to seed synonym rules in the CI test collection
  • Running tests against a partially indexed collection
  • Pinning different adapter/InstantSearch/Typesense server versions than production
  • Swapping latitude and longitude in geo-search seed data
  • Asserting on intermediate search results before debounce settles
  • Hardcoding facet counts instead of deriving them from seed data
  • Not cleaning up collections between test suite runs
Typesense Search Test Suite Run

10. Writing These Scenarios in Plain English with Assrt

Each scenario above is 20 to 50 lines of Playwright TypeScript with fragile CSS selectors like [class*="ais-Hits-item"] and [class*="ais-RefinementList-count"]. These selectors break whenever InstantSearch.js releases a new major version that changes its CSS class naming convention, or when your team switches from the default InstantSearch theme to a custom one. Assrt describes the test intent in plain English and resolves selectors at runtime, so a class name change triggers automatic regeneration instead of a broken test suite.

The faceted filtering scenario from Section 4 is a good example. In raw Playwright, you need to know the exact CSS class for the RefinementList item, the count element inside it, the Stats text format, and the checkbox input within the facet. In Assrt, you describe the user intent and the framework resolves each element at test time.

scenarios/typesense-full-suite.assrt

Assrt compiles each scenario block into the same Playwright TypeScript you saw in the preceding sections, committed to your repo as real test files you can read, run, and debug. When InstantSearch.js changes its CSS class naming from ais-Hits-item to something new, or your team switches to a headless InstantSearch implementation with custom components, Assrt detects the selector failures, analyzes the new DOM structure, and opens a pull request with updated locators. Your scenario files remain untouched.

Start with the search-as-you-type happy path. Once it passes in CI, add the faceted filtering scenario, then typo tolerance, then synonyms, then geo-search, then the pagination and sorting integration test. Within a single afternoon you can have comprehensive Typesense search coverage that accounts for debounce timing, synonym resolution, geo-boundaries, and adapter compatibility, all described in plain English that any team member can read and modify.

Related Guides

Ready to automate your testing?

Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.

$npm install @assrt/sdk