FBW3CTouchActionsIntegrationTests.m 15 KB

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