How to Test Data Export on Web (Complete Guide)
Data export is a common feature in web applications: users download reports, CSV spreadsheets, JSON payloads, PDF invoices, or backup archives. When the export flow breaks, the impact is immediate and
Why Data Export Testing Matters
Data export is a common feature in web applications: users download reports, CSV spreadsheets, JSON payloads, PDF invoices, or backup archives. When the export flow breaks, the impact is immediate and measurable. Users lose trust, support tickets spike, and downstream processes that rely on the exported file (e.g., data imports into analytics tools) fail silently.
From a quality perspective, export functionality touches several layers of the stack: the UI trigger, client‑side state handling, API contract, server‑side data fetching, transformation logic, and finally the binary response that the browser interprets as a download. A defect can appear in any of these layers, and because the feature is often invoked infrequently by manual testers, bugs can linger in production for weeks.
Testing export is not just about confirming that a file appears; it is about validating that the file contains the correct data, respects the expected format, is accessible to all users, does not leak sensitive information, and behaves reliably under load or adverse network conditions. The following sections walk through a complete, practical approach to testing data export on the web, from manual exploration to automated verification and autonomous, persona‑driven discovery.
Test Matrix for Data Export
A structured matrix helps ensure coverage across happy paths, error conditions, edge cases, accessibility, and security. Below is a comprehensive table that can be adapted to any web export feature. Each row includes a test identifier, a short description, the expected outcome, and a suggested priority (P0 = blocking, P1 = high, P2 = medium).
| Test ID | Description | Expected Outcome | Priority |
|---|---|---|---|
| EX‑01 | User clicks export button with default filters applied. | A file downloads with the correct name, MIME type, and contains all visible rows. | P0 |
| EX‑02 | User changes date range before export. | Exported file includes only records within the selected range. | P0 |
| EX‑03 | User selects a subset of rows via checkboxes and exports “selected only”. | File contains exactly the chosen rows, no extra data. | P0 |
| EX‑04 | User exports with no matching records (empty result set). | File downloads with appropriate header and zero data rows (or a defined empty‑state message). | P0 |
| EX‑05 | User triggers export while offline or with network latency simulated (e.g., 3G). | UI shows a loading indicator, then either a successful download or a clear error message after timeout. | P1 |
| EX‑06 | User attempts export without required permissions (e.g., role‑based access). | UI disables export button or shows an authorization error; no file is downloaded. | P1 |
| EX‑07 | Server returns HTTP 500 during export request. | UI displays a generic error toast, no file download, and logs the incident. | P1 |
| EX‑08 | Server returns HTTP 429 (rate limit) during export. | UI shows a retry‑after message; export can be retried after back‑off. | P1 |
| EX‑09 | Export generates a file larger than 50 MB. | Browser successfully downloads the file; memory usage stays bounded; progress indicator (if any) updates. | P1 |
| EX‑10 | Export generates a file with special characters in column values (e.g., commas, quotes, newlines). | CSV is properly escaped/quoted; JSON is valid; PDF renders correctly. | P1 |
| EX‑11 | Export includes personal data (PII) that should be masked per policy. | Exported file contains masked or redacted values where required. | P1 |
| EX‑12 | User initiates multiple concurrent exports from different tabs. | Each export completes independently; no file corruption or mixing of data. | P2 |
| EX‑13 | User triggers export while a page‑level spinner is already active from another action. | UI does not block the export; both actions proceed without conflict. | P2 |
| EX‑14 | Export button is reachable only via mouse; keyboard navigation skips it. | Button is focusable and activatable via Enter/Space; screen reader announces its purpose. | P1 |
| EX‑15 | Export button lacks accessible label or ARIA description. | Screen reader reads a meaningful label (e.g., “Export report as CSV”). | P1 |
| EX‑16 | Exported file name contains non‑ASCII characters (e.g., Japanese). | File is saved with the correct Unicode name on the host OS (where supported). | P2 |
| EX‑17 | Export endpoint does not set proper Content‑Disposition header. | Browser treats response as download with suggested filename; otherwise falls back to inline display (undesired). | P1 |
| EX‑18 | Export response omits Content‑Type or sets an incorrect MIME type. | Browser correctly handles the file (e.g., CSV opens in spreadsheet app). | P1 |
| EX‑19 | Export endpoint returns data gzipped without indicating Content‑Encoding: gzip. | Browser automatically decompresses and offers the correct file type. | P1 |
| EX‑20 | Export includes a cryptographic signature or hash for integrity verification. | Downloaded file validates against the supplied signature/hash. | P2 |
| EX‑21 | User attempts export after session timeout. | UI redirects to login or shows a session‑expired message; no export occurs. | P1 |
| EX‑22 | Export URL is directly accessed (e.g., via bookmark) without UI interaction. | Server validates request origin/authentication; returns appropriate error if unauthorized. | P2 |
| EX‑23 | Export functionality is used by a power‑user persona who repeatedly exports large datasets with custom filters. | System remains responsive; export times scale linearly with data size; no memory leaks. | P2 |
| EX‑24 | Export functionality is used by an elderly persona with reduced motor control. | Large click target, sufficient spacing, and clear visual feedback reduce misclicks. | P2 |
| EX‑25 | Export functionality is used by an adversarial persona attempting to inject script via filename or metadata. | Server sanitizes filename; response headers prevent execution; CSP blocks inline scripts. | P1 |
*The matrix above can be trimmed or expanded based on the specific export formats (CSV, XLSX, JSON, PDF) and the regulatory context of your application.*
Manual Testing Approach
Even when automation is in place, manual exploratory testing remains valuable for uncovering UX friction, subtle accessibility issues, and unexpected interactions with browser extensions or system settings. The following step‑by‑step guide outlines a repeatable manual session.
Preparation
- Identify Export Triggers – List every UI element that initiates an export (buttons, menu items, context‑menu actions, keyboard shortcuts).
- Define Data Sets – Prepare at least three representative data sets: a small set (< 10 rows), a medium set (≈ 100‑500 rows), and a large set (> 5 000 rows) to observe performance differences.
- Configure Browser Profile – Use a clean profile with no extensions, or a secondary profile that includes common blockers (uBlock Origin, Privacy Badger) to test extension interference.
- Set Up Monitoring – Open the browser’s developer tools → Network tab, enable “Preserve log”, and filter for the export request. Have a ready‑made folder for downloaded files to avoid clutter.
- Prepare Validation Tools – Install command‑line utilities:
csvlintorpython -m pandasfor CSV validation,jqfor JSON,qpdforpdfinfofor PDF metadata, and optionallyclamavfor basic malware scanning of downloaded files (if your organization permits).
Step‑by‑Step Execution
| Step | Action | Observation Points |
|---|---|---|
| 1 | Navigate to the page containing the export feature. | Confirm page loads without console errors. |
| 2 | Set any required filters (date range, search, toggles). | UI updates correctly; network requests reflect new parameters. |
| 3 | Focus the export trigger via Tab key. | Ensure visible focus ring; screen reader announces purpose. |
| 4 | Activate the trigger (Enter/Space or click). | Observe UI state: loading spinner, disabled button, toast messages. |
| 5 | Wait for the Network tab to show the export request. | Verify request method (GET/POST), URL, headers (Accept, Authorization), and payload (if POST). |
| 6 | When the response arrives, inspect its headers. | Look for Content-Type, Content-Disposition, Content-Encoding, Cache-Control. |
| 7 | Confirm the file appears in the download bar or designated folder. | Check filename matches expected pattern; note any automatic opening (undesired for certain types). |
| 8 | Open the downloaded file with the appropriate viewer. | Validate structure: header row, data rows, encoding (UTF‑8 vs. ISO‑8859‑1), presence of BOM if applicable. |
| 9 | Run a quick validation script (see Automation section for examples) to ensure row count matches applied filters. | Any mismatch indicates a logic bug. |
| 10 | Repeat steps 2‑9 for each data set size and each export format offered. | Observe performance trends, UI responsiveness, and memory consumption (via Chrome Task Manager). |
| 11 | Test error paths: simulate network throttling, revoke permissions, force server errors via a proxy (e.g., using toxiproxy or Chrome DevTools → Throttling → Offline). | Ensure graceful degradation and clear user feedback. |
| 12 | Test accessibility: navigate exclusively via keyboard, use a screen reader (NVDA, VoiceOver), and verify ARIA labels and live regions. | Confirm that announcements are timely and not overly verbose. |
| 13 | Test with browser extensions enabled (ad blocker, privacy protector). | Verify that extensions do not block the export request inadvertently (some may treat download URLs as tracking). |
| 14 | Perform a stress test: open multiple tabs, trigger exports concurrently, and monitor for file mixing or crashes. | Each download should be isolated; no tab should become unresponsive. |
| 15 | After completing the matrix, clear downloads and browser cache, then repeat the entire flow to ensure no state leakage. | Confirms idempotency and clean‑up of temporary resources. |
Common Pitfalls to Watch For
- Missing
Content‑Disposition: attachment– Browsers may display the response inline, causing confusion or unintended navigation. - Incorrect charset declaration – Exported CSV may appear garbled in Excel if saved as UTF‑8 without BOM on Windows.
- File name collisions – When multiple exports share the same static name, the browser may append numbers (e.g.,
report (1).csv), breaking downstream scripts that expect a fixed name. - Streaming vs. Blob generation – Servers that stream large data sets can close the connection prematurely if the client aborts, leaving a partially written file.
- Security headers – Overly restrictive
Content‑Security‑Policycan block the download if thesandboxdirective is misapplied. - Locale‑specific number/date formatting – Exported numbers may use a comma as decimal separator, causing import failures in systems expecting a dot.
Automated Testing Strategies
Automation provides repeatability, regression safety, and the ability to integrate export verification into CI pipelines. The approach splits into three layers: unit‑level validation of transformation logic, API‑level contract tests, and end‑to‑end (E2E) UI tests that confirm the full download handling by the browser.
Unit / Integration Tests for Export Logic
If the export format is generated by a pure function (e.g., exportToCSV(rows)), unit tests can assert the exact string output. Example in JavaScript/TypeScript:
// exportUtils.ts
export function toCSV<T>(data: T[], fields: (keyof T)[]): string {
const header = fields.map(f => String(f)).join(',');
const rows = data.map(o =>
fields
.map(f => {
const val = o[f];
return val == null ? '' : `"${String(val).replace(/"/g, '""')}"`;
})
.join(',')
);
return [header, ...rows].join('\n');
}
// exportUtils.test.ts
import { toCSV } from './exportUtils';
interface Record {
id: number;
name: string;
active: boolean;
}
test('toCSV handles quotes and newlines', () => {
const data: Record[] = [
{ id: 1, name: 'John "Junior" Doe', active: true },
{ id: 2, name: 'Jane\nDoe', active: false },
];
const result = toCSV(data, ['id', 'name', 'active']);
expect(result).toBe(
`id,name,active\n1,"John ""Junior"" Doe",true\n2,"Jane
Doe",false`
);
});
These tests guard against regressions in escaping, delimiter choice, and header ordering.
End‑to‑End Tests with Playwright
Playwright offers first‑class support for handling file downloads. Below is a complete example that tests a CSV export flow, validates the content, and checks for proper headers.
// export.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Data Export – CSV', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/reports');
// set a known filter
await page.selectOption('#date-range', 'last-30-days');
});
test('downloads CSV with correct rows and headers', async ({ page }) => {
// Start waiting for the download before clicking
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('button#export-csv'),
]);
const path = await download.path();
expect(path).toBeTruthy();
// Read file as text
const csv = await require('fs').promises.readFile(path!, 'utf-8');
// Simple line split validation
const lines = csv.trim().split('\n');
expect(lines[0]).toBe('id,name,email,created_at');
// Expect exactly 150 rows of data (based on test seed)
expect(lines.length - 1).toBe(150);
// Spot‑check a row
expect(lines[1]).toMatch(/^101,"Alice Smith","alice@example.com",/);
});
test('shows error when user lacks permission', async ({ page }) => {
// Simulate a 403 response using route fulfillment
await page.route('**/api/export/csv', route => {
route.fulfill({ status: 403, body: JSON.stringify({ error: 'Forbidden' }) });
});
await page.click('button#export-csv');
const toast = page.locator('.toast-error');
await expect(toast).toHaveText(/export failed/i);
await expect(toast).toBeVisible();
});
});
Key points:
page.waitForEvent('download')captures the download object without interfering with the UI.- The test can assert on HTTP status, headers, and the downloaded file’s binary or text content.
- Mocking the API (
page.route) enables testing of error paths without needing a flaky backend.
End‑to‑End Tests with Cypress
Cypress also supports download verification, though it requires setting the downloadsFolder in the configuration.
// cypress/integration/export_spec.js
describe('Export PDF invoice', () => {
beforeEach(() => {
cy.visit('/orders/12345/invoice');
cy.intercept('GET', '/api/invoice/12345/pdf', {
fixture: 'invoice.pdf',
statusCode: 200,
headers: {
'content-type': 'application/pdf',
'content-disposition': 'attachment; filename="invoice_12345.pdf"',
},
});
});
it('downloads PDF with correct filename', () => {
cy.contains('Download PDF').click();
// Cypress automatically saves the file to the downloads folder
cy.wait('@getInvoicePdf').its('response.statusCode').should('eq', 200);
cy.readFile('cypress/downloads/invoice_12345.pdf', 'binary')
.should('not.be.empty')
.and('have.length.gt', 1000); // simple size check
});
it('handles server error gracefully', () => {
cy.intercept('GET', '/api/invoice/12345/pdf', {
statusCode: 500,
body: { message: 'Internal error' },
});
cy.contains('Download PDF').click();
cy.get('.alert-danger').should('contain', 'Unable to download invoice');
});
});
Mocking Backend Responses
Both Playwright and Cypress allow you to intercept network calls and serve static fixtures. This technique is valuable for:
- Testing large data sets without actually generating millions of rows on the server.
- Simulating latency (
await page.waitForTimeout(2000)) or throttling viapage.context().setNetworkConditions(...). - Verifying that the client correctly handles different
Content‑Encodingvalues (gzip, brotli) by serving pre‑compressed fixtures.
Verifying File Contents
Beyond simple line counts, you may need to validate schema, numeric precision, or PDF structure. Helper libraries make this straightforward:
- CSV –
papaparse(Node) or Python’spandas.read_csv. - JSON –
ajvfor JSON schema validation. - PDF –
pdf-liborpdf-parseto extract text and assert on expected strings. - Excel (XLSX) –
sheetjs(xlsx) to read workbook and inspect cell values.
Example using papaparse in a Node test script:
const fs = require('fs');
const Papa = require('papaparse');
function validateCSV(filePath, expectedRows) {
const csv = fs.readFileSync(filePath, 'utf8');
const { data, errors } = Papa.parse(csv, { header: true, skipEmptyLines: true });
if (errors.length) throw new Error(`CSV parse errors: ${JSON.stringify(errors)}`);
if (data.length !== expectedRows) throw new Error(`Row count mismatch: ${data.length} vs ${expectedRows}`);
// Additional column checks
data.forEach((row, idx) => {
if (!row.id) throw new Error(`Missing id at row ${idx + 2}`);
if (!/^\S+@\S+\.\S+$/.test(row.email)) throw new Error(`Invalid email at row ${idx + 2}`);
});
console.log('CSV validation passed');
}
// Usage in a Playwright test after download
test('validate exported CSV', async ({ page }) => {
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('#export-csv'),
]);
const path = await download.path();
await expect(async () => {
validateCSV(path, 250);
}).toPass(); // using expect.poll or custom helper
});
Handling Asynchronous Downloads
Some implementations generate the file server‑side and return a temporary URL that the client polls until the file is ready. In such cases, the test must:
- Capture the initial response containing a
fileIdor polling endpoint. - Repeatedly request the status endpoint until
status: ready. - Then trigger the actual download (often a GET to
/download/:fileId).
Playwright example:
test('polling export flow', async ({ page }) => {
await page.route('**/api/export/start', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ fileId: 'abc123' }),
});
});
await page.route('**/api/export/status/**', route => {
const url = new URL(route.request().url());
const id = url.pathname.split('/').pop();
// Simulate processing: first call returns pending, second returns ready
const callCount = route.request().headers()['x-call-count'] || 0;
const newCount = parseInt(callCount, 0) + 1;
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
status: newCount >= 2 ? 'ready' : 'processing',
progress: newCount >= 2 ? 100 : 50,
}),
headers: { 'x-call-count': String(newCount) },
});
});
await page.click('#export-pdf');
const [download] = await Promise.all([
page.waitForEvent('download'),
page.waitForTimeout(5000), // give polling a moment to finish
]);
const path = await download.path();
expect(path).toBeTruthy();
});
CI Integration
- Store downloaded artifacts as build artifacts (if your CI permits) for manual inspection.
- Fail the job if any validation scripted if any validation throws or if the download event does not fire within a timeout (e.g., 30 s).
- Export test results to JUnit XML for trend analysis (e.g., increasing export time).
Tooling and Libraries
Choosing the right tooling reduces boilerplate and improves reliability. Below is a comparison of popular options for web export testing.
| Feature | Playwright | Puppeteer | Cypress | TestCafe |
|---|---|---|---|---|
| Built‑in download handling | ✅ page.waitForEvent('download') | ✅ page.waitForResponse + manual blob | ❌ Requires cypress-downloadfile plugin | ✅ t.waitForFileDownload |
| Cross‑browser (Chromium, Firefox, WebKit) | ✅ | ❌ (Chromium only) | ❌ (Chromium only, limited Firefox via experimental) | ✅ |
| Automatic waiting for network idle | ✅ | ✅ (via waitForNavigation) | ✅ (automatic retries) | ✅ |
| Ability to modify request/response | ✅ page.route | ✅ page.setRequestInterception | ✅ cy.intercept | ✅ t.addRequestHook |
| Built‑in test runner & reporting | ✅ (Playwright Test) | ❌ (needs external runner) | ✅ (Cypress Dashboard) | ✅ (TestCafe Studio) |
| Easy to run in Docker/CI | ✅ | ✅ | ✅ (larger image) | ✅ |
| Language support | JavaScript/TypeScript, Python, Java, .NET | JavaScript/Node | JavaScript/TypeScript | JavaScript/TypeScript, CoffeeScript |
Recommendation: For most teams, Playwright offers the best balance of cross‑browser support, powerful interception, and first‑class download events without extra plugins. Puppeteer is a lightweight alternative if you only target Chromium. Cypress excels when you need rich debugging UI and are comfortable with its opinionated architecture. TestCafe is a solid choice if you require simple setup and built‑in waiting but lack advanced network mocking.
Helper Utilities
Create a small reusable module to avoid repeating validation logic across tests:
// exportHelper.ts
import { expect } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
import Papa from 'papaparse';
export async function validateCSVDownload(
page: Page,
triggerSelector: string,
expectedRows: number,
optionalChecks?: (row: any, index: number) => void
): Promise<void> {
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click(triggerSelector),
]);
const filePath = await download.path();
if (!filePath) throw new Error('Download path is undefined');
const raw = await fs.promises.readFile(filePath, 'utf8');
const { data, errors } = Papa.parse(raw, { header: true, skipEmptyLines: true });
if (errors.length) throw new Error(`CSV parse errors: ${JSON.stringify(errors)}`);
expect(data.length).toBe(expectedRows);
if (optionalChecks) {
data.forEach((row, i) => optionalChecks(row, i));
}
}
Usage in a test:
test('export users CSV', async ({ page }) => {
await page.goto('/users');
await validateCSVDownload(
page,
'#export-users-csv',
123,
(row, idx) => {
expect(row.status).toBeOneOf(['active', 'inactive']);
if (row.createdAt) expect(Date.parse(row.createdAt)).not.toBeNaN();
}
);
});
Dealing with Browser‑Specific Download Behaviors
- Chrome/Edge – Shows a download bar at the bottom; accepts
downloadattribute onfor same‑origin blobs. - Firefox – May open a “Save As” dialog unless
browser.download.panel.shownis set tofalsevia preferences. In Playwright you can set this viabrowserContext = await browser.newContext({ acceptDownloads: true, ... }). - Safari – Tends to open PDFs inline; you may need to force attachment via
Content-Disposition: attachmentand verify that the downloaded file is not a preview.
When writing cross‑browser tests, parametrize the browser and assert on the MIME type rather than relying on UI dialogs.
Edge Cases That Only Appear in Production
Even with exhaustive lab testing, certain conditions surface only under real‑world traffic, varied client environments, or intermittent infrastructure issues. Below are the most common production‑only pitfalls for data export, along with detection strategies.
Large Data Sets
When the export exceeds a few megabytes, memory consumption on the server and client can spike. Symptoms include:
- Out‑of‑memory (OOM) crashes on the Node/Java process generating the file.
- Slow response times leading to user‑perceived lag or timeouts.
- Truncated output if the streaming buffer is not properly flushed.
Detection:
- Use a load‑testing tool (k6, Artillery) to hammer the export endpoint with concurrent requests for large payloads.
- Monitor server metrics (RSS, CPU) and client memory via Chrome Task Manager.
- In automated tests, generate a synthetic data set of, say, 2 million rows and assert that the download completes within an SLA (e.g., < 30 s) and that the file size matches the expected byte count.
Concurrent Exports
Power users may open multiple tabs and trigger exports simultaneously. Problems arise when:
- The backend uses a singleton temporary file path (e.g.,
/tmp/export.csv) causing overwrites. - Rate‑limiting or quota mechanisms mistakenly treat each tab as a separate user, triggering false throttling.
- Client‑side state (like a global “export in progress” flag) blocks subsequent clicks.
Detection:
- In Playwright, launch two contexts, navigate to the same page in each, and click export nearly simultaneously (
Promise.all([ctx1.click(...), ctx2.click(...)])). - Verify each download file has a unique name or contains the correct subset of data (if filters differ).
- Check server logs for any
500or429responses that are not expected.
Network Interruptions
Mobile users or those on unreliable Wi‑Fi may experience a dropped connection mid‑download. The client should:
- Detect the interruption (via the
downloadevent failing or theresponseending abruptly). - Present a retry option or resume capability if the server supports range requests.
- Not leave a partially written file that could be mistaken for a complete export.
Detection:
- Use Chrome DevTools → Network → Throttling → “Offline” after the request has started, or use a proxy like
toxiproxyto inject latency and packet loss. - Assert that the UI shows an error toast and that no file is saved (or that a
.partfile is cleaned up).
Browser Quirks
Different browsers treat the same response headers slightly differently:
- Chrome honors
downloadattribute on anchor tags; Firefox may ignore it if the origin is cross‑origin without proper CORS. - Safari may automatically open PDFs; to force a download you need
Content-Disposition: attachmentand optionallyContent-Type: application/octet-stream. - Internet Explorer 11 (if still supported) requires
window.navigator.msSaveBlobfor blob‑based downloads.
Detection:
- Parameterize your test suite across Chromium, Firefox, and WebKit (Playwright makes this trivial).
- For each browser, assert that the file is saved (not opened in a tab) and that the filename matches the provided header.
Locale and Encoding Issues
Exported CSV may be opened in Excel, which assumes the system locale for delimiter and decimal separator. If your API always uses a comma as delimiter and a dot for decimal, users in locales that use a comma as decimal will see mangled numbers.
Detection:
- Create test data with numbers like
1234,56(one thousand two hundred thirty‑four point five six) and verify that the exported file either: - Uses a semicolon delimiter (
;) when the locale demands it, or - Includes a UTF‑8 BOM and relies on the importing application to interpret correctly.
- Additionally, test special characters (e.g., “Straße”, emojis) to ensure UTF‑8 preservation.
Third‑Party Extensions and Ad Blockers
Some extensions rewrite network requests or block URLs that match known tracking patterns. An export endpoint that contains /export/ or /download/ in its path might be inadvertently blocked.
Detection:
- Run your manual exploratory session with popular extensions enabled (uBlock Origin, Privacy Badger, Ghostery).
- In automated tests, launch a browser context with
--disable-extensionsand then with a specific extension loaded viapage.context().addInitScript(if supported) to confirm the extension does not interfere.
CSP and Same‑Origin Policy
A overly restrictive Content‑Security‑Policy that includes sandbox without allow-same-origin can prevent the browser from treating the response as a download, causing it to be displayed as raw text.
Detection:
- Inspect the response headers in the Network tab; look for
Content-Security-Policy. - If
sandboxis present, verify thatallow-same-originis also included, or that the policy does not apply
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