AR Application Testing: Augmented Reality QA Guide
AR testing combines computer vision validation with standard mobile QA. Key areas: marker detection accuracy across lighting conditions, plane detection latency and stability, occlusion correctness, ARCore/ARKit framework behavior, and performance on your actual target device range. Automated screenshot comparison catches visual regressions in AR scenes. Human testing across environments is unavoidable for spatial accuracy validation.
Augmented reality applications add a layer of environmental dependency that makes testing harder than either web or native mobile. The app doesn't just run on a device — it interprets the physical world through a camera. Lighting, surface texture, ambient movement, and device angle all affect whether core features work. Building a reliable QA process means designing tests that account for this variability, not pretending it doesn't exist.
AR Testing Domains
AR QA breaks into five distinct areas, each requiring different tools and approaches:
- Tracking and detection — Does the app correctly identify markers, planes, faces, or objects?
- Rendering and occlusion — Are virtual objects positioned and occluded correctly?
- Performance — Does the app maintain frame rate on target devices under load?
- Environment sensitivity — Does behavior degrade gracefully in poor conditions?
- Platform API correctness — Are ARCore/ARKit/RealityKit APIs used as intended?
Marker Detection Testing
Image-based AR (scanning QR codes, product packaging, art) depends on the image recognition pipeline. Failures here are silent — the tracking just doesn't start.
Test Matrix for Marker Detection
Define a test matrix before writing any test:
| Variable | Test values |
|---|---|
| Lighting (lux) | 50 (dim), 300 (office), 1000 (daylight) |
| Angle of incidence | 0°, 30°, 45°, 60° |
| Distance from marker | 0.3m, 0.5m, 1.0m, 2.0m |
| Marker size | As designed, 50% scale, 150% scale |
| Marker condition | Clean, slightly crumpled, partial occlusion |
| Print quality | Reference print, photocopy, screen display |
Run this matrix against your marker library. Detection rates below 95% at recommended conditions are a bug. Detection rates below 70% at marginal conditions (dim lighting, 45° angle) need documentation in user-facing materials.
Automated Detection Rate Testing
ARCore and ARKit both expose detection state via their session APIs. Wrap detection in testable code:
// iOS/ARKit — testable marker detection wrapper
class MarkerDetectionSession: NSObject, ARSessionDelegate {
var detectionEvents: [(timestamp: TimeInterval, markerId: String, confidence: Float)] = []
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
for anchor in anchors {
if let imageAnchor = anchor as? ARImageAnchor {
detectionEvents.append((
timestamp: CACurrentMediaTime(),
markerId: imageAnchor.referenceImage.name ?? "unknown",
confidence: 1.0 // ARKit doesn't expose confidence; log detection time
))
}
}
}
func timeToDetection(for markerId: String) -> TimeInterval? {
guard let first = detectionEvents.first(where: { $0.markerId == markerId }) else {
return nil
}
return first.timestamp - sessionStartTime
}
}
// In your XCTest:
func testMarkerDetectedWithin3Seconds() {
let session = MarkerDetectionSession()
// Present reference image to device camera (use video playback in test rig)
presentTestVideo("reference_marker_standard_lighting.mp4")
let expectation = XCTestExpectation(description: "Marker detected")
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
XCTAssertNotNil(session.timeToDetection(for: "target_marker"),
"Marker not detected within 3 seconds under standard lighting")
expectation.fulfill()
}
wait(for: [expectation], timeout: 4.0)
}For Android/ARCore, use a similar pattern with Session.getAllTrackables() filtered by AugmentedImage.
Plane Detection Testing
Plane detection underlies most AR furniture, gaming, and retail experiences. Failures here break the experience completely.
What to Test
Detection latency: How long does it take to detect a horizontal plane from session start? On a clean flat surface, ARCore targets under 2 seconds. Test your target surface types:
- Light-colored carpet (difficult — low texture)
- Dark hardwood floor (easy — high contrast)
- Glass table (very difficult — reflective)
- Outdoor concrete (easy in daylight, hard in shadow)
Plane stability: Once detected, does the plane boundary drift? A plane that expands and contracts as the user moves causes placed objects to shift. Test by placing an object, walking in a circle, and measuring position delta.
// Android/ARCore — plane stability test
fun testPlaneStability() {
val session = createARSession()
val initialPose = detectFirstHorizontalPlane(session, timeoutSeconds = 5)
assertNotNull("No horizontal plane detected within 5s", initialPose)
// Simulate camera movement
simulateCameraOrbit(session, radius = 1.0f, steps = 20)
val finalPose = getTrackedPlanePose(session)
val positionDelta = distance(initialPose.translation, finalPose.translation)
assertTrue("Plane drifted ${positionDelta}m — exceeds 5cm threshold",
positionDelta < 0.05f)
}Vertical plane detection: Many apps need wall detection. This is harder than horizontal detection and has higher latency. Test separately with textured vs. uniform walls.
Detection in Poor Conditions
Test and document behavior (not just failure) for:
- Moving surfaces (tablecloths with fans nearby)
- Low-texture surfaces (white table, white floor)
- Reflective surfaces
- Surfaces covered with overlapping patterns
Your app should communicate detection state clearly — "Looking for surface..." spinner, not silent failure.
Lighting Condition Testing
Light affects both tracking quality and rendering quality. Test both.
Tracking Under Different Lighting
ARCore exposes ambient light intensity and color temperature via LightEstimate. Your tests should cover:
| Condition | Intensity (lux) | Expected behavior |
|---|---|---|
| Dark room | <50 | Show warning or degraded tracking notice |
| Dim indoor | 50-200 | Detection works, slower |
| Normal indoor | 200-500 | Full capability |
| Bright outdoor | >1000 | Full capability, check overexposure |
// Check if your app correctly handles dark conditions
fun testDarkRoomShowsWarning() {
val session = createARSessionWithLightEstimation()
simulateLightIntensity(session, lux = 30f)
val uiState = appViewModel.uiState.value
assertTrue("App should show low-light warning below 50 lux",
uiState.showsLowLightWarning)
}AR Rendering Under Variable Lighting
Virtual objects should look plausible in the scene. This is subjective but you can test the mechanics:
- Shadow direction: Does your app use ARKit's
directionalLightEstimateto orient shadows? Test that shadows shift when you move toward a window. - Material reflections: Glossy virtual surfaces should reflect ambient color. Test in warm light (incandescent) vs. cool light (LED, outdoor).
- Exposure matching: Virtual objects shouldn't be dramatically brighter or darker than the scene.
Use screenshot comparison for rendering correctness. Capture reference frames in controlled lighting, then compare against known-good renders.
ARCore vs. ARKit Behavioral Differences
If you're shipping on both platforms, expect behavioral differences and test for them explicitly:
| Behavior | ARCore | ARKit |
|---|---|---|
| Plane detection latency | 1-3s typical | 0.5-2s typical |
| Vertical planes | Supported from ARCore 1.6 | Supported ARKit 1.5+ |
| Face tracking | Requires front camera, separate API | ARFaceTrackingConfiguration |
| LiDAR support | Not available | iPhone 12 Pro+ |
| Environmental HDR | Via EnvironmentalHdrLightEstimate |
Via environmentTexturing |
| Occlusion | Depth API (ARCore 1.8+) | Scene depth (LiDAR devices) |
Write platform-conditional tests that assert the correct behavior per platform:
#if canImport(ARKit)
func testLiDARocclusionOnSupportedDevice() throws {
guard ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh) else {
throw XCTSkip("LiDAR not available on this device")
}
// Test occlusion behavior
}
#endifOcclusion Testing
Occlusion — virtual objects appearing behind real-world objects — is one of the hardest AR features to test automatically.
Testing Occlusion Mechanics
On devices with depth sensors (LiDAR iPhone/iPad), test:
- Virtual object placed behind a real physical object
- Expected: virtual object partially or fully hidden by the real object
- Actual: check that the depth mask correctly clips the virtual mesh
func testOcclusionBehindPhysicalObject() {
let config = ARWorldTrackingConfiguration()
config.frameSemantics = [.sceneDepth]
// Place virtual cube at 1m depth
let virtualCubeAnchor = ARAnchor(transform: matrix_identity_float4x4)
session.add(anchor: virtualCubeAnchor)
// Capture frame with physical object at 0.5m occluding the virtual cube
let frame = captureFrameWithPhysicalOccluder()
// Sample pixel at virtual object screen position
let occlusionSamplePoint = CGPoint(x: 0.5, y: 0.5)
let pixelDepth = frame.sceneDepth?.depthMap.value(at: occlusionSamplePoint)
XCTAssertLessThan(pixelDepth ?? Float.infinity, 0.8,
"Physical object should occlude virtual object at this position")
}For non-LiDAR devices: test that your app uses an appropriate fallback (model-based occlusion, plane-based occlusion, or transparent rendering with a note about device capability).
Performance on Target Devices
AR is GPU and CPU intensive. Define your target device list before testing, then test on the oldest/weakest device in that list.
Performance Metrics for AR
| Metric | Target | Critical threshold |
|---|---|---|
| Frame rate | 60 FPS | <30 FPS |
| CPU usage | <50% | >80% sustained |
| GPU usage | <70% | >90% |
| Memory | <300MB AR overhead | >500MB |
| Battery drain | <15%/hr | >25%/hr |
| Thermal state | Nominal | Critical (device throttles) |
On iOS, monitor thermal state:
NotificationCenter.default.addObserver(
forName: ProcessInfo.thermalStateDidChangeNotification,
object: nil,
queue: .main
) { _ in
let state = ProcessInfo.processInfo.thermalState
if state == .serious || state == .critical {
// Log thermal event with scene context
performanceMonitor.logThermalEvent(state, sceneId: currentSceneId)
}
}Automated Screenshot Comparison for AR
AR screenshots can be automated in controlled environments (fixed camera rig, controlled lighting, physical markers):
# Using Pillow for pixel-level comparison
from PIL import Image, ImageChops
import numpy as np
def compare_ar_screenshots(reference_path: str, actual_path: str, threshold: float = 0.02):
ref = Image.open(reference_path).convert('RGB')
actual = Image.open(actual_path).convert('RGB')
diff = ImageChops.difference(ref, actual)
diff_array = np.array(diff)
# Calculate percentage of pixels that differ significantly
significant_diff = np.sum(diff_array > 20) # >20 value difference per channel
total_pixels = diff_array.size
diff_ratio = significant_diff / total_pixels
return {
'diff_ratio': diff_ratio,
'passed': diff_ratio < threshold,
'diff_image': diff
}
# In your test
result = compare_ar_screenshots(
'reference/living_room_couch_placed.png',
'actual/living_room_couch_placed.png',
threshold=0.02 # 2% pixel difference tolerance
)
assert result['passed'], f"AR rendering regression: {result['diff_ratio']:.1%} pixel difference"Screenshot comparison works well for:
- Verifying virtual object placement hasn't shifted
- Catching rendering artifacts after SDK upgrades
- Validating UI overlay positioning
It doesn't work for dynamic scenes or scenes with varied real-world content.
Test Environment Setup
For repeatable AR testing:
- Controlled lighting rig: Dimmable LED panels on all sides, color temperature adjustable. Avoid windows.
- Test surface library: Flat boards with documented texture properties (high texture, low texture, reflective, dark, light)
- Camera rig: Motorized or manual camera mount that holds device at repeatable positions and angles relative to test surfaces
- Reference markers: Professionally printed on consistent paper stock, stored flat to prevent warping
- Video playback testing: Record target scenarios as video, play back on a monitor for automated detection rate tests
Summary
AR testing is a combination of computer vision validation and standard mobile QA, with environment simulation as the bridging challenge. Automate what you can — detection rate tests, performance metrics, screenshot comparisons — and invest in a physical test rig that makes your human testing sessions repeatable. The biggest mistakes are testing only in ideal conditions and assuming ARCore and ARKit behave identically when they don't.