ATX atx 对游戏分发包实践 2.0

重来看雨 · 2017年08月09日 · 最后由 lllllllllllllliu 回复于 2020年09月07日 · 3070 次阅读

咳咳,开始

前段时间,写了 - atx 对游戏分发包实践,竟然加精了。。还被加入 atx 的索引了。。😂
现在来个 2.0 主要是整体调整了,不再像 1.0 那傻傻的。一个渠道对应不同的游戏,即系 1--> N。2.0 是渠道和游戏脚本动态的组合,即系 N-->N
1.0 是一张张截图去执行,2.0 是根据截图的操作名称去执行。比如进入游戏的一系列操作,命名会是 ‘enter_game_001’---'enter_game_010'
新手指引 ‘guide_001’---'guide_100'等等。简单来说,就是 利用排序去循环执行。。不知道你们看懂了没有。。哈哈哈哈,我都不知道怎么描述了 😂

项目结构

  • 说明
  • channel:渠道的文件夹,主要存放渠道的截图和渠道的测试脚本
  • game : 游戏的文件夹,主要存放游戏的截图和游戏的测试脚本
  • pubic : 1.methods 一个公共类,渠道和游戏的脚本都继承这个类,很多方法都写在这个类 2.get_names 一个公共类,主要是获取配置文件的渠道和游戏名称
  • result :测试报告和用例执行 fail 截图
  • configure : 配置文件
  • main :一个 test case,在这里动态 导入相应的渠道和游戏测试脚本
  • run_case : run test case 的。

一,先从 pubic.methods 说起

methods 是游戏和渠道脚本都继承的类,因此这个类有大量的公共方法

1,划动操作

需要划动的是话,调用 swipe() 即可,当然也可以直接调用 up_swipe() 等

def size(self):
    if configure.device_name == '':
        size = 'adb shell wm size'
    else:
        size = 'adb -s %s shell wm size' % configure.device_name
    a =  os.popen(size)
    for i in a:
        pass
    size =i.split(': ')
    size = size[1].split('x')
    size= [int(size[0]),int(size[1])] #size[0] 为width size[1] 为high
    return size
def swipe(self,driver,direction):
    if direction == 'up':
        return self.up_swipe(driver)
    elif direction == 'down':
        return self.down_swipe(driver)
    elif direction =='right':
        return self.right_swipe(driver)
    elif direction == 'left':
        return self.left_swipe(driver)
    else:
        print '没传滑动方向,不做滑动操作,up,down,right,left'
def up_swipe(self,driver):
    size = self.size()
    driver.swipe(size[0] /2, size[1] *0.8, size[0] /2, size[1] *0.2, steps=10)
    sleep(1)
def down_swipe(self,driver):
    size = self.size()
    driver.swipe(size[0] / 2, size[1] * 0.2, size[0] / 2, size[1] * 0.8, steps=10)
    sleep(1)
def right_swipe(self,driver):
    size = self.size()
    driver.swipe(size[0] *0.2, size[1] /2, size[0] *0.8, size[1] /2, steps=10)
    sleep(1)
def left_swipe(self,driver):
    size = self.size()
    driver.swipe(size[0] *0.8, size[1] /2, size[0] *0.2, size[1] /2, steps=10)
    sleep(1)

2,获取截图名称的重要方法

下面的方法,无论是什么图片操作,都需要用到这个方法。

def get_name(self,name,way_name='game'):
    '''
    通过os.listdir 去获取文件夹所有截图,通过 re.match,获取所需要的截图名称
    :param name: 截图名称的前缀       如 name == 'pay_in'
    :param way_name: 区分游戏截图还是渠道截图
    :return: 返回一个有截图名称的list,如 ['pay_in_01','pay_in_02'......]
    '''
    if way_name == 'game':
        path = './/'+way_name+'/'+configure.game_name+'_images'
    else:
        path = './/' + way_name + '/' + configure.channel_name + '_images'
    a = os.listdir(os.getcwd() + os.sep + path)
    b = []
    name = '%s.*'%name
    for i in a:
        logo = re.match(name, i, re.M | re.I)
        if logo:
            logo=logo.group().split('.')   #截图名称格式一般是这样enter_game_01.1920x1080.png
            b.append(logo[0])             #分割后,获取的是 enter_game_01
    b = list(set(b)) #去重
    b = sorted(b)   #排序
    return b

