FBW3CTouchActionsIntegrationTests.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. /**
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. */
  9. #import <XCTest/XCTest.h>
  10. #import "FBIntegrationTestCase.h"
  11. #import "XCUIElement.h"
  12. #import "XCUIDevice.h"
  13. #import "XCUIApplication+FBTouchAction.h"
  14. #import "FBTestMacros.h"
  15. #import "XCUIDevice+FBRotation.h"
  16. #import "FBRunLoopSpinner.h"
  17. #import "FBXCodeCompatibility.h"
  18. @interface FBW3CTouchActionsIntegrationTestsPart1 : FBIntegrationTestCase
  19. @end
  20. @interface FBW3CTouchActionsIntegrationTestsPart2 : FBIntegrationTestCase
  21. @property (nonatomic) XCUIElement *pickerWheel;
  22. @end
  23. @implementation FBW3CTouchActionsIntegrationTestsPart1
  24. - (void)verifyGesture:(NSArray<NSDictionary<NSString *, id> *> *)gesture orientation:(UIDeviceOrientation)orientation
  25. {
  26. [[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:orientation];
  27. NSError *error;
  28. XCTAssertTrue([self.testedApplication fb_performW3CActions:gesture elementCache:nil error:&error]);
  29. FBAssertWaitTillBecomesTrue(self.testedApplication.alerts.count > 0);
  30. }
  31. - (void)setUp
  32. {
  33. [super setUp];
  34. static dispatch_once_t onceToken;
  35. dispatch_once(&onceToken, ^{
  36. [self launchApplication];
  37. [self goToAlertsPage];
  38. });
  39. [self clearAlert];
  40. }
  41. - (void)tearDown
  42. {
  43. [self clearAlert];
  44. [self resetOrientation];
  45. [super tearDown];
  46. }
  47. - (void)testErroneousGestures
  48. {
  49. NSArray<NSArray<NSDictionary<NSString *, id> *> *> *invalidGestures =
  50. @[
  51. // Empty chain
  52. @[],
  53. // Chain element without 'actions' key
  54. @[@{
  55. @"type": @"pointer",
  56. @"id": @"finger1",
  57. @"parameters": @{@"pointerType": @"touch"},
  58. },
  59. ],
  60. // Chain element with empty 'actions'
  61. @[@{
  62. @"type": @"pointer",
  63. @"id": @"finger1",
  64. @"parameters": @{@"pointerType": @"touch"},
  65. @"actions": @[],
  66. },
  67. ],
  68. // Chain element without type
  69. @[@{
  70. @"id": @"finger1",
  71. @"parameters": @{@"pointerType": @"touch"},
  72. @"actions": @[
  73. @{@"type": @"pointerMove", @"duration": @0, @"x": @100, @"y": @100},
  74. ],
  75. },
  76. ],
  77. // Chain element without id
  78. @[@{
  79. @"type": @"pointer",
  80. @"parameters": @{@"pointerType": @"touch"},
  81. @"actions": @[
  82. @{@"type": @"pointerMove", @"duration": @0, @"x": @100, @"y": @100},
  83. ],
  84. },
  85. ],
  86. // Chain element with empty id
  87. @[@{
  88. @"type": @"pointer",
  89. @"id": @"",
  90. @"parameters": @{@"pointerType": @"touch"},
  91. @"actions": @[
  92. @{@"type": @"pointerMove", @"duration": @0, @"x": @100, @"y": @100},
  93. ],
  94. },
  95. ],
  96. // Chain element with unsupported type
  97. @[@{
  98. @"type": @"key",
  99. @"id": @"finger1",
  100. @"parameters": @{@"pointerType": @"touch"},
  101. @"actions": @[
  102. @{@"type": @"pointerMove", @"duration": @0, @"x": @100, @"y": @100},
  103. ],
  104. },
  105. ],
  106. // Chain element with unsupported pointerType (default)
  107. @[@{
  108. @"type": @"pointer",
  109. @"id": @"finger1",
  110. @"actions": @[
  111. @{@"type": @"pointerMove", @"duration": @0, @"x": @100, @"y": @100},
  112. ],
  113. },
  114. ],
  115. // Chain element with unsupported pointerType (non-default)
  116. @[@{
  117. @"type": @"pointer",
  118. @"id": @"finger1",
  119. @"parameters": @{@"pointerType": @"pen"},
  120. @"actions": @[
  121. @{@"type": @"pointerMove", @"duration": @0, @"x": @100, @"y": @100},
  122. ],
  123. },
  124. ],
  125. // Chain element without action item type
  126. @[@{
  127. @"type": @"pointer",
  128. @"id": @"finger1",
  129. @"parameters": @{@"pointerType": @"touch"},
  130. @"actions": @[
  131. @{@"duration": @0, @"x": @1, @"y": @1},
  132. @{@"type": @"pointerDown"},
  133. @{@"type": @"pause", @"duration": @100},
  134. @{@"type": @"pointerUp"},
  135. ],
  136. },
  137. ],
  138. // Chain element with singe up action
  139. @[@{
  140. @"type": @"pointer",
  141. @"id": @"finger1",
  142. @"parameters": @{@"pointerType": @"touch"},
  143. @"actions": @[
  144. @{@"type": @"pointerUp"},
  145. ],
  146. },
  147. ],
  148. // Chain element containing action item without y coordinate
  149. @[@{
  150. @"type": @"pointer",
  151. @"id": @"finger1",
  152. @"parameters": @{@"pointerType": @"touch"},
  153. @"actions": @[
  154. @{@"type": @"pointerMove", @"duration": @0, @"x": @1},
  155. @{@"type": @"pointerDown"},
  156. @{@"type": @"pause", @"duration": @100},
  157. @{@"type": @"pointerUp"},
  158. ],
  159. },
  160. ],
  161. // Chain element containing action item with an unknown type
  162. @[@{
  163. @"type": @"pointer",
  164. @"id": @"finger1",
  165. @"parameters": @{@"pointerType": @"touch"},
  166. @"actions": @[
  167. @{@"type": @"pointerMoved", @"duration": @0, @"x": @1, @"y": @1},
  168. @{@"type": @"pointerDown"},
  169. @{@"type": @"pause", @"duration": @100},
  170. @{@"type": @"pointerUp"},
  171. ],
  172. },
  173. ],
  174. // Chain element where action items start with an incorrect item
  175. @[@{
  176. @"type": @"pointer",
  177. @"id": @"finger1",
  178. @"parameters": @{@"pointerType": @"touch"},
  179. @"actions": @[
  180. @{@"type": @"pause", @"duration": @100},
  181. @{@"type": @"pointerMove", @"duration": @0, @"x": @1, @"y": @1},
  182. @{@"type": @"pointerDown"},
  183. @{@"type": @"pause", @"duration": @100},
  184. @{@"type": @"pointerUp"},
  185. ],
  186. },
  187. ],
  188. // Chain element where pointerMove action item does not contain coordinates
  189. @[@{
  190. @"type": @"pointer",
  191. @"id": @"finger1",
  192. @"parameters": @{@"pointerType": @"touch"},
  193. @"actions": @[
  194. @{@"type": @"pointerMove", @"duration": @0},
  195. @{@"type": @"pointerDown"},
  196. @{@"type": @"pause", @"duration": @100},
  197. @{@"type": @"pointerUp"},
  198. ],
  199. },
  200. ],
  201. // Chain element where pointerMove action item cannot use coordinates of the previous item
  202. @[@{
  203. @"type": @"pointer",
  204. @"id": @"finger1",
  205. @"parameters": @{@"pointerType": @"touch"},
  206. @"actions": @[
  207. @{@"type": @"pointerMove", @"duration": @0, @"origin": @"pointer"},
  208. @{@"type": @"pointerDown"},
  209. @{@"type": @"pause", @"duration": @100},
  210. @{@"type": @"pointerUp"},
  211. ],
  212. },
  213. ],
  214. // Chain element where action items contains negative duration
  215. @[@{
  216. @"type": @"pointer",
  217. @"id": @"finger1",
  218. @"parameters": @{@"pointerType": @"touch"},
  219. @"actions": @[
  220. @{@"type": @"pointerMove", @"duration": @0, @"x": @1, @"y": @1},
  221. @{@"type": @"pointerDown"},
  222. @{@"type": @"pause", @"duration": @-100},
  223. @{@"type": @"pointerUp"},
  224. ],
  225. },
  226. ],
  227. // Chain element where action items start with an incorrect one, because the correct one is canceled
  228. @[@{
  229. @"type": @"pointer",
  230. @"id": @"finger1",
  231. @"parameters": @{@"pointerType": @"touch"},
  232. @"actions": @[
  233. @{@"type": @"pointerMove", @"duration": @0, @"x": @1, @"y": @1},
  234. @{@"type": @"pointerCancel"},
  235. @{@"type": @"pointerDown"},
  236. @{@"type": @"pause", @"duration": @-100},
  237. @{@"type": @"pointerUp"},
  238. ],
  239. },
  240. ],
  241. ];
  242. for (NSArray<NSDictionary<NSString *, id> *> *invalidGesture in invalidGestures) {
  243. NSError *error;
  244. XCTAssertFalse([self.testedApplication fb_performW3CActions:invalidGesture elementCache:nil error:&error]);
  245. XCTAssertNotNil(error);
  246. }
  247. }
  248. - (void)testTap
  249. {
  250. NSArray<NSDictionary<NSString *, id> *> *gesture =
  251. @[@{
  252. @"type": @"pointer",
  253. @"id": @"finger1",
  254. @"parameters": @{@"pointerType": @"touch"},
  255. @"actions": @[
  256. @{@"type": @"pointerMove", @"duration": @0, @"origin": self.testedApplication.buttons[FBShowAlertButtonName], @"x": @0, @"y": @0},
  257. @{@"type": @"pointerDown"},
  258. @{@"type": @"pause", @"duration": @100},
  259. @{@"type": @"pointerUp"},
  260. ],
  261. },
  262. ];
  263. [self verifyGesture:gesture orientation:UIDeviceOrientationPortrait];
  264. }
  265. - (void)testDoubleTap
  266. {
  267. NSArray<NSDictionary<NSString *, id> *> *gesture =
  268. @[@{
  269. @"type": @"pointer",
  270. @"id": @"finger1",
  271. @"parameters": @{@"pointerType": @"touch"},
  272. @"actions": @[
  273. @{@"type": @"pointerMove", @"duration": @0, @"origin": self.testedApplication.buttons[FBShowAlertButtonName]},
  274. @{@"type": @"pointerDown"},
  275. @{@"type": @"pause", @"duration": @50},
  276. @{@"type": @"pointerUp"},
  277. @{@"type": @"pause", @"duration": @200},
  278. @{@"type": @"pointerDown"},
  279. @{@"type": @"pause", @"duration": @50},
  280. @{@"type": @"pointerUp"},
  281. ],
  282. },
  283. ];
  284. [self verifyGesture:gesture orientation:UIDeviceOrientationLandscapeLeft];
  285. }
  286. - (void)testLongPressWithCombinedPause
  287. {
  288. NSArray<NSDictionary<NSString *, id> *> *gesture =
  289. @[@{
  290. @"type": @"pointer",
  291. @"id": @"finger1",
  292. @"parameters": @{@"pointerType": @"touch"},
  293. @"actions": @[
  294. @{@"type": @"pointerMove", @"duration": @0, @"origin": self.testedApplication.buttons[FBShowAlertButtonName], @"x": @5, @"y": @5},
  295. @{@"type": @"pointerDown"},
  296. @{@"type": @"pause", @"duration": @200},
  297. @{@"type": @"pause", @"duration": @200},
  298. @{@"type": @"pause", @"duration": @100},
  299. @{@"type": @"pointerUp"},
  300. ],
  301. },
  302. ];
  303. [self verifyGesture:gesture orientation:UIDeviceOrientationLandscapeRight];
  304. }
  305. - (void)testLongPress
  306. {
  307. if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad) {
  308. XCTSkip(@"Failed on Azure Pipeline. Local run succeeded.");
  309. }
  310. UIDeviceOrientation orientation = UIDeviceOrientationLandscapeLeft;
  311. [[XCUIDevice sharedDevice] fb_setDeviceInterfaceOrientation:orientation];
  312. CGRect elementFrame = self.testedApplication.buttons[FBShowAlertButtonName].frame;
  313. NSArray<NSDictionary<NSString *, id> *> *gesture =
  314. @[@{
  315. @"type": @"pointer",
  316. @"id": @"finger1",
  317. @"parameters": @{@"pointerType": @"touch"},
  318. @"actions": @[
  319. @{@"type": @"pointerMove", @"duration": @0, @"x": @(elementFrame.origin.x + 1), @"y": @(elementFrame.origin.y + 1)},
  320. @{@"type": @"pointerDown"},
  321. @{@"type": @"pause", @"duration": @500},
  322. @{@"type": @"pointerUp"},
  323. ],
  324. },
  325. ];
  326. [self verifyGesture:gesture orientation:orientation];
  327. }
  328. - (void)testForceTap
  329. {
  330. if (![XCUIDevice.sharedDevice supportsPressureInteraction]) {
  331. return;
  332. }
  333. NSArray<NSDictionary<NSString *, id> *> *gesture =
  334. @[@{
  335. @"type": @"pointer",
  336. @"id": @"finger1",
  337. @"parameters": @{@"pointerType": @"touch"},
  338. @"actions": @[
  339. @{@"type": @"pointerMove", @"duration": @0, @"origin": self.testedApplication.buttons[FBShowAlertButtonName]},
  340. @{@"type": @"pointerDown"},
  341. @{@"type": @"pause", @"duration": @500},
  342. @{@"type": @"pointerDown", @"pressure": @1.0},
  343. @{@"type": @"pause", @"duration": @50},
  344. @{@"type": @"pointerDown", @"pressure": @1.0},
  345. @{@"type": @"pause", @"duration": @50},
  346. @{@"type": @"pointerUp"},
  347. ],
  348. },
  349. ];
  350. [self verifyGesture:gesture orientation:UIDeviceOrientationLandscapeLeft];
  351. }
  352. @end
  353. @implementation FBW3CTouchActionsIntegrationTestsPart2
  354. - (void)setUp
  355. {
  356. [super setUp];
  357. static dispatch_once_t onceToken;
  358. dispatch_once(&onceToken, ^{
  359. [self launchApplication];
  360. [self goToAttributesPage];
  361. });
  362. self.pickerWheel = self.testedApplication.pickerWheels.allElementsBoundByIndex.firstObject;
  363. }
  364. - (void)tearDown
  365. {
  366. [self resetOrientation];
  367. [super tearDown];
  368. }
  369. - (void)verifyPickerWheelPositionChangeWithGesture:(NSArray<NSDictionary<NSString *, id> *> *)gesture
  370. {
  371. NSString *previousValue = self.pickerWheel.value;
  372. NSError *error;
  373. XCTAssertTrue([self.testedApplication fb_performW3CActions:gesture elementCache:nil error:&error]);
  374. XCTAssertNil(error);
  375. XCTAssertTrue([[[[FBRunLoopSpinner new]
  376. timeout:2.0]
  377. timeoutErrorMessage:@"Picker wheel value has not been changed after 2 seconds timeout"]
  378. spinUntilTrue:^BOOL{
  379. return ![self.pickerWheel.fb_takeSnapshot.value isEqualToString:previousValue];
  380. }
  381. error:&error]);
  382. XCTAssertNil(error);
  383. }
  384. - (void)testSwipePickerWheelWithElementCoordinates
  385. {
  386. CGRect pickerFrame = self.pickerWheel.frame;
  387. NSArray<NSDictionary<NSString *, id> *> *gesture =
  388. @[@{
  389. @"type": @"pointer",
  390. @"id": @"finger1",
  391. @"parameters": @{@"pointerType": @"touch"},
  392. @"actions": @[
  393. @{@"type": @"pointerMove", @"duration": @0, @"origin": self.pickerWheel, @"x": @0, @"y":@0},
  394. @{@"type": @"pointerDown"},
  395. @{@"type": @"pointerMove", @"duration": @500, @"origin": self.pickerWheel, @"x": @0, @"y": @(pickerFrame.size.height / 2)},
  396. @{@"type": @"pointerUp"},
  397. ],
  398. },
  399. ];
  400. [self verifyPickerWheelPositionChangeWithGesture:gesture];
  401. }
  402. - (void)testSwipePickerWheelWithRelativeCoordinates
  403. {
  404. CGRect pickerFrame = self.pickerWheel.frame;
  405. NSArray<NSDictionary<NSString *, id> *> *gesture =
  406. @[@{
  407. @"type": @"pointer",
  408. @"id": @"finger1",
  409. @"parameters": @{@"pointerType": @"touch"},
  410. @"actions": @[
  411. @{@"type": @"pointerMove", @"duration": @250, @"origin": self.pickerWheel, @"x": @0, @"y": @0},
  412. @{@"type": @"pointerDown"},
  413. @{@"type": @"pointerMove", @"duration": @500, @"origin": @"pointer", @"x": @0, @"y": @(-pickerFrame.size.height / 2)},
  414. @{@"type": @"pointerUp"},
  415. ],
  416. },
  417. ];
  418. [self verifyPickerWheelPositionChangeWithGesture:gesture];
  419. }
  420. - (void)testSwipePickerWheelWithAbsoluteCoordinates
  421. {
  422. CGRect pickerFrame = self.pickerWheel.frame;
  423. NSArray<NSDictionary<NSString *, id> *> *gesture =
  424. @[@{
  425. @"type": @"pointer",
  426. @"id": @"finger1",
  427. @"parameters": @{@"pointerType": @"touch"},
  428. @"actions": @[
  429. @{@"type": @"pointerMove", @"duration": @0, @"x": @(pickerFrame.origin.x + pickerFrame.size.width / 2), @"y": @(pickerFrame.origin.y + pickerFrame.size.height / 2)},
  430. @{@"type": @"pointerDown"},
  431. @{@"type": @"pointerMove", @"duration": @500, @"origin": @"pointer", @"x": @0, @"y": @(pickerFrame.size.height / 2)},
  432. @{@"type": @"pointerUp"},
  433. ],
  434. },
  435. ];
  436. [self verifyPickerWheelPositionChangeWithGesture:gesture];
  437. }
  438. @end