第一部分-脚本等于工作

把脚本比作工作,亦无不可

以下是一名程序员的日常

name : Kiefer Hagin
from : 美国·西雅图
desc :
我来概括一下我的日常。先说一下,我 20 多岁,单身。就职于一家 10 人的小型创业公司,产品团队有 5 人。
8:30 :起床、洗漱、冲咖啡、穿衣、喝咖啡
9:15 :走去市场抢早餐(通常是鸡蛋、土豆煎饼、蔬菜、大米)(程序员的那些事 注:好奇怪的早餐啊,我没脑补出来是什么样的)
9:45 - 10:00:到办公室(我走路大约 10 分钟),一边吃一边看邮件
10:00 - 10:30 :查看任务,看看是否有需要 Code Review 的 PR,如有必要,和同事讨论
10:30 -12:30 :一边写代码,一边听音乐,或者喝咖啡
12:30 - 13:30 :吃午饭,看油管视频,或听播客,或做些 code review。偶尔会和朋友/同事一起吃午饭。
13:30 - 16:00 :写代码
16:00 - 16:30 :小憩一会或散步,然后和同事讨论。在这个时间段,每周或许会有次产品会议。
16:30 - 18:00 : 打包代码,准备好部署到 stage 环境中测试
18:00 - 18:30 :如果一切正常并测试通过,把每日build 配置到生产环境
18:30 - 20:00 :回家,做晚饭或订餐,在 Quora 看或写帖子,弹吉他,放松一下
20:00 - 22:00 :如果有 stage 环境还有 Bug,继续改 Bug。如果白天工作没问题,那我通常看一些创业/软件/或我想知道的东西,或者做自己的副项目。如果累了,打一会游戏。
22:00 - 23:00 : 打坐、弹吉他、尽量让大脑静下来。如果想熬夜,看 Netflix。
23:00 - 24:00 : 要么看 Netflix,要么阅读。通常是阅读。科幻和非科幻的,我换着来看。
24:00 - 00:30 :睡觉
我的周末比较杂乱。通常和朋友一起玩,远离电脑。尽可能做一些社交活动。如果需要,周末有时候会工作,不过很少加班了,也许一个月中,有一个周末会加班。

第二部分-理想的脚本

脚本和工作一样,哒哒哒的按预先规划好的流程执行,除了偶尔的小意外,基本上每天都一样.
我们来做一个QQ发消息的脚本.
细分为三个工作
1 在桌面打开QQ
2 在消息列表页打开一个聊天窗口
3 在聊天窗口发送消息

理想的脚本大概是这样的

var work=[在桌面打开QQ,在消息列表页打开一个聊天窗口,在聊天窗口发送消息]

额,没办法执行...
也许应该把work设计为一个对象,给他加上一个do方法.
加个名字,方便排错

function Work(name,flow){
  this.name=name || ''
  this.flow=flow || []
}
Work.prototype.do=function(){
  for(let i=0;i<this.flow.length;i++){
    this.flow[i].do()
  }
}

Work写完了,测试一下是否能正确执行
编写三个work

var work1=new Work('work1')

工作的流程就是工作本身,比如原子是不可分割的,虽然还可以分为质子和中子

work1.flow=[work1]
work1.do=function(){log(this.name)}
var work2=new Work('work2')
work2.flow=[work2]
work2.do=function(){log(this.name)}
var work3=new Work('work3')
work3.flow=[work3]
work3.do=function(){log(this.name)}

三个小work创建完成,组成一个大work

var bigWork=new Work('bigWork')
bigWork.flow=[work1,work2,work3]
bigWork.do()

测试成功

第三部分-工作是个什么东西

工作是什么?
百度百科的解释:
所谓工作就是劳动者通过劳动(包括体力劳动和脑力劳动)将生产资料转换为生活资料以满足人们生存和继续社会发展事业的过程。工作没有高低贵贱之分,只有社会分工不同。

也即是说 工作=生产资料+劳动, 毕竟巧妇难为无米之炊.

实际工作中,比如老板让你写个hello world 放到网站主页,起码得有个电脑,然后你写hello world发布到线上测试,测试成功了还要报告老板,然后老板再给你别的任务

