chromium/ios/testing/system_alert_handler.mm

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/testing/system_alert_handler.h"

#import <XCTest/XCTest.h>

#import "base/test/ios/wait_util.h"

using base::test::ios::kWaitForUIElementTimeout;

namespace {

// Returns true if any static text child of `element` (XCUIElement*) contains
// `text` (NSString*).
BOOL ElementStaticTextContainsText(XCUIElement* element, NSString* text) {
  NSPredicate* label_contains_text =
      [NSPredicate predicateWithFormat:@"%K CONTAINS[c] %@", @"label", text];
  return
      [element.staticTexts containingPredicate:label_contains_text].count > 0;
}

// Closes the `alert` (XCUIElement*) if it matches a known text/button pair and
// returns whether it succeeded or not in closing it.
BOOL HandleSingleAlert(XCUIElement* alert) {
  NSDictionary<NSString*, NSArray<NSString*>*>* text_to_buttons =
      TextToButtonsOfKnownSystemAlerts();
  for (NSString* text in text_to_buttons) {
    if (ElementStaticTextContainsText(alert, text)) {
      NSLog(@"Found alert containing text: %@", text);

      for (NSString* button in text_to_buttons[text]) {
        if (!alert.buttons[button].exists) {
          NSLog(@"Button %@ doesn't exist. Skip tapping.", button);
          continue;
        }

        NSLog(@"Tapping alert button: %@", button);
        [alert.buttons[button] tap];
        return YES;
      }
    }
  }
  return NO;
}

}  // namespace

NSDictionary<NSString*, NSArray<NSString*>*>* TextToButtonsOfKnownSystemAlerts(
    void) {
  static NSDictionary<NSString*, NSArray<NSString*>*>* text_to_buttons = nil;
  static dispatch_once_t once_token;
  dispatch_once(&once_token, ^{
    text_to_buttons = @{
      @"Software Update" : @[ @"Later", @"Remind Me Later" ],
      @"A new iOS update is now available." : @[ @"Close" ],
      @"Carrier Settings Update" : @[ @"Not Now" ],
      @"would like to find and connect to devices on your local network" :
          @[ @"OK", @"Allow" ],
      @"Unable to activate Touch ID on this iPhone." : @[ @"OK" ],
      @"Like to Access the Microphone" : @[ @"OK" ],
      @"Edit Home Screen" : @[ @"Dismiss" ],
      @"Apple ID Verification" : @[ @"Not Now" ],
      @"iPhone is not Activated" : @[ @"Dismiss" ],
      @"Apple Account Verification" : @[ @"Not Now" ],
      @"to find devices on local networks" : @[ @"Allow" ],
    };
  });
  return text_to_buttons;
}

BOOL HandleKnownSystemAlertsIfVisible(void) {
  XCUIApplication* springboard_app = [[XCUIApplication alloc]
      initWithBundleIdentifier:@"com.apple.springboard"];
  XCUIElement* alert = springboard_app.alerts.firstMatch;

  // Limit attempt times. If attempt limit is exceeded, it means something went
  // wrong in tapping the buttons.
  int attempt = 0;
  while (
      attempt < 5 &&
      [alert
          waitForExistenceWithTimeout:kWaitForUIElementTimeout.InSecondsF()]) {
    NSLog(@"Alert on screen: %@", alert.label);
    if (!HandleSingleAlert(alert)) {
      return NO;
    }
    attempt++;
  }
  return attempt < 5;
}