How to Test Bookmarks on Web (Complete Guide)
Bookmarks are a seemingly simple UI element, yet they sit at the intersection of navigation state, persistence, and user expectations. When a user clicks a star icon, presses Ctrl+D, or selects “Add t
Why Bookmark Testing Deserves Focus
Bookmarks are a seemingly simple UI element, yet they sit at the intersection of navigation state, persistence, and user expectations. When a user clicks a star icon, presses Ctrl+D, or selects “Add to bookmarks” from a menu, the application must:
- Capture the current URL (including query strings, fragments, and any authentication tokens that are safe to store).
- Persist that record in a storage mechanism—localStorage, IndexedDB, cookies, or a backend sync service.
- Update the UI to reflect the new state (highlight the icon, show a toast, add an entry to a side panel).
- Allow later retrieval, editing, deletion, and organization (folders, tags, reordering).
If any of these steps fails, the user loses a trusted shortcut, may inadvertently expose sensitive data, or experiences confusion that erodes trust in the product. In production, bookmark bugs often surface only under specific conditions: incognito mode, page‑level CSP restrictions, or when a service workers. Because the feature is low‑bandwidth network throttles a sync request. Because the flow touches many subsystems, a dedicated test effort catches regressions that generic UI suites miss.
Core Concepts Behind Web Bookmarks
Before writing tests, clarify what the feature actually does in your codebase.
- Trigger mechanisms – button click, keyboard shortcut, context‑menu item, drag‑and‑drop from the address bar.
- State capture – reading
window.location.href, optionally stripping credentials or tracking parameters. - Persistence layer – could be:
localStorage.setItem('bookmarks', JSON.stringify(array))- IndexedDB object store with a versioned schema
- Backend endpoint
POST /api/bookmarksthat returns a sync token - UI feedback – toggling a CSS class, showing a Snackbar, updating a list component via React/Vue/Svelte state.
- Retrieval flow – reading the stored list, rendering each entry, handling click to navigate.
- Edit/delete – opening a modal, updating the stored record, persisting again.
- Sync – if you support cross‑device bookmarks, a background service worker or periodic fetch reconciles local changes with a remote store.
Understanding these pieces lets you map test cases to specific code paths and storage APIs.
Comprehensive Test Matrix
Below is a detailed matrix that covers the dimensions you should verify. Each row represents a test scenario; columns indicate the observable (manual,notes) column shows the test type of test | scenario | steps | expected outcome | notes |
| Happy path | Add bookmark from toolbar button | 1. Navigate to https://example.com/page?utm=1 2. Click star icon 3. Confirm toast | Bookmark appears in list, URL stored exactly as shown (including query) | Verify storage entry matches window.location.href |
|---|---|---|---|---|
| Happy path | Add via keyboard shortcut | 1. Focus page 2. Press Ctrl+D (or Cmd+D on macOS) 3. Confirm dialog | Same as above | Ensure shortcut works when focus is in iframe or shadow DOM |
| Error path | Duplicate bookmark | 1. Add bookmark for URL A 2. Attempt to add same URL again | UI shows “already bookmarked” toast, no duplicate entry | Check that storage array length stays unchanged |
| Error path | Invalid URL (e.g., javascript:alert(1)) | 1. Paste javascript:alert(1) in address bar 2. Attempt to bookmark | Bookmark rejected or sanitized; no script execution | Important for XSS safety |
| Edge case | Bookmark with fragment only (#section) | 1. Navigate to https://example.com#section 2. Add bookmark | Stored URL includes fragment; clicking later scrolls to element | Some browsers strip fragments when reading location.href; handle explicitly preserve manually verify |
| Edge case | Very long URL (>2000 chars) | 1. Generate a URL with long query string 2. Add bookmark | Storage accepts full string; UI truncates display if needed | Test backend payload limits |
| Edge case | Incognito/private window | 1. Open incognito tab 2. Add bookmark 3. Close incognito 4. Reopen normal window | Bookmark not persisted (if using localStorage) OR appears only if sync enabled | Clarify your privacy policy |
| Edge case | Service worker offline | 1. Enable offline via DevTools 2. Add bookmark 3. Go online | Bookmark stored locally; sync queued; no errors thrown | Verify background sync registration |
| Accessibility | Keyboard navigation to bookmark button | 1. Tab to bookmark button 2. Press Enter/Space | Activation occurs, focus moves appropriately | Ensure ARIA label (aria-label="Bookmark this page" ) |
| Accessibility | Screen reader announcement | 1. Focus bookmark button with NVDA/JAWS 2. Activate | Announces “Bookmark added” or similar live region message | Use aria-live="polite" |
| Security | CSP blocking inline script in bookmark URL | 1. Set CSP script-src 'self' 2. Attempt to bookmark https://example.com? 3. Add | URL stored but script never executes when navigating | Confirm that navigation respects CSP |
| Security | XSS via stored bookmark title | 1. Add bookmark with title 2. Render list | Title escaped; no script execution | Use textContent or proper sanitization |
| Privacy | Sync transmits only URL, not referral params | 1. Add bookmark with ?ref=partner&tracking=id 2. Observe network request | Request payload contains stripped URL or hashed version | Verify that sensitive query params are not leaked |
| Concurrent edit | Two tabs modify same bookmark | 1. Open two tabs to bookmarks page 2. In tab A rename bookmark X 3. In tab B delete bookmark X 4. Save both | Final state reflects last write wins or merge strategy defined; no lost data | Test conflict resolution logic |
| Performance | Large bookmark list (>500 items) | 1. Populate list via API 2. Open bookmarks pane 3. Scroll | UI renders within 16 ms per frame; no jank | Use virtualization or pagination if needed |
*Table 1: Test matrix for bookmark functionality (happy path, error paths, edge cases, accessibility, security/privacy, concurrency, performance).*
Manual Testing Approach
A disciplined manual session catches nuances that automated scripts can overlook, especially when the feature relies on user‑level expectations. Follow this step‑by‑step checklist for each build.
- Preparation
- Open Chrome/Firefox/Edge devtools, disable cache (Network → Disable cache) to avoid false positives from stale service workers.
- Clear existing bookmarks (
localStorage.clear()or IndexedDB delete) to start from a clean slate. - If you have a backend sync endpoint, point it to a stub service (e.g., Mockoon) that logs requests and returns 200/400 as needed.
- Happy‑path verification
- Navigate to a variety of pages: static HTML, SPA route with hash, page with POST‑redirect‑GET flow, and a page that sets
history.replaceState. - Trigger the bookmark action via each supported method (button, shortcut, context menu).
- After each action, verify:
- A visual cue (icon fill, toast) appears within 300 ms.
- The storage entry contains the exact URL you expect (use Application → Local Storage/IndexedDB).
- The bookmark appears in the list with correct title and favicon (if you show one).
- Error‑path verification
- Attempt to bookmark the same URL twice; confirm that the UI shows a non‑intrusive “already saved” message and that the storage count does not increase.
- Try to bookmark a
javascript:ordata:URL; ensure the action is blocked or sanitized. - Simulate a network failure when your app calls a sync endpoint (devtools → Network → Throttle → Offline). Confirm that the UI still shows a local save and that a background sync retry is queued.
- Edge‑case verification
- Test with URLs that contain fragments, ports, userinfo (
user:pass@example.com), and unusually long query strings. - Open an incognito window, add a bookmark, then close the window. Re‑open a normal window and confirm the bookmark is absent (unless you deliberately sync incognito data).
- Disable JavaScript temporarily and verify that the bookmark button gracefully degrades (e.g., falls back to a native browser bookmark prompt).
- Accessibility verification
- Navigate using only the keyboard: Tab to the bookmark button, press Enter/Space, ensure focus moves to a logical next element (close button of toast or first list item).
- Run a screen reader (NVDA on Windows, VoiceOver on macOS) and confirm that activation announces the result via a live region.
- Check color contrast of the bookmark icon (WCAG AA ≥ 4.5:1 for normal state, ≥ 3:1 for disabled).
- Security/privacy verification
- With CSP enabled, attempt to bookmark a URL that contains an inline script payload; after navigation, open devtools → Console to ensure no script ran.
- Inspect the network tab when syncing: confirm that any sensitive query parameters (e.g.,
token=,session=) are stripped or hashed before leaving the client. - Try to inject HTML into the bookmark title field; verify that the rendered list escapes the content (use
textContentor a sanitizer like DOMPurify).
- Concurrency & performance verification
- Open two tabs to the bookmarks manager. In one tab rename a bookmark; in the other delete the same bookmark. Save both and observe the final state—does the app follow a “last write wins” policy or show a conflict dialog?
- Populate the store with 500+ bookmarks (via console script) and open the pane; measure frame timing with the Performance panel. Look for dropped frames or long layout thrash.
- Post‑test cleanup
- Remove all test bookmarks, reset any stub servers, and re‑enable cache if you disabled it.
Document each step in a test‑run spreadsheet, marking PASS/FAIL and attaching screenshots or console logs for failures. This manual baseline becomes the oracle against which you compare automated runs.
Automated Testing Approaches
Automation shines for regression detection and CI gating. Because bookmark behavior touches storage, UI updates, and sometimes network, choose tools that can interrogate each layer.
Tool selection
| Tool | Strengths for bookmark testing | Limitations |
|---|---|---|
| Playwright (Node/Java/ Python/.NET) | Cross‑browser (Chromium, Firefox, WebKit), auto‑waits, direct access to page.evaluate() for storage inspection, built‑in tracing, supports service workers and offline contexts | Heavier binary install; less mature community for very niche browsers |
| Cypress | Excellent debugging UI, automatic waiting, easy to stub network (cy.intercept()), good for SPA assertions | Runs only in Chromium‑family browsers (as of v13), no native support for multiple tabs/contexts (requires plugins) |
| Selenium WebDriver | Broadest browser coverage (including Safari via SafariDriver), mature grid infrastructure | Verbose waits, flakier without explicit synchronisation, harder to access IndexedDB directly |
| TestCafe | No WebDriver needed, runs on any browser that supports ES6, built‑in waiting | Smaller ecosystem, fewer third‑party plugins |
For most teams, Playwright offers the best trade‑off: you can inspect localStorage and IndexedDB from the test context, emulate incognito, and throttle network with a single API.
Setting up a Playwright test suite
# Install
npm i -D @playwright/test
npx playwright install # downloads browsers
# Create test file: tests/bookmark.spec.ts
#### Basic happy‑path test
import { test, expect } from '@playwright/test';
test.describe('Bookmark feature', () => {
test('adds a bookmark via toolbar button', async ({ page }) => {
await page.goto('https://example.com/article?utm=newsletter');
// Wait for the bookmark button to be ready
const bookmarkBtn = page.getByLabel('Bookmark this page');
await expect(bookmarkBtn).toBeEnabled();
// Click and wait for toast
await bookmarkBtn.click();
const toast = page.getByRole('status', { name: /bookmark added/i });
await expect(toast).toBeVisible({ timeout: 5000 });
// Verify storage
const url = await page.evaluate(() => localStorage.getItem('bookmarks'));
const bookmarks = JSON.parse(url ?? '[]');
expect(bookmarks).toContainEqual(
expect.objectContaining({ url: 'https://example.com/article?utm=newsletter' })
);
// Verify UI list entry
const listItem = page.getByRole('link', { name: /article/i });
await expect(listItem).toBeAttached();
});
});
#### Edge‑case: incognito
test('does not persist bookmark in incognito when sync is off', async ({ context }) => {
const incognito = await context.newContext();
const page = await incognito.newPage();
await page.goto('https://example.com');
await page.getByLabel('Bookmark this page').click();
await expect(page.getByRole('status', { name: /bookmark added/i })).toBeVisible();
// Close incognito
await incognito.close();
// Reopen normal context and verify absence
const normalPage = await context.newPage();
await normalPage.goto('https://example.com/bookmarks');
const list = normalPage.getByRole('list');
await expect(list.getByRole('link')).toHaveCount(0);
});
#### Security: CSP protection
test('CSP prevents execution of script‑laden bookmark URL', async ({ page }) => {
// Inject a restrictive CSP via page.addInitScript
await page.addInitScript(() => {
document.head.insertAdjacentHTML(
'beforeend',
`<meta http-equiv="Content-Security-Policy" content="script-src 'self'">`
);
});
await page.goto('https://example.com');
await page.getByLabel('Bookmark this page').click();
// Attempt to navigate to a bookmarked URL that contains a script
const bookmarkedUrl = 'https://example.com/?<script>alert(1)</script>';
await page.evaluate((url) => {
// Simulate clicking a bookmark entry that sets location.href
(window as any)._testNavigate = (u) => { location.href = u; };
}, bookmarkedUrl);
// Trigger the navigation (depends on your UI)
await page.getByRole('link', { name: /example\.com/i }).click();
// Ensure no alert fired
const alertPromise = page.waitForEvent('dialog', { timeout: 2000 }).catch(() => null);
const dialog = await alertPromise;
expect(dialog).toBeNull();
});
#### Performance: large list rendering
test('renders 500 bookmarks without dropping frames', async ({ page }) => {
// Seed storage via evaluate
await page.addInitScript(() => {
const items = Array.from({ length: 500 }, (_, i) => ({
id: i,
url: `https://example.com/item${i}`,
title: `Item ${i}`
}));
localStorage.setItem('bookmarks', JSON.stringify(items));
});
await page.goto('https://example.com/bookmarks');
await page.waitForFunction(() => {
const list = document.querySelector('ul#bookmark-list');
return list && list.children.length === 500;
});
// Measure frame drops using the Performance API
const metrics = await page.evaluate(() => {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'layout-shift') window._ls = entry;
}
});
observer.observe({ entryTypes: ['layout-shift'] });
return performance.now();
});
await page.waitForTimeout(2000); // let any layout work settle
const layoutShift = await page.evaluate(() => (window as any)._ls?.score ?? 0);
expect(layoutShift).toBeLessThan(0.1); // minimal jank
});
These examples illustrate how to assert on UI, storage, network, and performance concerns in a single test suite. Adjust selectors and storage keys to match your implementation.
Edge Cases That Appear Only in Production
Even the most thorough matrix can miss issues that manifest under real‑world traffic patterns. Below are several production‑only gotchas and how to surface them.
| Issue | Why it hides in test | Detection technique |
|---|---|---|
| Service worker stale cache | Local dev often disables SW or uses updateViaCache: 'none' | Deploy a version with a changed SW script, then simulate a user who has the old SW cached (use chrome://serviceworker-internals to force a wait). Verify that bookmark addition still writes to the correct storage layer (sometimes the SW intercepts fetch and mocks the response). |
| Third‑party extension interference | Extensions like ad‑blockers or password managers can inject scripts that alter the DOM or capture clicks | Run tests in a clean profile *and* in a profile with popular extensions (uBlock Origin, LastPass). Compare outcomes; if a click is swallowed, add a fallback listener or adjust z‑index. |
| Network throttling with progressive bookmark UI | Fast LAN in CI hides delayed toast or skeleton UI | Use page.route('**/api/bookmarks', route => route.fulfill({ status: 200, body: JSON.stringify([]), delay: 2000 })) to simulate latency; ensure the UI shows a loading indicator and does not allow duplicate clicks. |
| Cross‑origin iframe bookmarking | Tests often run on the top‑level page; real users may bookmark content inside an embedded widget | Create a test page that loads a third‑party iframe (same‑origin or cross‑origin with proper CORS). Attempt to bookmark via the iframe’s context menu; verify that the stored URL is the iframe’s src and not the parent’s. |
| Page visibility API interactions | When a tab is backgrounded, some frameworks pause timers that debounce bookmark saves | In Playwright, use await page.evaluate(() => document.visibilityState = 'hidden') then trigger the bookmark action; confirm the save still occurs (or is queued) and not lost. |
| Storage quota exceeded | Local test devices rarely hit the 5 MB limit; production users with many bookmarks can | In a loop, localStorage.setItem('bookmark' + i, JSON.stringify({url: https://example.com/${i}})) until you exceed quota, then attempt a normal add; expect a graceful error UI (toast) and no data corruption. |
| Concurrent sync conflicts | CI runs a single instance; real users may have multiple devices editing simultaneously | Simulate two Playwright contexts representing different devices, each editing the same bookmark via a mock sync endpoint that returns a conflict status (409). Verify the UI presents a merge dialog or prompts the user. |
| Service worker background sync failure | Background sync may be blocked by battery saver policies | After adding a bookmark, go offline, then online, then open DevTools → Application → Background Sync and manually trigger a failure (return 500). Confirm the UI shows a retry banner and does not lose the entry. |
Incorporate these scenarios into your exploratory test charter: run a short “production‑like” session on a staging environment that mimics real network conditions, extensions, and multiple tabs. Document any deviation from the matrix and treat it as a regression risk.
Accessibility Deep Dive
Bookmark interactions must satisfy WCAG 2.2 AA at a minimum. Below are concrete checks and how to automate them.
1. Keyboard operability
- Every bookmark action must be reachable via
Tab. - Activating the button with
EnterorSpacemust produce the same result as a mouse click.
Automated check (Playwright):
test('bookmark button is keyboard operable', async ({ page }) => {
await page.goto('https://example.com');
await page.keyboard.press('Tab'); // repeat until focus lands on button
const btn = page.getByLabel('Bookmark this page');
await expect(btn).toBeFocused();
await btn.press('Enter');
await expect(page.getByRole('status', { name: /bookmark added/i })).toBeVisible();
});
2. ARIA labeling and live regions
- The button needs an accessible name (
aria-labelor visible text). - When a bookmark is added, a live region (
aria-live="polite"or"assertive") should announce the outcome.
Automated check:
test('bookmark button has accessible name', async ({ page }) => {
const btn = page.getByLabel('Bookmark this page');
await expect(btn).toHaveAttribute('aria-label', /bookmark/i);
});
test('addition announces via live region', async ({ page }) => {
await page.goto('https://example.com');
await page.getByLabel('Bookmark this page').click();
const live = page.getByRole('status'); // assumes you used role="status"
await expect(live).toContainText(/added/i);
});
3. Color contrast
- Use a contrast‑checking tool (e.g., axe-core) to verify that the icon’s fill vs. background meets 4.5:1 for normal state and 3:1 for disabled.
Automated check with axe:
npx playwright test --config=playwright.axe.config.ts
where playwright.axe.config.ts includes:
import { defineConfig, devices } from '@playwright/test';
import { axe } from 'playwright-axe';
export default defineConfig({
use: {
baseURL: 'https://example.com',
trace: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
async globalSetup() {
// inject axe into each page
await global.page.addInitScript(() => {
// axe core is loaded via CDN in the test harness
});
},
async testFinished({ outcome }) {
if (outcome === 'failed') {
const results = await axe.run(global.page);
expect(results.violations).toEqual([]);
}
}
});
4. Focus management
After a bookmark is added, focus should move to a logical next element (e.g., the toast’s dismiss button or the newly added list item).
Automated check:
test('focus moves to toast after add', async ({ page }) => {
await page.goto('https://example.com');
await page.getByLabel('Bookmark this page').click();
const toast = page.getByRole('status', { name: /bookmark added/i });
await expect(toast).toBeFocused();
});
Run these checks on every commit; they catch regressions that functional tests might miss because they don’t assert on ARIA attributes or focus order.
Security and Privacy Considerations
Bookmark features can inadvertently become a vector for data leakage or code execution if not hardened.
1. URL sanitization
Never store raw location.href without stripping credentials (user:pass@) or sensitive query parameters (token=, session=).
Example sanitizer (utility function):
function sanitizeForBookmark(url) {
const u = new URL(url);
// Remove credentials
u.username = '';
u.password = '';
// Define a denylist of sensitive params
const DENY = ['token', 'session', 'api_key', 'auth'];
for (const p of DENY) u.searchParams.delete(p);
return u.toString();
}
Unit test this function with varied inputs; then assert in your end‑to‑end test that the stored bookmark matches the sanitized output.
2. CSP and script execution
If you allow users to drag a URL from the address bar onto a bookmark area, ensure that the drop handler does not eval() or setTimeout() the string.
Test: simulate a drop event with a javascript: payload and confirm that no alert() fires.
test('drop does not execute javascript URL', async ({ page }) => {
await page.goto('https://example.com');
const dt = new DataTransfer();
dt.setData('text/plain', 'javascript:alert(1)');
await page.dispatchEvent('.bookmark-dropzone', 'drop', {
dataTransfer: dt,
screenX: 0,
screenY: 0,
clientX: 0,
clientY: 0,
});
// No dialog should appear
const dialog = await page.waitForEvent('dialog', { timeout: 1000 }).catch(() => null);
expect(dialog).toBeNull();
});
3. Same‑origin policy for cross‑domain bookmarks
When a user bookmarks a link that points to a different origin, your app must not attempt to read the target’s localStorage or cookies. The stored bookmark is merely a string; any later navigation should be a standard location.href assignment, which the browser treats as a normal navigation (subject to its own CSP).
Test: add a bookmark to https://evil.com and then click it; verify that the navigation goes to the external site and that your origin’s storage is untouched.
test('navigating to external bookmark does not leak origin storage', async ({ page }) => {
await page.goto('https://example.com');
await page.getByLabel('Bookmark this page').click();
// add external bookmark via UI (assume there is an "Add external" button)
await page.getByLabel('Add external URL').click();
await page.getByLabel('URL input').fill('https://evil.com');
await page.getByLabel('Save').click();
await page.getByRole('link', { name: /evil\.com/i }).click();
await expect(page).toHaveURL('https://evil.com/');
// ensure our origin's storage unchanged
const stored = await page.evaluate(() => localStorage.getItem('bookmarks'));
expect(stored).not.toContain('evil.com');
});
4. Sync endpoint validation
If you persist bookmarks to a backend, enforce strict input validation: limit URL length, reject schemes other than http/https, and sanitize outgoing data to prevent injection into downstream systems (e.g., SQL, LDAP).
Contract test (using Pact or manual mock):
test('backend rejects malformed URL', async ({ request }) => {
const res = await request.post('/api/bookmarks', {
data: { url: 'javascript:alert(1)', title: 'XSS' }
});
expect(res.status).toBe(400);
const json = await res.json();
expect(json.error).toMatch(/invalid url/i);
});
Add these security checks to your CI pipeline as separate test suites or as part of your bookmark feature tests.
Autonomous, Persona‑Driven Exploration with SUSA
Scripted tests excel at verifying known paths, but they can miss emergent behaviors that arise from real‑world usage patterns. An autonomous QA agent that simulates diverse user personas can surface those hidden defects.
How SUSA works in this context
- Persona profiles – SUSA ships with built‑in behavior models:
- *Curious*: clicks every visible icon, tries right‑click menus, explores hidden settings.
- *Impatient*: performs rapid double‑clicks, skips toast messages, attempts to bookmark while a page is still loading.
- *Novice*: relies on tooltips, avoids keyboard shortcuts, may mis‑click the star icon.
- *Adversarial*: attempts to inject scripts, uses unusual URLs, tries to bookmark
data:orblob:URIs. - *Elderly*: prefers larger touch targets, may zoom the page, uses screen‑reader navigation.
- *Accessibility*: navigates solely via keyboard and screen reader, expects ARIA labels and live regions.
- Exploration loop – For each persona, SUSA loads the target URL, then repeatedly:
- Observes the current DOM and accessibility tree.
- Selects an action weighted by the persona’s tendency (e.g., Curious has high probability to open context menus).
- Executes the action (click, keypress, drag, voice command simulation).
- Records the resulting state (URL, storage changes, network requests, console errors, accessibility violations).
- Persists discovered screens and dead ends in a graph; subsequent runs avoid re‑exploring known‑good paths and focus on novel edges.
- Bug detection – SUSA automatically flags:
- JavaScript exceptions or unhandled promise rejections.
- Accessibility violations detected via axe‑core integrated into the agent.
- Network responses with error statuses (4xx/500) that occur after a bookmark action.
- UI inconsistencies (e.g., bookmark icon state not matching storage).
Concrete example: finding a race condition only the *Impatient* persona triggers
Suppose your debounce logic for saving a bookmark looks like:
let saveTimeout;
function scheduleSave() {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => persistBookmarks(), 300);
}
If a user clicks the star button twice within 150 ms, the first click clears the timeout, the second click sets a new timeout, but the first click’s intention to save immediately is lost. In a manual test you might not notice because you wait for the toast. The *Impatient* persona, however, will rapidly double‑tap the button and then immediately navigate away. SUSA would observe:
- No
persistBookmarks()call fired (checked via a spy on the function). - Storage unchanged after navigation.
- A console warning: “Bookmark save skipped due to rapid double‑click”.
The agent logs this as a defect and suggests increasing the debounce window or using a leading‑edge trigger.
Integrating SUSA into your CI
While SUSA runs autonomously, you can still gate builds on its findings:
# Install the agent
pip install susatest-agent
# Run a 5‑minute exploration against your staging build
susatest explore \
--url https://staging.example.com \
--personas curious impatient novice adversarial elderly accessibility \
--duration 5m \
--output susa-report.json \
--fail-on-severity high
The agent produces a JSON report with categories: crash, anr, accessibility, security, ux-friction. You can fail the build if any high‑severity item appears (e.g., a crash or an XSS attempt that succeeded). Because Suesa’s exploration is guided by personas, it often discovers issues that a static test matrix would never consider—such as a bookmark button that becomes invisible when the page is zoomed to 200 % (elderly persona) or a context‑menu entry that is missing when the page is rendered inside an iframe (curious persona).
Why this complements scripted tests
- Coverage of unexpected interaction order – Scripts follow a predetermined sequence; personas can interleave actions in ways a developer never imagined.
- Realistic timing variations – Personas model human latency, bursts, and pauses, exposing race conditions and debounce flaws.
- Bias toward edge UI states – Curious and adversarial personas deliberately try to break assumptions (e.g., right‑click on disabled elements, drag‑and‑drop onto non‑drop zones).
- Automatic regression baseline – The agent remembers which states it has already deemed “safe”; new regressions show up as newly discovered dead ends or error states.
Even if you do not adopt a full autonomous platform, you can emulate its spirit by adding exploratory charters to
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