工作名称:编写hello world放到网站主页
工作流程:打开电脑,编写代码,测试修改bug,报告老板
工作必备:程序员一枚,鼓励师一枚,电脑一台

第四部分-判断工作是否成功了

我们编写qq发消息:

工作名称:qq发消息
工作流程:桌面打开QQ,消息列表页点击一个消息窗口,聊天页面发送消息
工作必备:qq软件一个

上面只是工作的大概流程,我们写脚本更在意的是某个点击或者上滑动作是否有效,是否达到我们预期的效果,比如点击QQ,QQ就打开了,有可能点击无效,QQ没有打开.

工作的校验是否执行成功是很重要的,添加一个校验工作是否执行成功的方法

function Work(name,flow){
  this.name=name || ''
  this.flow=flow || []
}

每个工作校验是否成功的方法都不一样,此处留空,按实际情况编写校验方法

Work.prototype.isSuccess=function(){}

在do方法中添加isSuccess

Work.prototype.do=function(){
  for(let i=0;i<this.flow.length;i++){
    this.flow[i].do()
  }
  if(this.isSuccess()){
    log('Congratulations!',this.name,'is success')
  }else{
    throw util.format('Oh, No,%s is fail',this.name)
  }
}

第五部分-如何去描述一个鸭子

autojs的特色就是控件,我们一般也是用控件来写脚本,最常用的就是text,desc,id,bounds,那么我们判断脚本是否执行成功,也用控件来进行判断,当然如果需要别的方法,你也可以重写.

function Inspect(name){
  this.name=name
  this.viewArr=[] // 要检查的控件信息
  this.wordArr=[] // 要检查的文字信息
}

如何去描述一个鸭子?
如果它看起来像鸭子,走起来像鸭子,叫起来像鸭子,它就是鸭子。--麦卡锡
各种引申
你在网线那一头
它是丑小鸭
蛤?
如果他看起来像女人 走起来像女人 叫起来像女人 他可能是女人 但也有可能是ladyboy
他看起来像吧主 走起来像吧主 叫起来像吧主 但他不是吧主
江主席已经回答你了,你真是图样图森破
玩具鸭子不是鸭子,因为不能吃。要抓住所有属性,而不是特性。
如果地面看起来是平的,走起来也是平的,那么地球就是平面的。

第六部分-控件的检查方法

function Inspect(name,type){
  this.name=name
  this.type='viewArr' || type
  this.viewArr=[] // 要检查的控件信息
  this.wordArr=[] // 要检查的文字信息
  //检查方法一开始我觉得加到prototype上比较好,
  //后来考虑到某些情况下需要重写inspect方法,
  //也就是说每个实例的inspect方法需要各自保存一份,
  //就放到构造函数里了
  this.inspect=function (){
    switch(this.type){
      case 'viewArr':
        return this.inspectViewArr()
        break;
      case 'wordArr':
        return this.inspectWordArr()
        break;
      default:
        throw util.format('没有这种检查方法%s,你自己加',this.type)
    }
  }
}

检查控件就和检查鸭子一样,
如果它看起来像鸭子,走起来像鸭子,叫起来像鸭子,它就是鸭子。--麦卡锡

Inspect.prototype.inspectViewArr=function (){
  var viewArr=this.viewArr
  log(viewArr)
  var common=require('./common')
  if(!(common.isArrayFn(viewArr))){throw 'viewArr必须是数组'}
  function getSelectorRule(attr,val){
    log(attr)
    switch(attr){
      case 'id':
        return 'id("'+val+'").'
      case 'text':
        return 'text("'+val+'").'
      case 'desc':
        return 'desc("'+val+'").'
      default:
        throw '没有这个属性'
    }
  }
  function getSelector(view){
    var arr = Object.keys(view);
    if(arr.length == 0){
      throw '不允许传入空对象'
    }
    var findRule='findOnce()'
    var rule=''
    for(var k in view){
      rule+=getSelectorRule(k,view[k])
    }
    return rule+findRule
  }
  for(let i=0;i<viewArr.length;i++){
    var findViewRule=getSelector(viewArr[i])
    log(findViewRule)
    if(eval(findViewRule)){log(util.format('这个规则%s找到控件了'),findViewRule)}
    else{
      log(util.format('这个规则%s没有找到控件'),findViewRule)
      return false
    }
  }
  return true
}
Inspect.prototype.inspectWordArr=function (){
  //检查页面是否包含指定文字
  //common中存放一些公共的函数
  var common=require('./common')
  var allWords=common.getAllWords()
  var result=common.bigArrContainsSmallArr(allWords,this.wordArr)
  return result
}

