How to Automate Pull To Refresh Testing (Step-by-Step)
How to Automate Pull To Refresh Testing (Step-by-Step)
How to Automate Pull To Refresh Testing (Step-by-Step)
Pull‑to‑refresh is a common interaction pattern in mobile and web apps. Users drag down a list to trigger a data reload, and the UI shows a spinner or indicator until new content arrives. Automating this gesture is valuable because the action touches several layers of the stack: touch input, scroll physics, network mocking, and UI state verification. When done correctly, automated pull‑to‑refresh tests catch regressions that manual exploration often misses, such as race conditions that only appear under load, accessibility blockers that prevent the gesture from being recognized, or stale‑data bugs that surface after a background sync. In this guide we walk through a complete, repeatable process for automating pull‑to‑refresh testing, from deciding when to invest in automation to running the tests in CI and reporting results. Each section includes concrete examples, code snippets, and practical tips you can apply immediately.
How to Automate Pull To Refresh Testing (Step-by-Step): Overview
Before writing any test, clarify the scope of what you want to verify. Pull‑to‑refresh can be broken down into three observable outcomes:
- Gesture recognition – the system correctly interprets a downward drag as a refresh request.
- Data reload – the app issues a new network call (or uses a mock) and updates the list with fresh items.
- UI feedback – a loading indicator appears, disappears at the right time, and no visual glitches occur.
Define a pass/fail criterion for each outcome. For example, a test passes if after the gesture the spinner is visible for at least 200 ms, the network mock returns a 200 response with new data, and the list displays at least one item whose timestamp is newer than the previous state. Document these criteria in a test case template; they become the assertions you will automate.
Next, decide which platforms you need to cover. If your app is native Android, you will likely use Espresso or UIAutomator. For iOS, XCUITest is the default. For hybrid or web‑based views, Playwright or Appium (with the webview context) works well. If you want a single script that runs on both platforms without maintaining separate locators, consider a cross‑platform tool like Appium with the Flutter driver or React Native testing library. The choice influences the language, the waiting mechanisms, and the way you simulate the drag gesture.
Finally, set up a baseline for flake detection. Run the candidate test five times on a clean device or emulator and record the pass rate. If the rate drops below 90 %, investigate sources of non‑determinism such as animation timing, network latency, or device performance before proceeding.
How to Automate Pull To Refresh Testing (Step-by-Step): Framework Selection
Choosing a framework is not just about language preference; it impacts test stability, maintenance overhead, and integration with your CI pipeline. Below is a comparison of the most common options for pull‑to‑refresh automation.
| Framework | Language | Gesture Support | Network Mocking | Cross‑Platform | Typical Setup Time |
|---|---|---|---|---|---|
| Espresso (Android) | Java/Kotlin | Built‑in swipe APIs | OkHttpMock, WireMock | No (Android only) | Low |
| XCUITest (iOS) | Swift/Obj‑C | XCUIGestureRecognizer | OHHTTPSticks, Mocker | No (iOS only) | Low |
| Appium | Java, JS, Python, Ruby | TouchAction / PointerInput | Any HTTP mock via proxy | Yes (Android/iOS/Web) | Medium |
| Playwright | JS/TS, Python, .NET | page.mouse.move/down/up | page.route for API interception | Yes (Chromium, Firefox, WebKit) | Low |
| SUSA autonomous exploration | No code needed | AI‑driven gestures | Built‑in traffic shaping | Yes (APK/URL) | Very low (upload only) |
When to pick each
- Espresso/XCUITest – best for pure native apps where you need the fastest execution and deep integration with the test runner (Gradle, Xcode). Use them if you already have a UI test suite and want to keep everything in the same language.
- Appium – choose when you need to test a hybrid app, a webview inside a native shell, or when you want a single script that can run on both Android and iOS with minimal changes. The trade‑off is slower start‑up times and a heavier dependency on the Appium server.
- Playwright – ideal for progressive web apps or when the pull‑to‑refresh lives in a web view that you can isolate. Its auto‑waiting and built‑in network mocking reduce flake dramatically.
- SUSA autonomous exploration – useful for bootstrapping tests without writing any code. The platform explores the app, discovers pull‑to‑refresh candidates, and generates ready‑to‑run Appium or Playwright scripts. Use it when you want to quickly create a baseline test suite for a new release or when you lack dedicated QA engineers.
If your team already maintains a test automation framework, extend it rather than introduce a new one. For example, add a PullToRefreshHelper class to your existing Espresso suite; this keeps locators and utilities in one place.
How to Automate Pull To Refresh Testing (Step-by-Step): Writing Stable Tests
Stability starts with a clear test structure: arrange, act, assert. Keep each test focused on a single aspect of the pull‑to‑refresh interaction. Below is a template in Kotlin using Espresso that you can adapt to other frameworks.
@RunWith(AndroidJUnit4::class)
class PullToRefreshTest {
private val mockWebServer = MockWebServer()
@Before
fun setUp() {
mockWebServer.start()
// Configure the app to point at mockWebServer.url("/items")
// (dependency injection or flavor‑specific resources)
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@Test
fun pullToRefresh_loadsNewData() {
// Arrange: seed initial list
mockWebServer.enqueue(MockResponse()
.setResponseCode(200)
.setBody(initialJson))
launchActivity<MainActivity>()
// Assert initial list shows old timestamps
onView(withId(R.id.item_list))
.check(matches(hasDescendant(withText("2024-09-01"))))
// Act: perform pull‑to‑refresh
onView(withId(R.id.swipe_refresh))
.perform(swipeDown())
// Arrange mock for refresh response
mockWebServer.enqueue(MockResponse()
.setResponseCode(200)
.setBody(freshJson))
// Assert: spinner appears
onView(withId(R.id.progress_bar))
.check(matches(isDisplayed()))
// Assert: new data appears after spinner disappears
onView(withId(R.id.item_list))
.check(matches(hasDescendant(withText("2024-09-03"))))
onView(withId(R.id.progress_bar))
.check(matches(not(isDisplayed())))
}
private val initialJson = """[{ "id":1, "timestamp":"2024-09-01T10:00:00Z"}]"""
private val freshJson = """[{ "id":2, "timestamp":"2024-09-03T10:00:00Z"}]"""
}
Key stability techniques
- Deterministic test data – use a mock server (MockWebServer, WireMock, or Playwright’s
page.route) to return predictable payloads. Avoid hitting real backends during UI tests. - Explicit waits for UI state – Espresso’s
IdlingResourceor Playwright’sexpect().toBeVisible()automatically wait for animations to finish. Do not rely onThread.sleep. - Isolate the gesture – target the
SwipeRefreshLayout(Android) or the scrollable container directly. Performing the gesture on a generic view can cause flakiness if the view hierarchy changes. - Reset state between runs – clear databases, shared preferences, or local storage in
@Before/@Afterhooks. This prevents cross‑test contamination. - Use version‑controlled mocks – store JSON fixtures in the repository under
src/test/resources/mocks. Tag them with the API version they correspond to, so you can detect contract drift early.
If you prefer Playwright, the same test looks like this:
import { test, expect } from '@playwright/test';
test.describe('Pull-to-refresh', () => {
test('loads fresh data after drag down', async ({ page }) => {
// Mock the API
await page.route('**/items', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(initialJson)
});
});
await page.goto('https://example.com/feed');
await expect(page.locator('.item')).toContainText('2024-09-01');
// Perform pull‑to‑refresh
await page.mouse.move(0, 0);
await page.mouse.down();
await page.mouse.move(0, 200); // drag down 200px
await page.mouse.up();
// Switch mock to fresh data
await page.route('**/items', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(freshJson)
});
});
// Loading indicator
await expect(page.locator('.spinner')).toBeVisible();
// New data appears
await expect(page.locator('.item')).toContainText('2024-09-03');
await expect(page.locator('.spinner')).not.toBeVisible();
});
});
Both examples share the same pattern: set up mocked responses, perform the drag, verify UI feedback, then validate the new data.
How to Automate Pull To Refresh Testing (Step-by‑Step): Locator Strategies for Pull‑to‑Refresh Gestures
A reliable locator is the foundation of a stable gesture. Avoid brittle selectors that depend on dynamic IDs or positions that can shift with UI updates. Instead, prioritize accessibility IDs, content descriptions, or test‑specific attributes.
Android
- SwipeRefreshLayout – assign
android:id="@+id/swipe_refresh"and reference it withonView(withId(R.id.swipe_refresh)). - RecyclerView items – use
hasDescendant(withText("expected‑string"))or a customViewMatcher that checks a data‑bound tag (view.getTag(R.id.item_id) === 42`). - Content description – if the refresh indicator is an image, set
android:contentDescription="@string/refresh_indicator"and locate viawithContentDescription.
iOS
- Accessibility identifier – in Interface Builder set the
Accessibility Identifierof the refresh control to"refreshControl"and locate withXCUIElementQuery.identifiers["refreshControl"]. - Table/collection cells – use
cells.matching(identifier: "cell_\(index)")orNSPredicateonvalueattributes.
Web / Playwright
- Data‑test attributes – add
data-testid="refresh-trigger"to the container that receives the drag event. Locate withpage.locator('[data-testid="refresh-trigger"]'). - Role‑based selectors – if the refresh indicator is a button with an accessible label, use
page.getByRole('button', { name: /refresh/i }). - Avoid CSS‑only selectors – classes like
.pull-to-refreshcan change with a redesign; combine them with an attribute ([data-testid]).
When the pull‑to‑refresh gesture is implemented via a custom scroll listener rather than a standard component, you may need to locate the scrollable container itself. In that case, locate by a unique accessibility label (contentDescription or accessibilityLabel) and then perform the drag relative to its bounds.
Handling Dynamic Lists
If the list is populated with data that changes each run (e.g., timestamps), avoid locating items by their text. Instead, attach a stable test tag to each row when you bind the view holder:
itemView.setTag(R.id.test_row_id, position) // stable integer
Then in the test:
onView(allOf(withId(R.id.row_container), withTagValue(equalTo(R.id.test_row_id, 0), isA(Int::class.java))))
This approach guarantees that you are always interacting with the same logical row regardless of its content.
How to Automate Pull To Refresh Testing (Step-by‑Step): Handling Waits, Synchronization, and Flakiness
Even with good locators, timing issues are the most common source of flake in pull‑to‑refresh tests. The gesture triggers a cascade: touch event → scroll detection → network request → UI update. Each step can introduce variance.
1. Use Framework‑Provided Idling Mechanisms
- Espresso – register an
IdlingResourcethat signals when the app’s main thread is idle *and* when any pending OkHttp calls have completed. A simple implementation counts active calls and notifies Espresso when the count reaches zero. - XCUITest – employ
expectation(for: NSPredicate, evaluatedWith: handler)to wait for a predicate such asspinner.isDisplayed == true. - Playwright – leverage auto‑waiting: actions like
clickorfillwait for the element to be actionable; for network responses usepage.waitForResponse(() => true)orpage.route(...).fulfill.
2. Mock Network Latency Intentionally
Introduce a controlled delay in your mock server to simulate real‑world conditions and verify that the UI handles loading states correctly.
// MockWebServer
mockWebServer.enqueue(new MockResponse()
.setBody(freshJson)
.setBodyDelay(2, TimeUnit.SECONDS)); // 2‑second latency
If the test passes with this delay, you have confidence that the spinner will stay visible long enough for users on slower connections.
3. Validate Animation Completion
Some apps animate the pull distance with a spring effect. After performing the drag, wait for the scroll position to settle before checking the spinner. In Espresso you can use:
onView(withId(R.id.recycler_view))
.check(matches(not(isScrolling()))); // custom IdlingResource for RecyclerView
In Playwright:
await page.waitForFunction(() => {
const el = document.querySelector('.virtual-list');
return el.scrollTop === 0; // assuming pull‑to‑refresh resets to top
});
4. Flakiness Detection and Retry Strategy
Record the result of each test run in a CI artifact (e.g., a JSON file). If a test fails, automatically rerun it up to two times before marking it a genuine failure. This catches intermittent issues like occasional frame drops without hiding real regressions.
# Example GitHub Actions step
- name: Run UI tests
run: ./gradlew connectedAndroidTest
continue-on-error: true
- name: Retry flaky tests
if: failure()
run: ./gradlew connectedAndroidTest --tests "*PullToRefreshTest*" --max-workers 1
5. Device/Emulator Consistency
Run tests on a fixed API level (e.g., Android 13) and a specific device pixel density. Avoid using the latest emulator image that may change under the hood. For iOS, lock to a specific Xcode simulator version (e.g., iPhone 14, iOS 17.2). Document the exact image IDs in your CI configuration.
How to Automate Pull To Refresh Testing (Step‑by‑Step): Data Setup, Teardown, and State Management
Pull‑to‑refresh tests often depend on a known starting state. If the app persists data across launches (e.g., cached feed, user preferences), you must reset it reliably.
1. Use App‑Specific Reset Mechanisms
Many apps expose a debug endpoint or a developer setting that clears the cache. Trigger it via ADB (adb shell am broadcast -a com.example.app.CLEAR_CACHE) or via a hidden UI button that you only enable in test builds.
2. Database Wiping
If the app uses Room or SQLite, you can delete the database file directly:
@Before
fun clearDb() {
val context = ApplicationProvider.getApplicationContext<Context>()
val db = Room.databaseBuilder(context, AppDatabase::class.java, "app-db")
.allowMainThreadQueries()
.build()
db.clearAllTables()
}
For Core Data on iOS, delete the persistent store URL in setUp().
3. Shared Preferences / UserDefaults
Clear them before each test:
PreferenceManager.getDefaultSharedPreferences(context).edit().clear().apply()
let defaults = UserDefaults.standard
defaults.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
4. Network State
Ensure that any background sync services are disabled or mocked. If the app uses WorkManager, you can inject a test Configuration that sets setScheduler(None) to prevent automatic work execution.
5. State Isolation Between Tests
If you run multiple pull‑to‑refresh scenarios in the same test class (e.g., testing empty‑state, error‑state, success‑state), re‑initialize the mock server enqueues in each @Test method rather than relying on a shared queue. This prevents cross‑test contamination where leftover responses from a previous test affect the next one.
6. Teardown Verification
After each test, assert that no stray network calls remain pending. In MockWebServer you can call mockWebServer.takeRequest() with a short timeout and expect null to confirm the queue is empty.
@After
fun verifyNoPendingRequests() {
assertNull(mockWebServer.takeRequest(100, TimeUnit.MILLISECONDS))
}
This final check catches cases where the app fails to cancel a request on navigation away, which could otherwise cause flake in subsequent runs.
How to Automate Pull To Refresh Testing (Step‑by‑Step): Running Tests in CI and Collecting Reports
Integrating pull‑to‑refresh tests into your continuous delivery pipeline ensures regressions are caught before they reach users. The steps below apply to both Android and iOS, with notes for web‑based implementations.
1. Choose the Right Execution Environment
- Android – Use Firebase Test Lab or a self‑hosted pool of Android emulators (API 28‑33). Allocate enough RAM (≥2 GB) and enable hardware graphics acceleration (
-gpu on) to reproduce touch latency accurately. - iOS – Utilize macOS runners with Xcode 15+ and simulate a range of devices (iPhone SE, iPhone 14 Pro, iPad Air). Turn off “Connect Hardware Keyboard” to ensure the touch events are not interfered with.
- Web – Run Playwright tests on the same Ubuntu image used for your unit tests; install the required browsers via
playwright install.
2. Parallelize Wisely
Pull‑to‑refresh tests are relatively fast (usually <5 s each), but they can still benefit from parallelism when you have a large suite. However, avoid over‑subscribing the device’s GPU or CPU, which can increase frame‑drop probability. A good rule of thumb is to run no more than two UI tests per emulator/core.
# GitHub Actions matrix for Android
strategy:
matrix:
api-level: [28, 29, 30, 31]
device: [pixel_4, pixel_5]
3. Capture Artifacts for Debugging
When a test fails, you need enough information to reproduce the issue locally. Configure your test runner to pull:
- Screenshots – taken automatically on failure (Espresso’s
Screenshot, XCUITest’sattachment, Playwright’spage.screenshot()). - Video – enable video recording in Firebase Test Lab or use
adb shell screenrecordlocally. - Logs – pull
logcat(Android) orsyslog(iOS) and attach them as plain text. - Network traces – if you use a mock server, export the request log; for real‑network tests, enable
HttpLoggingInterceptoror use Charles Proxy to export a HAR file.
Store these artifacts as build artifacts or upload them to an artifact repository (e.g., AWS S3, Azure Blob Storage) with a link in the test report.
4. Generate a Unified Test Report
Combine JUnit XML (Android) or XCResult (iOS) with a custom summary that highlights pull‑to‑refresh specific metrics:
| Test Case | Pass Rate (last 10 runs) | Avg. Duration (ms) | Flakiness Index |
|---|---|---|---|
| PullToRefresh_success | 0.9 | 1820 | 0.1 |
| PullToRefresh_error | 0.8 | 2100 | 0.2 |
| PullToRefresh_empty | 1.0 | 1500 | 0.0 |
The Flakiness Index can be computed as 1 - (passes / total runs). Flag any test with an index > 0.15 for investigation.
5. Alert on Regression
Set up a rule in your CI that fails the build if any pull‑to‑refresh test’s pass rate drops below a threshold (e.g., 0.85) compared to the baseline stored in a configuration file. This prevents gradual degradation from going unnoticed.
6. Example GitHub Actions Workflow (Android)
name: UI Tests
on:
push:
branches: [main]
pull_request:
jobs:
android-ui:
runs-on: ubuntu-latest
strategy:
matrix:
api-level: [29, 30]
device: [pixel_4]
steps:
- uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: '17'
- name: Cache Gradle
uses: actions/cache@v3
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Run tests
run: ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.androidx.test.executor.MaxShard=2
- name: Collect artifacts
if: failure()
uses: actions/upload-artifact@v3
with:
name: ui-test-artifacts-${{ matrix.api-level }}-${{ matrix.device }}
path: |
app/build/outputs/androidTest-results/**/*
app/build/outputs/screenshots/**/*
A similar workflow can be written for iOS using xcodebuild test with destination parameters and xcresulttool to extract JUnit XML.
How to Automate Pull To Refresh Testing (Step‑by‑Step): Leveraging Autonomous Exploration to Bootstrap Tests
Writing the first pull‑to‑refresh test manually can be time‑consuming, especially when you are unfamiliar with the app’s gesture implementation. Autonomous exploration tools like SUSA can dramatically reduce this initial effort by automatically discovering pull‑to‑refresh candidates and generating starter scripts.
How SUSA Works
- Upload – you provide an APK (Android) or a URL (web). The agent installs the app on a cloud‑hosted device or launches a headless browser.
- Exploration – the agent executes a set of persona‑driven scripts (curious, impatient, power user, etc.). Each persona has a distinct interaction profile: the curious persona taps and long‑presses random elements, the impatient persona performs fast swipes, the accessibility persona uses voice‑over navigation, and so on.
- Detection – while exploring, the agent monitors for UI patterns that match a pull‑to‑refresh signature: a vertical drag that triggers a spinner, a network request to a known endpoint, or a change in the list’s timestamp order.
- Generation – once a candidate is identified, SUSA records the exact gesture (start coordinates, distance, duration) and the surrounding view hierarchy. It then emits a ready‑to‑run test script in the language/framework of your choice (Appium Java, Playwright TypeScript, or Espresso Kotlin).
- Iteration – you can run the generated script locally, assert the expected outcome, and then commit it to your repository. Subsequent runs of SUSA will remember previously explored screens and avoid re‑testing the same paths, making each execution faster.
Benefits for Pull‑to‑Refresh
- No guesswork on coordinates – the agent captures the exact drag vector that the app responds to, eliminating the trial‑and‑error of estimating swipe length.
- Locator suggestions – SUSA outputs locators based on stable attributes (content‑description, test‑id) that it observed during exploration, reducing the chance of brittle selectors.
- Baseline assertions – the generated script includes a simple assertion (e.g., “spinner appears after drag”). You can then enrich it with data validation and mocking as described in earlier sections.
- Cross‑persona coverage – because the exploration runs with multiple personas, you automatically obtain variations: a slow drag (elderly persona), a fast flick (impatient persona), and a drag assisted by voice‑over (accessibility persona). This surfaces issues that only manifest under specific interaction styles.
Using the Generated Script
Suppose SUSA produced the following Appium Java snippet:
@Test
public void pullToRefresh_discoveredBySusa() {
// Locate the refresh container via the accessibility ID SUSA recorded
MobileElement refresh = driver.findElementByAccessibilityId("pull_to_refresh_container");
// Perform the drag: start at (x, y) and move 0, -180px (upward in screen coordinates)
new TouchAction<>(driver)
.press(PointOption.point(240, 1200))
.waitOption(WaitOptions.waitOptions(Duration.ofMillis(200)))
.moveTo(PointOption.point(240, 1020))
.release()
.perform();
// Verify spinner appears
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("progress_spinner")));
// TODO: replace with your mock server verification
assertTrue(driver.findElement(By.id("first_item")).getText().contains("2024-09-03"));
}
You would then replace the TODO with a call to your mock server (e.g., enqueuing a fresh JSON response) and add assertions about the new data. Keep the generated locator if it proves stable; otherwise, replace it with a test‑id you control.
When to Rely on Autonomous Exploration
- Early‑stage projects – when the UI is still evolving and you want a quick smoke test without investing in manual script authoring.
- Regression safety net – run SUSA nightly on a staging build; any new pull‑to‑refresh patterns it discovers can be reviewed and turned into deterministic tests.
- Exploratory bug hunting – use the adversarial persona to attempt rapid, erratic drags that might expose race conditions or crashes that a polite manual tester would miss.
Limitations remain: the agent does not understand business logic, so you must still add domain‑specific assertions (e.g., verifying that a discount code is applied after a refresh). Treat the generated script as a starting point, not a final solution.
Test Matrix: Manual vs Automated Approaches
To help you decide where to invest effort, the following matrix contrasts manual exploratory testing with automated pull‑to‑refresh testing across several dimensions.
| Dimension | Manual Testing | Automated Testing |
|---|---|---|
| Setup time | Low – just a device and tester | Medium – requires framework, mock server, CI config |
| Execution speed | Slow – depends on tester availability | Fast – runs in seconds on CI |
| Repeatability | Variable – human inconsistency | High – same steps each run |
| Coverage of edge cases | Limited – tester may miss rare gestures | Broad – can simulate fast/slow, multiple personas, network latency |
| Feedback loop | Minutes to hours (wait for tester) | Seconds to minutes (CI pipeline) |
| Cost per run | High – salaried tester time | Low – compute minutes |
| Maintenance | Low – no code to maintain | Medium – test code needs updates when UI changes |
| Detects performance regressions | Subjective – relies on tester feel | Objective – can measure spinner duration, frame drops |
| Scales with device matrix | Poor – each device needs a tester | Excellent – run same script on many devices/emulators |
| Best for | Early UI exploration, usability studies | Regression guarding, CI gating, performance monitoring |
From the matrix, automation pays off when you need repeatable, fast feedback across many device configurations, or when you want to catch performance‑related regressions that are hard to perceive manually. Manual testing remains valuable for exploratory work and for validating subjective UX aspects that are difficult to encode in assertions.
Checklist for Reliable Pull‑to‑Refresh Automation
Use this short checklist before you consider a pull‑to‑refresh test ready for CI.
- [ ] Gesture target – located via a stable attribute (content‑description, test‑id, accessibility ID). No reliance on dynamic indices or hard‑coded coordinates.
- [ ] Mock network – all API calls triggered by the pull are intercepted and return deterministic payloads.
- [ ] Loading indicator verification – test asserts the spinner/progress bar is visible for a minimum time (e.g., ≥150 ms) and then disappears.
- [ ] Data validation – at least one item in the list shows newer information than the pre‑refresh state (timestamp, ID, counter).
- [ ] State reset – database, shared preferences, and cache are cleared before each test; no leftover mock responses in the mock server queue.
- [ ] Idle waiting – use framework‑provided idling resources or explicit waits; no
Thread.sleeporawait.sleep. - [ ] Flake guard – run the test ≥5 times locally; record pass rate ≥ 90 % before committing.
- [ ] Artifact capture – on failure, screenshots, video, and logs are automatically attached.
- [ ] CI integration – test runs on every PR, results are reported in the PR checks, and failures block merge.
- [ ] Performance threshold – optional: assert that the total time from drag start to data display is under a defined SLA (e.g., 2000 ms).
If every item is checked, you can
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