3,判断图片是否存在的方法

有 2 个,一个是判断是否需要过指引,一个是判断是否进入到正确的第三方支付

def guide_exist(self,driver,name):
    '''
    判断目标图片是否存在,该方法主要判断 游戏引导第一个图片
    :param driver:
    :param name: 目标截图前缀
    :return: 目标图存在则返回非空,不存在则返回 None
    '''
    global image
    name = self.get_name(name)
    for x in xrange(10):
        print '第 %s 次寻找: %s' % (x + 1, name[0])
        image = self.images_or_none(driver, name[0] + '@auto.png')
        if image:
            print '引导图片存在,需要过引导: ' ,name[0] ,image
            break
        if x == 9:
            print '引导图片不存在,不需要过剧情: ',name[0],image
    return image

def pic_exist(self,driver,name,way_name):
    '''
    支付操作第三步,判断图片是否存在
    该方法,主要是判断,第三方支付界面的图片
    判断当前界面是相应的第三方支付界面
    :param driver:
    :param name: 目标图片
    :param way_name: 区分游戏和渠道截图的路径
    :return: 图片存在,返回非空,不存在 返回None
    '''
    name = name[0].replace('in_01', 'exist')
    name = self.get_name(name, way_name)
    for i in xrange(5):
        print strftime('%Y-%m-%d-%H-%M-%S') + '第 %s 次寻找: %s' % (i + 1, name[0])
        image = self.images_or_none(driver, name[0] + '@auto.png', way_name)
        if image:
            print(strftime('%Y-%m-%d-%H-%M-%S') + '--查找*******图片 %s 成功---图片信息 %s' % (name[0], image))
            return image
        if i == 4:
            return None

4,图片点击

介绍 2 种方法,一种是找不到图片会 return None(game_click()),一种是找不到当前图片,会继续找下一张(game_pay())

def game_click(self,driver,name):
    '''
    寻找和点击所有目标图片,如 目标图片是 guide_001 -----guide_100,只需 name 传入 'guide' 就会寻找和点击这些图片
    :param driver:
    :param name: 目标图片的前缀
    :return: 所有图片都找到和点击,返回一个非空,如果某张图片达到循环最大值都没找到,则返回 None
    '''
    name = self.get_name(name)
    for i in name:
        for x in xrange(10):
            print (strftime('%Y-%m-%d-%H-%M-%S')+'第 %s 次寻找: %s' %(x+1,i))
            image = self.images_or_none(driver,i+'@auto.png')
            if image:
                sleep(2)
                driver.click(image[0][0],image[0][1])
                #self.click_images(driver,i+'@auto.png')
                print (strftime('%Y-%m-%d-%H-%M-%S')+'--点击图片 %s 成功---图片信息 %s' %(i,image))
                break
            if x == 9:
                print '无法匹配到图片:', i
                return image
    return 'ok'

def game_pay(self,driver,name):
    '''
    调起渠道支付界面的方法,先判断 activity,如果当前的activity是渠道支付界面的,则不再做操作,
    如果不是,则寻找和点击目标图片,目标图片不存在,则跳过,继续操作下一张图片.
    :param driver:
    :param name: m目标截图名称
    :return: 如果循环,达到最大次数,返回None
    '''
    name = self.get_name(name)
    for x in xrange(10):
        if self.get_view_info(driver) == self.get_channel():
            print unicode('已经进入到支付界面')
            return 'ok'
        else:
            for i in name:
                print (strftime('%Y-%m-%d-%H-%M-%S')+'寻找: %s' % (i))
                image = self.images_or_none(driver, i + '@auto.png')
                if image:
                    sleep(1)
                    driver.click(image[0][0], image[0][1])
                    print(strftime('%Y-%m-%d-%H-%M-%S') + '--点击图片 %s 成功---图片信息 %s' % (i, image))

        if x == 9:
            print unicode('进入支付界面失败')
            return None