检查方法写完了,我们来测试一下
打开桌面查找桌面是不是有QQ

var findQQ=new Inspect('findQQ')
findQQ.viewArr=[{text:'QQ'}]
var r=findQQ.inspect()
log(util.format('findQQ=%j',findQQ))
log(r)

测试成功

第七部分-发送QQ消息之工作流的实现

终于快结束了
我们开始编写脚本
脚本目的:发送QQ消息
脚本大概流程:桌面打开QQ-->消息列表页点击列表中某一项-->聊天窗口随便发个消息

var common=require('./common')
var init=require('./init')
var Work=init.Work
var workExample=new Work('发送QQ消息之工作流的实现')
var 桌面打开QQ=new Work('桌面打开QQ')
var 消息列表页点击列表中某一项=new Work('消息列表页点击列表中某一项')
var 聊天窗口随便发个消息=new Work('聊天窗口随便发个消息')

设定工作流

workExample.flow=[桌面打开QQ,消息列表页点击列表中某一项,聊天窗口随便发个消息]

设置每个工作执行之前和执行之后的状态

桌面打开QQ.statusMustbeBeforeDo={viewArr:[{text:"QQ"}]}
桌面打开QQ.statusMustbeAfterDo={viewArr:[{text:"消息"}]}
消息列表页点击列表中某一项.statusMustbeBeforeDo={viewArr:[{text:"消息"}]}
消息列表页点击列表中某一项.statusMustbeAfterDo={viewArr:[{text:"发送"}]}
聊天窗口随便发个消息.statusMustbeBeforeDo={viewArr:[{text:"发送"}]}
聊天窗口随便发个消息.statusMustbeAfterDo={viewArr:[{id:"chat_item_content_layout"}]}

设置每个工作的内容

桌面打开QQ.action=function(){
  var QQ=text('QQ').findOnce()
  if(QQ){
    common.clickView(QQ)
  }
}
消息列表页点击列表中某一项.action=function(){
  press(599,478,1)
}
聊天窗口随便发个消息.action=function(){
  var inputView=id('input').findOnce()
  if(inputView){inputView.setText('hello 2019')}
  var sendView=text('发送').findOnce()
  if(sendView){sendView.click()}
}
workExample.do()

测试成功

主程序

var common = require('./common')
var init = require('./init')
var Work = init.Work
var workExample = new Work('发送QQ消息之工作流的实现')
var 桌面打开QQ = new Work('桌面打开QQ')
var 消息列表页点击列表中某一项 = new Work('消息列表页点击列表中某一项')
var 聊天窗口随便发个消息 = new Work('聊天窗口随便发个消息')
workExample.flow = [桌面打开QQ, 消息列表页点击列表中某一项, 聊天窗口随便发个消息]
桌面打开QQ.statusMustbeBeforeDo = {
  viewArr: [{
    text: "QQ"
  }]
}
桌面打开QQ.statusMustbeAfterDo = {
  viewArr: [{
    text: "消息"
  }]
}
消息列表页点击列表中某一项.statusMustbeBeforeDo = {
  viewArr: [{
    text: "消息"
  }]
}
消息列表页点击列表中某一项.statusMustbeAfterDo = {
  viewArr: [{
    text: "发送"
  }]
}
聊天窗口随便发个消息.statusMustbeBeforeDo = {
  viewArr: [{
    text: "发送"
  }]
}
聊天窗口随便发个消息.statusMustbeAfterDo = {
  viewArr: [{
    id: "chat_item_content_layout"
  }]
}
桌面打开QQ.action = function () {
  var QQ = text('QQ').findOnce()
  if (QQ) {
    common.clickView(QQ)
  }
}
消息列表页点击列表中某一项.action = function () {
  press(599, 478, 1)
}
聊天窗口随便发个消息.action = function () {
  var inputView = id('input').findOnce()
  if (inputView) {
    inputView.setText('hello 2019')
  }
  var sendView = text('发送').findOnce()
  if (sendView) {
    sendView.click()
  }
}
workExample.do()

