Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ @interface AgentDeviceRunnerViewController : UIViewController
@property(nonatomic, assign) NSUInteger firstAlertActions;
@property(nonatomic, assign) NSUInteger replacementAlertActions;
@property(nonatomic, strong) UILabel *textEntryWriteBackStatus;
@property(nonatomic, assign) NSUInteger textEntryRenderedEdits;
@property(nonatomic, assign) NSUInteger textEntryWriteBacks;
@property(nonatomic, copy, nullable) NSString *textEntryRenderedValue;
@property(nonatomic, assign) NSTimeInterval textEntryLastEditTime;
Expand Down Expand Up @@ -187,8 +186,7 @@ - (void)updateAlertActionStatus {
- (void)updateTextEntryWriteBackStatus {
NSTimeInterval burstSpan = self.textEntryLastEditTime - self.textEntryBurstStartTime;
self.textEntryWriteBackStatus.text = [NSString
stringWithFormat:@"edits=%lu write-backs=%lu burst-edits=%lu burst-ms=%lu min-gap-ms=%lu",
(unsigned long)self.textEntryRenderedEdits,
stringWithFormat:@"write-backs=%lu burst-edits=%lu burst-ms=%lu min-gap-ms=%lu",
(unsigned long)self.textEntryWriteBacks,
(unsigned long)self.textEntryBurstEdits,
(unsigned long)llround(burstSpan * 1000),
Expand Down Expand Up @@ -292,7 +290,6 @@ - (void)agentDeviceTextEntryDidChange:(UITextField *)textField {
}
} else {
self.textEntryRenderedValue = [textField.text copy];
self.textEntryRenderedEdits += 1;
}
[self updateTextEntryWriteBackStatus];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,21 @@ typedef NS_ENUM(NSInteger, RunnerSynthesizedTextEntryStatus) {

@interface RunnerSynthesizedTextEntry : NSObject

// Characters per second the synthesized text-input records are typed at. Declared here, where the
// typing happens, so the delivery budget that bounds a burst is charged the same pace the app sees.
// The edit-acknowledge window that pace is sized for is a separate assumption about the app
// (TextEntryTiming.synthesizedAcknowledgeWindowSeconds), not a value derived from this one.
+ (NSUInteger)typingSpeedCharactersPerSecond;

// Synthesizes keyboard input for the current first responder without resolving an
// XCUIElement or serializing the application's accessibility tree.
//
// `charactersPerSecond` is XCTest's `typingSpeed:` argument. The caller declares it because the
// same number is what the caller's delivery budget charges a burst (#2955): a pace owned here and
// read back from Swift would let a call site type at a speed its own budget never charged.
+ (RunnerSynthesizedTextEntryResult *)synthesizeTextWithApplication:(id)application
text:(NSString *)text;
text:(NSString *)text
charactersPerSecond:(NSUInteger)charactersPerSecond;

// Replaces the current first responder's contents with one synthesized Command-A record
// followed by a text-input record, typed at the bounded pace declared in the implementation.
// followed by a text-input record, typed at the pace the caller declares.
+ (RunnerSynthesizedTextEntryResult *)replaceTextWithApplication:(id)application
text:(NSString *)text;
text:(NSString *)text
charactersPerSecond:(NSUInteger)charactersPerSecond;

@end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,10 @@

static NSString *const RunnerTextSynthesisSurface = @"text";

// XCTest's `typingSpeed:` argument is characters per second. At 60 the 11 characters of a `fill`
// arrived at a fixture field across 131 ms (~13 ms per gap), which is faster than an app that owns
// its field's value and re-applies it after the edit (a controlled React Native `TextInput`, an
// async validator) can acknowledge: such a write lands between two characters of the burst and
// erases what was typed while it was in flight, leaving a value that is stable short of the
// request. 12 characters/second spaces them ~83 ms apart on average, which reduces that loss but
// does not remove it: XCTest does not space the characters evenly, and two of them can reach the
// app a few milliseconds apart. Against a fixture app that acknowledges each edit within 40 ms, 60
// characters/second left 1 of 11 characters in 20 of 20 bursts, and this pace left 10 or 11. The
// command refuses a field left short; back-pressure from the field (#2906) is what would prevent
// it. The app-owned-value lane test pins the average spacing the app sees, and the delivery budget
// in TextEntryTiming bounds what the pace costs a long text.
static const NSUInteger RunnerTextEntryTypingSpeedCharactersPerSecond = 12;
// The pace (`typingSpeed:`) is a caller argument, not a constant here: TextEntryTiming owns it so
// the synthesized delivery budget charges the pace the app actually sees (#2955). XCTest's
// `typingSpeed:` is characters per second, and it spaces them unevenly — two characters of a paced
// burst can reach the app a few milliseconds apart at any pace.

typedef id (*RunnerTextMsgSendInit)(id, SEL, NSString *);
typedef id (*RunnerTextMsgSendInitPath)(id, SEL);
Expand Down Expand Up @@ -49,6 +40,7 @@
static RunnerSynthesizedTextEntryResult *RunnerSynthesizeTextWithMode(
id application,
NSString *text,
NSUInteger charactersPerSecond,
BOOL replace
);

Expand All @@ -65,25 +57,24 @@ @implementation RunnerSynthesizedTextEntryResult

@implementation RunnerSynthesizedTextEntry

+ (NSUInteger)typingSpeedCharactersPerSecond {
return RunnerTextEntryTypingSpeedCharactersPerSecond;
}

+ (RunnerSynthesizedTextEntryResult *)synthesizeTextWithApplication:(id)application
text:(NSString *)text {
return RunnerSynthesizeTextWithMode(application, text, NO);
text:(NSString *)text
charactersPerSecond:(NSUInteger)charactersPerSecond {
return RunnerSynthesizeTextWithMode(application, text, charactersPerSecond, NO);
}

+ (RunnerSynthesizedTextEntryResult *)replaceTextWithApplication:(id)application
text:(NSString *)text {
return RunnerSynthesizeTextWithMode(application, text, YES);
text:(NSString *)text
charactersPerSecond:(NSUInteger)charactersPerSecond {
return RunnerSynthesizeTextWithMode(application, text, charactersPerSecond, YES);
}

@end

static RunnerSynthesizedTextEntryResult *RunnerSynthesizeTextWithMode(
id application,
NSString *text,
NSUInteger charactersPerSecond,
BOOL replace
) {
@try {
Expand Down Expand Up @@ -151,7 +142,7 @@ + (RunnerSynthesizedTextEntryResult *)replaceTextWithApplication:(id)application
bridge.typeTextSelector,
text,
0.0,
RunnerTextEntryTypingSpeedCharactersPerSecond,
charactersPerSecond,
YES
);
((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.core.addPathSelector, path);
Expand Down
Loading
Loading