# Where a UI regression test starts matters
A UI test can pass or fail depending on what the previous run left behind. An app might still be signed in, have cached data, or open on a screen the test wasn't expecting.
Since v0.29.0, tapflow flow run resets device state before running flows. The reset isn't identical on iOS and Android, and it doesn't make the entire device clean.
What happens before a flow runs
By default, tapflow clears app data for the build under test and any other apps referenced by the flows.
On iOS, when the relay can identify the build's app, tapflow uninstalls it instead. The reset also clears:
- Keychain data
- Privacy permissions managed by
simctl privacy - Pasteboard contents
- Simulated location
These are device-wide changes. If you run a flow on a simulator someone else is using, their sign-ins can be cleared too.
Root certificates installed through simctl keychain add-root-cert remain trusted.
Android has a narrower reset. It clears data for the relevant apps and, on emulators streamed over gRPC, clears the clipboard. It doesn't perform the same device-wide reset as iOS.
The CLI prints what was reset, and the run page records it. You can skip the reset with --no-reset when you need to preserve the existing state.
Run reset vs. clearState
There are two ways to reset state in tapflow, and they serve different purposes.
The run reset happens once before the flows begin. clearState is a step you can place inside a flow to reset the app under test.
On Android, clearState uses pm clear. On iOS, it empties the app's data container. Neither removes the installed binary.
Here's a simple login flow:
name: login-smoke appId: com.example.app steps: - clearState - launchApp - assertVisible: "Sign in" - tapOn: "Sign in" - assertVisible: "Orders" The run reset prepares the device. clearState makes sure the app starts without data from an earlier session, and launchApp opens it.
The order matters. A plain launchApp doesn't restart an app that's already running. It brings the app to the foreground on its current screen.
clearState stops the app before clearing its data, so placing it before launchApp gives the flow a fresh app state.
There is one exception to the launch behavior: when launch arguments or environment variables are supplied, tapflow stops and restarts the app to apply them. This also applies when the app has launch argument defaults in tapflow.config.json.
Launching with different settings
Since v0.30.0, launchApp supports arguments and environment variables.
For example, you can select a staging host or enable a debug option without building a separate binary:
- launchApp: arguments: FIRDebugEnabled: true # Firebase DebugView, iOS only apiHost: staging.example.com env: # iOS only LOG_LEVEL: debug On iOS, arguments are passed as -key value pairs and can be read through UserDefaults. On Android, they're passed as intent extras.
Environment variables are supported only on iOS. Supplying env on Android fails the step.
You can also configure default launch values for an app using launch.<bundle id> in tapflow.config.json.
For secrets, envFrom reads values by name from the relay's environment or .env file, rather than storing them directly in the configuration.
Be careful with those values. Anyone who can upload a build under the same bundle ID can receive them. Only configure secrets that every uploader is allowed to access.
On Android, environment variable defaults are skipped with a warning in the relay logs.
Run records include argument values but mask environment variable values. Both the relay and agent must support v0.30.0 launch options; otherwise, the step fails.
Assertions shouldn't guess
Once the app is in the expected state, the flow still needs to check the right elements.
assertVisible and assertNotVisible wait for their conditions rather than relying on fixed delays.
If tapOn matches multiple elements, the runner fails and lists the candidates instead of choosing the first match.
I covered selector behavior in Episode 7: A UI test that guesses is worse than one that fails.
Those checks are useful, but they can't fix a test that started with unexpected app data.
What the reset doesn't cover
The reset isn't a guarantee of a completely clean device.
iOS clears more shared state, while Android primarily clears the relevant apps' data. A passing test on one platform doesn't mean both started from identical conditions.
Version compatibility also matters.
The run reset requires a v0.29.0 or newer agent and relay. If either is older, the CLI reports that the device wasn't reset and continues running the flows.
Check the reset output when reviewing a test result, especially if you're relying on a fresh app state.
For parallel runs, the relay selects devices that aren't being used by someone else. Each selected device is reset unless --no-reset is specified.
Single-device runs work differently. They use the device you specify, or the only booted device if none is specified. That device might already be in use, so be careful with the iOS-wide reset.
Try it
Install tapflow and run a flow:
npm install -g tapflow tapflow flow run .tapflow/flows/login-smoke.yaml To keep the existing device state:
tapflow flow run .tapflow/flows/login-smoke.yaml --no-reset The reset won't make every device identical, but it gives each run a more predictable starting point and tells you what was cleared.
Links
原文:https://www.a8q9.cn/en/article/where-a-ui-regression-test-starts-matters-c9e470.html