【iOS自动化测试】通过NSXMLParser解析XML&获取XPath

399 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情


本文以iPhone自带的计算器App为例,探究如何解析页面的XML,并获取控件的XPath~

计算器App页面、对应的XML字符串

iOS计算器

// 描述iPhone计算器页面的xml
<?xml version="1.0" encoding="UTF-8"?>
<XCUIElementTypeApplication type="XCUIElementTypeApplication" name="计算器" label="计算器" enabled="true" visible="true" x="0" y="0" width="414" height="736">
    <XCUIElementTypeWindow type="XCUIElementTypeWindow" enabled="true" visible="true" x="0" y="0" width="414" height="736">
        <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="true" x="0" y="0" width="414" height="736">
            <XCUIElementTypeStaticText type="XCUIElementTypeStaticText" value="3333" name="结果" label="结果" enabled="true" visible="true" x="0" y="20" width="414" height="219"/>
            <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="true" x="0" y="239" width="414" height="497">
                <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="true" x="0" y="239" width="414" height="497">
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="清除" label="清除" enabled="true" visible="true" x="16" y="239" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="正数负数" label="正数负数" enabled="true" visible="true" x="115" y="239" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="百分比" label="百分比" enabled="true" visible="true" x="214" y="239" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="除" label="除" enabled="true" visible="true" x="313" y="239" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="7" label="7" enabled="true" visible="true" x="16" y="338" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="8" label="8" enabled="true" visible="true" x="115" y="338" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="9" label="9" enabled="true" visible="true" x="214" y="338" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="乘" label="乘" enabled="true" visible="true" x="313" y="338" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="4" label="4" enabled="true" visible="true" x="16" y="437" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="5" label="5" enabled="true" visible="true" x="115" y="437" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="6" label="6" enabled="true" visible="true" x="214" y="437" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="减" label="减" enabled="true" visible="true" x="313" y="437" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="1" label="1" enabled="true" visible="true" x="16" y="536" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="2" label="2" enabled="true" visible="true" x="115" y="536" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="3" label="3" enabled="true" visible="true" x="214" y="536" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="加" label="加" enabled="true" visible="true" x="313" y="536" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="0" label="0" enabled="true" visible="true" x="16" y="635" width="184" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="小数点" label="小数点" enabled="true" visible="true" x="214" y="635" width="85" height="85"/>
                    <XCUIElementTypeButton type="XCUIElementTypeButton" name="等于" label="等于" enabled="true" visible="true" x="313" y="635" width="85" height="85"/>
                </XCUIElementTypeOther>
            </XCUIElementTypeOther>
        </XCUIElementTypeOther>
    </XCUIElementTypeWindow>
    <XCUIElementTypeWindow type="XCUIElementTypeWindow" enabled="true" visible="false" x="0" y="0" width="414" height="736">
        <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="false" x="0" y="0" width="414" height="736">
            <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="false" x="0" y="736" width="414" height="226"/></XCUIElementTypeOther>
    </XCUIElementTypeWindow>
    <XCUIElementTypeWindow type="XCUIElementTypeWindow" enabled="true" visible="true" x="0" y="0" width="414" height="736">
        <XCUIElementTypeStatusBar type="XCUIElementTypeStatusBar" enabled="true" visible="true" x="0" y="0" width="414" height="20">
            <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="false" x="0" y="0" width="414" height="20"/>
            <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="true" x="0" y="0" width="414" height="20">
                <XCUIElementTypeOther type="XCUIElementTypeOther" name="信号强度:4(共 4 格)" label="信号强度:4(共 4 格)" enabled="true" visible="true" x="6" y="0" width="16" height="20"/>
                <XCUIElementTypeOther type="XCUIElementTypeOther" value="SSID" name="3 格(共 3 格无线局域网信号)" label="3 格(共 3 格无线局域网信号)" enabled="true" visible="true" x="25" y="0" width="14" height="20"/>
                <XCUIElementTypeOther type="XCUIElementTypeOther" name="09:41" label="09:41" enabled="true" visible="true" x="192" y="0" width="34" height="20"/>
                <XCUIElementTypeOther type="XCUIElementTypeOther" name="方向已锁定" label="方向已锁定" enabled="true" visible="true" x="306" y="0" width="12" height="20"/>
                <XCUIElementTypeOther type="XCUIElementTypeOther" name="闹钟" label="闹钟" enabled="true" visible="true" x="324" y="0" width="10" height="20"/>
                <XCUIElementTypeOther type="XCUIElementTypeOther" name="电池电量:100%, 使用交流电源" label="电池电量:100%, 使用交流电源" enabled="true" visible="true" x="340" y="0" width="69" height="20"/>
                <XCUIElementTypeOther type="XCUIElementTypeOther" name="电池电量:100%" label="电池电量:100%" enabled="true" visible="false" x="340" y="0" width="31" height="20"/></XCUIElementTypeOther>
        </XCUIElementTypeStatusBar>
    </XCUIElementTypeWindow>
</XCUIElementTypeApplication>

封装好的API

