appium-mac2-driver 1.17.5 → 1.18.1

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [1.18.1](https://github.com/appium/appium-mac2-driver/compare/v1.18.0...v1.18.1) (2024-07-20)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Add a test for the keyboard input and perform related fixes ([#311](https://github.com/appium/appium-mac2-driver/issues/311)) ([af826e8](https://github.com/appium/appium-mac2-driver/commit/af826e83f60dd278f44042e2ef36b81390f3ef19))
7
+
8
+ ## [1.18.0](https://github.com/appium/appium-mac2-driver/compare/v1.17.5...v1.18.0) (2024-07-18)
9
+
10
+
11
+ ### Features
12
+
13
+ * Add support of W3C key actions ([#309](https://github.com/appium/appium-mac2-driver/issues/309)) ([f83ff9f](https://github.com/appium/appium-mac2-driver/commit/f83ff9f41c972b64b45c88bd5a66d441d307267d))
14
+
1
15
  ## [1.17.5](https://github.com/appium/appium-mac2-driver/compare/v1.17.4...v1.17.5) (2024-07-09)
2
16
 
3
17
 
@@ -261,6 +261,18 @@
261
261
  },
262
262
  ],
263
263
 
264
+ // Keys are not balanced
265
+ @[@{
266
+ @"type": @"key",
267
+ @"id": @"keyboard",
268
+ @"actions": @[
269
+ @{@"type": @"keyDown", @"value": @"n"},
270
+ @{@"type": @"keyUp", @"value": @"n"},
271
+ @{@"type": @"keyUp", @"value": @"b"},
272
+ ],
273
+ },
274
+ ],
275
+
264
276
  ];
265
277
 
