How to Test Permission Dialogs on Web (Complete Guide)
Web applications frequently request access to device capabilities such as the camera, microphone, geolocation, notifications, or clipboard. These requests appear as modal dialogs managed by the browse
Why Permission Dialogs Matter on the Web
Web applications frequently request access to device capabilities such as the camera, microphone, geolocation, notifications, or clipboard. These requests appear as modal dialogs managed by the browser, not by the page itself. When a dialog is mishandled—whether because the UI blocks it, the script misinterprets the user’s choice, or the fallback experience is broken—users can encounter silent failures, security prompts that never disappear, or unexpected data leaks. In production, a single overlooked permission flow can lead to:
- Lost conversions – a user abandons a signup flow because the camera request never resolves.
- Privacy violations – a site continues to read the microphone after the user denied access, exposing audio.
- Accessibility barriers – screen‑reader users cannot perceive the dialog or cannot dismiss it via keyboard.
- Security regressions – a click‑jacking trick overlays a fake “Allow” button, tricking users into granting privileged access.
Because the dialog lives outside the DOM, traditional unit tests that render components in isolation never see it. End‑to‑end tests must interact with the browser’s native UI, which varies across Chrome, Firefox, Safari, and Edge, and across desktop and mobile viewports. A systematic test matrix combined with manual checks, automated scripts, and exploratory personas helps catch the subtle bugs that slip through scripted suites.
---
Test Matrix for Web Permission Dialogs
Below is a comprehensive matrix that covers the typical permission types, user outcomes, and contextual variations you should verify. Each cell represents a distinct test scenario; you can prioritize based on risk (e.g., camera/microphone for video conferencing, geolocation for maps, notifications for chat apps).
| Permission | User Choice | Page State Before Request | Expected UI/Behavior | Post‑Choice Validation | Edge‑Case Variants |
|---|---|---|---|---|---|
| Geolocation | Allow | Page idle, no prior denial | Browser shows location prompt; page receives position object | navigator.geolocation.getCurrentPosition resolves with coords; UI updates map marker | User changes system location settings mid‑test; mock location override via DevTools |
| Geolocation | Deny | Same as above | Prompt shows; page receives error callback with PERMISSION_DENIED | Error handler invoked; UI shows fallback (e.g., manual address entry) | User repeatedly denies; browser remembers choice and suppresses future prompts |
| Camera | Allow | Page on video‑chat component | Prompt appears; stream starts; video element shows local feed | getUserMedia({video:true}) resolves; video.srcObject populated | Device has multiple cameras; user selects front vs rear via OS picker |
| Camera | Deny | Same as above | Prompt appears; error callback with NOT_FOUND_ERROR or NOT_ALLOWED_ERROR | UI shows “camera unavailable” message; button disabled | User toggles hardware kill switch while prompt is open |
| Microphone | Allow | Page on voice‑note widget | Prompt appears; audio stream starts | getUserMedia({audio:true}) resolves; audio levels visible in UI | Mic muted at OS level; test both muted and unmuted states |
| Microphone | Deny | Same as above | Prompt appears; error callback | UI shows “mic disabled”; recording button disabled | User grants then revokes via site settings mid‑session |
| Notifications | Allow | Page after user clicks “Subscribe” bell | Prompt appears; permission granted | Notification.permission === "granted"; service worker can show push | User has system‑wide notifications disabled; browser still shows prompt |
| Notifications | Deny | Same as above | Prompt appears; permission denied | Notification.permission === "denied"; no push delivered | User later enables notifications via browser settings; page should re‑prompt after reload |
| Clipboard Write | Allow | Page on rich‑text editor with “Copy” button | Prompt appears (Chrome) or silent (if permitted) | navigator.clipboard.writeText resolves; content pasted elsewhere | User has clipboard blocked via enterprise policy; test failure path |
| Clipboard Write | Deny | Same as above | Prompt appears; reject with NotAllowedError | UI shows toast “copy failed”; fallback to manual selection | User grants then quickly revokes; verify no stale data left |
| Clipboard Read | Allow | Page on paste‑button component | Prompt appears (Chrome) or silent (if allowed) | navigator.clipboard.readText resolves; pasted value shown | Clipboard contains sanitized HTML; ensure no XSS |
| Clipboard Read | Deny | Same as above | Prompt appears; reject | UI shows “paste disabled”; button disabled | User copies sensitive data then denies; verify no data leakage |
| MIDI | Allow | Page on music‑synth widget | Prompt appears; access granted | navigator.requestMIDIAccess() resolves; inputs/outputs enumerated | User has no MIDI devices; test empty list handling |
| MIDI | Deny | Same as above | Prompt appears; error | UI shows “MIDI unavailable”; controls disabled | User plugs in device after denial; verify no auto‑regrant |
How to use the matrix
- Select the permission set relevant to your feature (e.g., a video conferencing app needs camera, microphone, and optionally screen share).
- Iterate over user choices (Allow/Deny) and, where applicable, the “ignore” or “remember my choice” options that browsers expose.
- Apply edge‑case variants (system‑level toggles, device changes, policy restrictions) to ensure resilience.
- Record PASS/FAIL for each cell; a single FAIL in a critical path (Allow → expected behavior) blocks release.
---
Manual Testing Approach
Even with strong automation, a manual pass catches nuances that scripts may overlook, especially around timing, visual focus, and OS‑level interactions.
Step‑by‑Step Procedure
- Identify trigger points – List every UI element that initiates a permission request (buttons, links, auto‑on‑load scripts).
- Isolate each trigger – Navigate to the page, clear site permissions (via browser settings → “Clear data and permissions”), and reload to ensure a clean slate.
- Execute the trigger – Click the element or perform the action that should cause the dialog.
- Observe the dialog – Verify:
- The dialog appears centered (or per browser spec) and does not shift layout.
- The message text matches the permission being requested and includes a clear purpose (e.g., “Allow ‘MyApp’ to access your camera?”).
- Buttons are labeled consistently (“Allow” / “Block” or “Allow” / “Deny” per browser).
- Focus is trapped inside the dialog; pressing Tab cycles only between the two action buttons.
- Pressing Escape dismisses the dialog and leaves the page in the denied state (or the prior state, depending on spec).
- Choose an outcome – Click Allow, Deny, or close via the X (if present).
- Validate the page reaction –
- For Allow: confirm the expected API resolves, UI updates, and any fallback UI is hidden.
- For Deny: confirm the error callback fires, UI shows an appropriate message, and any dependent controls are disabled.
- Test persistence – Reload the page; the browser should remember the user’s choice and not re‑show the prompt unless the site explicitly requests again after a user gesture.
- Repeat for each permission type and each trigger point.
- Check accessibility – Run a screen reader (NVDA, VoiceOver, TalkBack) and verify that the dialog is announced, its purpose is clear, and the user can interact via keyboard alone.
- Document any deviation – Capture screenshots, console errors, and network logs for each FAIL case.
Tips for Consistency
- Use a fresh incognito window for each test run to avoid leftover site permissions.
- Keep a permission‑reset script handy:
# Chrome/Chromium
rm -rf "$HOME/Library/Application Support/Google/Chrome/Default/Session Storage"
rm -rf "$HOME/Library/Application Support/Google/Chrome/Default/Local Storage"
(Adjust paths for your OS.)
- For mobile Safari, use the Settings → Safari → Advanced → Website Data to wipe permissions.
- Pair manual testing with a test‑rail or spreadsheet that mirrors the matrix; tick off each cell as you go.
---
Automated Approaches and Tooling
Automating permission dialogs requires driving the browser’s native UI, which most test frameworks expose via Chrome DevTools Protocol (CDP) or WebDriver bi‑directional channels. Below are the most reliable techniques and a comparison table to help you pick the right stack.
1. Selenium/WebDriver with ChromeDriver
- Strengths – Mature, language‑agnostic, works on desktop and mobile emulation.
- Limitation – The standard WebDriver spec does not expose a direct API to interact with browser permission prompts; you must rely on Chrome‑specific switches or CDP extensions.
How to enable automatic allowance (Chrome):
ChromeOptions options = new ChromeOptions();
options.addArguments("--use-fake-ui-for-media-stream"); // auto‑allow cam/mic
options.addArguments("--disable-features=Infobar"); // suppress permission infobar
options.setExperimentalOption("prefs", Map.of(
"profile.default_content_setting_values.notifications", 1, // 1=allow, 2=block
"profile.default_content_setting_values.geolocation", 1
));
WebDriver driver = new ChromeDriver(options);
- To simulate denial, set the prefs to
2(block) or use--disable-features=RendererCodeIntegritycombined with CDP to override the prompt.
2. Playwright
- Playwright provides first‑class support for handling dialogs via
page.on('dialog', ...). Permission prompts are treated as dialogs in Chromium and Firefox, but Safari treats them as separate UI—Playwright can still intercept them via CDP on Chromium/WebKit.
Example – Auto‑allow geolocation:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext({
permissions: ['geolocation'] // auto‑grant
});
const page = await context.newPage();
await page.goto('https://example.com/geo');
// Trigger the request
await page.click('#get-location');
// Dialog is auto‑handled; verify result
const lat = await page.evaluate(() =>
new Promise(res => navigator.geolocation.getCurrentPosition(p => res(p.coords.latitude)))
);
console.log(lat);
await browser.close();
})();
- To deny, pass
permissions: []or usecontext.grantPermissions([])andcontext.revokePermissions([])after the prompt appears.
3. Cypress
- Cypress runs inside the browser and cannot directly interact with Chrome’s permission UI. However, you can stub the
navigator.permissions.queryAPI to return a desired state, which works for many feature‑detect flows but not for the actual UI prompt.
Stub example (Cypress):
Cypress.Commands.add('mockGeolocation', (state) => {
cy.window().then(win => {
cy.stub(win.navigator.permissions, 'query').returns(Promise.resolve({ state }));
});
});
// In test
cy.mockGeolocation('granted');
cy.visit('/map');
cy.get('#locate').click();
// Expect map to update based on mocked position
- For true UI‑level testing, combine Cypress with cypress-plugin-tab to focus the dialog and use
cy.realPress('Enter')—but this is brittle and not recommended for CI.
4. Puppeteer (Node)
- Similar to Playwright but lower‑level. You can launch Chrome with
--disable-features=Infobarand usepage.setGeolocationOverrideorpage.setPermissionOverride.
Example – Override camera:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.setPermissionOverride({ origin: 'https://example.com', name: 'camera', state: 'granted' });
await page.goto('https://example.com/cam');
await page.click('#start-video');
// Verify video element gets a stream
await browser.close();
})();
Comparison Table
| Framework | Native Dialog Handling | Cross‑Browser (Chrome/Firefox/Safari) | Language Support | Setup Complexity | Best For |
|---|---|---|---|---|---|
| Selenium/WebDriver | Requires Chrome flags or CDP extensions; not uniform | Chrome/Firefox (via GeckoDriver) – Safari limited | Java, C#, Python, JS, Ruby | Medium (need driver binaries) | Large enterprise suites, multi‑language teams |
| Playwright | Built‑in dialog interception; permission overrides via context | Chrome, Firefox, WebKit (Safari) | JavaScript/TypeScript, Python, .NET, Java | Low (single installer) | Modern web apps, CI/CD, visual testing |
| Cypress | Cannot access real UI; relies on API stubbing | Chrome, Firefox, Edge (limited Safari via experimental) | JavaScript/JS) | Low (bundled) | Fast developer feedback, unit‑like E2E |
| Puppeteer | Chrome‑only (or Chromium); permission overrides via CDP | Chrome/Chromium only | JavaScript/TypeScript | Low | Chrome‑focused automation, scraping |
Recommendation – For a permission‑centric test suite, start with Playwright because it offers uniform handling across the three major browsers, clear APIs for granting/revoking permissions, and built‑in tracing for debugging failures.
Sample Playwright Test Suite
// permission-tests.spec.js
const { test, expect } = require('@playwright/test');
test.describe('Camera permission flow', () => {
test('user allows camera and sees local video', async ({ page }) => {
const context = await browser.newContext({
permissions: ['camera'] // auto‑grant
});
const page = await context.newPage();
await page.goto('/video-chat');
await page.click('#start-call');
// Wait for video element to have a non‑zero width
await expect(page.locator('video')).toHaveAttribute('width', /[1-9]\d+/);
await context.close();
});
test('user denies camera and UI shows fallback', async ({ page }) => {
const context = await browser.newContext({
permissions: [] // default to deny
});
const page = await context.newPage();
await page.goto('/video-chat');
await page.click('#start-call');
await expect(page.locator('#camera-error')).toBeVisible();
await expect(page.locator('video')).toBeHidden();
await context.close();
});
test('system‑level camera toggle respected', async ({ page }) => {
// This test requires a real device or emulated device with a hardware toggle.
// For brevity, we illustrate the approach: launch with --disable-features=HardwareMediaKeyHandling
// and then use OS‑level scripts to flip the toggle; verification is same as above.
});
});
*Run with* npx playwright test permission-tests.spec.js --headed to see the dialogs (if you want to verify UI) or headless for CI.
---
Edge Cases That Only Show Up in Production
Scripted tests often run in sanitized environments (clean profiles, no extensions, default device sets). Production users encounter a variety of conditions that can turn a passing test into a silent failure. Below are the most common production‑only edge cases and how to surface them during testing.
| Edge Case | Why It Breaks in Production | Detection Strategy |
|---|---|---|
| Concurrent permission requests (e.g., site asks for cam *and* mic on page load) | Browsers may queue prompts; the second prompt can appear behind the first, causing the user to miss it or click the wrong button. | Simulate rapid successive triggers (e.g., Promise.all([camBtn.click(), micBtn.click()])) and verify that each dialog is handled independently and that UI state reflects the combined outcome. |
| Permission prompt obscured by overlay or modal | A custom UI modal (e.g., cookie banner) with higher z-index can sit over the browser’s native dialog, making buttons untouchable. | Inject a high‑z-index div after navigation and attempt to trigger a permission; verify that the dialog still receives focus (use page.evaluate(() => document.activeElement)). |
| Device policy restrictions (enterprise‑managed Chrome, Android work profile) | Policies can force‑deny or silently ignore requests, causing API rejections without UI. | Use Chrome’s --enterprise-force-flag to simulate policy flags, or test on a device managed via MDM if available. |
| Changing permission state mid‑session (user revokes via site settings) | After a user grants, they may later revoke via the browser’s permission manager; the site must handle the change without crashing. | After granting, open a new tab to chrome://settings/content/siteDetails?site= programmatically (via CDP) and toggle the permission, then return to the app and verify error handling. |
| Locale‑specific dialog text | Some browsers localize the permission message; tests that hard‑code English strings fail in non‑English locales. | Run the test matrix with Chrome launched with --lang=fr or --lang=ja and assert that the dialog’s role and button labels are still accessible (e.g., via ARIA). |
| Viewport‑dependent prompt positioning (mobile vs desktop) | On narrow viewports, the prompt may appear at the top or bottom, potentially being hidden behind fixed headers/footer. | Test with device emulation (iPhone X, Pixel 2) and with custom viewport sizes (320px width) to ensure the dialog is not clipped. |
| Slow network delaying the API response | If the site assumes immediate resolution of getUserMedia, a lag can leave UI in a loading state forever. | Throttle the network (page.route('**/*', route => route.continue()); await page.setOffline(true); …) and verify that loading indicators appear and time‑out handling works. |
| Extension that auto‑clicks dialogs (e.g., password manager) | An extension may automatically press “Allow” or “Block”, skewing test results. | Launch Chrome with --disable-extensions for baseline tests; then run a second suite with a known problematic extension to ensure your app does not rely on the extension’s behavior. |
| Permission prompt after navigation away and back (single‑page app) | SPA may destroy and recreate the component that requested permission; the browser may still consider the permission granted/denied, but the component’s state is stale. | Navigate away (page.goto('/other')), then back (page.goBack()), and re‑trigger the request; ensure UI updates correctly. |
Incorrect handling of the permission property (e.g., checking navigator.permissions.query instead of the actual API) | Some developers rely on the Permission API to predict UI, which can be inaccurate if the user changed settings via the browser UI. | After granting via UI, manually change the setting in the browser UI, then call navigator.permissions.query and confirm it reflects the new state *and* the actual API call behaves accordingly. |
How to incorporate these into your matrix
Add a secondary dimension labeled “Context” with values like *Concurrent*, *Overlay*, *Policy*, *Locale*, *Viewport*, *Throttle*, *Extension*, *SPA‑Nav*, *Setting‑Change*. For each permission/user‑choice cell, run a subset of these contexts based on risk. For high‑risk features (camera/mic), run all contexts; for low‑risk (notifications), run a representative sample.
---
Accessibility Considerations
Permission dialogs are native browser UI, but the surrounding page must remain accessible. Overlooking accessibility can lock out users who rely on keyboards, screen readers, or assistive technologies.
Keyboard Navigation
- Focus trap – When the dialog opens, focus must move to the first actionable button (usually “Allow”). Pressing Tab should cycle only between the two buttons; pressing Shift+Tab should reverse the order.
- Escape key – Must close the dialog and leave the page in the denied state (or unchanged if the browser treats dismissal as deny).
- Space/Enter – Activates the focused button.
Automated check (Playwright)
test('dialog traps focus', async ({ page }) => {
await page.goto('/cam');
await page.click('#start-cam');
const dialog = page.waitForEvent('dialog'); // permission dialog appears as dialog in Chromium
await dialog;
// After dialog appears, check active element
const focused = await page.evaluate(() => document.activeElement);
expect(focused).toMatch(/allow|deny/i); // adjust per locale
// Press Tab twice; should still be inside dialog
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const focused2 = await page.evaluate(() => document.activeElement);
expect(focused2).toMatch(/allow|deny/i);
});
Screen Reader Announcements
- The dialog should announce its purpose (e.g., “Camera access requested”) and the available actions.
- If the page updates after the decision (e.g., shows a video preview), that change must be announced via ARIA live regions or role alerts.
Manual test – Enable VoiceOver (macOS) or NVDA (Windows), trigger the request, and listen for the announcement. Verify that after granting, the screen reader announces “Video started” or similar.
Contrast and Scaling
- Although the dialog is rendered by the browser, ensure that any custom fallback UI you show (e.g., a “Enable camera” banner) meets WCAG AA contrast (≥4.5:1 for normal text).
- Test with browser zoom (200%) and system font scaling to ensure no text is clipped.
Reduced Motion
- Some users prefer reduced motion; avoid animating the page behind the dialog in a way that triggers vestibular issues. Keep transitions subtle or respect the
prefers-reduced-mediaquery.
Automated reduced‑motion check
test('respects prefers-reduced-media', async ({ page }) => {
await page.goto('/settings');
await page.evaluate(() => {
Object.defineProperty(window.matchMedia, 'prefers-reduced-media', {
value: window.matchMedia('(prefers-reduced-media: reduce)')
});
});
await page.click('#request-cam');
// Ensure no CSS animation with duration > 50ms runs on background elements
const moving = await page.evaluate(() => {
return [...document.querySelectorAll('*')]
.some(el => {
const style = getComputedStyle(el);
return style.animationDuration !== '0s' && parseFloat(style.animationDuration) > 0.05;
});
});
expect(moving).toBe(false);
});
Testing Checklist for Accessibility
| Item | Verification Method |
|---|---|
| Focus enters dialog on open | page.evaluate(() => document.activeElement) |
| Tab loops only between action buttons | Repeated page.keyboard.press('Tab') + focus check |
| Escape closes dialog | page.keyboard.press('Escape') + verify denied state |
| Screen reader announces purpose | Manual VoiceOver/NVDA listen |
| Fallback UI meets contrast | Use axe-core or manual color contrast tool |
| No disruptive motion behind dialog | Check for animation durations when prefers-reduced-media: reduce is active |
| Dialog remains visible at 200% zoom | Manual zoom test or page.setViewportSize({ width: 1280, height: 720 * 2 }) |
---
Security and Privacy Implications
Permission dialogs are the gatekeepers to powerful APIs. Mishandling them can lead to unintended data exposure, click‑jacking, or persistent abuse.
Click‑Jacking and UI Redress
- An attacker can overlay a transparent iframe containing the victim’s site and trick the user into clicking what they think is a benign button, while actually granting permission.
- Mitigation – Use the
Permissions-Policyheader to disallow sensitive features in untrusted contexts, and ensure your site does not allow framing unless explicitly needed (X-Frame-Options: SAMEORIGINor CSPframe-ancestors).
Test – Create a malicious test page that attempts to frame your app and trigger a permission request; verify that the request is either blocked or that the user sees a clear indication of the origin (browsers already show the origin in the dialog, but confirm it’s visible).
Permission Persistence and Leakage
- Once granted, a permission persists until the user revokes it or the browser clears data. A compromised site could continue to access the camera/mic after the user navigates away, especially if the site retains a reference to the MediaStream.
- Mitigation – Release resources as soon as they are no longer needed (
stream.getTracks().forEach(track => t.stop());). Implement a visibility‑change listener to stop streams when the page is hidden.
Automated cleanup verification
test('stops camera on page hide', async ({ page }) => {
const context = await browser.newContext({ permissions: ['camera'] });
const page = await context.newPage();
await page.goto('/cam');
await page.click('#start-cam');
const stream = await page.evaluate(() =>
new Promise(res => navigator.mediaDevices.getUserMedia({video:true}).then(res))
);
// Hide the page
await page.evaluate(() => document.visibilityState = 'hidden');
// In practice, listen to visibilitychange; here we just call stop
await page.evaluate(() => {
const stream = window.__lastStream;
stream.getTracks().forEach(t => t.stop());
});
// Verify tracks are stopped
const stopped = await page.evaluate(() => {
return window.__lastStream.getTracks().every(t => t.readyState === 'ended');
});
expect(stopped).toBe(true);
await context.close();
});
Fingerprinting via Permission State
- Sites can infer user preferences or device capabilities by probing the permission state (
navigator.permissions.query). Repeated queries can be used as a fingerprinting vector. - Mitigation – Browsers already limit the granularity of the permission API (e.g., returning only
prompt,granted,denied). Ensure your site does not rely on fine‑grained distinctions that could be exploited.
Test – Call navigator.permissions.query({name:'camera'}) repeatedly from a console and verify that the returned object does not expose additional entropy beyond the three states.
Data Minimization
- Only request the permission when the user initiates an action that requires it (just‑in‑time). Avoid asking on page load unless the feature is core to the initial experience.
- Audit – Scan your codebase for
getUserMedia,navigator.geolocation.getCurrentPosition,Notification.requestPermission, etc., and ensure each call is wrapped behind a user gesture (click, keypress, touchstart).
Automated gesture detection (simplified)
test('camera request only after user gesture', async ({ page }) => {
await page.goto('/cam');
// Simulate no gesture
await page.evaluate(() => {
// Override getUserMedia to throw if called without user gesture
const original = navigator.mediaDevices.getUserMedia;
navigator.mediaDevices.getUserMedia = (...args) => {
if (!document.userInteraction) {
throw new Error('Not triggered by user gesture');
}
return original.apply(this, args);
};
let userInteraction = false;
document.addEventListener('click', () => userInteraction = true);
document.userInteraction = userInteraction;
});
await page.waitForTimeout(100); // ensure no auto call
// Now click
await page.click('#start-cam');
// Expect no error
});
Security Header Checklist
| Header | Purpose | Recommended Value for Permission‑Heavy Sites |
|---|---|---|
Permissions-Policy | Limits which features can be used in the page or its iframes | camera=(), microphone=(), geolocation=(), fullscreen=(self) |
Content-Security-Policy | Controls sources of scripts, frames, etc. | frame-ancestors 'self'; script-src 'self' |
Referrer-Policy | Controls referrer info sent with requests | no-referrer-when-downgrade |
X-Frame-Options | Click‑jacking protection | SAMEORIGIN |
Cross-Origin-Opener-Policy | Isolates browsing context | same-origin |
Validate these headers in CI using a simple curl check or a plugin like security-headers-webpack-plugin.
---
Using Autonomous, Persona‑Driven Exploration
Scripted tests excel at verifying known flows, but they cannot anticipate the unpredictable ways real users interact with permission dialogs. Autonomous QA platforms that simulate diverse user personas can surface bugs hidden in edge‑case combinations (e.g., an impatient user repeatedly clicking “Allow” while a dialog is still animating, or an elderly user missing the dialog due to low contrast).
How Persona‑Driven Exploration Works
- Personas – Each persona embodies a distinct behavior profile:
- Curious – explores every button, reads dialog text, may grant then revoke to see what happens.
- Impatient – clicks rapidly, may double‑click or press Enter before the dialog finishes rendering.
- Novice – relies on default actions, may miss the close icon, expects the page to guide them.
- Adversarial – tries to bypass prompts, uses keyboard shortcuts, attempts to right‑click or open dev tools.
- Elderly – prefers larger touch targets, may need more time, may use screen magnifier.
- Accessibility – depends on screen reader, keyboard-only navigation, high‑contrast modes.
- Power User – knows browser settings, may pre‑grant permissions via
chrome://settings/content, expects the site to respect those choices.
- Exploration Engine – The platform autonomously navigates the app, generating sequences of UI interactions (taps, scrolls, typing, keyboard shortcuts) guided by the persona’s policy. It records every screen visited, every network request, and every browser‑level event (including permission dialog openings and closures).
- Learning Loop – After each run, the engine updates a model of “dead ends” (screens where no further progress is possible) and “promising paths” (those that lead to new states or errors). Subsequent runs focus on unexplored areas, increasing coverage over time.
- Outcome Reporting – The platform maps observed outcomes to the test matrix: e.g., a persona that repeatedly denied camera may trigger the “Deny → error callback” cell, while a curious persona that granted then revoked may hit a “Grant → revoke → re‑request” transition not covered in scripted suites.
Concrete Example: Finding a Hidden Dead‑Button Bug
Imagine a video‑chat app that shows a “Start Call” button only after the user has granted camera access. The button is disabled while the permission prompt is open, but the UI fails to re‑enable it if the user denies the permission and then immediately clicks the button again (the click event fires before the disabled state is reapplied). A scripted test that follows a linear “grant → click” path never sees this scenario, but an Impatient persona might:
- Click “Start Call” (prompt appears).
- Rapidly click the button again twice while the prompt is still visible (the UI hasn’t yet disabled the button).
- The prompt is denied; the button remains enabled due to a race condition.
- A second click triggers another prompt, leading to an endless loop of prompts.
An autonomous explorer that models rapid repeated actions would eventually hit this loop, flag it as a “dead end” (the user is stuck in a prompt storm), and surface it as a bug.
Integrating Persona Exploration with Your CI
- Run a baseline – Execute your scripted test suite on every commit.
- Schedule exploratory runs – Nightly or weekly, launch the autonomous agent against the latest build with a curated set of personas (e.g., Curious, Impatient, Elderly, Accessibility).
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