Settings Page Testing Best Practices (2026)
Settings Page Testing Best Practices (2026) start with recognizing that a settings screen is not a static form but a living contract between the user and the application. Every toggle, dropdown, or te
Settings Page Testing Best Practices (2026) start with recognizing that a settings screen is not a static form but a living contract between the user and the application. Every toggle, dropdown, or text field represents a promise about how the app will behave after the user leaves the page, and a breach of that contract erodes trust, triggers support tickets, and can even expose security gaps. In 2026, teams that treat settings as an after‑test afterthought see higher churn, more frequent hot‑fixes, and slower release cycles because regressions slip through undetected until they surface in production. This guide lays out a concrete, opinionated framework for testing settings pages that balances rigor with practicality, shows what to automate, what to keep manual, how to measure effectiveness, and which pitfalls to avoid. The approach works for native Android/iOS apps, hybrid web views, and pure‑web settings portals, and it integrates naturally with autonomous, persona‑driven exploration tools such as SUSA.
Settings Page Testing Best Practices (2026): Why It Matters
The contract view of settings
A settings page is the primary place where users assert control over privacy, notifications, data usage, theme, and feature flags. When a user flips a switch, they expect the underlying system to persist that choice across sessions, devices, and app updates. If the persistence layer fails, the user experiences a silent regression: the app behaves as if the setting were unchanged, leading to confusion (“Why am I still getting notifications?”) and potentially regulatory violations (e.g., GDPR consent mismatches). Treating the settings page as a contract forces testers to verify three layers simultaneously: UI presentation, state persistence, and side‑effect propagation.
Production failure modes that slip through
In the field, the most common settings‑related incidents are:
- Silent revert – a toggle appears to stay on but the backend flag resets after a background sync.
- Partial apply – a theme change updates only part of the UI, leaving legacy components styled incorrectly.
- Accessibility regression – a newly added setting introduces a low‑contrast control that passes visual review but fails WCAG 2.2 AA.
- Security bypass – a developer disables a certificate‑pinning switch for debugging and forgets to re‑enable it, exposing MITM risk.
- Locale drift – a language‑selection dropdown stores the locale code incorrectly, causing date‑format bugs in downstream screens.
These defects rarely appear in unit tests because they involve cross‑component interactions and asynchronous state propagation. They also evade basic UI regression suites that only check for the presence of elements, not their behavioral correctness after a user action.
Business impact of inadequate settings testing
A 2025 study of 120 mobile apps found that settings‑related bugs accounted for 22 % of post‑release severity‑1 incidents and increased average mean‑time‑to‑resolve (MTTR) by 3.4 hours per incident. The same study showed that teams with a dedicated settings test matrix reduced settings‑related incidents by 68 % within three months. The ROI is clear: investing a few hours in structured settings testing saves dozens of engineering hours in triage and patching.
Settings Page Testing Best Practices (2026): Core Principles
Isolation and statefulness
Each test must start from a known, clean state. Settings are inherently stateful; leftover values from a previous test can mask bugs or create false positives. Use a device or emulator snapshot, or invoke an API that resets the app to factory defaults before each test run. For web settings, clear localStorage, sessionStorage, and cookies, and reload the page with a fresh user profile.
Config drift detection
Beyond verifying that a user action updates a setting, tests must confirm that the updated value survives:
- App restart
- Process kill and relaunch
- Device reboot (for persistent storage)
- Account logout/login (if settings are tied to a user profile)
- OTA update (if the setting is stored remotely)
Implement a “drift check” that reads the setting after each of these events and asserts equality with the expected value.
Persona‑aware validation
Different users interact with settings in distinct ways. A power user may rapidly toggle many switches; an elderly user may rely on larger touch targets and voice navigation; a curious novice may explore every option; an adversarial tester may attempt to inject malformed values. Your test suite should include behavior profiles that emulate these personas, either manually or through an autonomous explorer that varies input speed, interaction order, and error‑prone actions.
Observability over UI‑only checks
Automated checks that only assert the presence of a checkbox are insufficient. Pair UI assertions with:
- Direct reads of the underlying storage mechanism (SharedPreferences, UserDefaults, localStorage, remote config API)
- Calls to downstream services that consume the setting (e.g., a notification‑service API to verify that a push‑toggle actually disables registration)
- Accessibility audits (axe, WCAG contrast checks) run on the mutated UI
Incremental, layered testing
Start with a smoke layer that verifies basic render and navigation. Add a functional layer that exercises each control type. Finally, add a resilience layer that simulates interruptions (network loss, low battery, incoming call) while a setting is being changed. This layering makes it easier to pinpoint where a regression originates.
Settings Page Testing Best Practices (2026): Building a Test Matrix
A test matrix provides a repeatable way to ensure coverage across the many dimensions of a settings page. Below is a sample matrix that teams can adapt to their own UI controls. Each row represents a distinct test scenario; columns capture the essential attributes needed for triage and automation decisions.
| Test ID | UI Control Type | Dimension Tested | Description | Expected Result | Automation Feasibility |
|---|---|---|---|---|---|
| S001 | Toggle Switch | Persistence | Turn on “Background Sync”, kill app, relaunch | Switch remains ON, background sync active | High (UI + state read) |
| S002 | Dropdown (Language) | Locale propagation | Select “Japanese (Japan)”, restart device | All UI strings appear in Japanese, date format changes to JA locale | Medium (requires locale check) |
| S003 | Slider (Volume) | Accessibility | Set volume to 20 % using TalkBack | Slider moves, announced value matches, contrast ratio ≥ 4.5:1 | Medium (UI + accessibility audit) |
| S004 | Text Field (Server URL) | Input validation | Enter “http://invalid..com”, submit | Error toast appears, field retains focus, no network call made | High (validation + network spy) |
| S005 | Checkbox (Data Sharing) | Security drift | Disable sharing, logout, login with another account | Sharing remains disabled for the new account | High (state persistence across users) |
| S006 | Radio Group (Theme) | Visual regression | Switch to Dark theme, take screenshot, compare to baseline | No unintended layout shifts, contrast passes WCAG AA | Low (visual diff tool needed) |
| S007 | Button (“Reset to Defaults”) | Reset correctness | Press button, verify all settings revert to factory defaults | Every control matches default values, no stale values remain | High (state read + UI) |
| S008 | Switch (Experimental Feature) | Feature‑flag gating | Enable flag, navigate to feature page, verify feature appears | Feature UI visible, API calls include flag=true | Medium (UI + API spy) |
| S009 | Toggle (Push Notifications) | Interruption resilience | Toggle off while receiving a push, then toggle back on | Push registration correctly pauses/resumes, no duplicate tokens | Medium (requires push simulator) |
| S010 | Switch (Battery Saver) | System interaction | Enable battery‑saver, simulate low‑battery event, verify setting sticks | Setting stays ON, system logs show power‑mode change | Low (requires system‑level hooks) |
How to use the matrix
- Prioritization – Assign risk scores (e.g., based on user impact and likelihood) to each Test ID. Execute high‑risk items on every commit; medium‑risk items can run nightly; low‑risk items can be part of weekly exploratory runs.
- Automation decision – The “Automation Feasibility” column guides where to invest in coded tests. High‑feasibility items become part of the CI pipeline; medium‑feasibility items may be semi‑automated (e.g., using a script that drives the UI but relies on manual visual verification); low‑feasibility items stay manual or are handled by specialized tooling (visual diff, system‑level monitors).
- Traceability – Link each Test ID to a requirement or user story in your tracking tool. When a bug is filed, reference the matrix ID to quickly locate the missing coverage.
Manual Testing Checklist (2026)
Even with strong automation, certain aspects of settings testing benefit from human intuition, especially when evaluating new controls, ambiguous wording, or edge‑case interaction patterns. The following checklist is designed for a 15‑minute exploratory session that can be run by a developer, QA engineer, or product designer.
Quick sanity checks (5 min)
- Launch and render – Open the settings page from the home screen and from deep‑link URLs. Verify that the layout does not break on font‑size scaling (up to 200 %).
- Default state – Confirm every control shows the factory‑default value (as defined in the spec or remote config). Take a screenshot for baseline comparison.
- Navigation – Ensure that tapping any setting that opens a sub‑screen returns correctly via the back gesture/button, and that the parent page reflects any changes made in the child screen.
- Accessibility spot‑check – Run a quick axe or Google Accessibility Scanner run; note any contrast or touch‑target failures > 48 dp.
Edge‑case exploration (7 min)
- Rapid toggling – Using two fingers, flip a switch on/off as fast as possible for 10 seconds. Observe whether the UI lags, the switch sticks in an intermediate state, or the underlying flag toggles incorrectly.
- Invalid input – For each text field, paste strings that exceed the max length, contain Unicode control characters, or mimic SQL/XSS patterns. Verify that the app shows an appropriate inline error and does not proceed.
- Interrupt during save – While a setting is being persisted (show a progress spinner if applicable), simulate a network loss, incoming call, or device lock. After the interruption, confirm the setting either rolled back cleanly or completed successfully without corruption.
- Cross‑account consistency – Log in with a second user account, change a setting, log out, log back in with the first account, and verify that the setting is unchanged (i.e., settings are not leaking across accounts).
- Locale shift – Change the device language to a right‑to‑left language (e.g., Arabic) while the settings page is open. Ensure the layout mirrors correctly and that all text is fully visible.
- Permission gating – If a setting requires a runtime permission (e.g., location), attempt to change it before granting the permission. The app should either disable the control or show a permission rationale dialog.
Using SUSA for persona‑driven runs (3 min)
If you have access to the SUSA autonomous QA platform, launch a short persona‑driven session targeting the settings page:
- Select the “elderly” persona to verify larger touch targets and slower interaction speed.
- Choose the “adversarial” persona to attempt boundary‑value and malformed‑input attacks.
- Run the “curious novice” persona to see whether every setting is discoverable without hidden menus.
SUSA will automatically log any crashes, ANRs, accessibility violations, and UI regressions, producing a concise report that you can merge into your exploratory notes.
Automation Strategy: What to Automate vs Keep Manual (2026)
Deterministic flows – prime candidates for automation
- Toggle persistence – UI action → state read → app restart → state re‑assert.
- Dropdown propagation – Select option → verify localized strings in multiple screens.
- Form validation – Inject invalid data → assert error message and blocked submission.
- Reset‑to‑defaults – Click button → compare all controls against a baseline JSON.
These flows have clear pass/fail criteria, minimal visual ambiguity, and can be fully exercised with instrumented UI frameworks.
Flaky UI interactions – keep manual or augment with smart waits
- Animations‑heavy transitions – Settings that trigger complex page transitions (e.g., modal sheets with parallax) may cause flaky element locators if waits are not tuned.
- Gesture‑dependent controls – Sliders that require precise drag distance can be flaky on emulators with differing touch‑precision.
- System‑level dialogs – Permission prompts, OS‑level battery‑saver warnings, or external account choosers are outside the app’s control and often require manual oversight.
A pragmatic approach is to automate the core interaction but wrap it with a retry loop that detects a known flaky condition (e.g., missing element after animation) and either waits longer or marks the test as “needs manual review”.
Sample Appium test (Java) – toggle persistence
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
public class SettingsToggleTest {
private AppiumDriver<MobileElement> driver;
private WebDriverWait wait;
@Before
public void setUp() throws Exception {
Map<String, Object> caps = new HashMap<>();
caps.put("platformName", "Android");
caps.put("deviceName", "Pixel_8_API_34");
caps.put("appPackage", "com.example.myapp");
caps.put("appActivity", ".MainActivity");
caps.put("automationName", "UiAutomator2");
caps.put("noReset", true); // we will reset manually
driver = new AndroidDriver<>(new URL("http://localhost:4723/wd/hub"), caps);
wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
@After
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
private void resetToFactory() {
// Assuming the app provides a reset endpoint via adb shell
driver.executeScript("mobile: shell",
Map.of("command", "am", "args",
List.of("broadcast", "-a", "com.example.myapp.RESET_SETTINGS")));
}
@Test
public void testBackgroundSyncTogglePersistsAfterReboot() {
resetToFactory();
// Navigate to Settings
driver.findElement(By.id("nav_settings")).click();
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("settings_screen")));
// Locate the toggle
MobileElement syncToggle = driver.findElement(By.id("toggle_background_sync"));
// Ensure it starts OFF (default)
assertFalse(syncToggle.isSelected());
// Turn ON
syncToggle.click();
assertTrue(syncToggle.isSelected());
// Persistency check: kill app and relaunch
driver.closeApp();
driver.launchApp();
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("settings_screen")));
MobileElement syncToggleAfter = driver.findElement(By.id("toggle_background_sync"));
assertTrue("Toggle did not persist after app restart", syncToggleAfter.isSelected());
// Optional: verify that the background service is actually running
Boolean serviceRunning = (Boolean) driver.executeScript("mobile: shell",
Map.of("command", "pm", "args",
List.of("list", "package", "-f", "com.example.myapp.syncservice")));
assertTrue(serviceRunning);
}
}
Sample Playwright test (TypeScript) – language dropdown propagation
import { test, expect } from '@playwright/test';
test.describe('Settings – Language Dropdown', () => {
test.beforeEach(async ({ page }) => {
// Start with a clean profile
await page.context().clearCookies();
await page.context().clearPermissions();
await page.goto('https://example.com/settings');
});
test('persists language choice across reload and updates UI strings', async ({
page,
}) => {
// Default language is English
await expect(page.locator('h1')).toHaveText('Settings');
// Open language dropdown
await page.locator('#language-select').click();
await page.locator('text=日本語 (Japan)').click();
// Verify UI updated immediately
await expect(page.locator('h1')).toHaveText('設定');
// Reload page – setting should persist
await page.reload();
await expect(page.locator('h1')).toHaveText('設定');
// Verify a downstream page reflects locale
await page.goto('https://example.com/profile');
await expect(page.locator('text=最終更新日')).toBeVisible(); // Japanese string
// Change back to English
await page.goto('https://example.com/settings');
await page.locator('#language-select').click();
await page.locator('text=English (United States)').click();
await page.reload();
await expect(page.locator('h1')).toHaveText('Settings');
});
});
What these snippets illustrate
- The Appium test couples UI interaction with a direct state verification (
isSelected) and an app‑restart persistence check. - The Playwright test validates that a UI change propagates to localized strings on a different page and survives a reload.
- Both examples avoid reliance on fragile visual checks; they read the underlying state (toggle selection, text content) which is far more reliable.
Metrics, Coverage, and Reporting (2026)
Coverage metrics that matter
| Metric | Definition | Target (2026) | How to capture |
|---|---|---|---|
| Control Coverage | % of distinct UI controls (toggle, slider, text, etc.) exercised at least once | ≥ 95 % | Instrument test runner to log each control ID interacted with |
| State Persistence Coverage | % of controls for which a persistence assertion (value after restart) is made | ≥ 90 % | Add a wrapper that, after each UI action, triggers a restart and reads the setting |
| Persona Coverage | % of defined personas (curious, impatient, novice, adversarial, elderly, accessibility, power user) that have exercised the settings page in a given time window | ≥ 80 % per week | Use SUSA or similar to tag runs with persona IDs |
| Accessibility Compliance | % of WCAG 2.2 AA checks passed on the settings page after each mutation | 100 % (no new violations) | Run axe-core after each test; fail the build on any new violation |
| Mean Time to Detect (MTTD) | Average time from introduction of a settings‑related regression to its detection in CI | ≤ 15 min | Correlate commit timestamps with first failing test |
| Mean Time to Resolve (MTTR) | Average time to fix a settings bug after detection | ≤ 2 h (target) | Track issue lifecycle in your tracker |
These metrics give a quantitative view of both breadth (how many controls are touched) and depth (whether the critical persistence and accessibility guarantees are verified).
Dashboard example (markdown table for quick reference)
| Week | Control Coverage | Persistence Coverage | Persona Coverage | Accessibility Violations | MTTD (min) | MTTR (h) |
|---|---|---|---|---|---|---|
| 1 | 92 % | 85 % | 70 % | 2 | 22 | 3.1 |
| 2 | 96 % | 90 % | 78 % | 0 | 13 | 1.8 |
| 3 | 98 % | 93 % | 82 % | 0 | 10 | 1.4 |
| 4 | 99 % | 95 % | 88 % | 0 | 8 | 1.1 |
A simple line‑chart of these numbers over time makes it easy to spot regressions (e.g., a sudden dip in persistence coverage after a refactor of the settings storage layer).
Reporting anti‑patterns
- Only counting test cases – A high test‑case count does not guarantee that persistence or accessibility was checked.
- Ignoring flaky test marks – Tagging a test as “flaky” and allowing it to pass silently hides real regressions.
- Reporting UI screenshots without diff thresholds – Visual diff tools must be calibrated; otherwise, benign anti‑aliasing changes produce false positives.
CI/CD Integration and Pipeline Patterns (2026)
Gate criteria for settings changes
- Unit‑test gate – All model and view‑model tests for settings must pass.
- UI‑smoke gate – The settings page must launch and render without errors on the target device/emulator.
- Persistence gate – At least one persistence test per control type must pass (configured via a tag like
@settings-persistence). - Accessibility gate – Run axe‑core; fail if any new WCAG violation appears.
- Persona‑gate (optional but recommended) – Run a short SUSA session with the “adversarial” and “elderly” personas; fail if any crash or ANR is reported.
These gates can be expressed in a YAML‑based CI (GitHub Actions, GitLab CI, Azure Pipelines) as separate jobs that run in parallel where possible.
Example GitHub Actions workflow
name: Settings Page CI
on:
push:
paths:
- 'src/settings/**'
- 'test/settings/**'
pull_request:
paths:
- 'src/settings/**'
- 'test/settings/**'
jobs:
unit-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '21'
- name: Run unit tests
run: ./gradlew testDebugUnitTest
ui-smoke:
needs: unit-test
runs-on: macos-14
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run Playwright smoke
run: npx playwright test --project=chromium --grep @settings-smoke
persistence:
needs: unit-test
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Set up Android SDK
uses: android-actions/setup-android@v2
- name: Run Appium persistence suite
run: |
npm ci
npx appium &
npx wdio run wdio.conf.ts --spec ./test/settings/persistence/**/*.ts
accessibility:
needs: unit-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install axe-cli
run: npm i -g axe-cli
- name: Run axe on settings build
run: |
npx serve -s build/settings &
axe http://localhost:5000 --tags wcag2aa --output json > axe-report.json
# Fail if any violations
if [ $(jq '.violations | length' axe-report.json) -gt 0 ]; then
exit 1
fi
persona-explore:
needs: [unit-test, ui-smoke]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Install SUSA agent
run: pip install susatest-agent
- name: Run SUSA adversarial + elderly
run: |
susatest run \
--apk app/build/outputs/apk/debug/app-debug.apk \
--personas adversarial,elderly \
--max-steps 200 \
--output susa-report.json
# Fail on any crash or ANR
if [ $(jq '.crashes | length' susa-report.json) -gt 0 ]; then
exit 1
fi
if [ $(jq '.anrs | length' susa-report.json) -gt 0 ]; then
exit 1
fi
aggregate:
needs: [ui-smoke, persistence, accessibility, persona-explore]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Publish test results
uses: actions/upload-artifact@v4
with:
name: settings-test-artifacts
path: |
**/test-results/**/*.xml
**/susa-report.json
**/axe-report.json
Key takeaways from the pipeline
- Jobs are deliberately split so that a failure in one area (e.g., accessibility) does not block feedback from another (e.g., unit tests).
- The
persona-explorejob leverages SUSA to surface crashes or ANRs that deterministic scripts might miss. - Artifacts are aggregated at the end, enabling a single dashboard view for triage.
Common Anti‑Patterns and How to Fix Them (2026)
Over‑reliance on screenshots for validation
*Problem*: Teams often assert that a settings screen “looks correct” by comparing a baseline screenshot after each change. This approach catches visual regressions but misses functional ones (e.g., a toggle that appears on but does not persist).
*Fix*: Use screenshots only as a supplemental check for layout or contrast issues. Pair every screenshot test with at least one state‑based assertion (read the underlying preference or API flag). If you must keep visual testing, limit it to high‑risk controls (custom switches, complex togglers) and use a perceptual diff tool with a configurable threshold to ignore anti‑aliasing noise.
Ignoring reset state between tests
*Problem*: A test that leaves a switch ON can cause the next test to start from a non‑default state, leading to false passes (the bug is masked) or false negatives (the test fails because of leftover state).
*Fix*: Implement a deterministic reset hook in your test framework. For Android, use adb shell cmd settings put global ... or call a dedicated reset endpoint exposed via a debug flavor. For web, clear localStorage, sessionStorage, and IndexedDB databases related to settings, then reload the page. Log the reset action in your test output so auditors can verify that each test truly started clean.
Treating settings as static configuration
*Problem*: Some teams treat the settings page as a read‑only reference document, writing only a few “happy‑path” tests and assuming that any change will be caught by manual QA later. This assumption fails when settings are dynamically loaded from a remote config, feature‑flag service, or user‑specific A/B test bucket.
*Fix*: Model the settings page as a state machine with inputs (user actions, remote config pushes, account changes) and outputs (UI changes, API calls, persisted flags). Write tests that toggle each input and verify the corresponding outputs. Use mock servers or dependency injection to simulate remote config updates mid‑test, and assert that the UI reacts correctly without requiring a full app restart.
Skipping interruption and concurrency tests
*Problem*: Users frequently change a setting while receiving a call, low‑battery warning, or background sync. If the app does not handle these interruptions gracefully, the setting may be left in a half‑saved state, leading to data loss or security exposure.
*Fix*: Introduce a dedicated “interruption” layer in your test matrix (see Test IDs S009 and S010). Use platform APIs to simulate incoming calls (adb shell am broadcast -a android.intent.action.CALL), battery level changes (adb shell dumpsys battery set level 5), or network loss (adb shell emulation network speed gsm). After each interruption, re‑read the setting and assert that it is either correctly persisted or rolled back to the last known good state.
Forgetting cross‑account and cross‑device scenarios
*Problem*: A setting that is meant to be per‑user may inadvertently be stored in a shared preferences file, causing leakage between accounts. Similarly, a setting that should sync across devices may only be stored locally.
*Fix*: Include test cases that explicitly change a setting under Account A, log out, log in as Account B, and verify isolation. For sync‑enabled settings, use a second device or emulator, change the setting on device 1, force a sync, and confirm the setting appears on device 2. Automate these flows with a device‑farm service or a local multi‑device setup (e.g., using Firebase Test Lab).
Future‑Looking Enhancements (2026)
Autonomous exploration with SUSA
While deterministic scripts give confidence in known paths, they cannot anticipate every unusual user flow. SUSA’s autonomous agent explores the settings page by combining guided heuristics with reinforcement learning: it tries unusual tap sequences, rapid successive toggles, and voice‑driven navigation patterns that map to the “impatient”, “curious”, and “adversarial” personas. Each run feeds back into a knowledge graph of screens and dead ends, allowing the agent to avoid repeating fruitless actions and to focus on unexplored state spaces. Teams that schedule a daily 5‑minute SUSA run on the settings branch report a 30 % increase in discovery of latent crashes and ANRs that unit‑based tests never trigger.
AI‑driven anomaly detection
Beyond rule‑based assertions, emerging ML models can learn the normal distribution of setting‑change latency, battery impact, and network usage. When a new commit introduces a setting toggle that unexpectedly spikes CPU usage for 2 seconds after each flip, the model
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