需要用到的两个模块

init.js

log('加载了init模块')

function Work(name, flow) {
  this.name = name || ''
  this.flow = flow || []
  this.status = 'ready'
  this.timesMaxDo = 6
  this.timesDo = 0
  this.statusMustbeBeforeDo = {}
  // {
  //   viewArr:[{text:'QQ'}]
  // }
  this.statusMustbeAfterDo = {}
  this.action = function () {
    for (let i = 0; i < this.flow.length; i++) {
      this.flow[i].do()
    }
  }
  this.do = function () {
    if (this.timesDo > this.timesMaxDo) {
      this.handleError('timesDoOver')
    }
    var inspectStatusBeforeDo = new Inspect(this.statusMustbeBeforeDo)
    var inspectStatusAfterDo = new Inspect(this.statusMustbeAfterDo)
    if (this.timesDo > 0) {
      if (inspectStatusAfterDo.inspect()) {
        log(util.format('%s %s is OK', this.name, 'inspectStatusAfterDo'))
        this.timesDo = 0
        return true;
      }
    }
    if (!inspectStatusBeforeDo.inspect()) {
      this.handleError('statusStartError')
    } else {
      log(util.format('%s %s is OK', this.name, 'inspectStatusBeforeDo'))
    }
    this.action()
    this.timesDo++;
    if (!inspectStatusAfterDo.inspect()) {
      this.handleError('statusEndError')
    } else {
      log(util.format('%s %s is OK', this.name, 'inspectStatusAfterDo'))
      this.timesDo = 0
    }
  }
  this.handleError = function (err) {
    switch (err) {
      case 'statusStartError':
        log(util.format('%s %s is error', this.name, 'inspectStatusBeforeDo'))
        sleep(1000)
        this.timesDo++;
        this.do()
        break;
      case 'statusEndError':
        log(util.format('%s %s is error', this.name, 'inspectStatusAfterDo'))
        this.timesDo++;
        this.do()
        break;
      case 'timesDoOver':
        log(util.format('%s is error: %s', this.name, 'timesDoOver'))
        throw (util.format('%s is error: %s', this.name, 'timesDoOver'))
        break;
      default:
        throw util.format('%s occur error: ', this.name, err)
    }
  }
}
//做个检查类,来检查界面的状态,当然也可以拓展检查其他东西,时间,手机电量,wifi状态啦之类的,只要返回布尔值就可以了
function Inspect(status) {
  this.status = status || {}
  //status是指statusMustbeBeforeDo或者statusMustbeAfterDo,
  //一般就是检查控件是否存在
  //当然也可以是其他状态
  //比如时间,文件
  //毕竟状态是一种抽象的概念,
  //万事万物都有状态
  //至于检查的方法就各有章法了
  //至于status用数组还是对象来保存,
  //我都思考了好长时间
  //我觉得病人的状态按照心跳,血压,气色,来观察的
  //所以我就决定选择对象来保存,
  //其实数组也可以保存,都一样的,
  //感觉用数组或者对象保存,并没有什么区别
  this.inspect = function () {
    var status = this.status
    for (var k in status) {
      switch (k) {
        case 'viewArr':
          log(util.format('当前检查类型是%s', 'viewArr'))
          return this.inspectViewArr(status[k])
        case 'wordArr':
          log(util.format('当前检查类型是%s', 'viewArr'))
          return this.inspectWordArr(status[k])
        default:
          throw util.format('没有这种检查类型%s,你自己加吧', k)
      }
    }
    return true
  }
}
//检查控件就和检查鸭子一样,
//如果它看起来像鸭子,走起来像鸭子,叫起来像鸭子,它就是鸭子。--麦卡锡
Inspect.prototype.inspectViewArr = function (viewArr) {
  log(viewArr)
  var common = require('./common')
  if (!(common.isArrayFn(viewArr))) {
    throw 'viewArr必须是数组'
  }
  if (viewArr.length == 0) {
    return true
  }

  function getSelectorRule(attr, val) {
    log(attr)
    switch (attr) {
      case 'id':
        return 'id("' + val + '").'
      case 'text':
        return 'text("' + val + '").'
      case 'desc':
        return 'desc("' + val + '").'
      default:
        throw '没有这个属性'
    }
  }

  function getSelector(view) {
    var arr = Object.keys(view);
    if (arr.length == 0) {
      throw '不允许传入空对象'
    }
    var findRule = 'findOnce()'
    var rule = ''
    for (var k in view) {
      rule += getSelectorRule(k, view[k])
    }
    return rule + findRule
  }
  for (let i = 0; i < viewArr.length; i++) {
    var findViewRule = getSelector(viewArr[i])
    log(findViewRule)
    if (eval(findViewRule)) {
      log(util.format('这个规则%s找到控件了'), findViewRule)
    } else {
      log(util.format('这个规则%s没有找到控件'), findViewRule)
      return false
    }
  }
  return true
}
Inspect.prototype.inspectWordArr = function (wordArr) {
  //检查页面是否包含指定文字
  //common中存放一些公共的函数
  var common = require('./common')
  if (!(common.isArrayFn(wordArr))) {
    throw 'wordArr必须是数组'
  }
  var allWords = common.getAllWords()
  var result = common.bigArrContainsSmallArr(allWords, wordArr)
  return result
}
module.exports = {
  Work: Work
}