5,支付的操作

支付流程大概如下:选择第三方支付方式--->点击一个调起第三方支付的按钮--->到达第三方支付界面--->退出第三方支付界面
现在的方法对应是 : pay_in--->pay_up--->判断第三方支付界面---> pay_out
判断第三方支付界面有 2 种方式,一种是上述说到的 pic_exist(),判断第三方支付的图片,另一种就是 判断当前的 activity 或者 package

def pay_in(self,driver,name,way_name='channel',direction=None):
    '''
    支付界面第一步操作,找到相应的支付方式
    :param driver:
    :param name:目标截图名称
    :param way_name: 区分游戏和渠道截图的路径
    :param direction: 如果图片,是否需要滑动操作,如需要则传入 'up' 'down' 'right' 'left',不需要就是None
    :return:
    '''
    name = self.get_name(name, way_name)
    for i in name:
        for x in xrange(5):
            print strftime('%Y-%m-%d-%H-%M-%S') + '第 %s 次寻找: %s' % (x + 1, i)
            image = self.images_or_none(driver, i + '@auto.png', way_name)
            if image:
                driver.click(image[0][0], image[0][1])
                print(strftime('%Y-%m-%d-%H-%M-%S') + '--点击图片 %s 成功---图片信息 %s' % (i, image))
                break
            else:
                self.swipe(driver, direction)

def pay_up(self,driver,way_name):
    '''
    支付界面第二步操作,找到发起支付的按钮,如‘立即支付’ '确认支付'
    :param driver:
    :param way_name: 区分游戏和渠道截图的路径
    :return:
    '''
    name = self.get_name('pay_up', way_name)
    for i in xrange(5):
        print strftime('%Y-%m-%d-%H-%M-%S') + '第 %s 次寻找: %s' % (i + 1, name[0])
        image = self.images_or_none(driver, name[0] + '@auto.png', way_name)
        if image:
            driver.click(image[0][0], image[0][1])
            print(strftime('%Y-%m-%d-%H-%M-%S') + '--点击图片 %s 成功---图片信息 %s' % (name[0], image))
            break

def pay_out(self,driver,name,way_name='channel'):
    '''
    支付操作第四部 退出支付界面,不能直接BACK键退出的,需要用图片退出才用该方法
    :param driver:
    :param name: 目标截图
    :param way_name: 区分游戏和渠道截图的路径
    :return:
    '''
    name = self.get_name(name, way_name)
    for i in name:
        for x in xrange(5):
            print strftime('%Y-%m-%d-%H-%M-%S') + '第 %s 次寻找: %s' % (x + 1, i)
            image = self.images_or_none(driver,i+'@auto.png',way_name)
            if image:
                driver.click(image[0][0], image[0][1])
                print(strftime('%Y-%m-%d-%H-%M-%S') + '--点击图片 %s 成功---图片信息 %s' % (i, image))
                break

def get_view_info(self,driver,act_or_package=1):
    '''
    获取当前界面的 packages,activity
    :param driver:
    :param act_or_package:  0 是package 1 是activity 2是 PID
    :return:
    '''
    sleep(2)
    return driver.current_app()[act_or_package]

def pay_exist_pic(self,driver,name,way_name='channel',direction=None):
    '''
    其实就是 pay_in pay_up 和 pic_exist 的调用。。。
    使用:判断是否进入到相应的第三方支付界面,通过 图片判断,
    :param driver:
    :param name: 目标图片
    :param way_name: 区分游戏和渠道截图的路径
    :param direction: 是否滑动
    :return:  pic_exist
    '''
    self.pay_in(driver,name,way_name,direction)
    self.pay_up(driver,way_name)
    return self.pic_exist(driver,name,way_name)

