@aarwitz/tapp 0.17.0-rc.4 → 0.17.0-rc.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -139,6 +139,13 @@ export function isAndroidAppSnapshot(snapshot, appId) {
139
139
  return ownsActivity || ownsElements;
140
140
  }
141
141
 
142
+ function isSystemOverlayOverApp(snapshot, appId) {
143
+ if (!snapshot || !appId || !String(snapshot.activity || "").startsWith(`${appId}/`)) return false;
144
+ const packages = new Set((snapshot.elements || []).map((element) => element.package).filter(Boolean));
145
+ return packages.size > 0 && !packages.has(appId)
146
+ && [...packages].every((name) => name === "android" || name.startsWith("com.android.systemui"));
147
+ }
148
+
142
149
  export class AndroidDriver {
143
150
  constructor({ adbPath = resolveAdbPath(), serial = "", appId = "" } = {}) {
144
151
  if (!adbPath) throw new Error("Android testing needs adb. Install Android SDK platform-tools or set ANDROID_SDK_ROOT.");
@@ -208,12 +215,32 @@ export class AndroidDriver {
208
215
  return this.waitForOwnedSnapshot();
209
216
  }
210
217
 
211
- async waitForOwnedSnapshot(timeoutMs = 4_000) {
218
+ async closeSystemDialogs() {
219
+ // A crash dialog from the previous app can outlive that process and cover a
220
+ // newly launched app. The shell broadcast only closes system-owned surfaces;
221
+ // it does not clear crash history or interact with the app under test.
222
+ await this.adb(["shell", "am", "broadcast", "-a", "android.intent.action.CLOSE_SYSTEM_DIALOGS"]);
223
+ }
224
+
225
+ async waitForOwnedSnapshot(timeoutMs = 12_000) {
212
226
  const deadline = Date.now() + timeoutMs;
213
227
  let latest;
228
+ let consecutiveOwned = 0;
229
+ let overlayDismissed = false;
214
230
  do {
215
231
  latest = await this.snapshot();
216
- if (isAndroidAppSnapshot(latest, this.appId)) return latest;
232
+ if (isAndroidAppSnapshot(latest, this.appId)) {
233
+ consecutiveOwned += 1;
234
+ // One mixed dumpsys/UIAutomator sample caused cross-run contamination.
235
+ // Require agreement twice before handing the surface to exploration.
236
+ if (consecutiveOwned >= 2) return latest;
237
+ } else {
238
+ consecutiveOwned = 0;
239
+ if (!overlayDismissed && isSystemOverlayOverApp(latest, this.appId)) {
240
+ overlayDismissed = true;
241
+ await this.closeSystemDialogs();
242
+ }
243
+ }
217
244
  if (Date.now() >= deadline) break;
218
245
  // Activity and UIAutomator are intentionally required to agree. During app
219
246
  // launch they may momentarily describe opposite sides of the transition;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aarwitz/tapp",
3
- "version": "0.17.0-rc.4",
3
+ "version": "0.17.0-rc.6",
4
4
  "mcpName": "io.github.aarwitz/tapp",
5
5
  "description": "Release contracts, autonomous QA, and evidence-backed CI gates for iOS, Android, and web.",
6
6
  "license": "MIT",