common.js

//点击控件
function clickView(view) {
  log(arguments.callee.name + '开始')
  log(view)
  if (view) {
    var x = view.bounds().centerX()
    var y = view.bounds().centerY()
    log('将要点击的坐标 %s,%s', x, y)
    press(x, y, 1)
  } else {
    throw '传入clickView中的view异常'
  }
  log(arguments.callee.name + '结束')
}
//去除头尾空格
String.prototype.strip = function () { 
  log(arguments.callee.name + '开始')
  var str = this,
     whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000'; 
  for (var i = 0, len = str.length; i < len; i++) {  
    if (whitespace.indexOf(str.charAt(i)) === -1) {   
      str = str.substring(i);   
      break;  
    } 
  } 
  for (i = str.length - 1; i >= 0; i--) {  
    if (whitespace.indexOf(str.charAt(i)) === -1) {   
      str = str.substring(0, i + 1);   
      break;  
    } 
  } 
  log(arguments.callee.name + '结束')
  return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
}
// 随机滑动
function swipeRnd(x1, y1, x2, y2, duration) {
  log(arguments.callee.name + '开始')
  var k = 20
  var x1 = x1 + random(-(k), k)
  var y1 = y1 + random(-(k), k)
  var x2 = x2 + random(-(k), k)
  var y2 = y2 + random(-(k), k)
  var duration = duration + random(-(k), k)
  swipeRnd2(x1, y1, x2, y2, duration)
  log(arguments.callee.name + '结束')
}
// 随机滑动  两点滑动 变为三点滑动
function swipeRnd3(x1, y1, x2, y2, duration) {
  log(arguments.callee.name + '开始')
  gesture(duration, [x1, y1], [x1 + random(10, 60), y1 - random(10, 80)], [x2, y2])
  log(arguments.callee.name + '结束')
}

function swipeFromDownToUp() {
  log(arguments.callee.name + '开始')
  var w = device.width
  var h = device.height
  var x1 = Math.floor(w / 5 * 1)
  var y1 = Math.floor(h / 5 * 4)
  var x2 = Math.floor(w / 5 * 3)
  var y2 = Math.floor(h / 5 * 1)
  var duration = 300
  log('滑动参数=', x1, y1, x2, y2, duration)
  swipeRnd(x1, y1, x2, y2, duration)
  log(arguments.callee.name + '结束')
  sleep(3000)
}
Array.prototype.minus = function (arr) {
  return this.filter(function (element) {
    for (let i = 0; i < arr.length; i++) {
      if (JSON.stringify(element) == JSON.stringify(arr[i])) {
        return false
      }
    }
    return true
  });
};
Array.prototype.intersect = function (arr) {
  return this.filter(function (element) {
    for (let i = 0; i < arr.length; i++) {
      if (JSON.stringify(element) == JSON.stringify(arr[i])) {
        return true
      }
    }
    return false
  });
};