def pay_exist_act(self,driver,name,act_or_packages = 1,way_name='channel',direction=None):
    '''
    其实就是 pay_in pay_up 和 get_view_info 的调用。。。
    使用:判断是否进入到相应的第三方支付界面,通过 activitv或者 package 判断
    :param driver:
    :param name: 目标图片
    :param act_or_packages: 0 是package 1 是activity 2是 PID
    :param way_name: 区分游戏和渠道截图的路径
    :param direction: 是否滑动
    :return: get_view_info
    '''
    self.pay_in(driver,name,way_name,direction)
    self.pay_up(driver,way_name)
    sleep(10)
    return self.get_view_info(driver,act_or_packages)
  • method 这个类讲完了,是否有疑问,为什么很多时候 return 都是 None,那是因为需要做断言。

二,渠道和游戏脚本

1,渠道脚本

这里主要介绍 支付的 pay_exist_act() 和 pay_exist_pic() 具体使用

def wechat(self,driver):

    if self.pay_exist_act(driver,'wechat_in',0) == wechat: #判断微信的package naem    
        for i in xrange(2):
            driver.keyevent('BACK')                        # 按2次back 键
        self.pay_out(driver,'wechat_out')                  #  渠道支付界面退出
        if self.get_view_info(driver) != channel_pay_activity:  #判断是否退出成功
            return 'ok'
        else:
            return None
    else:
        return None

def China_Mobile(self,driver):
    if self.pay_exist_pic(driver, 'China_Mobile_in'):   # 判断第三方支付界面
        driver.keyevent('BACK')                         # 退出支付界面
        if self.get_view_info(driver) != channel_pay_activity:  #判断是否退出成功
            return 'ok'
        else:
            return None
    else:
        return None

2,游戏脚本

游戏脚本内容比较少,一个就是进入游戏,一个调起支付

def enter_game(self, driver):
    '''进入游戏,有引导过引导'''
    self.game_click(driver,'enter_game_01')  #点击进入游戏
    sleep(10)
    if self.guide_exist(driver,'guide_001'):  #判断是否需要过指引
        result = self.game_click(driver, 'guide')  #过指引
        return result
    else:
        result = self.game_click(driver, 'enter_game_02')      #不需要过指引的操作 
        return result

def game_pay_up(self, driver):
    '''调起支付界面'''
    result = self.game_pay(driver, 'pay')
    return result

三,动态 import

1,配置信息 configure

channel_name = 'sdk2345'
game_name = 'hhw'
device_name = '2aa530ef'
package_name = 'xxxxx'
activity_name = 'com.platform.main.MainActivity'
account = 'zhaotest8'   #登录账号
password = '123456'   #登录密码

2,获取信息 get_names

class Get_name(object):
    '''
    根据configure 配置的游戏名和渠道名,返回包含上一层级的路径
    为动态 import 提供 str
    '''
    def get_game(self):
        if configure.game_name =='hhw':
            return 'game.hhw'
        elif configure.game_name == '':
            return 'game.xx'
        else:
            print unicode('该游戏没有脚本')
            exit()
    def get_channel(self):
        if configure.channel_name == 'sdk2345':
            return 'channel.sdk2345'
        elif configure.channel_name == '':
            return 'channel.xx'
        else:
            print unicode('该渠道没有脚本')
            exit()

3,import

def get_names(self,name):
    '''
    动态import 游戏和渠道相应的测试类
    :param name: 动态import 游戏还是 渠道
    :return: 返回 游戏 或者 渠道 实例 的类
    '''
    import public.get_names as get_names
    get_names = get_names.Get_name()
    if name =='game':
        game = __import__(get_names.get_game())
        game_name = get_names.get_game().split('.')[1]
        game = getattr(game, game_name)
        game = game.Game()
        return game
    if name == 'channel':
        channel = __import__(get_names.get_channel())
        channel_name = get_names.get_channel().split('.')[1]
        channel = getattr(channel, channel_name)
        channel = channel.Channel()
        return channel