266
278
  for (NSArray<NSDictionary<NSString *, id> *> *invalidGesture in invalidGestures) {
@@ -274,6 +286,7 @@
274
286
 
275
287
  - (void)testClick
276
288
  {
289
+ [self switchToButtonsTab];
277
290
  XCUIElement *checkbox = self.testedApplication.checkBoxes.firstMatch;
278
291
  NSNumber *value = checkbox.value;
279
292
 
@@ -300,6 +313,7 @@
300
313
 
301
314
  - (void)testRightClick
302
315
  {
316
+ [self switchToButtonsTab];
303
317
  XCUIElement *checkbox = self.testedApplication.checkBoxes.firstMatch;
304
318
  NSNumber *value = checkbox.value;
305
319
 
@@ -324,5 +338,36 @@
324
338
  XCTAssertTrue([checkbox.value boolValue] == [value boolValue]);
325
339
  }
326
340
 
341
+ - (void)testKeys
342
+ {
343
+ [self switchToEditsTab];
344
+ XCUIElement *edit = self.testedApplication.textFields.firstMatch;
345
+ [edit click];
346
+
347
+ NSArray<NSDictionary<NSString *, id> *> *gesture =
348
+ @[@{
349
+ @"type": @"key",
350
+ @"id": @"keyboard",
351
+ @"actions": @[
352
+ @{@"type": @"keyDown", @"value": @"\uE008"},
353
+ @{@"type": @"pause", @"duration": @500},
354
+ @{@"type": @"keyDown", @"value": @"n"},
355
+ @{@"type": @"keyUp", @"value": @"n"},
356
+ @{@"type": @"keyDown", @"value": @"b"},
357
+ @{@"type": @"keyUp", @"value": @"b"},
358
+ @{@"type": @"keyDown", @"value": @"a"},
359
+ @{@"type": @"keyUp", @"value": @"a"},
360
+ @{@"type": @"pause", @"duration": @500},
361
+ @{@"type": @"keyUp", @"value": @"\uE008"},
362
+ ],
363
+ },
364
+ ];
365
+ NSError *error;
366
+ XCTAssertTrue([self.testedApplication fb_performW3CActions:gesture
367
+ elementCache:nil
368
+ error:&error]);
369
+ XCTAssertNil(error);
370
+ XCTAssertEqualObjects(edit.value, @"NBA");
371
+ }
327
372
 
328
373
  @end
@@ -34,20 +34,22 @@ NSString *_Nullable FBRequireValue(NSDictionary<NSString *, id> *actionItem, NSE
34
34
  NSNumber *_Nullable FBOptDuration(NSDictionary<NSString *, id> *actionItem, NSNumber *_Nullable defaultValue, NSError **error);
35
35
 
36
36
  /**
37
- * Checks whether the given key action value is a W3C meta modifier
37
+ * Maps W3C meta modifier to XCTest compatible-one
38
+ * See https://w3c.github.io/webdriver/#actions
39
+ *
38
40
  * @param value key action value
39
- * @returns YES if the value is a meta modifier
41
+ * @returns the mapped modifier value or nil
40
42
  */
41
- BOOL FBIsMetaModifier(NSString *value);
43
+ NSNumber *_Nullable AMToMetaModifier(NSString *value);
42
44
 
43
45
  /**
44
- * Maps W3C meta modifier to XCTest compatible-one
46
+ * Maps W3C special key to XCTest compatible-one
47
+ * See https://w3c.github.io/webdriver/#actions
45
48
  *
46
49
  * @param value key action value
47
- * @param reverse whether to reverse the modifier value, so it cancels the previusly set one
48
- * @returns the mapped modifier value or 0 in case of failure
50
+ * @returns the mapped modifier value or nil
49
51
  */
50
- NSUInteger FBToMetaModifier(NSString *value, BOOL reverse);
52
+ NSString *_Nullable AMToSpecialKey(NSString *value);
51
53
 
52
54
  /**
53
55
  * Maps W3C button code to a XCTest compatible-one
@@ -64,39 +64,153 @@ BOOL FBIsMetaModifier(NSString *value)
64
64
  || charCode == 0xE008;
65
65
  }
66
66
 
67
- NSUInteger FBToMetaModifier(NSString *value, BOOL reverse)
67
+ NSNumber* AMToMetaModifier(NSString *value)
68
68
  {
69
- if (!FBIsMetaModifier(value)) {
70
- return 0;
69
+ if (0 == value.length) {
70
+ return nil;
71
71
  }
72
72
 
73
73
  unichar charCode = [value characterAtIndex:0];
74
- NSUInteger result = 0;
75
74
  switch (charCode) {
76
75
  case 0xE000:
77
- result = XCUIKeyModifierNone;
78
- break;
76
+ return @(XCUIKeyModifierNone);
79
77
  case 0xE03D:
80
- result = XCUIKeyModifierCommand;
81
- break;
78
+ return @(XCUIKeyModifierCommand);
82
79
  case 0xE009:
83
- result = XCUIKeyModifierControl;
84
- break;
80
+ return @(XCUIKeyModifierControl);
85
81
  case 0xE00A:
86
- result = XCUIKeyModifierOption;
87
- break;
82
+ return @(XCUIKeyModifierOption);
88
83
  case 0xE008:
89
- result = XCUIKeyModifierShift;
90
- break;
84
+ return @(XCUIKeyModifierShift);
91
85
  default:
92
- [FBLogger logFmt:@"Skipping the unsupported meta modifier with code %@", @(charCode)];
93
- result = 0;
94
- break;
86
+ return nil;
87
+ }
88
+ }
89
+
90
+ NSString* AMToSpecialKey(NSString *value)
91
+ {
92
+ if (0 == [value length]) {
93
+ return nil;
95
94
  }
96
- if (reverse) {
97
- result = ~result;
95
+
96
+ unichar charCode = [value characterAtIndex:0];
97
+ switch (charCode) {
98
+ case 0xE000:
99
+ return @"";
100
+ case 0xE002:
101
+ return XCUIKeyboardKeyHelp;
102
+ case 0xE003:
103
+ return XCUIKeyboardKeyDelete;
104
+ case 0xE004:
105
+ return XCUIKeyboardKeyTab;
106
+ case 0xE005:
107
+ return XCUIKeyboardKeyClear;
108
+ case 0xE006:
109
+ return XCUIKeyboardKeyReturn;
110
+ case 0xE007:
111
+ return XCUIKeyboardKeyEnter;
112
+ case 0xE00C:
113
+ return XCUIKeyboardKeyEscape;
114
+ case 0xE00D:
115
+ return @" ";
116
+ case 0xE00E:
117
+ case 0xE054:
118
+ return XCUIKeyboardKeyPageUp;
119
+ case 0xE00F:
120
+ case 0xE055:
121
+ return XCUIKeyboardKeyPageDown;
122
+ case 0xE010:
123
+ case 0xE056:
124
+ return XCUIKeyboardKeyEnd;
125
+ case 0xE011:
126
+ case 0xE057:
127
+ return XCUIKeyboardKeyHome;
128
+ case 0xE012:
129
+ case 0xE058:
130
+ return XCUIKeyboardKeyLeftArrow;
131
+ case 0xE013:
132
+ case 0xE059:
133
+ return XCUIKeyboardKeyUpArrow;
134
+ case 0xE014:
135
+ case 0xE05A:
136
+ return XCUIKeyboardKeyRightArrow;
137
+ case 0xE015:
138
+ case 0xE05B:
139
+ return XCUIKeyboardKeyDownArrow;
140
+ case 0xE017:
141
+ case 0xE05D:
142
+ return XCUIKeyboardKeyForwardDelete;
143
+ case 0xE018:
144
+ return @";";
145
+ case 0xE019:
146
+ return @"=";
147
+ case 0xE01A:
148
+ return @"0";
149
+ case 0xE01B:
150
+ return @"1";
151
+ case 0xE01C:
152
+ return @"2";
153
+ case 0xE01D:
154
+ return @"3";
155
+ case 0xE01E:
156
+ return @"4";
157
+ case 0xE01F:
158
+ return @"5";
159
+ case 0xE020:
160
+ return @"6";
161
+ case 0xE021:
162
+ return @"7";
163
+ case 0xE022:
164
+ return @"8";
165
+ case 0xE023:
166
+ return @"9";
167
+ case 0xE024:
168
+ return @"*";
169
+ case 0xE025:
170
+ return @"+";
171
+ case 0xE026:
172
+ return @",";
173
+ case 0xE027:
174
+ return @"-";
175
+ case 0xE028:
176
+ return @".";
177
+ case 0xE029:
178
+ return @"/";
179
+ case 0xE031:
180
+ return XCUIKeyboardKeyF1;
181
+ case 0xE032:
182
+ return XCUIKeyboardKeyF2;
183
+ case 0xE033:
184
+ return XCUIKeyboardKeyF3;
185
+ case 0xE034:
186
+ return XCUIKeyboardKeyF4;
187
+ case 0xE035:
188
+ return XCUIKeyboardKeyF5;
189
+ case 0xE036:
190
+ return XCUIKeyboardKeyF6;
191
+ case 0xE037:
192
+ return XCUIKeyboardKeyF7;
193
+ case 0xE038:
194
+ return XCUIKeyboardKeyF8;
195
+ case 0xE039:
196
+ return XCUIKeyboardKeyF9;
197
+ case 0xE03A:
198
+ return XCUIKeyboardKeyF10;
199
+ case 0xE03B:
200
+ return XCUIKeyboardKeyF11;
201
+ case 0xE03C:
202
+ return XCUIKeyboardKeyF12;
203
+ case 0xE050:
204
+ return XCUIKeyboardKeyShift;
205
+ case 0xE051:
206
+ return XCUIKeyboardKeyControl;
207
+ case 0xE052:
208
+ return XCUIKeyboardKeyOption;
209
+ case 0xE053:
210
+ return XCUIKeyboardKeyCommand;
211
+ default:
212
+ return charCode >= 0xE000 && charCode <= 0xE05D ? @"" : nil;
98
213
  }
99
- return result;
100
214
  }
101
215
 
102
216
  NSUInteger AMToXCTestButtonCode(NSUInteger w3cButtonCode)
@@ -76,6 +76,32 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
76
76
 
77
77
  @end
78
78
 
79
+ @interface FBW3CKeyItem : FBBaseActionItem
80
+
81
+ @property (nullable, readonly, nonatomic) FBW3CKeyItem *previousItem;
82
+
83
+ - (NSUInteger)calculateModifiersForChain:(NSArray *)allItems
84
+ lastItemIndex:(NSUInteger)lastItemIndex;
85
+
86
+ @end
87
+
88
+ @interface FBKeyUpItem : FBW3CKeyItem
89
+
90
+ @property (readonly, nonatomic) NSString *value;
91
+
92
+ @end
93
+
94
+ @interface FBKeyDownItem : FBW3CKeyItem
95
+
96
+ @property (readonly, nonatomic) NSString *value;
97
+
98
+ @end
99
+
100
+ @interface FBKeyPauseItem : FBW3CKeyItem
101
+
102
+ @property (readonly, nonatomic) double duration;
103
+
104
+ @end
79
105
 
80
106
 
81
107
  @implementation FBW3CGestureItem
@@ -392,6 +418,277 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
392
418
  @end
393
419
 
394
420
 
421
+ @implementation FBW3CKeyItem
422
+
423
+ - (nullable instancetype)initWithActionItem:(NSDictionary<NSString *, id> *)actionItem
424
+ application:(XCUIApplication *)application
425
+ previousItem:(nullable FBW3CKeyItem *)previousItem
426
+ offset:(double)offset
427
+ error:(NSError **)error
428
+ {
429
+ self = [super init];
430
+ if (self) {
431
+ self.actionItem = actionItem;
432
+ self.application = application;
433
+ self.offset = offset;
434
+ _previousItem = previousItem;
435
+ }
436
+ return self;
437
+ }
438
+
439
+ - (NSUInteger)calculateModifiersForChain:(NSArray *)allItems
440
+ lastItemIndex:(NSUInteger)lastItemIndex
441
+ {
442
+ NSUInteger result = 0;
443
+ for (NSUInteger index = 0; index <= lastItemIndex; index++) {
444
+ FBW3CKeyItem *item = [allItems objectAtIndex:index];
445
+ NSNumber *modifier = nil;
446
+ BOOL isKeyDown = [item isKindOfClass:FBKeyDownItem.class];
447
+ BOOL isKeyUp = !isKeyDown && [item isKindOfClass:FBKeyUpItem.class];
448
+ if (isKeyDown || isKeyUp) {
449
+ NSString *value = [item performSelector:@selector(value)];
450
+ modifier = AMToMetaModifier(value);
451
+ }
452
+ if (nil == modifier) {
453
+ continue;
454
+ }
455
+ if (isKeyDown) {
456
+ result |= [modifier unsignedIntValue];
457
+ } else {
458
+ result &= ~[modifier unsignedIntValue];
459
+ }
460
+ }
461
+ return result;
462
+ }
463
+
464
+ @end
465
+
466
+
467
+ @implementation FBKeyUpItem : FBW3CKeyItem
468
+
469
+ - (nullable instancetype)initWithActionItem:(NSDictionary<NSString *, id> *)actionItem
470
+ application:(XCUIApplication *)application
471
+ previousItem:(nullable FBW3CKeyItem *)previousItem
472
+ offset:(double)offset
473
+ error:(NSError **)error
474
+ {
475
+ self = [super initWithActionItem:actionItem
476
+ application:application
477
+ previousItem:previousItem
478
+ offset:offset
479
+ error:error];
480
+ if (self) {
481
+ NSString *value = FBRequireValue(actionItem, error);
482
+ if (nil == value) {
483
+ return nil;
484
+ }
485
+ _value = value;
486
+ }
487
+ return self;
488
+ }
489
+
490
+ + (NSString *)actionName
491
+ {
492
+ return FB_ACTION_ITEM_TYPE_KEY_UP;
493
+ }
494
+
495
+ - (BOOL)hasDownPairInItems:(NSArray *)allItems
496
+ currentItemIndex:(NSUInteger)currentItemIndex
497
+ {
498
+ NSInteger balance = 1;
499
+ for (NSInteger index = currentItemIndex - 1; index >= 0; index--) {
500
+ FBW3CKeyItem *item = [allItems objectAtIndex:index];
501
+ BOOL isKeyDown = [item isKindOfClass:FBKeyDownItem.class];
502
+ BOOL isKeyUp = !isKeyDown && [item isKindOfClass:FBKeyUpItem.class];
503
+ if (!isKeyUp && !isKeyDown) {
504
+ continue;
505
+ }
506
+
507
+ NSString *value = [item performSelector:@selector(value)];
508
+ if (isKeyDown && [value isEqualToString:self.value]) {
509
+ balance--;
510
+ }
511
+ if (isKeyUp && [value isEqualToString:self.value]) {
512
+ balance++;
513
+ }
514
+ }
515
+ return 0 == balance;
516
+ }
517
+
518
+ + (NSUInteger)defaultTypingFrequency
519
+ {
520
+ NSInteger defaultFreq = [[NSUserDefaults standardUserDefaults]
521
+ integerForKey:@"com.apple.xctest.iOSMaximumTypingFrequency"];
522
+ return defaultFreq > 0 ? defaultFreq : 60;
523
+ }
524
+
525
+ - (NSArray<XCPointerEventPath *> *)addToEventPath:(XCPointerEventPath *)eventPath
526
+ allItems:(NSArray *)allItems
527
+ currentItemIndex:(NSUInteger)currentItemIndex
528
+ error:(NSError **)error
529
+ {
530
+ if (![self hasDownPairInItems:allItems currentItemIndex:currentItemIndex]) {
531
+ NSString *description = [NSString stringWithFormat:@"Key Up action '%@' is not balanced with a preceding Key Down one in '%@'", self.value, self.actionItem];
532
+ if (error) {
533
+ *error = [[FBErrorBuilder.builder withDescription:description] build];
534
+ }
535
+ return nil;
536
+ }
537
+
538
+ NSNumber *modifier = AMToMetaModifier(self.value);
539
+ if (nil != modifier) {
540
+ return @[];
541
+ }
542
+
543
+ NSTimeInterval offsetSeconds = FBMillisToSeconds(self.offset);
544
+ XCPointerEventPath *result = nil == eventPath ? [[XCPointerEventPath alloc] initForTextInput] : eventPath;
545
+
546
+ NSString *specialKey = AMToSpecialKey(self.value);
547
+ NSUInteger modifiers = [self calculateModifiersForChain:allItems lastItemIndex:currentItemIndex];
548
+ if (nil != specialKey) {
549
+ if (specialKey.length > 0) {
550
+ [result typeKey:specialKey
551
+ modifiers:modifiers
552
+ atOffset:offsetSeconds];
553
+ }
554
+ return @[result];
555
+ }
556
+
557
+ NSUInteger len = [self.value length];
558
+ unichar buffer[len + 1];
559
+ [self.value getCharacters:buffer range:NSMakeRange(0, len)];
560
+ for (int i = 0; i < 1; i++) {
561
+ unichar charCode = buffer[i];
562
+ NSString *oneChar = [NSString stringWithFormat:@"%C", charCode];
563
+ // 0x7F is the end of the first half of the ASCII table, where (almoust) all
564
+ // chars have their own key representations
565
+ if (charCode <= 0x7F) {
566
+ [result typeKey:oneChar
567
+ modifiers:modifiers
568
+ atOffset:offsetSeconds];
569
+ } else {
570
+ // The typeText API does not respect modifiers
571
+ // while the typeKey API can only enter keys
572
+ [result typeText:oneChar
573
+ atOffset:offsetSeconds
574
+ typingSpeed:[self.class defaultTypingFrequency]
575
+ shouldRedact:NO];
576
+ }
577
+ }
578
+
579
+ return @[result];
580
+ }
581
+
582
+ @end
583
+
584
+ @implementation FBKeyDownItem : FBW3CKeyItem
585
+
586
+ - (nullable instancetype)initWithActionItem:(NSDictionary<NSString *, id> *)actionItem
587
+ application:(XCUIApplication *)application
588
+ previousItem:(nullable FBW3CKeyItem *)previousItem
589
+ offset:(double)offset
590
+ error:(NSError **)error
591
+ {
592
+ self = [super initWithActionItem:actionItem
593
+ application:application
594
+ previousItem:previousItem
595
+ offset:offset
596
+ error:error];
597
+ if (self) {
598
+ NSString *value = FBRequireValue(actionItem, error);
599
+ if (nil == value) {
600
+ return nil;
601
+ }
602
+ _value = value;
603
+ }
604
+ return self;
605
+ }
606
+
607
+ + (NSString *)actionName
608
+ {
609
+ return FB_ACTION_ITEM_TYPE_KEY_DOWN;
610
+ }
611
+
612
+ - (BOOL)hasUpPairInItems:(NSArray *)allItems
613
+ currentItemIndex:(NSUInteger)currentItemIndex
614
+ {
615
+ NSInteger balance = 1;
616
+ for (NSUInteger index = currentItemIndex + 1; index < allItems.count; index++) {
617
+ FBW3CKeyItem *item = [allItems objectAtIndex:index];
618
+ BOOL isKeyDown = [item isKindOfClass:FBKeyDownItem.class];
619
+ BOOL isKeyUp = !isKeyDown && [item isKindOfClass:FBKeyUpItem.class];
620
+ if (!isKeyUp && !isKeyDown) {
621
+ continue;
622
+ }
623
+
624
+ NSString *value = [item performSelector:@selector(value)];
625
+ if (isKeyUp && [value isEqualToString:self.value]) {
626
+ balance--;
627
+ }
628
+ if (isKeyDown && [value isEqualToString:self.value]) {
629
+ balance++;
630
+ }
631
+ }
632
+ return 0 == balance;
633
+ }
634
+
635
+ - (NSArray<XCPointerEventPath *> *)addToEventPath:(XCPointerEventPath *)eventPath
636
+ allItems:(NSArray *)allItems
637
+ currentItemIndex:(NSUInteger)currentItemIndex
638
+ error:(NSError **)error
639
+ {
640
+ if (![self hasUpPairInItems:allItems currentItemIndex:currentItemIndex]) {
641
+ NSString *description = [NSString stringWithFormat:@"Key Down action '%@' must have a closing Key Up successor in '%@'", self.value, self.actionItem];
642
+ if (error) {
643
+ *error = [[FBErrorBuilder.builder withDescription:description] build];
644
+ }
645
+ return nil;
646
+ }
647
+
648
+ return @[];
649
+ }
650
+
651
+ @end
652
+
653
+ @implementation FBKeyPauseItem
654
+
655
+ - (nullable instancetype)initWithActionItem:(NSDictionary<NSString *, id> *)actionItem
656
+ application:(XCUIApplication *)application
657
+ previousItem:(nullable FBW3CKeyItem *)previousItem
658
+ offset:(double)offset
659
+ error:(NSError **)error
660
+ {
661
+ self = [super initWithActionItem:actionItem
662
+ application:application
663
+ previousItem:previousItem
664
+ offset:offset
665
+ error:error];
666
+ if (self) {
667
+ NSNumber *duration = FBOptDuration(actionItem, nil, error);
668
+ if (nil == duration) {
669
+ return nil;
670
+ }
671
+ _duration = [duration doubleValue];
672
+ }
673
+ return self;
674
+ }
675
+
676
+ + (NSString *)actionName
677
+ {
678
+ return FB_ACTION_ITEM_TYPE_PAUSE;
679
+ }
680
+
681
+ - (NSArray<XCPointerEventPath *> *)addToEventPath:(XCPointerEventPath *)eventPath
682
+ allItems:(NSArray *)allItems
683
+ currentItemIndex:(NSUInteger)currentItemIndex
684
+ error:(NSError **)error
685
+ {
686
+ return @[];
687
+ }
688
+
689
+ @end
690
+
691
+
395
692
  @interface FBW3CGestureItemsChain : FBBaseActionItemsChain
396
693
 
397
694
  @end
@@ -474,7 +771,74 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
474
771
  return [[result reverseObjectEnumerator] allObjects];
475
772
  }
476
773
 
477
- - (nullable NSArray<XCPointerEventPath *> *)eventPathsWithGestureAction:(NSDictionary<NSString *, id> *)actionDescription forActionId:(NSString *)actionId error:(NSError **)error
774
+ - (nullable NSArray<XCPointerEventPath *> *)eventPathsWithKeyAction:(NSDictionary<NSString *, id> *)actionDescription
775
+ forActionId:(NSString *)actionId
776
+ error:(NSError **)error
777
+ {
778
+ static NSDictionary<NSString *, Class> *keyItemsMapping;
779
+ static NSArray<NSString *> *supportedActionItemTypes;
780
+ static dispatch_once_t onceToken;
781
+ dispatch_once(&onceToken, ^{
782
+ NSMutableDictionary<NSString *, Class> *itemsMapping = [NSMutableDictionary dictionary];
783
+ for (Class cls in @[FBKeyDownItem.class,
784
+ FBKeyPauseItem.class,
785
+ FBKeyUpItem.class]) {
786
+ [itemsMapping setObject:cls forKey:[cls actionName]];
787
+ }
788
+ keyItemsMapping = itemsMapping.copy;
789
+ supportedActionItemTypes = @[FB_ACTION_ITEM_TYPE_PAUSE,
790
+ FB_ACTION_ITEM_TYPE_KEY_UP,
791
+ FB_ACTION_ITEM_TYPE_KEY_DOWN];
792
+ });
793
+
794
+ NSArray<NSDictionary<NSString *, id> *> *actionItems = [actionDescription objectForKey:FB_KEY_ACTIONS];
795
+ if (nil == actionItems || 0 == actionItems.count) {
796
+ NSString *description = [NSString stringWithFormat:@"It is mandatory to have at least one item defined for each action. Action with id '%@' contains none", actionId];
797
+ if (error) {
798
+ *error = [[FBErrorBuilder.builder withDescription:description] build];
799
+ }
800
+ return nil;
801
+ }
802
+
803
+ FBW3CKeyItemsChain *chain = [[FBW3CKeyItemsChain alloc] init];
804
+ NSArray<NSDictionary<NSString *, id> *> *processedItems = [self preprocessedActionItemsWith:actionItems];
805
+ for (NSDictionary<NSString *, id> *actionItem in processedItems) {
806
+ id actionItemType = [actionItem objectForKey:FB_ACTION_ITEM_KEY_TYPE];
807
+ if (![actionItemType isKindOfClass:NSString.class]) {
808
+ NSString *description = [NSString stringWithFormat:@"The %@ property is mandatory to set for '%@' action item", FB_ACTION_ITEM_KEY_TYPE, actionItem];
809
+ if (error) {
810
+ *error = [[FBErrorBuilder.builder withDescription:description] build];
811
+ }
812
+ return nil;
813
+ }
814
+
815
+ Class keyItemClass = [keyItemsMapping objectForKey:actionItemType];
816
+ if (nil == keyItemClass) {
817
+ NSString *description = [NSString stringWithFormat:@"'%@' action item type '%@' is not supported. Only the following action item types are supported: %@", actionId, actionItemType, supportedActionItemTypes];
818
+ if (error) {
819
+ *error = [[FBErrorBuilder.builder withDescription:description] build];
820
+ }
821
+ return nil;
822
+ }
823
+
824
+ FBW3CKeyItem *keyItem = [[keyItemClass alloc] initWithActionItem:actionItem
825
+ application:self.application
826
+ previousItem:[chain.items lastObject]
827
+ offset:chain.durationOffset
828
+ error:error];
829
+ if (nil == keyItem) {
830
+ return nil;
831
+ }
832
+
833
+ [chain addItem:keyItem];
834
+ }
835
+
836
+ return [chain asEventPathsWithError:error];
837
+ }
838
+
839
+ - (nullable NSArray<XCPointerEventPath *> *)eventPathsWithGestureAction:(NSDictionary<NSString *, id> *)actionDescription
840
+ forActionId:(NSString *)actionId
841
+ error:(NSError **)error
478
842
  {
479
843
  static NSDictionary<NSString *, Class> *gestureItemsMapping;
480
844
  static NSArray<NSString *> *supportedActionItemTypes;
@@ -559,16 +923,19 @@ static NSString *const FB_KEY_ACTIONS = @"actions";
559
923
  {
560
924
  id actionType = [actionDescription objectForKey:FB_KEY_TYPE];
561
925
  if (![actionType isKindOfClass:NSString.class] ||
562
- !([actionType isEqualToString:FB_ACTION_TYPE_POINTER])) {
563
- NSString *description = [NSString stringWithFormat:@"Only actions of '%@' types are supported. '%@' is given instead for action with id '%@'",
564
- @[FB_ACTION_TYPE_POINTER], actionType, actionId];
926
+ !([actionType isEqualToString:FB_ACTION_TYPE_POINTER] || [actionType isEqualToString:FB_ACTION_TYPE_KEY])) {
927
+ NSString *description = [NSString stringWithFormat:@"Only actions of '%@' types are supported. '%@' is given instead for action with id '%@'", @[FB_ACTION_TYPE_POINTER, FB_ACTION_TYPE_KEY], actionType, actionId];
565
928
  if (error) {
566
929
  *error = [[FBErrorBuilder.builder withDescription:description] build];
567
930
  }
568
931
  return nil;
569
932
  }
570
933
 
571
- return [self eventPathsWithGestureAction:actionDescription forActionId:actionId error:error];
934
+ if ([actionType isEqualToString:FB_ACTION_TYPE_POINTER]) {
935
+ return [self eventPathsWithGestureAction:actionDescription forActionId:actionId error:error];
936
+ }
937
+
938
+ return [self eventPathsWithKeyAction:actionDescription forActionId:actionId error:error];
572
939
  }
573
940
 
574
941
  - (nullable XCSynthesizedEventRecord *)synthesizeWithError:(NSError **)error
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "appium-mac2-driver",
3
- "version": "1.17.5",
3
+ "version": "1.18.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "appium-mac2-driver",
9
- "version": "1.17.5",
9
+ "version": "1.18.1",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@appium/strongbox": "^0.x",
@@ -50,12 +50,13 @@
50
50
  }
51
51
  },
52
52
  "node_modules/@appium/logger": {
53
- "version": "1.5.0",
54
- "resolved": "https://registry.npmjs.org/@appium/logger/-/logger-1.5.0.tgz",
55
- "integrity": "sha512-wij7z6RgCxiu66jgpySQHxj0dcuGfSn7v97MBwZ9+IEiqFDrbZR1+ExrXpbmqNXkVWOqWvNFXCYROW0g5dfKuA==",
53
+ "version": "1.6.0",
54
+ "resolved": "https://registry.npmjs.org/@appium/logger/-/logger-1.6.0.tgz",
55
+ "integrity": "sha512-zUIeVUWmIDyInSHYUa4+TK6Bd/dL3kBJHVDZhAAKGsqzNLaaRESCSVmKVOYNGqZVqNe/wPKa+aRW9Z3iRTGE5Q==",
56
56
  "dependencies": {
57
57
  "console-control-strings": "1.1.0",
58
58
  "lodash": "4.17.21",
59
+ "lru-cache": "10.3.0",
59
60
  "set-blocking": "2.0.0"
60
61
  },
61
62
  "engines": {
@@ -78,9 +79,9 @@
78
79
  }
79
80
  },
80
81
  "node_modules/@appium/strongbox": {
81
- "version": "0.3.2",
82
- "resolved": "https://registry.npmjs.org/@appium/strongbox/-/strongbox-0.3.2.tgz",
83
- "integrity": "sha512-3UJi5MP+MBfDomaBtzuXtCzYJCSTpYKfM2bcFp4yl8AWpfSzZLgszOY31Gg8/u1R3LZ9irdBGYxb5wqyaQbYLw==",
82
+ "version": "0.3.3",
83
+ "resolved": "https://registry.npmjs.org/@appium/strongbox/-/strongbox-0.3.3.tgz",
84
+ "integrity": "sha512-p9muLFz7Xl+DgUZQgLTQ8HBwhkuZOrODBTADGACJbfMKT8FcOGr+guM12yCjPwucqVFa7SrM5qDUKxf4rkQDTw==",
84
85
  "dependencies": {
85
86
  "env-paths": "2.2.1",
86
87
  "slugify": "1.6.6"
@@ -91,13 +92,13 @@
91
92
  }
92
93
  },
93
94
  "node_modules/@appium/support": {
94
- "version": "5.1.1",
95
- "resolved": "https://registry.npmjs.org/@appium/support/-/support-5.1.1.tgz",
96
- "integrity": "sha512-YwludQ+V5mgGYJQjkuDJ29X0LSJehBeC5IwvaZxKdv/VKLKAbOl3QZqv3RKIGJqQInP6ZOD+doG3OVP64Y/txQ==",
95
+ "version": "5.1.2",
96
+ "resolved": "https://registry.npmjs.org/@appium/support/-/support-5.1.2.tgz",
97
+ "integrity": "sha512-KwaCiwKOLaAPLpZYWbWFnoFjUwRiFMXgtLE9a7lWC0a7fZfT59HTu78HNoDu7WvgMaiGVyDxOYjrkI+MeONKiw==",
97
98
  "dependencies": {
98
- "@appium/logger": "^1.5.0",
99
+ "@appium/logger": "^1.6.0",
99
100
  "@appium/tsconfig": "^0.3.3",
100
- "@appium/types": "^0.21.0",
101
+ "@appium/types": "^0.21.1",
101
102
  "@colors/colors": "1.6.0",
102
103
  "@types/archiver": "6.0.2",
103
104
  "@types/base64-stream": "1.0.5",
@@ -142,8 +143,8 @@
142
143
  "shell-quote": "1.8.1",
143
144
  "source-map-support": "0.5.21",
144
145
  "supports-color": "8.1.1",
145
- "teen_process": "2.1.6",
146
- "type-fest": "4.20.1",
146
+ "teen_process": "2.2.0",
147
+ "type-fest": "4.21.0",
147
148
  "uuid": "10.0.0",
148
149
  "which": "4.0.0",
149
150
  "yauzl": "3.1.3"
@@ -156,19 +157,15 @@
156
157
  "sharp": "0.33.4"
157
158
  }
158
159
  },
159
- "node_modules/@appium/support/node_modules/teen_process": {
160
- "version": "2.1.6",
161
- "resolved": "https://registry.npmjs.org/teen_process/-/teen_process-2.1.6.tgz",
162
- "integrity": "sha512-VCbxLX0EMjnq3kYS+UJBlqAIJJeNfAxDExLLX0jvWa8KyRPbVLzc+CHFwigwR8fTWIdsVOXR2f7owrSq1xtYrw==",
163
- "dependencies": {
164
- "bluebird": "^3.7.2",
165
- "lodash": "^4.17.21",
166
- "shell-quote": "^1.8.1",
167
- "source-map-support": "^0.x"
160
+ "node_modules/@appium/support/node_modules/semver": {
161
+ "version": "7.6.2",
162
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
163
+ "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
164
+ "bin": {
165
+ "semver": "bin/semver.js"
168
166
  },
169
167
  "engines": {
170
- "node": "^16.13.0 || >=18.0.0",
171
- "npm": ">=8"
168
+ "node": ">=10"
172
169
  }
173
170
  },
174
171
  "node_modules/@appium/tsconfig": {
@@ -184,16 +181,16 @@
184
181
  }
185
182
  },
186
183
  "node_modules/@appium/types": {
187
- "version": "0.21.0",
188
- "resolved": "https://registry.npmjs.org/@appium/types/-/types-0.21.0.tgz",
189
- "integrity": "sha512-Z2kx1W6oLwXTzu/aETBJ1AivdjTSWtlT5xBPckR9Nzh+JpGboBJtJsiAhmPHeEBLhEB/pqXKxKVlh9NV8eKGaw==",
184
+ "version": "0.21.1",
185
+ "resolved": "https://registry.npmjs.org/@appium/types/-/types-0.21.1.tgz",
186
+ "integrity": "sha512-xQRIWEYOe5ldItXhYiIxNT+0tt00p0g76YPgzOtFNDj73MWSuKGZfjfM3sl7RcK2wwLvVXc4aixK0QcLwN+UUA==",
190
187
  "dependencies": {
191
- "@appium/logger": "^1.5.0",
188
+ "@appium/logger": "^1.6.0",
192
189
  "@appium/schema": "^0.6.1",
193
190
  "@appium/tsconfig": "^0.3.3",
194
191
  "@types/express": "4.17.21",
195
192
  "@types/ws": "8.5.10",
196
- "type-fest": "4.20.1"
193
+ "type-fest": "4.21.0"
197
194
  },
198
195
  "engines": {
199
196
  "node": "^14.17.0 || ^16.13.0 || >=18.0.0",
@@ -531,9 +528,9 @@
531
528
  "integrity": "sha512-Q8oFIHJHr+htLrTXN2FuZfg+WXVHQRwU/hC2GpUu+Q8e3FUM9EDkS2pE3R2AO1ZGu56f479ybdMCNF1DAu8cAQ=="
532
529
  },
533
530
  "node_modules/@types/lodash": {
534
- "version": "4.17.6",
535
- "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz",
536
- "integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA=="
531
+ "version": "4.17.7",
532
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
533
+ "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA=="
537
534
  },
538
535
  "node_modules/@types/mime": {
539
536
  "version": "1.3.5",
@@ -554,9 +551,9 @@
554
551
  }
555
552
  },
556
553
  "node_modules/@types/node": {
557
- "version": "20.14.10",
558
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz",
559
- "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==",
554
+ "version": "20.14.11",
555
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz",
556
+ "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==",
560
557
  "dependencies": {
561
558
  "undici-types": "~5.26.4"
562
559
  }
@@ -691,9 +688,9 @@
691
688
  }
692
689
  },
693
690
  "node_modules/appium-xcode": {
694
- "version": "5.2.16",
695
- "resolved": "https://registry.npmjs.org/appium-xcode/-/appium-xcode-5.2.16.tgz",
696
- "integrity": "sha512-Ig+MygLJzEEDv0AEF/hWyntJUuikC/olcLdkALpZrmKkRnUgnMNkoQ7ahZK/owGrLrg2Xeol65egRwYsUR26/A==",
691
+ "version": "5.2.17",
692
+ "resolved": "https://registry.npmjs.org/appium-xcode/-/appium-xcode-5.2.17.tgz",
693
+ "integrity": "sha512-9pxQNGw5MZFJs9yfdjCFrIe+2DEaMEupMz5/DXzGffVGod315pTKpSDe2HJZaPpH/ztRtlrxmg67rccS6GzbNw==",
697
694
  "dependencies": {
698
695
  "@appium/support": "^5.0.3",
699
696
  "@types/lodash": "^4.14.192",
@@ -1435,9 +1432,9 @@
1435
1432
  "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
1436
1433
  },
1437
1434
  "node_modules/is-core-module": {
1438
- "version": "2.14.0",
1439
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz",
1440
- "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==",
1435
+ "version": "2.15.0",
1436
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz",
1437
+ "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==",
1441
1438
  "dependencies": {
1442
1439
  "hasown": "^2.0.2"
1443
1440
  },
@@ -1495,15 +1492,12 @@
1495
1492
  }
1496
1493
  },
1497
1494
  "node_modules/jackspeak": {
1498
- "version": "3.4.2",
1499
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.2.tgz",
1500
- "integrity": "sha512-qH3nOSj8q/8+Eg8LUPOq3C+6HWkpUioIjDsq1+D4zY91oZvpPttw8GwtF1nReRYKXl+1AORyFqtm2f5Q1SB6/Q==",
1495
+ "version": "3.4.3",
1496
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
1497
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
1501
1498
  "dependencies": {
1502
1499
  "@isaacs/cliui": "^8.0.2"
1503
1500
  },
1504
- "engines": {
1505
- "node": "14 >=14.21 || 16 >=16.20 || >=18"
1506
- },
1507
1501
  "funding": {
1508
1502
  "url": "https://github.com/sponsors/isaacs"
1509
1503
  },
@@ -2207,9 +2201,9 @@
2207
2201
  }
2208
2202
  },
2209
2203
  "node_modules/semver": {
2210
- "version": "7.6.2",
2211
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
2212
- "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
2204
+ "version": "7.6.3",
2205
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
2206
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
2213
2207
  "bin": {
2214
2208
  "semver": "bin/semver.js"
2215
2209
  },
@@ -2559,9 +2553,9 @@
2559
2553
  "extraneous": true
2560
2554
  },
2561
2555
  "node_modules/type-fest": {
2562
- "version": "4.20.1",
2563
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.20.1.tgz",
2564
- "integrity": "sha512-R6wDsVsoS9xYOpy8vgeBlqpdOyzJ12HNfQhC/aAKWM3YoCV9TtunJzh/QpkMgeDhkoynDcw5f1y+qF9yc/HHyg==",
2556
+ "version": "4.21.0",
2557
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.21.0.tgz",
2558
+ "integrity": "sha512-ADn2w7hVPcK6w1I0uWnM//y1rLXZhzB9mr0a3OirzclKF1Wp6VzevUmzz/NRAWunOT6E8HrnpGY7xOfc6K57fA==",
2565
2559
  "engines": {
2566
2560
  "node": ">=16"
2567
2561
  },
@@ -2717,9 +2711,9 @@
2717
2711
  "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
2718
2712
  },
2719
2713
  "node_modules/ws": {
2720
- "version": "8.17.1",
2721
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
2722
- "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
2714
+ "version": "8.18.0",
2715
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
2716
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
2723
2717
  "extraneous": true,
2724
2718
  "engines": {
2725
2719
  "node": ">=10.0.0"
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "mac",
7
7
  "XCTest"
8
8
  ],
9
- "version": "1.17.5",
9
+ "version": "1.18.1",
10
10
  "author": "Appium Contributors",
11
11
  "license": "Apache-2.0",
12
12
  "repository": {