4

我目前正在尝试使用frank(以及UISpec)为我们的新 iOS 应用程序编写一些验收测试。虽然框架支持触摸作为与视图交互的基本方式,但它目前不支持更多涉及的手势(例如,捏合、滑动等)。我至少需要添加对滑动的支持,因为这是我们应用程序功能的核心,如果没有它,我们的测试将毫无用处。

如果我能找到一种方法来模拟 Cocoa 中的事件,那么实现它应该相当简单。如果您使用 Apple 的 UIAutomation 框架(请参见此处),则可以发送滑动手势,因此有一个在外部生成这些事件的示例。我在网上四处搜索,但没有找到任何人这样做的例子(尽管之前有人要求类似的东西......)。

非常感谢您的帮助/想法...

4

1 回答 1

6

我昨天花了很多时间试图让这个工作最终得到某种解决方案。我对此并不完全满意,但这是我目前能做的最好的 - 如果有人可以提出任何改进或可行的替代方案,我会欢迎他们......

无论如何,对于其他试图做类似事情的人。我的解决方案基于这篇文章中详述的 API - 我记录了我想要模拟的事件序列,然后回放它们。唯一的问题是我无法让内置的播放 API 工作(我在底部提到的评论中遇到了同样的崩溃)。在 ASM 领域研究了一段时间后,我最终编写了自己的版本。

@implementation UIApplication (EventReplay)

///
/// - replayEventsFromFile:
///
- (void)replayEventsFromFile:(NSString *)filename 
{
  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
  NSString *filePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:filename];
  NSArray* eventList = [[NSArray arrayWithContentsOfFile:filePath] retain];
  [self replayEvents:eventList];
}

///
/// - replayEvents:
///
- (void)replayEvents:(NSArray *)events
{
  if (!events.count)
    return;

  NSDictionary *eventDict = [events objectAtIndex:0U];
  GSEventRef thisEvent = GSEventCreateWithPlist((CFDictionaryRef)eventDict);

  uint64_t eventTime = thisEvent->record.timestamp;
  thisEvent->record.timestamp = mach_absolute_time();

  mach_port_t appPort = GSCopyPurpleNamedPort([[[NSBundle mainBundle] bundleIdentifier] UTF8String]);
  GSSendEvent(&thisEvent->record, appPort);
  mach_port_deallocate(mach_task_self(), appPort); 

  if (events.count <= 1)
    return;

  NSIndexSet *remainderIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, events.count - 1)];
  NSArray *remainingEvents = [events objectsAtIndexes:remainderIndexes];

  GSEventRef nextEvent = GSEventCreateWithPlist((CFDictionaryRef)[remainingEvents objectAtIndex:0U]);
  NSTimeInterval nextEventDelay = GetTimeDelta(nextEvent->record.timestamp, eventTime);

  if (nextEventDelay > 0.05)
    [self performSelector:@selector(replayEvents:) withObject:remainingEvents afterDelay:nextEventDelay];
  else
    [self replayEvents:remainingEvents];

  CFRelease(nextEvent);
  CFRelease(thisEvent);
}

@end

上面的片段显示了我如何回放这些事件。我的实现相当笨拙——你会看到我不得不捏造这样一个事实,即如果我盲目地使用计时器来安排下一个事件,有时它不会触发——似乎是延迟太小。你看到的可怕的黑客似乎让一切正常

无论如何,希望这能以某种方式帮助别人。

于 2011-02-16T06:40:22.060 回答