How to Test Address Autocomplete on Web (Complete Guide)
Address autocomplete is a UI pattern that lets users start typing a location and receive a drop‑down of suggestions sourced from a geocoding service. When it works well, friction drops, conversion ris
Why Address Autocomplete Matters
Address autocomplete is a UI pattern that lets users start typing a location and receive a drop‑down of suggestions sourced from a geocoding service. When it works well, friction drops, conversion rises, and support tickets shrink. When it fails, users abandon checkout, enter wrong shipping data, or trigger downstream validation errors that surface as failed orders, fraud alerts, or compliance issues.
In production you will see three classes of failure that are rarely caught by unit tests:
- Data‑source mismatches – the API returns a format the front‑end does not expect (extra fields, missing
place_id, localized strings). - Interaction glitches – keyboard navigation, screen‑reader announcements, or touch‑event handling break under specific browser/OS combos.
- State‑leakage bugs – the component retains stale suggestions after a user clears the field, switches locales, or opens a modal that overlays the input.
Because autocomplete touches networking, DOM manipulation, ARIA handling, and persisting state, a single change in any of those layers can ripple into a visible defect. A focused test strategy therefore needs to cover the happy path, the ways the service can misbehave, the ways users can interact oddly, and the ways the component must stay accessible and secure.
Core Concepts and Common Implementations
Most web autocomplete widgets share a handful of responsibilities:
- Input listening – debounce keystrokes, ignore non‑printable keys, handle composition events for IME.
- Request throttling – limit calls to the geocoding endpoint (often via
lodash.debounceor RxJS). - Response parsing – normalize the payload into a uniform shape (
{ label, value, raw }). - Suggestion rendering – create a listbox, manage focus, highlight matches, and respect ARIA
listbox/optionroles. - Selection handling – copy the chosen value into the input, fire a
changeevent, and close the popup. - Cleanup – abort pending requests on unmount, clear timers, and remove event listeners.
Popular libraries (Google Maps Places API, Algolia Places, Mapbox Search, or custom back‑ends) differ mainly in the shape of their JSON response and the authentication method (API key vs. token). Knowing which library your project uses helps you predict failure modes: Google may return status: ZERO_RESULTS; Algolia may wrap results in hits; a custom endpoint might expose pagination cursors.
Test Matrix
Below is a comprehensive matrix that you can copy into a test‑plan spreadsheet. Each row is a distinct scenario; columns indicate the test type, expected outcome, and notes on automation feasibility.
| # | Category | Scenario | Expected Result | Manual? | Automated? | Notes |
|---|---|---|---|---|---|---|
| 1 | Happy path | User types “1600 Amphitheatre Parkway, Mountain View, CA” and selects first suggestion | Input shows full formatted address; hidden field receives place_id; no JS errors | ✅ | ✅ (e2e) | Verify network request contains correct query |
| 2 | Happy path – locale | UI language set to es; user types “Calle de Alcalá, Madrid” | Suggestions appear in Spanish; address components ordered per locale | ✅ | ✅ (e2e + i18n) | Check lang attribute on and ARIA labels |
| 3 | Happy path – keyboard | User types “New York”, presses ↓ twice, then Enter | Second suggestion highlighted; selection commits on Enter | ✅ | ✅ (e2e) | Ensure focus stays inside listbox |
| 4 | Happy path – touch | On mobile, user taps the input, types “San Fran”, taps suggestion | Keyboard may stay open; selected address appears; no double‑tap zoom | ✅ | ⚠️ (device lab) | Use real device or BrowserStack; check touch-action |
| 5 | Error path – API timeout | Geocoding service returns 504 after 8 s | UI shows “Unable to fetch suggestions” inline; retry button appears after 2 s | ✅ | ✅ (mock server) | Verify abortController usage |
| 6 | Error path – malformed JSON | Service returns HTML error page instead of JSON | UI gracefully falls back to empty list; logs warning to console | ✅ | ✅ (unit + mock) | Test with msw or nock |
| 7 | Error path – zero results | Query “xxxxxxxxxx” yields empty predictions array | Listbox stays hidden; input shows helper text “No matches found” | ✅ | ✅ (e2e) | Ensure ARIA aria-expanded="false" |
| 8 | Edge case – rapid keystrokes | User pastes a 200‑character string; component debounces at 300 ms | Only one request sent after debounce expires; UI does not flood | ✅ | ✅ (unit) | Use sinon fake timers |
| 9 | Edge case – IME composition | User types Japanese with IME; compositionend event fires | No request sent during composition; request sent after commit | ✅ | ✅ (unit) | Listen for compositionstart/compositionend |
| 10 | Edge case – clipboard paste | User pastes address from clipboard via Ctrl+V | Same behavior as typing; suggestions appear after debounce | ✅ | ✅ (e2e) | Verify paste event handling |
| 11 | Edge case – component re‑mount | User opens a modal that contains the autocomplete, closes it, reopens | Fresh state: no stale suggestions, pending requests cleared | ✅ | ✅ (e2e) | Check cleanup in useEffect return |
| 12 | Accessibility – screen reader | NVDA/Jaws reads each suggestion as option, announces selected value | All listbox items have role="option"; live region updates when selection changes | ✅ | ✅ (axe + e2e) | Verify aria-activedescendant |
| 13 | Accessibility – color contrast | Suggestion highlight uses #ffeb3b on white background | Contrast ratio ≥ 4.5:1 (AA) for normal text | ✅ | ✅ (axe) | Use automated contrast checks |
| 14 | Accessibility – keyboard trap | Pressing Tab moves focus out of listbox after last item | No trap; focus goes to next tabbable element | ✅ | ✅ (e2e) | Test with tab key sequences |
| 15 | Security – XSS via suggestion | Service returns in description | HTML is escaped; no script execution | ✅ | ✅ (unit) | Render suggestions as text or use DOMPurify |
| 16 | Security – API key leakage | Key embedded in JS bundle; network tab shows it in query string | Key is proxied through backend or uses token with restricted referrer | ✅ | ✅ (network sniff) | Verify via CSP or Subresource Integrity |
| 17 | Privacy – data minimization | Component sends only the current input value, not full form | No extra fields (e.g., user ID) appear in request payload | ✅ | ✅ (unit) | Inspect request body with devtools |
| 18 | Production‑only – network flaky | Simulate 30 % packet loss with tc or ToxiProxy | UI shows retry mechanism; no infinite spinner | ⚠️ (requires infra) | ✅ (chaos testing) | Use feature flag to enable throttling |
| 19 | Production‑only – locale mismatch | Browser Accept-Language is fr-FR but service defaults to en-US | Service honors Accept-Language or falls back gracefully | ⚠️ (needs i18n test) | ✅ (mock i18n) | Verify hl param for Google Places |
| 20 | Production‑only – third‑party CDN failure | Autocomplete CSS/JS served from CDN returns 404 | Fallback to bundled copy; UI remains functional | ⚠️ (requires CDN mock) | ✅ (service worker) | Test with offline simulation |
How to Read the Matrix
- Manual? indicates whether a human tester can reliably verify the scenario without tooling.
- Automated? shows if the scenario can be covered by unit, integration, or end‑to‑end (e2e) tests; a ⚠️ means it requires special infrastructure (device lab, network throttling, chaos engineering).
- Notes give implementation hints that will reappear in the automation sections.
Manual Testing Approach
A disciplined manual session follows a repeatable script that mirrors the matrix but adds exploratory steps. Below is a step‑by‑step checklist you can paste into a test‑case management tool.
- Preparation
- Load the page in Chrome, Firefox, and Safari (latest stable).
- Open DevTools → Network → Preserve log; filter to the autocomplete endpoint.
- Enable the Accessibility pane and axe core extension.
- Set device toolbar to a common mobile viewport (e.g., iPhone 12).
- Baseline Happy Path
- Click the address input.
- Type “1 Apple Park Way”.
- Observe the request URL contains the query string properly encoded.
- Verify the suggestion list appears within 500 ms.
- Use arrow keys to move highlight; press Enter on the third item.
- Confirm the input now shows the full formatted address and a hidden field (if any) holds the place ID.
- Keyboard‑Only Navigation
- Disable mouse; use only Tab, Shift+Tab, Arrow keys, Enter, Escape.
- Ensure focus never leaves the input/listbox loop unintentionally.
- Verify Escape closes the listbox and returns focus to the input.
- Touch Interaction
- Switch to device mode; tap the input.
- Use the on‑screen keyboard to type “Mount”.
- Tap a suggestion; confirm the soft keyboard does not dismiss unexpectedly.
- Rotate device; ensure the listbox re‑anchors correctly.
- Error Injection
- Use DevTools → Network → Block request URL or set status to 504.
- Type a query; observe the fallback UI (error message, retry button).
- Click retry; confirm a new request is issued and the listbox repopulates.
- Change the block to return malformed HTML; verify no exception is thrown and the list stays empty.
- Accessibility Audit
- Run axe core; note any violations (missing roles, insufficient contrast).
- With NVDA, turn on speech; navigate the listbox with arrow keys; listen for announcement of each suggestion’s label and the “selected” state when Enter is pressed.
- Check that the listbox has
aria-expandedtoggling correctly.
- State and Cleanup
- Open a modal that duplicates the autocomplete component.
- Type a query, close the modal, reopen it; ensure the input is empty and no stale suggestions appear.
- Use DevTools → Performance to record a long typing session; confirm no memory leak (steady JS heap).
- Security & Privacy Spot‑check
- In the Network tab, locate the autocomplete request; verify that any API key appears only as a placeholder or is omitted (proxied).
- Paste a string containing
; confirm the request sends the raw text but the rendered suggestion list shows escaped characters. - Review the request payload; ensure no extra fields like
userIdorsessionTokenare inadvertently included.
- Exploratory Bursts
- Turn off JavaScript; does the fallback plain text input still work?
- Change the system language to a right‑to‑left locale (e.g., Arabic) and verify UI mirroring.
- Simulate a slow 3G connection (DevTools → Throttling) and watch for UI jank or duplicate requests.
Following this script will catch the majority of matrix items. The exploratory bursts (step 9) are where you often discover production‑only bugs that automated scripts miss because they rely on predictable timing or static mocks.
Automated Testing Approaches
Unit Tests
Isolate the pure logic: debounce, response normalization, suggestion filtering, and ARIA state updates. Use Jest (or Vitest) with jsdom for DOM‑based assertions.
// debounce.test.js
import { debounce } from './autocomplete-utils';
test('debounce delays execution', () => {
const fn = jest.fn();
const debounced = debounce(fn, 150);
debounced();
debounced();
jest.advanceTimersByTime(100);
expect(fn).not.toHaveBeenCalled();
jest.advanceTimersByTime(60);
expect(fn).toHaveBeenCalledTimes(1);
});
// normalizeResponse.test.js
import { normalizeGoogleResponse } from './responseParsers';
test('extracts label and place_id', () => {
const raw = {
predictions: [
{ description: '1600 Amphitheatre Parkway, Mountain View, CA 94043, USA',
place_id: 'ChIJ2eUgeAK6j4ARbn5u_wAGqWA',
structured_formatting: { main_text: '1600 Amphitheatre Parkway' } }
]
};
const result = normalizeGoogleResponse(raw);
expect(result).toEqual([
{ label: '1600 Amphitheatre Parkway', value: 'ChIJ2eUgeAK6j4ARbn5u_wAGqWA', raw: raw.predictions[0] }
]);
});
Unit tests give you fast feedback on algorithmic changes and guard against regressions in the debounce timing or response shape.
Integration Tests (Component Level)
Render the autocomplete component in isolation with React Testing Library or Vue Test Utils. Mock the fetch call using MSW (Mock Service Worker) to simulate success, error, and empty responses.
// autocomplete.integration.test.js
import { render, screen, waitFor } from '@testing-library/react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import Autocomplete from './Autocomplete';
const server = setupServer(
rest.get('https://example.com/geocode', (req, res, ctx) => {
const query = req.url.searchParams.get('input');
if (!query) return res(ctx.status(400));
return res(
ctx.json({
predictions: [
{ description: `${query}, USA`, place_id: `id_${query}` }
]
})
);
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test('shows suggestions after debounce', async () => {
render(<Autocomplete />);
const input = screen.getByRole('textbox', { name: /address/i });
await userEvent.type(input, '1600 Amphitheatre');
// advance timers if debounce uses setTimeout
await waitFor(() => {
expect(screen.getByRole('option')).toBeInTheDocument();
});
const firstOption = screen.getByRole('option');
expect(firstOption).toHaveTextContent(/1600 Amphitheatre/);
});
Integration tests verify that the component wires up networking, state, and rendering correctly while keeping the test suite fast (no real browser).
End‑to‑End Tests
Use Playwright (recommended for its auto‑waiting and tracing) or Cypress to drive a real browser against a staging deployment. The following Playwright script covers the happy path, keyboard navigation, and error simulation.
// tests/address-autocomplete.spec.js
const { test, expect } = require('@playwright/test');
test.describe('Address autocomplete', () => {
test.beforeEach(async ({ page }) => {
await page.goto('https://staging.example.com/checkout');
});
test('user can select an address via keyboard', async ({ page }) => {
const input = page.locator('input[aria-label="Street address"]');
await input.fill('1600 Amphitheatre Pkwy');
await page.waitForTimeout(300); // matches debounce
const listbox = page.locator('role=listbox');
await expect(listbox).toBeVisible();
await page.keyboard.press('ArrowDown');
await page.keyboard.press('Enter');
await expect(input).toHaveValue(/1600 Amphitheatre Parkway/);
});
test('shows error when geocoding service fails', async ({ page }) => {
// Intercept and fail the request
await page.route('**/geocode', route => route.fulfill({ status: 504 }));
const input = page.locator('input[aria-label="Street address"]');
await input.fill('Invalid Street');
await page.waitForTimeout(300);
const errorMsg = page.locator('text=Unable to fetch suggestions');
await expect(errorMsg).toBeVisible();
const retryBtn = page.locator('button:has-text("Retry")');
await expect(retryBtn).toBeEnabled();
await retryBtn.click();
await page.waitForResponse('**/geocode');
await expect(page.locator('role=listbox')).toBeVisible();
});
test('maintains ARIA attributes', async ({ page }) => {
const input = page.locator('input[aria-label="Street address"]');
await input.fill('New York');
await page.waitForTimeout(300);
const listbox = page.locator('role=listbox');
await expect(listbox).toHaveAttribute('aria-expanded', 'true');
await page.keyboard.press('Escape');
await expect(listbox).toHaveAttribute('aria-expanded', 'false');
});
});
Why Playwright?
- Auto‑waits for elements to be stable, reducing flaky
waitForTimeoutusage. - Built‑in tracing captures DOM snapshots, network logs, and console output for each test run.
- Easy to emulate devices, geolocation, and color‑vision deficiencies.
Performance & Load Tests
While not functional, measuring request volume under load helps catch debounce misconfigurations that cause a thundering herd. Use k6 or Artillery to simulate many virtual users typing rapidly.
// k6 script
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
stages: [
{ duration: '2m', target: 50 }, // ramp-up
{ duration: '5m', target: 50 }, // steady
{ duration: '2m', target: 0 }, // ramp-down
],
};
export default function () {
const payload = JSON.stringify({ input: '1600 Amphitheatre' });
const params = { headers: { 'Content-Type': 'application/json' } };
const res = http.post('https://api.example.com/geocode', payload, params);
check(res, { 'status 200': (r) => r.status === 200 });
sleep(0.2); // simulate think time between keystrokes
}
Run the script against a staging endpoint; watch the server’s autoscaling metrics and the frontend’s network panel for duplicated requests.
Tooling and Code Examples
Below is a quick‑reference table that maps testing goals to concrete tools, their strengths, and a one‑liner command or snippet.
| Goal | Tool | Why Fit | Example Command / Snippet |
|---|---|---|---|
| Unit logic (debounce, normalization) | Jest + jsdom | Fast, zero‑browser overhead | npm test -- --testPathPattern=autocomplete |
| Component rendering + mock API | React Testing Library + MSW | Declarative queries, realistic network mocks | npx jest --watch |
| End‑to‑end (real browser) | Playwright | Auto‑wait, tracing, multi‑browser | npx playwright test --project=chromium |
| Accessibility audit | axe-core (CLI or integrated) | WCAG 2.1 rules, CI‑friendly | npx axe-playwright --tags wcag2aa |
| Visual regression | Storybook + Chromatic | Catch CSS regressions in suggestion styling | npx chromatic --project-token= |
| Network throttling & offline simulation | DevTools → Network → Throttle / Service Worker | Real‑world flaky conditions | N/A (manual) |
| Chaos injection (latency, errors) | ToxiProxy or tc + netem | Simulate packet loss, latency spikes | toxiproxy-cli create autocomplete -l localhost:8080 -u upstream:8080 |
| Performance / load | k6 | Scriptable, CLI‑friendly, integrates with CI | k6 run load-test.js |
| Accessibility screen‑reader testing | NVDA (Windows) / VoiceOver (macOS) | Actual assistive tech feedback | N/A (manual) |
| Security scanning (XSS, CSP) | OWASP ZAP passive scan | Detects reflected XSS in suggestions | zap-baseline.py -t https://staging.example.com |
Sample Playwright Helper for Mocking the Geocoding Endpoint
// playwright-helper.js
exports.setupGeocodeMock = async (page, mode = 'ok') => {
await page.route('**/geocode', async route => {
if (mode === 'error') {
return route.fulfill({ status: 504, body: 'Gateway Timeout' });
}
if (mode === 'empty') {
return route.fulfill({ status: 200, body: JSON.stringify({ predictions: [] }) });
}
const url = new URL(route.request().url());
const query = url.searchParams.get('input') || '';
const mock = {
predictions: [{ description: `${query}, USA`, place_id: `mock_${query}` }]
};
return route.fulfill({ status: 200, body: JSON.stringify(mock) });
});
};
Use it in a test:
const { setupGeocodeMock } = require('./playwright-helper');
test('handles empty response gracefully', async ({ page }) => {
await setupGeocodeMock(page, 'empty');
await page.goto('https://staging.example.com/');
const input = page.locator('input[aria-label="Street address"]');
await input.fill('Nowhere');
await page.waitForTimeout(300);
await expect(page.locator('text=No matches found')).toBeVisible();
});
Example of an Axe Integration in CI
Add this to your playwright.config.ts:
import { defineConfig, devices } from '@playwright/test';
import { AxePlaywright } from 'axe-playwright';
export default defineConfig({
testDir: './tests',
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'], },
},
],
// Global setup that runs axe after each test
async teardown() {
const axe = new AxePlaywright();
const results = await axe.run(page);
if (results.violations.length > 0) {
console.error('Accessibility violations:', results.violations);
process.exit(1);
}
},
});
This will fail the build if any WCAG rule is violated during a test run.
Autonomous, Persona‑Driven Exploration
Even the most thorough matrix can miss edge cases that only appear when real users with varied habits interact with the UI. Autonomous testing platforms like SUSA address this by:
- Loading the APK or URL (in our case, a web URL) and constructing a state graph of reachable screens.
- Applying persona profiles – each persona has a distinct distribution of input speed, error‑prone behavior, assistive‑technology usage, and intention (e.g., “impatient” users may mash keys rapidly, “elderly” users may rely heavily on screen‑reader navigation, “adversarial” users may attempt to inject scripts).
- Executing exploratory actions – taps, scrolls, keyboard sequences, voice commands, and paste events are generated according to the persona’s model.
- Detecting observable failures – JavaScript exceptions, ANR‑like long tasks, ARIA violations, network errors, and unexpected DOM mutations are flagged.
- Feeding discoveries back – the platform remembers which states led to dead ends or crashes and prioritizes them in subsequent runs, continuously expanding coverage.
How Persona‑Driven Finds Missed Autocomplete Bugs
| Persona | Typical Behavior | Bug It May Surface |
|---|---|---|
| Impatient | Types fast, presses Enter before suggestions appear | Race condition where the input value is cleared before the popup renders, leaving the field blank. |
| Novice | Relies on placeholder text, rarely uses arrow keys | Missing aria-label on the input causing screen readers to announce “edit text” instead of “Street address”. |
| Accessibility | Uses VoiceOver, navigates with touch gestures | Touch‑start on the suggestion list does not move focus, leaving the user stuck. |
| Adversarial | Pastes strings with HTML entities, attempts XSS | Improper sanitization leading to script execution in the suggestion container. |
| Elderly | Uses zoom, prefers larger tap targets | Suggestion list items too small (≥ 44 dp) causing mis‑taps. |
| Power user | Uses keyboard shortcuts, expects Ctrl+Enter to submit form | Lack of custom shortcut handling causing unexpected form submission. |
| Curious | Types partial address, then deletes characters rapidly | Debounce not resetting on backspace, resulting in stale suggestions after deletion. |
SUSA’s built‑in heuristics for each persona translate into concrete action sequences. For example, the “impatient” persona might generate a stream of keydown events with 30 ms intervals, followed by an immediate Enter. If the component’s debounce is set to 250 ms, the test will expose a scenario where the request is cancelled mid‑flight and the UI shows a stale list.
Integrating SUSA Into Your Pipeline
You can run SUSA as a containerized step in CI:
docker run --rm \
-e SUSA_URL=https://staging.example.com \
-e SUSA_PERSONAS=impatient,accessible,adversarial \
-e SUSA_OUTPUT=junit \
susatest/agent:latest
The agent will upload a JUnit XML report that your CI can parse, marking the build as unstable if any persona discovers a crash or WCAG violation. Because SUSA explores without pre‑written scripts, it often finds the “unknown unknowns” that slip past matrix‑based test cases.
Production‑Only Edge Cases and Monitoring
Some defects only manifest under real‑world traffic patterns or environmental factors that are hard to reproduce locally.
1. CDN or Third‑Party Script Latency
If the autocomplete CSS is served from a CDN that occasionally experiences high latency, the page may render the input before the dropdown stylesheet loads, causing mis‑aligned panels. Detect this with Real User Monitoring (RUM) by measuring the time between DOMContentLoaded and the first getBoundingClientRect of the suggestion list.
2. Locale‑Specific Shape Data
Certain countries return address components in a different order (e.g., Japan: prefecture → city → street). If your UI assumes a fixed order (street, city, state, zip), the formatted address may appear garbled. Use server‑side logs to capture the raw structured_formatting object from Google Places and compare it against the displayed string.
3. Ad‑Blocker Interference
Some ad‑blocking lists mistakenly flag geocoding endpoints as tracking. When blocked, the request fails silently, and the UI may show a spinner forever. Monitor the percentage of requests with status (blocked) via your CSP reporting endpoint or a custom window.onerror handler that logs net::ERR_BLOCKED_BY_CLIENT.
4. Battery‑Saving Mode (Mobile)
On Android, battery saver can increase the timeout of background timers, effectively breaking a setTimeout‑based debounce. Test by enabling battery saver in device settings and measuring the actual delay between keystrokes and request emission.
5. Network Switching (Wi‑Fi → Cellular)
When a user moves between networks, the browser may reuse a stale socket, leading to a NET::ERR_CERT_DATE_INVALID if the certificate chain changed mid‑session. Capture these events with the navigator.connection API and log any change in type or effectiveType.
#### Monitoring Snippet (Browser‑Side)
// collect.js
(function () {
const endpoint = '/_/autocomplete/metrics';
function send(payload) {
navigator.sendBeacon(endpoint, JSON.stringify(payload));
}
// debounce timing
let lastKey = 0;
document.querySelector('input[autocomplete]').addEventListener('input', e => {
const now = performance.now();
if (lastKey) {
send({ type: 'debounce-delay', value: now - lastKey });
}
lastKey = now;
});
// request outcome
const originalFetch = window.fetch;
window.fetch = async (...args) => {
const res = await originalFetch.apply(this, args);
send({ type: 'fetch-result', url: args[0], status: res.status, ok: res.ok });
return res;
};
// visibility changes (possible tabbing away while suggestions open)
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
send({ type: 'page-hidden', timestamp: performance.now() });
}
});
})();
Deploy this snippet via your tag manager or embed it in the bundle. Aggregate the events in your analytics backend and set alerts for outliers (e.g., debounce delay > 2× configured value, fetch error rate > 1 %).
Checklist for Release
Before merging a change that touches the address autocomplete, run through this concise list. Treat each item as a gate; if any fails, block the merge.
| ✅ Item | How to Verify |
|---|---|
Unit tests pass (npm test) | No regressions in debounce, normalization, filtering. |
Integration tests pass (npm run test:integration) | Component renders, handles mock success/error/empty. |
E2E smoke passes (npx playwright test --project=chromium) | Happy path, keyboard navigation, error fallback work in Chrome. |
Accessibility audit clean (npx axe-playwright) | Zero WCAG 2.1 AA violations on the autocomplete region. |
| Persona‑driven smoke (SUSA) passes | No crashes, ANRs, or security flags from impatient, accessible, adversarial personas. |
| Network resilience (manual or ToxiProxy) | UI shows retry spinner on 504, recovers after service restored. |
| No API key leakage (Network tab inspection) | Key absent from query strings or headers; request proxied if required. |
| Privacy compliance (request payload inspection) | Only the current input value is sent; no extra PII. |
| Visual regression baseline (Chromatic) | No unexpected styling changes in suggestion list across breakpoints. |
| Performance budget (Lighthouse) | Time to interactive < 2 s; total blocking time < 50 ms for the autocomplete module. |
| Documentation updated (JSX props, Storybook) | New props or behavior changes are documented; examples reflect latest API. |
If any item is red, create a ticket, fix the root cause, and re‑run the checklist.
Closing Takeaways
Address autocomplete sits at the intersection of networking, DOM state, and assistive‑technology contracts. A solid testing strategy therefore needs layers:
- Fast unit tests that lock down the pure functions (debounce, parsing, filtering).
- Component‑level integration tests with realistic API mocks to confirm that networking, state, and rendering work together.
- End‑to‑end scenarios exercised in real browsers (Playwright or Cypress) to verify keyboard, touch, and screen‑reader interactions.
- Accessibility automation (axe) run on every commit to catch regressions in ARIA roles, contrast, and focus management.
- Exploratory, persona‑driven runs (via a platform like SUSA) that surface the “unknown unknowns” – rapid‑fire keystrokes
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free