4,调用

game = self.get_names('game')
channel = self.get_names('channel')
channel.login(self.driver)
game.enter_game(self.driver)
channel.wechat(self.driver)

四,运行一些情况

1,配置信息写错,也就是 get_names 里没 return 的


2,配置信息正确的(不需要过新手指引的)

3,配置信息正确(需要过剧情的),测试结果为 pass

开始部分:是 更新,公告,登录,判断是否需要过引导,以及引导的操作

这部分:是 引导结束,转到 支付流程,和第三方支付的操作

这部分:最后一个第三方支付执行完成,测试结束

各位看官。感谢看完。。有什么建议和指正,一定要告诉我啊。谢谢

共收到 16 条回复 时间 点赞

有点没看懂,为啥要把过新手流程的操作序列 放到截图的名称里呢 。。😅

单数 回复

新手引导,需要一步一步走嘛,把每一步的需要点击的地方截图,就会出现一堆新手指引的图。把这部分图,加上一个标记和排序,如 guide-01--guide-20,但是整个游戏操作,除了新手引导,还会有其他的图放在一起,这个时候,就需要筛选图片了,调用 get_name()
筛选带有 'guide'的图,就能筛选出来了整个新手引导的图了,然后遍历 筛选后的图, 就把新手引导走完了。

现在你不是一个人了

codeskyblue 回复

感觉差不多。貌似都是搞游戏的多,像我搞分发包的不多。哈哈哈

提几个东西 d.display会直接返回屏幕的尺寸  atx 中的 match 方法会直接返回查找到的坐标,不知道你写的images_or_none有没有用到
我这边就有人搞游戏渠道测试的

codeskyblue 回复
def images_or_none(self,driver,images_name,way_name='game',timeout = 10):
    self.driver = driver
    game_name = configure.game_name
    channel_name = configure.channel_name
    if way_name == 'game':
        try:
            self.wait_images(self.driver, images_name,way_name,timeout)
        except:
            pass
        images =  self.driver.exists('./'+way_name+'/'+game_name+'_images/'+images_name)
        return images
    else:
        try:
            self.wait_images(self.driver, images_name, way_name, timeout)
        except:
            pass
        images = self.driver.exists('./' + way_name + '/' + channel_name + '_images/' + images_name)
        return images

images_or_none 用了 两个 atx 中的两个方法。wait_images 和 driver.exists,我刚看了下 exists,也是调用 match 的。

重来看雨 回复

是的,几乎所有图像相关的函数都是对 match 的封装

codeskyblue 回复

刚刚调整了一下 images_or_none ,直接调用 wait 就可以了。

def images_or_none(self,driver,images,way_name='game',timeout = 10,safe=True):
    self.driver = driver
    game_name = configure.game_name
    channel_name = configure.channel_name
    if way_name =='game':
        images= self.driver.wait('./'+way_name+'/' + game_name + '_images/' + images,timeout,safe)
        return images
    else:
        images = self.driver.wait('./' + way_name + '/' + channel_name + '_images/' + images, timeout,safe)
        return images

MMORPG 游戏做到渠道包测试就很蛋疼了,充值相关的功能出来的太晚了,需要处理游戏相关东西。😂

spring-ssh 回复

是的,主要是新手剧情

mark,感谢分享

重来看雨 回复

请问游戏新手要寻路的那种怎么做呢。不是固定点击某个地方有指引的那种,是移动手柄走路

xrandy 回复

这种可能会无解。。

我尝试用图片识别的方法去试了试,发现精度很差,请问博主没有遇见过图像识别不准确的情况吗

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册