TestKase
TestKase
|Docs
AutomateMobile

Uploading your app

Try a demo app with no upload, then upload your own .ipa, .apk or simulator build — and what happens to an iOS build along the way.

To test your own app, you upload it once. Then any session can ask for it by id.

Try it first without uploading

farm://app-sample is our demo app. It is already on every device, so there is nothing to upload and nothing to clean up.

"appium:app": "farm://app-sample",

One id works everywhere. We ship it as three builds, and pick the one your device needs:

You asked forYou get
An iPhone or iPadThe .ipa build
An Android phone or emulatorThe .apk build
An iOS simulatorThe zipped simulator build

It belongs to no account, so it never expires and never counts against your limit.

What is in it

A sign-in screen, then two tabs.

Sign in with test / test. Anything else shows an error, so you can assert on a failure too.

Every element has an accessibility id, and the ids are the same on iOS and Android. One script runs on both:

idWhat it is
login-email, login-password, login-submitThe sign-in form
login-errorAppears only when sign-in fails
tab-counter, tab-itemsThe two tabs
counter-value, counter-increment, counter-decrementA number you can change
item-1 … item-20List rows. Tapping one selects it
selected-itemReads Selected: Item 3 after you tap item-3
const aid = (id) => new By("accessibility id", id);

await driver.findElement(aid("login-email")).sendKeys("test");
await driver.findElement(aid("login-password")).sendKeys("test");
await driver.findElement(aid("login-submit")).click();
await driver.findElement(aid("counter-increment")).click();

Uploading your own build

Three files are accepted. They are the same three we ship the sample app in:

  • .ipa for iPhones and iPads
  • .apk for Android phones and emulators
  • .zip for an iOS simulator — a zipped .app

The extension decides the platform, so name the file correctly.

Upload all three if you want one library that covers every device. A session picks by what it asked for, the same way the sample app does.

Upload takes three calls. The file goes straight to storage and never passes through our API:

1. POST /api/v1/apps/upload-url   → you get a one-time upload address
2. PUT  <that address>            → the file itself, up to 500 MB
3. POST /api/v1/apps/commit       → you get back an id

Or use My apps in the dashboard, which does the same three steps for you.

The commit gives you two ids. The one you want is the farm URL:

farm://app-a1b2c3d4e5f60718

That is the only one a session accepts. It comes back as farmUrl from the API, and the copy button beside each build in My apps copies exactly that value.

Upload once, use it in as many sessions as you like.

Limits

LimitValue
File size500 MB
Filename256 characters
Builds per person10 iOS and 10 Android

The size is checked before you get an upload address, so an oversized build never starts transferring.

An oversized build gets a confusing message. The error reads "Provide filename and sizeBytes", and never mentions the size. If a request that looks correct is refused with that, check the file size first, then the filename length.

Hitting the 10-build limit refuses the new upload. It never deletes an old one. Delete a build you no longer need, then retry.

That is true for uploads from My apps and from this API. Apps uploaded through the Espresso API (POST /espresso/v1/app) work differently: when your Android library is full, a new Espresso upload replaces the oldest app uploaded through the Espresso API, and that app's farm URL stops working. Apps you uploaded here are never replaced that way. If none of the 10 came through the Espresso API, the Espresso upload is refused instead.

Apps uploaded through the XCUITest API (POST /xcuitest/v1/app) follow the same rule on iOS: when your iOS library is full, a new XCUITest upload replaces the oldest app uploaded through the XCUITest API, and that app's farm URL stops working. Apps you uploaded here are never replaced that way. If none of the 10 came through the XCUITest API, the XCUITest upload is refused instead.

Espresso test suites (androidTest APKs) and XCUITest test suites (zipped -Runner.apps) have their own libraries and never appear in My apps.

Using your build in a session

Pass the farm URL as appium:app:

capabilities: {
  platformName: "iOS",
  "appium:deviceName": "iPhone 11",
  "appium:app": "farm://app-a1b2c3d4e5f60718",
  "testkase:options": {
    accessKey: "xyz_your_access_key_here",
    name: "checkout smoke",
    recording: true,
  },
}
The separator is a hyphen. farm://app-1234 works. farm://app/1234 does not, and neither does an https link, a file path, or the other id. A wrong value is refused at the start of the session, so nothing runs against the wrong app.

Things that can still go wrong with a correct id:

The messageWhat it means
App not foundNo build with that id, or it belongs to another organisation
This uploaded app has expiredThe build is older than 90 days
Platform mismatchAn Android build sent to an iPhone, an iPhone build sent to a simulator, and so on
browserName and appium:app are mutually exclusiveYou asked for an app and a browser. Pick one

What happens to an iOS build

Our iPhones are locked down, so an app cannot be installed the usual way. We install yours through Apple's management system instead.

You do nothing differently. But your build is re-signed on the way in, and its bundle id becomes:

com.solvionit.devicefarm.uploaded.<sha12>
Re-signing replaces your original signature. Anything tied to it may stop working in a session: push notifications, Apple Pay, App Groups and iCloud are the usual ones. The bundle id changes too, so a test that checks the bundle id needs to expect the new value.

An App Store download will not work. A Store build has no provisioning profile inside it, so re-signing refuses it. Upload a Development or Ad-Hoc export from your own build instead.

If you want the real shipping version on the device, use Install from App Store rather than uploading.

How long builds are kept

Uploaded builds are deleted after 90 days. A sweep runs every six hours, so a build disappears soon after it ages out. Upload as part of your pipeline rather than relying on one from last quarter.

This is separate from session recordings and logs, which are kept for 30 days after the session.