For four years, the mobile E2E tests at Jaja Finance looked the way most fintech teams set them up: Appium, TypeScript and WebdriverIO in a separate repository, running every night against the real backend. We deprecated all of it and moved to native tests that live in the app repo and never call a live service.
Jaja, a UK credit card issuer, runs iOS, Android and web apps for four white-label brands on shared infrastructure. Four brands multiply every testing decision, which is why we’ve written separately about keeping QA costs flat across brands. This piece covers the mobile side: the setup we replaced the old suite with, and the part most mocking advocates skip, which is what mocks can’t catch.
Mobile E2E tests fail mostly for reasons that have nothing to do with your app: a real backend that’s slow or down, plus the timing quirks of driving a device from outside. In payments the backend part dominates, because one test can touch an issuer processor, a KYC provider, a fraud screening vendor and a PSP sandbox, and each of them fails on its own schedule.
The device layer adds its own noise. Appium drives the app from outside, through an automation server and an emulator that has to boot, render and finish animating before any assertion runs. Android’s own Espresso setup guide tells you to switch off system animations on test devices to avoid flakiness. A web or API suite clears 20–30 scenarios a minute. A mobile suite clears a handful.
Our nightly suite had about 20 tests and took almost an hour. A backend service would hiccup at 3 a.m., the run went red, and nothing in the app had changed.
| “Only a very small share of its failures were real bugs. Most were backend hiccups, environment downtime, timing and device issues, so QA spent every morning triaging failures that weren’t bugs.” — Volodymyr Litvinchuk, Test Automation Engineer, Kindgeek |
That morning triage is the real cost. Google found that almost 16% of its tests showed some flakiness and about 1.5% of all runs produced a flaky result. At Google’s scale, that’s an infrastructure problem. On a mobile team with six developers and one QA engineer, it becomes a trust problem: once people learn that red usually means noise, they stop reading the report.
We split the old suite’s work across three layers. Most of the volume moved to the one that never calls a real backend.
| Layer | Tooling | Backend | When it runs | Who owns it |
|---|---|---|---|---|
| Native UI tests | Kotlin + Espresso (Android), Swift + XCUITest (iOS), in the app repo | Mocked, fixtures built from real responses | Every pull request on Android; twice weekly on iOS | Developers and automation QA |
| Screenshot tests | Reference snapshots, pixel comparison, both platforms | Mocked | Alongside feature work | Developers and automation QA |
| Small E2E suite (web only) | 15–16 Playwright tests | Real dev environment | Every morning | QA |
Android tests use Kotlin and Espresso. iOS uses Swift and XCUITest, part of Apple’s XCTest framework. Both sit in the app repository beside the code they test, so a developer who changes a screen sees its tests in the same pull request. It’s the same reasoning behind writing tests in the language your team already ships: people review code they can read.
We mock every backend response in these tests, which takes PSP timeouts, sandbox restarts and unexpected KYC payloads out of the picture. The tests check screen states, component integration and user flows without a single network call. We build the fixtures from real backend responses, so they’re accurate on the day someone writes them.
On Android we run 200–220 of these tests per brand, in parallel, on every pull request. iOS runs the same approach twice a week. XCUITest is slower, and on Bitrise each run spends paid credits, so we accepted slower iOS feedback in exchange for a sane CI bill. If you copy this setup, make that call on purpose and with your own CI numbers in front of you.
A mock describes the backend as it was when someone wrote it. It can go stale in two ways, and only one of them is visible from the mobile repo.
“The backend changes something, and nobody tells mobile. The mocked tests can’t catch this, by design.”
— Volodymyr Litvinchuk, Test Automation Engineer, Kindgeek
The layers that call the real backend pick it up instead: API tests, the morning E2E run on the dev environment, and manual testing of the mobile apps. When one of them finds drift, we fix the app and its mocks together.
Every contract problem we’ve seen so far surfaced on the dev environment before release. We’d still rather state the limit plainly than call mocks risk-free.
“It’s possible a production bug was partly caused by a stale mock, but we’ve never traced a production defect back to the mocked tests as the cause.”
— Volodymyr Litvinchuk, Test Automation Engineer, Kindgeek
That uncertainty is the reason the real-backend layer stays, small as it is.
Our functional UI tests used to check two things at once: whether flows worked (tap a button, a payment card appears) and whether every label, icon and string on screen matched. The second check caused most of the breakage. One copy change on one screen could fail an entire flow.
Screenshot tests took that job over. Developers capture reference snapshots and the framework compares each new build pixel by pixel, which let the functional tests shrink back to interactions and state changes. With four brands each carrying its own palette, logo and copy on shared screens, visual checks are the part of testing that grows fastest, so giving them their own layer paid off quickly.
We kept end-to-end tests, but only on web. Fifteen or sixteen Playwright tests run every morning against the real dev backend and cover core journeys only. We retired the mobile E2E tests completely.
QA reads those results first thing. If a service is misconfigured or a contract drifted overnight, the team knows before manual testing starts. This suite is also one of the layers that catches the unannounced backend changes the mocks miss.
Twenty scenarios is our ceiling. Google’s testing team has long suggested a shape of roughly 70% unit, 20% integration and 10% end-to-end tests. Push 100 scenarios into this layer and each new one costs more to maintain than it returns.
We never ran an Appium vs Espresso contest about which tool is better. The trouble with our mobile E2E tests was where they ran: outside the app, against a live backend, in a separate repository away from feature work. Espresso and XCUITest put the tests inside the codebase, with the backend mocked. The numbers moved accordingly.
“Now we have 200-220 mocked tests, run in parallel for each brand, and the total run still takes about an hour. That’s roughly ten times the coverage in the same time.”
— Volodymyr Litvinchuk, Test Automation Engineer, Kindgeek
The bigger change is what a red run means. Failures are now mostly real regressions, or tests that need an update after an intended change. They show up on the pull request, so the developer who caused them fixes them right away instead of QA finding them the next morning.
Teams usually ask how long the move took. The answer has two parts.
“Switching over was quick. Building up coverage took much longer.”
— Volodymyr Litvinchuk, Test Automation Engineer, Kindgeek
We never ran a migration project. New features got UI tests as part of their own development. Existing features got separate automation tickets, which the team worked alongside other sprint tasks, and manual regression covered anything still waiting for tests.
Developers and automation QA both write mocked UI and screenshot tests in the app repo. Whoever writes a test owns its mock data, and test changes go through PR review like any other code. QA also reviews every PR that touches test folders and decides during refinement which scenario belongs at which level.
When a run fails, QA traces it to the merge that caused it, and the author fixes it the same day. With six developers to one QA engineer, we haven’t found another arrangement that keeps coverage up without sliding back into manual regression. When automation lives in its own repo with its own team, nobody else knows what it covers, and soon nobody trusts it.
If we started a mobile fintech product today, we’d sequence testing like this.
When your mobile suite goes red, who looks at it, and how often is it right? On Jaja’s apps the answer used to be QA, every morning, and rarely. Now it’s the developer who opened the pull request, and most of the time the failure points at a real problem.
If your team still starts the day sorting real failures from backend noise, look at the test architecture before you look at the tests. Put the volume in mocked native tests, keep a small real-backend layer for contract drift, and give every test an owner.
AI payment reconciliation applies machine learning and language models to match payment transactions across PSPs, acquirers, banks, billing systems and the general ledger, then classifies the remaining breaks. It extends rule-based matching by scoring likely pairs where references are missing, incorrect or unstructured. The output is a confidence-scored match, which a rule or a person then turns into a posting. In practice it runs as a pipeline: ingestion, normalisation, matching, exception management, ledger posting.
We start by mapping every path from customer payment to ledger entry and baselining the current auto-match rate, exception volume and close duration. Next we connect the PSPs, banks, billing platform and ERP, and normalise all of it into a single transaction model. Deterministic rules come first, since exact-reference and tolerance matching clears most volume, and AI matching takes the remainder. The AI layer then runs in shadow mode for one or two full cycles, you set confidence thresholds for auto-posting versus review, and scope grows one payment flow at a time.
Yes, and those cases are the reason for the AI layer. Where a reference is truncated, mistyped or absent, the system falls back on amount proximity, date windows, counterparty similarity, historical payment behaviour and free-text bank narratives. A payment reconciliation AI model combines those signals into a confidence score and proposes the most likely candidate, score attached. Low-confidence proposals reach a reviewer with the evidence attached, so the context sits in one place.
It can, and fee reconciliation is where overcharging surfaces. The system reconstructs the bridge between gross sales and net deposit, covering interchange, scheme fees, MDR, refunds, chargebacks, reserves and FX spread, then ties each deduction to the underlying transactions. It then checks effective rates against your contracted schedule and flags the variance. For FX, it separates genuine rate and timing variance from booking errors, so only the real differences reach your team.
Most of it can. Clean, well-referenced flows routinely reach high automatic match rates, and continuous matching removes the period-end crunch. Disputed deductions, contractual fee disagreements, related-party items and anything turning on accounting judgement still need a person, and regulators expect meaningful human oversight of AI in financial processes. The realistic target is high autonomy with explicit governance: AI handles your volume, your people own the judgement and the controls.
AI payment reconciliation matches payment records across PSPs, acquirers, banks, billing platforms and the general…
Adyen processed €803.8 billion in H1 2026, with Platforms as its fastest-growing segment and volume…
The top fintech MVP development companies build products that move real money under real regulatory…
A sub-merchant portal is a self-service interface that lets sellers, service providers, and other businesses…
Payments fraud is a constant wherever money moves. Payments fraud reached 76% of US organisations…
Ninety percent of technology professionals now use AI at work, according to DORA's 2025 research.…