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 for | You get |
|---|---|
| An iPhone or iPad | The .ipa build |
| An Android phone or emulator | The .apk build |
| An iOS simulator | The 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:
| id | What it is |
|---|---|
login-email, login-password, login-submit | The sign-in form |
login-error | Appears only when sign-in fails |
tab-counter, tab-items | The two tabs |
counter-value, counter-increment, counter-decrement | A number you can change |
item-1 … item-20 | List rows. Tapping one selects it |
selected-item | Reads 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:
.ipafor iPhones and iPads.apkfor Android phones and emulators.zipfor 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 idOr 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-a1b2c3d4e5f60718That 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
| Limit | Value |
|---|---|
| File size | 500 MB |
| Filename | 256 characters |
| Builds per person | 10 iOS and 10 Android |
The size is checked before you get an upload address, so an oversized build never starts transferring.
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,
},
}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 message | What it means |
|---|---|
| App not found | No build with that id, or it belongs to another organisation |
| This uploaded app has expired | The build is older than 90 days |
| Platform mismatch | An Android build sent to an iPhone, an iPhone build sent to a simulator, and so on |
| browserName and appium:app are mutually exclusive | You 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>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.