function bigArrContainsSmallArr(bigArr, smallArr) {
  //对于重复的元素采用计数的方式对比
  var bigArrObj = {}
  var smallArrObj = {}
  for (let i = 0; i < bigArr.length; i++) {
    var has = bigArrObj.hasOwnProperty(bigArr[i])
    if (has) {
      bigArrObj[bigArr[i]]++;
    } else {
      bigArrObj[bigArr[i]] = 1
    }
  }
  for (let i = 0; i < smallArr.length; i++) {
    var has = smallArrObj.hasOwnProperty(smallArr[i])
    if (has) {
      smallArrObj[smallArr[i]]++;
    } else {
      smallArrObj[smallArr[i]] = 1
    }
  }
  for (var k in smallArrObj) {
    if (bigArrObj.hasOwnProperty(k) && bigArrObj[k] >= smallArrObj[k]) {} else {
      return false
    }
  }
  return true
}
/**
  * @method getAllWords
  * @param  setting 是个对象, 决定是否获取text,desc,id,以及是否去重; 默认获取text和desc,不获取id,默认去重
  * @desc 默认设置为{
    getText: true,
    getDesc: true,
    getId: false,
    removeRepetitiveElements: true
  }
  * @desc 获取页面所有文字,可以指定text,desc,id三个中的任意几个
  * @return 所有文字组成的数组
  */
function getAllWords(setting) {
  var setting = setting || {}
  var defaultSetting = {
    getText: true,
    getDesc: true,
    getId: false,
    removeRepetitiveElements: false
  }
  Object.assign(defaultSetting, setting);
  log(defaultSetting)
  var allStr = []
  var getDescAndTextAndIdOfNode = function (node) {
    if (node) {
      if (defaultSetting.getText) {
        var text = node.text()
        if (!!text) {
          allStr.push(text)
        }
      }
      if (defaultSetting.getDesc) {
        var desc = node.desc()
        if (!!desc) {
          allStr.push(desc)
        }
      }
      if (defaultSetting.getId) {
        var id = node.id()
        if (!!id) {
          allStr.push(id)
        }
      }
    }
    for (let i = 0; i < node.childCount(); i++) {
      getDescAndTextAndIdOfNode(node.child(i));
    }
  }
  var getFrameLayoutNode = function () {
    return className('FrameLayout').findOne(2000)
  }
  getDescAndTextAndIdOfNode(getFrameLayoutNode())

  function removeRepetitiveElements(arr) {
    var obj = {}
    for (let i = 0; i < arr.length; i++) {
      if (obj.hasOwnProperty(arr[i])) {} else {
        obj[arr[i]] = true
      }
    }
    return Object.keys(obj)
  }
  if (defaultSetting.removeRepetitiveElements) {
    allStr = removeRepetitiveElements(allStr)
  }
  return allStr
}
// JavaScript 标准文档中定义: [[Class]] 的值只可能是下面字符串中的一个: Arguments, Array, Boolean, Date, Error, Function, JSON, Math, Number, Object, RegExp, String.
function isArrayFn(value) {
  if (typeof Array.isArray === "function") {
    return Array.isArray(value);
  } else {
    return Object.prototype.toString.call(value) === "[object Array]";
  }
}

function isObjectFn(value) {
  return Object.prototype.toString.call(value) === "[object Object]";
}
var common = {
  clickView: clickView,
  bigArrContainsSmallArr: bigArrContainsSmallArr,
  swipeRnd: swipeRnd,
  swipeRnd3: swipeRnd3,
  swipeFromDownToUp: swipeFromDownToUp,
  getAllWords: getAllWords,
  isArrayFn: isArrayFn,
  isObjectFn: isObjectFn
}
module.exports = common

终于出现了一个把大象放进冰箱的人了。

  • 13
    帖子
  • 1999
    浏览

与 Auto.js 的连接断开,我们正在尝试重连,请耐心等待