End-to-end UI tests for SuperwallKit. The app in this repo configures the SDK, runs one scenario per test (register a placement, purchase, restore, dismiss, …) and reports back to an XCUITest runner, which checks what ends up on screen.
The tests are numbered (test0, test1, …), and each runs in four schemes:
| Scheme | Language | Purchases handled by |
|---|---|---|
UI Tests -swift -automatic |
Swift | the SDK |
UI Tests -swift -advanced |
Swift | a purchase controller in the app |
UI Tests -objc -automatic |
Objective-C | the SDK |
UI Tests -objc -advanced |
Objective-C | a purchase controller in the app |
SuperwallKit releases are gated on all four schemes passing on Limrun, against the exact commit being released.
- macOS 27 and Xcode 27.
- The iOS 27 simulator runtime. Tests run on an iPhone 18 Pro locally; Limrun's simulators are iPhone 16 Pros.
- For Limrun: Node,
npm install --global lim, andlim login(orLIM_API_KEY).
./runTests.sh # all four schemes
./runTests.sh "UI Tests -swift -automatic" # one scheme
TESTS="0 5 12" ./runTests.sh "UI Tests -swift -automatic" # only these testsrunTests.sh creates one simulator per shard (SHARDS, default 4), sets them to en_GB and runs each
shard with its own xcodebuild. A failed test gets one retry. Results go to test-results/.
Other options: DEVICE and RUNTIME pick the simulator, LOCALE / LANGUAGE its region (the
references are en_GB). See the top of runTests.sh.
You can also run a scheme from Xcode, with parallel testing turned off.
scripts/limrun-test.sh # Swift automatic, every test, 20 shards
scripts/limrun-test.sh -s "UI Tests -objc -advanced" # another scheme
scripts/limrun-test.sh -t "4 60 103" # a few tests
scripts/limrun-test.sh -f # stop at the first test that fails twiceEach shard creates its own iOS 27 simulator and Xcode 27 sandbox, sets the simulator to en_GB, runs its
tests with lim xcode test, and deletes both instances. Tests are split by their recorded durations
(scripts/test-durations.json, updated after every run). Failed tests, and tests whose shard died
before running them, are retried. Results are written as NDJSON to limrun-results/ and summarised at
the end.
Options: -n shards (default 20, the organization's limit per instance type), -x / -o the Xcode
and iOS versions (default 27), -r the results directory (give each run its own when running schemes
at the same time), -f (or FAIL_FAST=1) to stop early.
With -f, a failed test is retried straight away on a fresh instance instead of at the end. If it fails
again, every shard is stopped, its instances are deleted and the run fails, so you only see that
failure. SuperwallKit's release workflow uses it. Leave it off to get the full list of failures.
Limrun leaves skipped tests and known issues (below) out of its results, so the summary lists them as "skipped or known issues".
The project tracks SuperwallKit's develop branch. To test a specific commit instead:
scripts/pin-superwallkit.sh <full commit SHA>This changes the project file only in your working copy; don't commit it. The SDK's release workflow uses it to test the commit it's about to release.
- Runner and app.
Automated UI Testing(XCUITest) launches the app and tells it which test to run. The app runs the scenario fromUITests_Swift.swiftorUITests_ObjC.mand asks the runner over HTTP for anything that needs XCUITest: taps, assertions, StoreKit actions.Communicator.swifthandles both sides. Ports come from the simulator's UDID, so several runs can share a Mac. - Assertions.
assert(after:)waits until the screen settles, then compares a JSON description of it (paywall identifier, loading state, visible text, alerts) withAutomated UI Testing/__Snapshots__/Screens/Test-N.K.json. These references don't depend on the device, so they work on Limrun as well as locally. - Recorded network responses. Superwall API responses and paywall pages are replayed from
Fixtures/, so tests don't depend on the dev backend or dashboard changes. Anything not recorded goes to the network. - StoreKit. Products and purchases come from
Products.storekitthrough StoreKit testing. - Generated code. The "Autogenerate functions" build phase generates
Testable.swiftandTests.swift(one test class per test number) from the test functions. Don't edit them by hand.
Tests that fail because of an SDK bug (not the test) are listed in KnownIssue in
Automated UI Testing/Automated_UI_Testing.swift. Their failures are recorded as expected, so the run
stays green and the reason appears in the results. At the moment:
- 9 and 99: a "present always" paywall shown to a subscribed user is sometimes closed straight away.
- 134 and 169 (advanced):
restorePurchases()called from code with a purchase controller doesn't sendrestoreStart.
Remove an entry once the SDK is fixed.
- A screen doesn't match its reference. The failure shows a diff. If the paywall or campaign changed on the dashboard on purpose, re-record its fixtures and references (see CONTRIBUTING.md).
- Prices like
$4.00instead of$4.99. The simulator's region uses a decimal comma, and StoreKit readsProducts.storekitwith it.runTests.shandlimrun-test.shset en_GB; check the simulator if you run tests another way. - No products, or purchases that never happen. StoreKit testing only applies to an app that has been launched on that simulator before. The runner does this once per run; a fresh simulator used outside the runner may need a launch first.
- SDK logs. Set
TEST_RUNNER_SWK_SDK_DEBUG=1for a run to turn on SuperwallKit's debug logging.
See CONTRIBUTING.md for adding tests and re-recording references.