@@ -43,6 +43,7 @@ @interface AgentDeviceRunnerFixtureViewController () <UNUserNotificationCenterDe
4343// Held for the app's lifetime: a window nobody retained would be gone before the test queried it.
4444@property (nonatomic , strong ) UIWindow *secondWindow;
4545@property (nonatomic , strong ) UILabel *navigationBackStatus;
46+ @property (nonatomic , strong ) NSTimer *animationTimer;
4647@property (nonatomic , strong ) UILabel *maskedToggleStatus;
4748@property (nonatomic , strong ) UILabel *maskedDeliveryStatus;
4849@property (nonatomic , strong ) UILabel *maskedTextLengthStatus;
@@ -205,6 +206,12 @@ - (BOOL)textFieldShouldReturn:(UITextField *)textField {
205206
206207@implementation AgentDeviceRunnerFixtureViewController
207208
209+ // `--hide-status-bar` stands in for a landscape iPhone, full-screen video, or an RN screen that hides
210+ // its status bar.
211+ - (BOOL )prefersStatusBarHidden {
212+ return [NSProcessInfo .processInfo.arguments containsObject: @" --hide-status-bar" ];
213+ }
214+
208215// An animation that never ends is what "busy" looks like to XCTest while it decides whether the app
209216// may receive an event: the app keeps reporting work in flight, which is the state that cost an alert
210217// command its whole deadline in #2546. It stops the moment an alert button is answered, since that
@@ -544,8 +551,12 @@ - (void)updateMaskedToggleStatus {
544551 [NSString stringWithFormat: @" presses=%lu " , (unsigned long )self .maskedTogglePresses];
545552}
546553
547- - (void )navigationBackPressed {
548- self.navigationBackStatus .text = @" Back performed" ;
554+ - (void )navigationBackSwiped : (UIScreenEdgePanGestureRecognizer *)recognizer {
555+ if (recognizer.state == UIGestureRecognizerStateEnded) {
556+ // A back navigation redraws most of the screen, so the fixture repaints all of it too.
557+ self.view .backgroundColor = UIColor.systemTealColor ;
558+ self.navigationBackStatus .text = @" Back performed" ;
559+ }
549560}
550561
551562- (void )agentDeviceWillAttachToWindow : (UIWindow *)window {
@@ -569,34 +580,42 @@ - (void)viewDidLoad {
569580
570581 UILabel *label = self.label ;
571582
572- // Keep the fixture behind a launch argument so normal runner snapshots remain unchanged.
583+ // Keep the fixture behind a launch argument so normal runner snapshots remain unchanged. A
584+ // left-edge pan stands in for UIKit's interactive pop: it begins only for an edge-masked touch.
573585 if ([NSProcessInfo .processInfo.arguments containsObject: @" --agent-device-back-regression" ]) {
574586 self.navigationBackStatus = label;
575587 label.accessibilityIdentifier = @" agent-device-back-status" ;
576- if ([NSProcessInfo .processInfo.arguments containsObject: @" --navigation-bar" ]) {
577- UINavigationBar *bar = [[UINavigationBar alloc ] initWithFrame: CGRectMake (0 , 62 , self .view.bounds.size.width, 44 )];
578- UINavigationItem *item = [[UINavigationItem alloc ] initWithTitle: @" Thread" ];
579- item.leftBarButtonItem = [[UIBarButtonItem alloc ] initWithTitle: @" Go back" style: UIBarButtonItemStylePlain target: self action: @selector (navigationBackPressed )];
580- bar.items = @[item];
581- [self .view addSubview: bar];
582- } else {
583- // The control must sit under the point the runner's top-leading fallback aims at, whose
584- // bounds are known: x lands at most 44 from the frame's leading edge, y at
585- // clamp(safeTopInset + 22, 56, 96). Anchoring to the view's leading (not the safe-area's)
586- // keeps the button under x=44 on a landscape iPhone, where the side inset would otherwise
587- // start a safe-area anchor past it; the safe-area top plus 12 with a 64-pt height spans the
588- // whole clamped y range for every inset an iPhone or iPad reports.
589- UIButton *back = [UIButton buttonWithType: UIButtonTypeSystem];
590- back.translatesAutoresizingMaskIntoConstraints = NO ;
591- [back setTitle: @" Go back" forState: UIControlStateNormal];
592- [back addTarget: self action: @selector (navigationBackPressed ) forControlEvents: UIControlEventTouchUpInside];
593- [self .view addSubview: back];
594- [NSLayoutConstraint activateConstraints: @[
595- [back.leadingAnchor constraintEqualToAnchor: self .view.leadingAnchor constant: 12 ],
596- [back.topAnchor constraintEqualToAnchor: self .view.safeAreaLayoutGuide.topAnchor constant: 12 ],
597- [back.widthAnchor constraintEqualToConstant: 88 ],
598- [back.heightAnchor constraintEqualToConstant: 64 ],
599- ]];
588+ UIScreenEdgePanGestureRecognizer *edgePan =
589+ [[UIScreenEdgePanGestureRecognizer alloc ] initWithTarget: self action: @selector (navigationBackSwiped: )];
590+ edgePan.edges = UIRectEdgeLeft;
591+ [self .view addGestureRecognizer: edgePan];
592+ // `--animating` keeps the whole screen changing on its own, as a launch that is still rendering:
593+ // 16 bands take independent random shades, so no two samples of the screen match.
594+ if ([NSProcessInfo .processInfo.arguments containsObject: @" --animating" ]) {
595+ NSMutableArray <UIView *> *bands = [NSMutableArray array ];
596+ CGRect bounds = self.view .bounds ;
597+ for (NSUInteger index = 0 ; index < 16 ; index++) {
598+ UIView *band = [[UIView alloc ] initWithFrame: CGRectMake (
599+ 0 , bounds.size.height * index / 16 , bounds.size.width, bounds.size.height / 16 )];
600+ band.autoresizingMask = UIViewAutoresizingFlexibleWidth;
601+ [self .view insertSubview: band atIndex: 0 ];
602+ [bands addObject: band];
603+ }
604+ [self .animationTimer invalidate ];
605+ self.animationTimer = [NSTimer scheduledTimerWithTimeInterval: 0.05 repeats: YES block: ^(NSTimer *timer) {
606+ for (UIView *band in bands) {
607+ band.backgroundColor = [UIColor colorWithWhite: arc4random_uniform (256 ) / 255.0 alpha: 1 ];
608+ }
609+ }];
610+ }
611+ // `--slow-toggle` repaints the whole screen every 0.5 s, slower than one verification delay, as
612+ // a carousel does: one quiet pair of samples must not pass it for settled.
613+ if ([NSProcessInfo .processInfo.arguments containsObject: @" --slow-toggle" ]) {
614+ __weak UIView *view = self.view ;
615+ [self .animationTimer invalidate ];
616+ self.animationTimer = [NSTimer scheduledTimerWithTimeInterval: 0.5 repeats: YES block: ^(NSTimer *timer) {
617+ view.backgroundColor = [view.backgroundColor isEqual: UIColor.blackColor] ? UIColor.whiteColor : UIColor.blackColor ;
618+ }];
600619 }
601620 }
602621
0 commit comments