支持以下功能:

  • 获取当前页面全部控件的数组
  • 查找指定的控件(例如计算器的某个按钮)
  • 获取特定控件的值(例如计算器结果框的值)
  • 执行相应的测试逻辑
//
//  ViewController.m
//  XML解析
//
//  Created by YPF on 2018/8/16.
//  Copyright © 2018年 原鹏飞. All rights reserved.
//

#import "ViewController.h"

@interface ViewController() <NSXMLParserDelegate>

@property (nonatomic, assign) NSInteger x;
@property (nonatomic, assign) NSInteger y;

@property (nonatomic, copy) NSString *xmlStr;
@property (nonatomic, copy) NSString *xpath;
@property (nonatomic, strong) NSMutableArray *pathArrM;
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *qualifiedElementArrM;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view.
    
    _xmlStr = @"<?xml version="1.0" encoding="UTF-8"?>\n<XCUIElementTypeApplication type="XCUIElementTypeApplication" name="计算器" label="计算器" enabled="true" visible="true" x="0" y="0" width="414" height="736">\n  <XCUIElementTypeWindow type="XCUIElementTypeWindow" enabled="true" visible="true" x="0" y="0" width="414" height="736">\n    <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="true" x="0" y="0" width="414" height="736">\n      <XCUIElementTypeStaticText type="XCUIElementTypeStaticText" value="77777" name="结果" label="结果" enabled="true" visible="true" x="0" y="20" width="414" height="219"/>\n      <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="true" x="0" y="239" width="414" height="497">\n        <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="true" x="0" y="239" width="414" height="497">\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="清除" label="清除" enabled="true" visible="true" x="16" y="239" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="正数负数" label="正数负数" enabled="true" visible="true" x="115" y="239" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="百分比" label="百分比" enabled="true" visible="true" x="214" y="239" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="" label="" enabled="true" visible="true" x="313" y="239" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="7" label="7" enabled="true" visible="true" x="16" y="338" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="8" label="8" enabled="true" visible="true" x="115" y="338" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="9" label="9" enabled="true" visible="true" x="214" y="338" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="" label="" enabled="true" visible="true" x="313" y="338" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="4" label="4" enabled="true" visible="true" x="16" y="437" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="5" label="5" enabled="true" visible="true" x="115" y="437" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="6" label="6" enabled="true" visible="true" x="214" y="437" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="" label="" enabled="true" visible="true" x="313" y="437" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="1" label="1" enabled="true" visible="true" x="16" y="536" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="2" label="2" enabled="true" visible="true" x="115" y="536" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="3" label="3" enabled="true" visible="true" x="214" y="536" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="" label="" enabled="true" visible="true" x="313" y="536" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="0" label="0" enabled="true" visible="true" x="16" y="635" width="184" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="小数点" label="小数点" enabled="true" visible="true" x="214" y="635" width="85" height="85"/>\n          <XCUIElementTypeButton type="XCUIElementTypeButton" name="等于" label="等于" enabled="true" visible="true" x="313" y="635" width="85" height="85"/>\n        </XCUIElementTypeOther>\n      </XCUIElementTypeOther>\n    </XCUIElementTypeOther>\n  </XCUIElementTypeWindow>\n  <XCUIElementTypeWindow type="XCUIElementTypeWindow" enabled="true" visible="false" x="0" y="0" width="414" height="736">\n    <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="false" x="0" y="0" width="414" height="736">\n      <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="false" x="0" y="736" width="414" height="226"/>\n    </XCUIElementTypeOther>\n  </XCUIElementTypeWindow>\n  <XCUIElementTypeWindow type="XCUIElementTypeWindow" enabled="true" visible="true" x="0" y="0" width="414" height="736">\n    <XCUIElementTypeStatusBar type="XCUIElementTypeStatusBar" enabled="true" visible="true" x="0" y="0" width="414" height="20">\n      <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="false" x="0" y="0" width="414" height="20"/>\n      <XCUIElementTypeOther type="XCUIElementTypeOther" enabled="true" visible="true" x="0" y="0" width="414" height="20">\n        <XCUIElementTypeOther type="XCUIElementTypeOther" name="信号强度:4(共 4 格)" label="信号强度:4(共 4 格)" enabled="true" visible="true" x="6" y="0" width="16" height="20"/>\n        <XCUIElementTypeOther type="XCUIElementTypeOther" value="SSID" name="3 格(共 3 格无线局域网信号)" label="3 格(共 3 格无线局域网信号)" enabled="true" visible="true" x="25" y="0" width="14" height="20"/>\n        <XCUIElementTypeOther type="XCUIElementTypeOther" name="09:41" label="09:41" enabled="true" visible="true" x="192" y="0" width="34" height="20"/>\n        <XCUIElementTypeOther type="XCUIElementTypeOther" name="方向已锁定" label="方向已锁定" enabled="true" visible="true" x="306" y="0" width="12" height="20"/>\n        <XCUIElementTypeOther type="XCUIElementTypeOther" name="闹钟" label="闹钟" enabled="true" visible="true" x="324" y="0" width="10" height="20"/>\n        <XCUIElementTypeOther type="XCUIElementTypeOther" name="电池电量:100%, 使用交流电源" label="电池电量:100%, 使用交流电源" enabled="true" visible="true" x="340" y="0" width="69" height="20"/>\n        <XCUIElementTypeOther type="XCUIElementTypeOther" name="电池电量:100%" label="电池电量:100%" enabled="true" visible="false" x="340" y="0" width="31" height="20"/>\n      </XCUIElementTypeOther>\n    </XCUIElementTypeStatusBar>\n  </XCUIElementTypeWindow>\n</XCUIElementTypeApplication>\n";
    
    _pathArrM = [[NSMutableArray alloc] init];
}


