How to Debug Dark Mode Rendering Bugs in Mobile Apps
How to Debug Dark Mode Rendering Bugs in Mobile Apps starts with understanding that dark mode is not just a color inversion but a separate UI theme that can expose hidden assumptions in your code. Whe
How to Debug Dark Mode Rendering Bugs in Mobile Apps starts with understanding that dark mode is not just a color inversion but a separate UI theme that can expose hidden assumptions in your code. When a user switches to dark mode, resources that were never tested under low‑light conditions may surface as unreadable text, invisible icons, or layout shifts that break usability. This guide walks you through a repeatable process to reproduce, diagnose, and fix those bugs, blending manual techniques with automated exploration so you can catch issues before they reach production.
How to Debug Dark Mode Rendering Bugs in Mobile Apps: Understanding Dark Mode Fundamentals
Dark mode on iOS and Android is implemented through trait collections (iOS) and configuration qualifiers (Android). The system supplies a separate set of resource files—colors, drawables, and sometimes layout adjustments—when the user interface mode is set to dark. However, many apps inadvertently bypass this mechanism by:
- Hardcoding RGB values in code or XML.
- Using asset catalogs that lack a dark variant.
- Relying on system colors that change meaning between light and dark (e.g.,
labelvssecondaryLabel). - Performing custom drawing in
draw(_:)oronDraw()without checking the current theme. - Overriding trait collections or configuration programmatically for specific views.
These oversights cause rendering bugs that are invisible in light mode but become glaring in dark mode. Recognizing the root cause categories helps you focus your debugging effort.
Key Concepts to Keep in Mind
- Trait collection (iOS) – A
UITraitCollectionthat bundles user interface style, size class, and other environment traits. Access it viatraitCollection.userInterfaceStyle. - Configuration qualifiers (Android) – Resource folders such as
values-night/anddrawable-night/that the system selects whenuiModeincludesMODE_NIGHT_YES. - Automatic dark mode – Both platforms can force dark mode via developer settings, which is essential for reproducible testing.
- Inheritance – Views inherit the trait collection from their parent unless explicitly overridden; a misplaced override can create isolated light‑mode islands inside a dark UI.
Understanding these mechanics gives you a mental map for where to look when a component looks off.
How to Debug Dark Mode Rendering Bugs in Mobile Apps: Setting Up a Reliable Reproduction Environment
Before you can fix a bug you must be able to see it consistently. Dark mode bugs often depend on device state, system version, or even the specific theme variant (e.g., “dark” vs “dark‑high‑contrast”). A stable test matrix eliminates flakiness.
Device and OS Coverage
| Platform | OS Versions to Test | Dark‑Mode Activation Method |
|---|---|---|
| iOS | 13.0 – latest | Settings → Display & Brightness → Dark; or UITraitCollection override in Xcode scheme |
| Android | 8.0 (API 26) – latest | Settings → Display → Theme; or adb shell cmd uimode night yes/no |
| Both | Emulators/Simulators | Same as device; use -userInterfaceStyle dark launch argument for iOS simulators |
Testing on at least one recent OS version and one legacy version ensures you catch bugs that rely on newer APIs (e.g., iOS 13’s UIColor { … } dynamic provider) as well as those caused by fallback paths.
Automating the Toggle
iOS (simulator)
# Launch simulator with dark mode forced
xcrun simctl boot "iPhone 14"
xcrun simctl ui "iPhone 14" appearance dark
Android (emulator or device)
# Force night mode
adb shell cmd uimode night yes
# Verify
adb shell settings get system ui_mode_night
# Returns 1 for night, 0 for day
You can wrap these commands in a shell script that toggles the mode, launches the app, and captures a screenshot for visual regression.
Test Matrix Example
Create a simple spreadsheet or markdown table that lists each screen, user flow, and the expected dark‑mode appearance. Mark each cell as PASS/FAIL after a run. Below is a miniature example for a login flow:
| Screen / State | Light Mode Expected | Dark Mode Expected | Observed (Light) | Observed (Dark) | Status |
|---|---|---|---|---|---|
| Login background | #FFFFFF | #121212 | #FFFFFF | #FFFFFF | FAIL (bg not dark) |
| Primary button text | #000000 | #FFFFFF | #000000 | #000000 | FAIL (text not inverted) |
| Error message color | #FF0000 | #FF0000 | #FF0000 | #FF0000 | PASS (hard‑coded red OK) |
| Icon (vector) tint | #000000 | #FFFFFF | #000000 | #000000 | FAIL (icon stays black) |
When you see a pattern—e.g., all backgrounds failing—you can hypothesize a missing night resource or a forced light override.
How to Debug Dark Mode Rendering Bugs in Mobile Apps: Manual Inspection Techniques
Even with automated checks, a trained eye is indispensable for spotting subtle contrast issues, misaligned icons, or custom drawing glitches.
Visual Inspection Workflow
- Switch to dark mode using the method from the previous section.
- Navigate to each screen via the most common entry points (launcher, deep link, push notification).
- Zoom in (iOS: Accessibility → Zoom; Android: Developer options → Show layout bounds) to see pixel‑level details.
- Check contrast with a tool like the WebAIM Contrast Checker (screenshot → upload) or the built-in Android “Show layout bounds” which highlights view outlines.
- Verify touch targets are at least 48 dp (Android) or 44 pt (iOS) and that they have sufficient visual feedback.
- Look for hidden UI – elements that blend into the background (e.g., a white icon on a light gray surface that becomes invisible when the surface turns dark).
Using Platform‑Specific Overlays
- iOS – Enable “Increase Contrast” in Settings → Accessibility → Display & Text Size. This forces the system to use higher‑contrast colors, making missing dark resources more obvious.
- Android – Turn on “Simulate color space” → “Monochromacy” in Developer options to see if any information relies solely on hue.
Command‑Line Screenshot Comparison
You can automate a basic visual diff with ImageMagick:
# Capture screenshots
adb shell screencap -p /sdcard/light.png
adb pull /sdcard/light.png .
adb shell cmd uimode night yes
adb shell screencap -p /sdcard/dark.png
adb pull /sdcard/dark.png .
# Convert to same size and compare
convert light.png -resize 800x light_resized.png
convert dark.png -resize 800x dark_resized.png
compare -metric RMSE light_resized.png dark_resized.png diff.png
A high RMSE indicates a visible change; you can then inspect diff.png to locate the region.
How to Debug Dark Mode Rendering Bugs in Mobile Apps: Using Logs, Crash Reports, and System Traces
Rendering bugs rarely crash the app, but they often leave traces in logs or manifest as performance hiccups (e.g., extra layout passes). Knowing where to look speeds up diagnosis.
iOS Logging
- Console.app – Filter by your app’s bundle identifier. Look for messages like:
<Warning>: Attempt to set a non‑dynamic color as background on a view that inherits trait collection.
viewDidLayoutSubviews takes under each trait collection. A sudden spike can indicate a layout loop caused by conflicting constraints that only appear in dark mode.Android Logging
- Logcat – Use tags such as
ViewRootImpl,Activity, or your custom tags. Example warning:
W/ViewRootImpl: Cancelling event due to no window focus: MotionEvent { action=ACTION_DOWN, ... }
This can happen if a dark‑mode theme causes a dialog to be dismissed immediately because its background is transparent.
- Systrace – Capture a trace while toggling dark mode:
adb shell atrace --async_start -t 10s gfx view input
# perform toggle and navigation
adb shell atrace --async_stop -t 10s
Look for extended Choreographer#doFrame durations or repeated Layout passes.
Crash and ANR Reports
Even if the app doesn’t crash, a dark‑mode‑specific ANR can appear when a heavy custom view does extra work in onDraw() after checking the UI mode. Check the Firebase Crashlytics or Play Console ANR dashboard, filtering by the “dark mode” flag (you can set a custom key in your code when isNightMode is true).
Example: Detecting a Forced Light Override
Add a diagnostic log in iOS:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.userInterfaceStyle == .dark {
print("🌙 Dark mode active for \(self)")
// Verify that background color is dynamic
if let bg = backgroundColor, !bg.isDynamic {
print("⚠️ Static background color detected: \(bg)")
}
}
}
In Android, you can log the current night mode:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val nightMode = when (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES -> "Night"
Configuration.UI_MODE_NIGHT_NO -> "Day"
else -> "Undefined"
}
Log.d("ThemeTracker", "UI mode changed to $nightMode")
}
These logs let you confirm that the system is indeed delivering the dark trait collection to the problematic view.
How to Debug Dark Mode Rendering Bugs in Mobile Apps: Profiling Render Performance and Overdraw
Dark mode can exacerbate overdraw because developers sometimes add semi‑transparent overlays to achieve a “dimmed” look, not realizing that the underlying layers are already dark. Excessive overdraw hurts battery life and can cause frame drops.
iOS Overdraw Detection
- Debug → Color Blended Layers in the Xcode simulator shows red for overdraw. Switch to dark mode and navigate; any persistent red indicates layers blending unnecessarily.
- Instruments → Core Animation – FPS drop > 2 fps when opening a screen suggests expensive drawing.
Android Overdraw Detection
- Developer options → Show GPU overdraw – Colors indicate overdraw severity (true color = no overdraw, blue = 1x, green = 2x, pink = 3x, red = 4x+). Enable this, switch to dark mode, and walk through your app.
- Profile GPU Rendering – Bars exceeding the 16 ms line indicate missed frames.
Example: Fixing Unnecessary Overdraw
Suppose you have a layout:
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">
<ImageView
android:id="@+id/logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_logo"
android:tint="?attr/colorOnSurface" />
</FrameLayout>
In dark mode, the container background should be @android:color/black. If you forget to provide a night variant, the container stays white, and the logo tint (which resolves to a dark gray) ends up drawn over a bright background, causing extra blending. The fix is to add values-night/colors.xml:
<resources>
<color name="container_background">#121212</color>
</resources>
and reference it in the layout:
<FrameLayout
android:background="@?attr/container_background"
... />
After applying the night resource eliminates the white layer, reducing overdraw from 2x to 1x.
How to Debug Dark Mode Rendering Bugs in Mobile Apps: Automated Detection with SUSA
Manual checks are valuable but do not scale across dozens of screens and frequent releases. Autonomous QA platforms like SUSA can explore an app without scripts, exercising real user flows while toggling dark mode in the background, then report any visual or functional regressions.
How SUSA Works for Dark Mode
- Exploration – After you upload an APK or point SUSA at a web URL, the agent launches the app and begins interacting with UI elements using a blend of curated personas (curious, impatient, adversarial, etc.). Each persona has its own tap timing, scroll velocity, and likelihood to open menus or fill forms.
- Theme Toggle – SUSA automatically switches the system UI mode between light and dark at configurable intervals (e.g., every 30 seconds) while continuing exploration. This creates a matrix of states: each visited screen is seen in both themes.
- Visual Diff – For every screen capture, SUSA computes a perceptual hash and compares light vs. dark renders. Significant differences trigger a flag for manual review.
- Accessibility & Contrast – The agent runs an automated contrast checker (based on WCAG 2.1 AA) on all text and icon pairs, reporting any failures that appear only in dark mode.
- Regression Scripts – When a defect is confirmed, SUSA exports an Appium test (Android) or Playwright script (web) that reproduces the exact steps and theme state, enabling you to add it to your CI pipeline.
Running SUSA from the CLI
# Install the agent
pip install susatest-agent
# Authenticate (you need an API key from susatest.com)
susatest login --key <YOUR_KEY>
# Start an exploration session
susatest run \
--app ./my-app.apk \
--device pixel_4_api_33 \
--iterations 5 \
--dark-mode-interval 30s \
--output ./susa-report/
The generated report includes:
- A list of screens with contrast failures.
- Side‑by‑side screenshots showing light/dark differences.
- Exported Appium test files under
susa-report/scripts/.
Integrating with CI
Add a step to your GitHub Actions workflow:
- name: Run SUSA dark mode exploration
run: |
pip install susatest-agent
susatest login --key ${{ secrets.SUSA_API_KEY }}
susatest run --app ./app/build/outputs/apk/release/app-release.apk \
--device emulator-5554 \
--iterations 3 \
--dark-mode-interval 20s \
--output ./susa-output
continue-on-error: false # fail the job if any dark mode defect is found
Because SUSA explores without hardcoded scripts, it catches bugs that appear only after a specific sequence of actions (e.g., opening a navigation drawer, then switching theme, then tapping a deep‑linked screen) that a static test matrix might miss.
How to Debug Dark Mode Rendering Bugs in Mobile Apps: Common Causes and Fixes
Below is a categorized list of the most frequent dark‑mode rendering problems, with concrete code examples and the corresponding remedy.
| Cause Category | Typical Symptom | Root Cause | Fix (iOS) | Fix (Android) |
|---|---|---|---|---|
| Hardcoded Colors | Background stays white, text too light | UIColor.white or #FFFFFFFF used directly | Replace with UIColor { $0.userInterfaceStyle == .dark ? .black : .white } or use asset catalog colors | Replace #FFFFFFFF with ?attr/android:colorBackground or define values-night/colors.xml |
| Missing Night Resources | Icons disappear, images appear with wrong tint | No -night variant in Assets.xcassets or drawable-night/ | Add a night variant to the asset catalog; ensure Appearance is set to “Any, Dark” | Create drawable-night/ folder and provide alternative drawables (e.g., white icons) |
| Forced Light Override | Specific view remains light despite system dark | overrideUserInterfaceStyle = .light or setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) | Remove the override or set to .unspecified | Call AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) |
| Incorrect Use of System Colors | Labels turn gray instead of white | Using UIColor.label in a custom view that doesn’t inherit trait collection | Ensure the view’s traitCollection is not nil; use UIColor { $0.userInterfaceStyle == .dark ? .label : .secondaryLabel } | Use ?attr/colorOnSurface and verify the theme is applied via MaterialComponents |
| Custom Drawing Ignoring Theme | Canvas draws with hardcoded RGB | CGContextSetRGBFillColor or canvas.drawColor(Color.RED) without checking UITraitCollection or Configuration.uiMode | In draw(_:) read traitCollection.userInterfaceStyle and pick colors accordingly | In onDraw() read resources.configuration.uiMode and switch palette |
| Layout Constraints Tied to Constants | Views overlap or get clipped | Constant spacing that assumes light‑mode metrics (e.g., taller header) | Use UIView.animate with trait collection changes or adjust constraints in traitCollectionDidChange | Override onConfigurationChanged and update ConstraintSet or MarginLayoutParams |
| Image Rendering Mode | Template images stay black | UIImage rendered with .alwaysOriginal instead of .alwaysTemplate | Set image = UIImage(named: "icon")?.withRenderingMode(.alwaysTemplate) | Use android:tint="?attr/colorOnSurface" on ImageView and ensure the source is a vector or tintable bitmap |
Practical Example: Fixing a Hardcoded Background in a SwiftUI View
struct CardView: View {
var body: some View {
VStack {
Text("Hello")
.font(.title)
}
.padding()
.background(Color.white) // ← problematic
.cornerRadius(12)
}
}
Replace the static color with a dynamic one:
.background(
Color(UIColor { $0.userInterfaceStyle == .dark ? UIColor.systemBackground : UIColor.white })
)
Or even simpler, use the built‑in semantic color:
.background(Color(UIColor.systemBackground))
Because UIColor.systemBackground automatically adapts to light/dark, the card now blends correctly.
Practical Example: Android Vector Tint
Suppose you have a menu icon defined as:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M3,18h18v-2H3v2zM3,13h18v-2H3v2zM3,6h18v-2H3v2z"/>
</vector>
If you use it as android:src="@drawable/ic_menu" without a tint, it stays black in dark mode and may blend into a dark toolbar. Fix:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_menu"
android:tint="?attr/colorOnSurface"
android:contentDescription="@string/menu"/>
Now the icon adopts the theme’s on‑surface color (white in dark, black in light).
How to Debug Dark Mode Rendering Bugs in Mobile Apps: Edge Cases That Appear Only in Production
Some bugs survive internal QA because they depend on factors that are hard to replicate in a lab: user‑generated content, dynamic theming from remote configs, or accessibility settings that combine with dark mode.
1. Remote‑Config Driven Themes
A/B testing frameworks may push a JSON payload that overrides primary colors. If the payload does not contain a night variant, the app will apply the light‑mode colors even when the system is dark.
Detection:
Log the effective color values after applying remote config. Compare them to the values returned by UIColor { … } or ContextCompat.getColor(stateList) under both UI modes.
Fix:
Merge remote config values with the theme’s base palette, falling back to system colors when a night value is missing.
val baseColor = if (uiModeNight) R.color.primary_dark else R.color.primary_light
val remoteColor = remoteConfig.getColor("primary_color") ?: ContextCompat.getColor(this, baseColor)
binding.root.setBackgroundColor(remoteColor)
2. User‑Generated Images with Embedded Profiles
A photograph uploaded by a user may contain an embedded ICC profile that shifts perceived brightness. When displayed in a dark‑mode UI, the image can look washed out, prompting users to think the app is broken.
Detection:
Use a color‑space conversion library (e.g., ImageIO on iOS, BitmapFactory.Options.inPreferredConfig = Config.RGB_565 on Android) to strip or convert profiles to sRGB before display.
Fix:
Always convert incoming images to sRGB and, if necessary, apply a slight brightness boost for dark mode (e.g., multiply luminance by 1.1) to maintain perceived contrast.
3. Combined Accessibility Settings
Users who enable “Increase Contrast” or “Reduce Transparency” alongside dark mode may experience clipping if your app relies on translucent views to achieve a certain visual effect.
Detection:
Check UIAccessibility.isDarkerSystemColorsEnabled (iOS) or Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED (Android) in conjunction with the UI mode.
Fix:
Provide an alternative opaque background when these flags are true, or avoid relying on translucency for critical information.
4. Dark Mode in Embedded Web Views
If your app uses WKWebView or WebView to show remote content, the web page may not respect the native dark mode unless you explicitly forward the preference.
Detection:
Inject a JavaScript snippet that reads window.matchMedia('(prefers-color-scheme: dark)') and logs the result. Compare with the native UI mode.
Fix:
Set WKWebView's allowsDarkMode = true (iOS 15+) or use WebView.setForceDark(WebView.FORCE_DARK_ON) (Android). For older versions, inject a CSS media query that overrides the page’s colors based on window.devicePixelRatio and prefers-color-scheme.
5. Battery Saver or Extreme Power Saving Modes
Some OEMs aggressively throttle GPU or reduce color depth when battery saver is on, which can make gradients appear banded in dark mode.
Detection:
Enable battery saver, capture a screenshot, and inspect gradients for banding. Use a tool like pngcheck to verify bit depth.
Fix:
Avoid relying on subtle gradients for critical UI; use solid colors or ensure that gradient stops are at least 10 % apart in luminance to survive reduced bit depth.
How to Debug Dark Mode Rendering Bugs in Mobile Apps: Prevention Checklist and CI Integration
Preventing dark‑mode regressions is cheaper than fixing them after release. Embed the following checklist into your development workflow and automate as much as possible.
Checklist for Each UI Change
- [ ] Semantic Colors – All colors sourced from asset catalogs (iOS) or theme attributes (Android). No hardcoded
#RRGGBBorUIColor.whiteliterals. - [ ] Night Variants – For every color, drawable, or image used in UI, verify a
-nightvariant exists (or that the resource is template/tintable). - [ ] Trait Collection Propagation – Ensure custom views do not override
overrideUserInterfaceStyleorsetDefaultNightModewithout a clear reason. - [ ] Contrast Verification – Run an automated contrast check (WCAG AA ≥ 4.5:1 for normal text, ≥ 3:1 for large text) on all text/icon pairs in both themes.
- [ ] Dynamic Drawing – In
draw(_:)/onDraw(), read the current UI mode before choosing colors or stroke widths. - [ ] Layout Safety – Verify that constraints or layout parameters do not depend on hardcoded dimensions that change with theme (e.g., header height).
- [ ] Accessibility Interaction – Test with increased contrast, reduced transparency, and font scaling enabled alongside dark mode.
- [ ] Web View Dark Mode Propagation – Confirm that
WKWebView.allowsDarkModeorWebView.setForceDarkis set appropriately. - [ ] Remote Config Safety – Any color or image fetched remotely must be merged with theme defaults; log a warning if a night value is missing.
- [ ] Performance Guard – Enable overdraw/GPU profiling in dark mode for at least one user flow per release; reject PRs if overdraw > 2x on critical screens.
Automating the Checklist
#### iOS Fastlane Lane
lane :darkmode_check do
run_tests(scheme: "MyApp",
devices: ["iPhone 14"],
environment: {
"UI_TEST_DARK_MODE" => "1"
})
# Run a custom script that captures screenshots and runs contrast analysis
sh "./scripts/darkmode_contrast_check.rb"
end
#### Android Gradle Task
task checkDarkmodeContrast {
doLast {
def screenshots = fileTree(dir: "$buildDir/screenshots", include: "**/*.png")
screenshots.each { file ->
def result = `java -jar contrast-checker.jar ${file.absolutePath}`
if (result.contains("FAIL")) {
throw new GradleException("Contrast fail on ${file}")
}
}
}
}
Add these tasks to your pull‑request validation pipeline so that any commit that introduces a dark‑mode violation fails the build before it reaches QA.
Using SUSA for Regression Guard
As mentioned earlier, SUSA can be run in CI to explore the app in both themes and export Appium/Playwright scripts. Store those scripts in your repository and add them to your UI test suite. This creates a feedback loop: every time SUSA discovers a new dark‑mode edge case, you get a reproducible test that prevents regression.
- name: Export SUSA regression tests
run: |
susatest run --app ./app.apk --device emulator-5554 --dark-mode-interval 15s --output ./susa-out
cp susa-out/scripts/*.java src/androidTest/java/com/example/app/darkmode/
Now your Espresso tests will include the newly discovered scenario, guaranteeing it stays covered.
How to Debug Dark Mode Rendering Bugs in Mobile Apps: Closing Takeaways
Dark mode is not a superficial skin; it is a distinct UI configuration that can expose assumptions baked into your codebase, resource files, and drawing routines. By treating dark mode as a first‑class citizen in your testing strategy—just like you do for different screen sizes or OS versions—you shift the burden from reactive bug‑bashing to proactive prevention.
Key actions to remember:
- Reproduce reliably – Use platform‑specific toggles (
adb shell cmd uimode night, Xcode appearance settings, or simulator launch arguments) and maintain a test matrix that captures each screen in both themes. - Inspect systematically – Combine manual contrast checks with automated tools (overdraw highlights, contrast checkers, perceptual hashes) to catch both obvious and subtle regressions.
- Leverage logs and traces – Trait collection changes, configuration updates, and custom drawing calls leave signals in console output, systrace, and Instruments; use them to confirm that the dark mode theme is actually reaching the problematic view.
- Fix the root‑symptoms – Hardcoded colors, missing night resources, forced light overrides, and theme‑unaware custom drawing account for the majority of issues; address each with the patterns shown.
- Guard against production‑only edge cases – Remote configs, user‑generated content, accessibility combos, web view handling, and power‑saver modes can resurrect bugs that never appeared in lab tests.
- Automate prevention – Enforce semantic colors, night variants, and contrast checks via linting, unit tests, and CI pipelines. Integrate autonomous explorers like SUSA to continuously validate dark mode behavior across real user flows.
- Turn discoveries into regression tests – Export the scripts or test cases generated by your exploratory runs and add them to your automated suite so the same bug cannot slip through again.
When you internalize this workflow, dark mode ceases to be a source of after‑hours firefighting and becomes another dimension of quality you can measure, track, and improve with the same rigor you apply to performance, security, and functional correctness. Happy testing, and may your UI stay legible whether the user prefers the bright noon or the deep night.
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