/**
 懒加载筛选出的控件数组

 @return 筛选结果数组
 */
- (NSMutableArray *)qualifiedElementArrM {
    if (!_qualifiedElementArrM) {
        _qualifiedElementArrM = [[NSMutableArray alloc] init];
    }
    
    return _qualifiedElementArrM;
}


- (IBAction)clickStartBtn:(NSButton *)sender {
    
    // 假设这里判断点(100, 350)对应的是哪个控件
    _x = 100;
    _y = 350;
    
    [self useXMLParserWithData:[self.xmlStr dataUsingEncoding:NSUTF8StringEncoding]];
    
    NSLog(@"筛选结果:%@", self.qualifiedElementArrM);
    NSLog(@"elementXpath = %@", [self.qualifiedElementArrM.firstObject valueForKey:@"elementXpath"]);
}


- (void)useXMLParserWithData:(NSData *)data {
    
    NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:data];
    [xmlParser setDelegate:self];
    [xmlParser parse];
}


#pragma mark - NSXMLParserDelegate

/**
 开始解析文档

 @param parser 解析器
 */
- (void)parserDidStartDocument:(NSXMLParser *)parser {
    NSLog(@"开始解析XML文件!");
}


/**
 开始标签

 @param parser 解析器
 @param elementName 元素名称
 @param namespaceURI 命名空间
 @param qName 限定名
 @param attributeDict 属性字典
 */
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict {
    
    [self.pathArrM addObject:elementName];
    
    if ([elementName isEqualToString:@"XCUIElementTypeButton"]) {
        NSLog(@"找到一个按钮 qualifiedName = %@, attributes = %@", qName, attributeDict);
        
        // 判断x坐标
        if ([attributeDict valueForKey:@"x"].integerValue <= self.x && self.x <= [attributeDict valueForKey:@"x"].integerValue + [attributeDict valueForKey:@"width"].integerValue) {
            
            // 判断y
            if ([attributeDict valueForKey:@"y"].integerValue <= self.y && self.y <= [attributeDict valueForKey:@"y"].integerValue + [attributeDict valueForKey:@"height"].integerValue) {
                
                NSString *elementXpath = [NSString stringWithFormat:@"//%@[@name='%@']", [self.pathArrM componentsJoinedByString:@"/"], attributeDict[@"name"]];
                
                NSDictionary *elementDict = @{@"elementXpath":elementXpath,
                                              @"attributeDict":attributeDict
                                              };
                
                // 加入筛选合格的控件数组
                [self.qualifiedElementArrM addObject:elementDict];
            }
        }
        
    }
    
    if ([elementName isEqualToString:@"XCUIElementTypeStaticText"]) {
        NSLog(@"找到一个文本框 qualifiedName = %@, attributes = %@", qName, attributeDict);
        
        // 判断x坐标
        if ([attributeDict valueForKey:@"x"].integerValue <= self.x && self.x <= [attributeDict valueForKey:@"x"].integerValue + [attributeDict valueForKey:@"width"].integerValue) {
            
            // 判断y
            if ([attributeDict valueForKey:@"y"].integerValue <= self.y && self.y <= [attributeDict valueForKey:@"y"].integerValue + [attributeDict valueForKey:@"height"].integerValue) {
                
                NSString *elementXpath = [NSString stringWithFormat:@"//%@[@name='%@']", [self.pathArrM componentsJoinedByString:@"/"], attributeDict[@"name"]];
                
                NSDictionary *elementDict = @{@"elementXpath":elementXpath,
                                              @"attributeDict":attributeDict
                                              };
                
                // 加入筛选合格的控件数组
                [self.qualifiedElementArrM addObject:elementDict];
            }
        }
    }
    
}


/**
 找到指定的字符串

 @param parser 解析器
 @param string 指定要查找的字符串
 */
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    
}


/**
 结束标签

 @param parser 解析器
 @param elementName 元素名称
 @param namespaceURI 命名空间
 @param qName 限定名
 */
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    NSLog(@"XML解析完成一个元素:elementName = %@,qualifiedName = %@🐶", elementName, qName);
    
    if ([elementName isEqualToString:self.pathArrM.lastObject]) {
        [self.pathArrM removeLastObject];
    }
}


/**
 文档解析结束

 @param parser 解析器
 */
- (void)parserDidEndDocument:(NSXMLParser *)parser {
    
}


#pragma mark - 自带方法

- (void)setRepresentedObject:(id)representedObject {
    [super setRepresentedObject:representedObject];

    // Update the view, if already loaded.
}


@end