Jekyll2019-06-23T21:16:14+08:00http://www.xiaocai.name/feed.xmlcmxiaocai.github.ioxiaocai的个人博客cmxiaocai(二) 用python做android游戏自动化测试2018-03-21T00:00:00+08:002018-03-21T00:00:00+08:00http://www.xiaocai.name/2018/03/21/%E7%94%A8python%E5%81%9Aandroid%E6%B8%B8%E6%88%8F%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95(2)<blockquote>
<p>在上一篇《(一)用python做android游戏自动化测试》中,我们实现了最基础的(截图 -> 图像识别 -> 计算位置 -> 点击位置)步骤。在实际的测试过程中会一直重复的以上步骤,所以本次的目标是将这些重复的UI交互操作进行封装。</p>
</blockquote>
<h2 id="创建ui交互类">创建UI交互类</h2>
<blockquote>
<p>首先在<code class="highlighter-rouge">adb.py</code>同级目录创建<code class="highlighter-rouge">uiDriver.py</code>文件,并申明<code class="highlighter-rouge">uiDriver</code>类</p>
</blockquote>
<p><em>目录结构参考:</em></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
├── images
│ └── btn_close_full.png
├── libs
│ ├── __init__.py
│ ├── adb.py
│ └── uiDriver.py
└── run.py
</code></pre></div></div>
<p><em>file: libs/uiDriver.py</em></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env python</span>
<span class="c"># coding=utf-8</span>
<span class="s">"""Android UI交互操作"""</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">'xiaocai'</span>
<span class="kn">import</span> <span class="nn">sys</span><span class="p">,</span> <span class="n">time</span><span class="p">,</span> <span class="n">cv2</span>
<span class="kn">from</span> <span class="nn">libs</span> <span class="kn">import</span> <span class="n">adb</span>
<span class="k">class</span> <span class="nc">uiDriver</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">adb</span> <span class="o">=</span> <span class="n">adb</span><span class="o">.</span><span class="n">adbKit</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="find函数封装">find函数封装</h2>
<blockquote>
<p>接着我们要将上一篇的(截图 -> 图像识别 -> 计算位置)操作步骤封装到find方法中。另外新增一个precision参数用于设定匹配图像的相似度,这样可以在某些特殊场景下通过降低相似度达到匹配效果。</p>
</blockquote>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="k">class</span> <span class="nc">uiDriver</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="o">...</span>
<span class="k">def</span> <span class="nf">find</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">img</span><span class="p">,</span> <span class="n">precision</span><span class="o">=</span><span class="mf">0.9</span><span class="p">):</span>
<span class="c"># 截图</span>
<span class="bp">self</span><span class="o">.</span><span class="n">adb</span><span class="o">.</span><span class="n">screenshots</span><span class="p">()</span>
<span class="c"># 载入图像</span>
<span class="n">targetImg</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s">"screencap.png"</span><span class="p">)</span>
<span class="n">findImg</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s">"images/"</span><span class="o">+</span><span class="n">img</span><span class="p">)</span>
<span class="n">findHeight</span><span class="p">,</span> <span class="n">findWidth</span><span class="p">,</span> <span class="n">findChannel</span> <span class="o">=</span> <span class="n">findImg</span><span class="o">.</span><span class="n">shape</span><span class="p">[::]</span>
<span class="c"># 模板匹配</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">matchTemplate</span><span class="p">(</span><span class="n">targetImg</span><span class="p">,</span> <span class="n">findImg</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">TM_CCOEFF_NORMED</span><span class="p">)</span>
<span class="n">minVal</span><span class="p">,</span><span class="n">maxVal</span><span class="p">,</span><span class="n">minLoc</span><span class="p">,</span><span class="n">maxLoc</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">minMaxLoc</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="k">print</span> <span class="s">'find: '</span><span class="o">+</span><span class="n">img</span><span class="p">,</span> <span class="n">maxVal</span><span class="p">,</span> <span class="n">maxLoc</span>
<span class="c"># 计算位置</span>
<span class="n">pointUpLeft</span> <span class="o">=</span> <span class="n">maxLoc</span>
<span class="n">pointLowRight</span> <span class="o">=</span> <span class="p">(</span><span class="n">maxLoc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">findWidth</span><span class="p">,</span> <span class="n">maxLoc</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">findHeight</span><span class="p">)</span>
<span class="n">pointCentre</span> <span class="o">=</span> <span class="p">(</span><span class="n">maxLoc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="p">(</span><span class="n">findWidth</span><span class="o">/</span><span class="mi">2</span><span class="p">),</span> <span class="n">maxLoc</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="p">(</span><span class="n">findHeight</span><span class="o">/</span><span class="mi">2</span><span class="p">))</span>
<span class="c"># 匹配度要求</span>
<span class="k">if</span> <span class="n">maxVal</span> <span class="o"><=</span> <span class="n">precision</span><span class="p">:</span>
<span class="k">print</span> <span class="s">'Can</span><span class="se">\'</span><span class="s">t find '</span><span class="o">+</span><span class="n">img</span><span class="p">,</span> <span class="s">'(max:'</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">maxVal</span><span class="p">),</span> <span class="s">'expect:'</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">precision</span><span class="p">)</span><span class="o">+</span><span class="s">')'</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">return</span> <span class="n">pointCentre</span>
</code></pre></div></div>
<blockquote>
<p>封装完之后run.py主逻辑上的代码更加精简了</p>
</blockquote>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env python</span>
<span class="c"># coding=utf-8</span>
<span class="s">"""demo"""</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">'xiaocai'</span>
<span class="kn">import</span> <span class="nn">commands</span><span class="p">,</span> <span class="n">cv2</span>
<span class="kn">from</span> <span class="nn">libs</span> <span class="kn">import</span> <span class="n">adb</span><span class="p">,</span><span class="n">uiDriver</span>
<span class="n">uiKit</span> <span class="o">=</span> <span class="n">uiDriver</span><span class="o">.</span><span class="n">uiDriver</span><span class="p">()</span>
<span class="n">adbKit</span> <span class="o">=</span> <span class="n">adb</span><span class="o">.</span><span class="n">adbKit</span><span class="p">()</span>
<span class="n">pos</span> <span class="o">=</span> <span class="n">uiKit</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s">'btn_close_full.png'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">pos</span><span class="p">:</span>
<span class="n">adbkit</span><span class="o">.</span><span class="n">click</span><span class="p">(</span><span class="n">pos</span><span class="p">)</span>
</code></pre></div></div>
<blockquote>
<p>还可以将find和click操作合并在一起,避免主逻辑上有太多的if判断</p>
</blockquote>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="k">class</span> <span class="nc">uiDriver</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="o">...</span>
<span class="k">def</span> <span class="nf">findClick</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">img</span><span class="p">,</span> <span class="n">precision</span><span class="o">=</span><span class="mf">0.9</span><span class="p">):</span>
<span class="n">point</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">img</span><span class="p">,</span> <span class="n">precision</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">point</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="bp">self</span><span class="o">.</span><span class="n">adb</span><span class="o">.</span><span class="n">click</span><span class="p">(</span><span class="n">point</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">True</span>
</code></pre></div></div>
<blockquote>
<p>最后我们通过一个简单的case来验证一下程序是否能够走通,“在《王国纪元》中分别打开关闭「联盟」和「设置」面板”,如下图:</p>
</blockquote>
<p><img src="http://cmxiaocai.github.io/uploads/201803/15211699400595.jpg" alt="" /></p>
<blockquote>
<p>通过截图工具将这两个按钮保存下来(注意分辨率),从截图中可以看出这两个按钮的上半部分会有一些干扰所以这里只截取了icon的下半部份作为匹配图像。</p>
</blockquote>
<p><img src="http://cmxiaocai.github.io/uploads/201803/15211691847029.jpg" alt="" /></p>
<blockquote>
<p>通过封装好的代码实现</p>
</blockquote>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">uiKit</span><span class="o">.</span><span class="n">findClick</span><span class="p">(</span><span class="s">'btn_seting.png'</span><span class="p">)</span>
<span class="n">uiKit</span><span class="o">.</span><span class="n">findClick</span><span class="p">(</span><span class="s">'btn_close_full.png'</span><span class="p">)</span>
<span class="n">uiKit</span><span class="o">.</span><span class="n">findClick</span><span class="p">(</span><span class="s">'btn_task.png'</span><span class="p">)</span>
<span class="n">uiKit</span><span class="o">.</span><span class="n">findClick</span><span class="p">(</span><span class="s">'btn_close_full.png'</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="wait函数封装">wait函数封装</h2>
<blockquote>
<p>通过上面的例子我们已经可以通过<code class="highlighter-rouge">findClick()</code>之间的调用,连贯的完成一系列的操作。但游戏场景中存在数据加载、延迟、性能等等干扰因素,会使find方法错过最佳图像匹配时机。因此我们需要在<code class="highlighter-rouge">find()</code>与<code class="highlighter-rouge">find()</code>之间加入一个预期的等待时间。</p>
</blockquote>
<blockquote>
<p>为uiDriver类新增wait函数,通过设置检测间隔和超时时间实现等待。大致如下:</p>
</blockquote>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="k">class</span> <span class="nc">uiDriver</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="o">...</span>
<span class="k">def</span> <span class="nf">wait</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">img</span><span class="p">,</span> <span class="n">interval</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">precision</span><span class="o">=</span><span class="mf">0.9</span><span class="p">):</span>
<span class="n">startTime</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="n">point</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">img</span><span class="p">,</span> <span class="n">precision</span><span class="p">)</span>
<span class="k">if</span> <span class="n">point</span><span class="p">:</span>
<span class="k">return</span> <span class="n">point</span>
<span class="n">diff</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">startTime</span>
<span class="k">if</span> <span class="n">diff</span> <span class="o">>=</span> <span class="n">timeout</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">interval</span><span class="p">)</span>
</code></pre></div></div>
<blockquote>
<p>继续以《王国纪元》为例子验证一个简单的CASE,游戏载入成功之后检测并关闭弹窗,场景如下图:</p>
</blockquote>
<p><img src="http://cmxiaocai.github.io/uploads/201803/15216153931885.jpg" alt="" /></p>
<p><strong>示例:</strong></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">point</span> <span class="o">=</span> <span class="n">uiKit</span><span class="o">.</span><span class="n">wait</span><span class="p">(</span>
<span class="n">img</span><span class="o">=</span><span class="s">'btn_close_full.png'</span><span class="p">,</span>
<span class="n">interval</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span>
<span class="n">timeout</span><span class="o">=</span><span class="mi">10</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">point</span><span class="p">:</span>
<span class="n">uiKit</span><span class="o">.</span><span class="n">click</span><span class="p">(</span><span class="n">point</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="总结">总结</h2>
<blockquote>
<p>本章我们通过简单的封装uiDriver类可以使主干代码更加精简可读比如以下代码,不过用心的同学会发现这段代码在<code class="highlighter-rouge">异常</code>的情况下会很难定位到<code class="highlighter-rouge">问题</code>,在下一章会向大家讲解如何生成测试报表,感兴趣的同学可以继续关注博客。</p>
</blockquote>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env python</span>
<span class="c"># coding=utf-8</span>
<span class="s">"""demo"""</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">'xiaocai'</span>
<span class="kn">import</span> <span class="nn">commands</span><span class="p">,</span> <span class="n">cv2</span>
<span class="kn">from</span> <span class="nn">libs</span> <span class="kn">import</span> <span class="n">adb</span><span class="p">,</span><span class="n">uiDriver</span>
<span class="n">uiKit</span> <span class="o">=</span> <span class="n">uiDriver</span><span class="o">.</span><span class="n">uiDriver</span><span class="p">()</span>
<span class="n">adbKit</span> <span class="o">=</span> <span class="n">adb</span><span class="o">.</span><span class="n">adbKit</span><span class="p">()</span>
<span class="s">'''
CASE01: 游戏载入后关闭弹窗
'''</span>
<span class="n">point</span> <span class="o">=</span> <span class="n">uiKit</span><span class="o">.</span><span class="n">wait</span><span class="p">(</span>
<span class="n">img</span><span class="o">=</span><span class="s">'btn_close_full.png'</span><span class="p">,</span>
<span class="n">interval</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span>
<span class="n">timeout</span><span class="o">=</span><span class="mi">10</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">point</span><span class="p">:</span>
<span class="n">uiKit</span><span class="o">.</span><span class="n">click</span><span class="p">(</span><span class="n">point</span><span class="p">)</span>
<span class="s">'''
CASE02: 打开/关闭面板
'''</span>
<span class="n">uiKit</span><span class="o">.</span><span class="n">findClick</span><span class="p">(</span><span class="s">'btn_seting.png'</span><span class="p">)</span>
<span class="n">uiKit</span><span class="o">.</span><span class="n">findClick</span><span class="p">(</span><span class="s">'btn_close_full.png'</span><span class="p">)</span>
<span class="n">uiKit</span><span class="o">.</span><span class="n">findClick</span><span class="p">(</span><span class="s">'btn_task.png'</span><span class="p">)</span>
<span class="n">uiKit</span><span class="o">.</span><span class="n">findClick</span><span class="p">(</span><span class="s">'btn_close_full.png'</span><span class="p">)</span>
<span class="s">'''
CASE N: 以下省略N个CASE
'''</span>
<span class="o">...</span>
<span class="k">print</span> <span class="s">'successful.'</span>
</code></pre></div></div>cmxiaocai在上一篇《(一)用python做android游戏自动化测试》中,我们实现了最基础的(截图 -> 图像识别 -> 计算位置 -> 点击位置)步骤。在实际的测试过程中会一直重复的以上步骤,所以本次的目标是将这些重复的UI交互操作进行封装。 创建UI交互类 首先在adb.py同级目录创建uiDriver.py文件,并申明uiDriver类 目录结构参考: . ├── images │ └── btn_close_full.png ├── libs │ ├── __init__.py │ ├── adb.py │ └── uiDriver.py └── run.py file: libs/uiDriver.py #!/usr/bin/env python # coding=utf-8 """Android UI交互操作""" __author__ = 'xiaocai' import sys, time, cv2 from libs import adb class uiDriver(object): def __init__(self): self.adb = adb.adbKit() find函数封装 接着我们要将上一篇的(截图 -> 图像识别 -> 计算位置)操作步骤封装到find方法中。另外新增一个precision参数用于设定匹配图像的相似度,这样可以在某些特殊场景下通过降低相似度达到匹配效果。 ... class uiDriver(object): ... def find(self, img, precision=0.9): # 截图 self.adb.screenshots() # 载入图像 targetImg = cv2.imread("screencap.png") findImg = cv2.imread("images/"+img) findHeight, findWidth, findChannel = findImg.shape[::] # 模板匹配 result = cv2.matchTemplate(targetImg, findImg, cv2.TM_CCOEFF_NORMED) minVal,maxVal,minLoc,maxLoc = cv2.minMaxLoc(result) print 'find: '+img, maxVal, maxLoc # 计算位置 pointUpLeft = maxLoc pointLowRight = (maxLoc[0]+findWidth, maxLoc[1]+findHeight) pointCentre = (maxLoc[0]+(findWidth/2), maxLoc[1]+(findHeight/2)) # 匹配度要求 if maxVal <= precision: print 'Can\'t find '+img, '(max:'+str(maxVal), 'expect:'+str(precision)+')' return False return pointCentre 封装完之后run.py主逻辑上的代码更加精简了 #!/usr/bin/env python # coding=utf-8 """demo""" __author__ = 'xiaocai' import commands, cv2 from libs import adb,uiDriver uiKit = uiDriver.uiDriver() adbKit = adb.adbKit() pos = uiKit.find('btn_close_full.png') if pos: adbkit.click(pos) 还可以将find和click操作合并在一起,避免主逻辑上有太多的if判断 ... class uiDriver(object): ... def findClick(self, img, precision=0.9): point = self.find(img, precision) if not point: return False self.adb.click(point) return True 最后我们通过一个简单的case来验证一下程序是否能够走通,“在《王国纪元》中分别打开关闭「联盟」和「设置」面板”,如下图: 通过截图工具将这两个按钮保存下来(注意分辨率),从截图中可以看出这两个按钮的上半部分会有一些干扰所以这里只截取了icon的下半部份作为匹配图像。 通过封装好的代码实现 uiKit.findClick('btn_seting.png') uiKit.findClick('btn_close_full.png') uiKit.findClick('btn_task.png') uiKit.findClick('btn_close_full.png') wait函数封装 通过上面的例子我们已经可以通过findClick()之间的调用,连贯的完成一系列的操作。但游戏场景中存在数据加载、延迟、性能等等干扰因素,会使find方法错过最佳图像匹配时机。因此我们需要在find()与find()之间加入一个预期的等待时间。 为uiDriver类新增wait函数,通过设置检测间隔和超时时间实现等待。大致如下: ... class uiDriver(object): ... def wait(self, img, interval=0.5, timeout=2, precision=0.9): startTime = time.time() while True: point = self.find(img, precision) if point: return point diff = time.time() - startTime if diff >= timeout: return False time.sleep(interval) 继续以《王国纪元》为例子验证一个简单的CASE,游戏载入成功之后检测并关闭弹窗,场景如下图: 示例: point = uiKit.wait( img='btn_close_full.png', interval=2, timeout=10 ) if point: uiKit.click(point) 总结 本章我们通过简单的封装uiDriver类可以使主干代码更加精简可读比如以下代码,不过用心的同学会发现这段代码在异常的情况下会很难定位到问题,在下一章会向大家讲解如何生成测试报表,感兴趣的同学可以继续关注博客。 #!/usr/bin/env python # coding=utf-8 """demo""" __author__ = 'xiaocai' import commands, cv2 from libs import adb,uiDriver uiKit = uiDriver.uiDriver() adbKit = adb.adbKit() ''' CASE01: 游戏载入后关闭弹窗 ''' point = uiKit.wait( img='btn_close_full.png', interval=2, timeout=10 ) if point: uiKit.click(point) ''' CASE02: 打开/关闭面板 ''' uiKit.findClick('btn_seting.png') uiKit.findClick('btn_close_full.png') uiKit.findClick('btn_task.png') uiKit.findClick('btn_close_full.png') ''' CASE N: 以下省略N个CASE ''' ... print 'successful.'在jenkins中用phing插件构建php项目2018-03-01T00:00:00+08:002018-03-01T00:00:00+08:00http://www.xiaocai.name/2018/03/01/%E5%9C%A8jenkins%E4%B8%AD%E7%94%A8phing%E6%8F%92%E4%BB%B6%E6%9E%84%E5%BB%BAphp%E9%A1%B9%E7%9B%AE<blockquote>
<p>phing是PHP 项目构建工具,在jenkins配合phing使用能够优雅的实现打包部署。</p>
</blockquote>
<h2 id="phing安装">phing安装</h2>
<blockquote>
<p>装好php后下载phar包即可</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> wget http://www.phing.info/get/phing-2.16.1.phar
cp phing-2.16.1.phar /usr/bin/phing
chmod +x /usr/bin/phing
phing -h
</code></pre></div></div>
<h2 id="开始">开始</h2>
<blockquote>
<p>创建一个’build.xml’配置文件</p>
</blockquote>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml <span class="nv">version</span><span class="o">=</span><span class="s2">"1.0"</span> <span class="nv">encoding</span><span class="o">=</span><span class="s2">"UTF-8"</span>?>
<project <span class="nv">name</span><span class="o">=</span><span class="s2">"test.com"</span> <span class="nv">basedir</span><span class="o">=</span><span class="s2">"./"</span> <span class="nv">default</span><span class="o">=</span><span class="s2">"start"</span><span class="o">></span>
<target <span class="nv">name</span><span class="o">=</span><span class="s2">"start"</span><span class="o">></span>
<<span class="nb">echo </span><span class="nv">msg</span><span class="o">=</span><span class="s2">"start..."</span> />
</target>
</project>
</code></pre></div></div>
<blockquote>
<p>phing运行构建</p>
</blockquote>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>root@local test_build01]# phing <span class="nt">-f</span> build.xml
Buildfile: /home/phing_build/test_build01/build.xml
test.com <span class="o">></span> start:
<span class="o">[</span><span class="nb">echo</span><span class="o">]</span> start...
BUILD FINISHED
Total <span class="nb">time</span>: 0.0310 seconds
</code></pre></div></div>
<h2 id="完整示例">完整示例</h2>
<blockquote>
<p>composer install -> clear -> tar打包</p>
</blockquote>
<p><strong>build.xml</strong></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml <span class="nv">version</span><span class="o">=</span><span class="s2">"1.0"</span> <span class="nv">encoding</span><span class="o">=</span><span class="s2">"UTF-8"</span>?>
<project <span class="nv">name</span><span class="o">=</span><span class="s2">"test.com"</span> <span class="nv">basedir</span><span class="o">=</span><span class="s2">"./"</span> <span class="nv">default</span><span class="o">=</span><span class="s2">"clear"</span><span class="o">></span>
<property <span class="nv">name</span><span class="o">=</span><span class="s2">"build.dir"</span> <span class="nv">value</span><span class="o">=</span><span class="s2">"build"</span> />
<property <span class="nv">name</span><span class="o">=</span><span class="s2">"project.dir"</span> <span class="nv">value</span><span class="o">=</span><span class="s2">"test.com"</span> />
<property <span class="nv">name</span><span class="o">=</span><span class="s2">"project.name"</span> <span class="nv">value</span><span class="o">=</span><span class="s2">"test.com"</span> />
<target <span class="nv">name</span><span class="o">=</span><span class="s2">"prepare"</span><span class="o">></span>
<<span class="nb">echo </span><span class="nv">msg</span><span class="o">=</span><span class="s2">"Making directory ./</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">"</span> />
<delete <span class="nv">dir</span><span class="o">=</span><span class="s2">"./</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">"</span> />
<mkdir <span class="nv">dir</span><span class="o">=</span><span class="s2">"./</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">"</span> />
</target>
<target <span class="nv">name</span><span class="o">=</span><span class="s2">"build:copy"</span> <span class="nv">depends</span><span class="o">=</span><span class="s2">"prepare"</span><span class="o">></span>
<copy <span class="nv">todir</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">"</span> <span class="o">></span>
<fileset <span class="nv">defaultexcludes</span><span class="o">=</span><span class="s2">"true"</span> <span class="nv">expandsymboliclinks</span><span class="o">=</span><span class="s2">"true"</span> <span class="nv">dir</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">project</span><span class="p">.dir</span><span class="k">}</span><span class="s2">"</span><span class="o">></span>
<include <span class="nv">name</span><span class="o">=</span><span class="s2">"**"</span> />
</fileset>
</copy>
<delete <span class="nv">file</span><span class="o">=</span><span class="s2">"./</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">/composer.lock"</span> />
<delete <span class="nv">dir</span><span class="o">=</span><span class="s2">"./</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">/vendor"</span> />
<<span class="nb">exec </span><span class="nv">checkreturn</span><span class="o">=</span><span class="s2">"true"</span> <span class="nb">command</span><span class="o">=</span><span class="s2">"ls -l"</span> <span class="nv">dir</span><span class="o">=</span><span class="s2">"./</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">"</span> <span class="nv">logoutput</span><span class="o">=</span><span class="s2">"true"</span> />
</target>
<target <span class="nv">name</span><span class="o">=</span><span class="s2">"build:composer"</span> <span class="nv">depends</span><span class="o">=</span><span class="s2">"build:copy"</span><span class="o">></span>
<<span class="k">if</span><span class="o">></span>
<available <span class="nv">file</span><span class="o">=</span><span class="s2">"./</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">/composer.json"</span> />
<<span class="k">then</span><span class="o">></span>
<<span class="nb">exec </span><span class="nv">checkreturn</span><span class="o">=</span><span class="s2">"true"</span> <span class="nb">command</span><span class="o">=</span><span class="s2">"composer install --no-dev --prefer-dist -o --no-progress"</span> <span class="nv">passthru</span><span class="o">=</span><span class="s2">"true"</span> <span class="nv">logoutput</span><span class="o">=</span><span class="s2">"true"</span> <span class="nv">dir</span><span class="o">=</span><span class="s2">"./</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">"</span> />
</then>
<<span class="k">else</span><span class="o">></span>
<<span class="nb">echo </span><span class="nv">message</span><span class="o">=</span><span class="s2">"ComposerInstall: composer.json does not exist!"</span> />
</else>
</if>
</target>
<target <span class="nv">name</span><span class="o">=</span><span class="s2">"deploy:create-package"</span> <span class="nv">depends</span><span class="o">=</span><span class="s2">"build:composer"</span><span class="o">></span>
<tstamp>
<format <span class="nv">property</span><span class="o">=</span><span class="s2">"build.timestamp"</span> <span class="nv">pattern</span><span class="o">=</span><span class="s2">"%Y%m%d%H%M%S"</span>/>
</tstamp>
<property <span class="nv">name</span><span class="o">=</span><span class="s2">"build.release"</span> <span class="nv">value</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">project</span><span class="p">.name</span><span class="k">}</span><span class="s2">-</span><span class="k">${</span><span class="nv">build</span><span class="p">.timestamp</span><span class="k">}</span><span class="s2">"</span> />
<property <span class="nv">name</span><span class="o">=</span><span class="s2">"package.name"</span> <span class="nv">value</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">build</span><span class="p">.release</span><span class="k">}</span><span class="s2">.tar.gz"</span> />
<<span class="nb">echo</span><span class="o">></span>Creating dist package <span class="k">${</span><span class="nv">package</span><span class="p">.name</span><span class="k">}</span></echo>
<<span class="nb">tar </span><span class="nv">destfile</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">package</span><span class="p">.name</span><span class="k">}</span><span class="s2">"</span> <span class="nv">basedir</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">"</span> />
</target>
<target <span class="nv">name</span><span class="o">=</span><span class="s2">"clear"</span> <span class="nv">depends</span><span class="o">=</span><span class="s2">"deploy:create-package"</span><span class="o">></span>
<delete <span class="nv">file</span><span class="o">=</span><span class="s2">"./</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">/composer.lock"</span> />
<delete <span class="nv">file</span><span class="o">=</span><span class="s2">"./</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">/composer.json"</span> />
<delete <span class="nv">file</span><span class="o">=</span><span class="s2">"./</span><span class="k">${</span><span class="nv">build</span><span class="p">.dir</span><span class="k">}</span><span class="s2">/*.md"</span> />
</target>
</project>
</code></pre></div></div>
<p><strong>result:</strong></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>root@local test_build01]# phing <span class="nt">-f</span> build_01.xml
Buildfile: /home/phing_build/test_build01/build_01.xml
test.com <span class="o">></span> prepare:
<span class="o">[</span><span class="nb">echo</span><span class="o">]</span> Making directory ./build
<span class="o">[</span>delete] Deleting directory /home/phing_build/test_build01/build
<span class="o">[</span>mkdir] Created dir: /home/phing_build/test_build01/build
test.com <span class="o">></span> build:copy:
<span class="o">[</span>copy] Created 82 empty directories <span class="k">in</span> /home/phing_build/test_build01/build
<span class="o">[</span>copy] Copying 325 files to /home/phing_build/test_build01/build
<span class="o">[</span>delete] Deleting: /home/phing_build/test_build01/build/composer.lock
<span class="o">[</span>delete] Deleting directory /home/phing_build/test_build01/build/vendor
<span class="o">[</span><span class="nb">exec</span><span class="o">]</span> total 4
<span class="o">[</span><span class="nb">exec</span><span class="o">]</span> drwxr-xr-x 6 root root 132 Mar 1 17:28 app
<span class="o">[</span><span class="nb">exec</span><span class="o">]</span> <span class="nt">-rw-r--r--</span> 1 root root 206 Mar 1 17:28 composer.json
<span class="o">[</span><span class="nb">exec</span><span class="o">]</span> drwxr-xr-x 2 root root 59 Mar 1 17:28 config
<span class="o">[</span><span class="nb">exec</span><span class="o">]</span> drwxr-xr-x 6 root root 89 Mar 1 17:28 public
<span class="o">[</span><span class="nb">exec</span><span class="o">]</span> drwxr-xr-x 2 root root 22 Mar 1 17:28 scripts
<span class="o">[</span><span class="nb">exec</span><span class="o">]</span> drwxr-xr-x 5 root root 45 Mar 1 17:28 templates
test.com <span class="o">></span> build:composer:
Do not run Composer as root/super user! See https://getcomposer.org/root <span class="k">for </span>details
Loading composer repositories with package information
Updating dependencies
Package operations: 10 installs, 0 updates, 0 removals
- Installing psr/http-message <span class="o">(</span>1.0.1<span class="o">)</span>: Loading from cache
- Installing slim/php-view <span class="o">(</span>2.2.0<span class="o">)</span>: Loading from cache
- Installing guzzlehttp/promises <span class="o">(</span>v1.3.1<span class="o">)</span>: Loading from cache
- Installing guzzlehttp/psr7 <span class="o">(</span>1.4.2<span class="o">)</span>: Loading from cache
- Installing guzzlehttp/guzzle <span class="o">(</span>6.3.0<span class="o">)</span>: Loading from cache
- Installing psr/container <span class="o">(</span>1.0.0<span class="o">)</span>: Loading from cache
- Installing container-interop/container-interop <span class="o">(</span>1.2.0<span class="o">)</span>: Loading from cache
- Installing nikic/fast-route <span class="o">(</span>v1.3.0<span class="o">)</span>: Downloading <span class="o">(</span>100%<span class="o">)</span>
- Installing pimple/pimple <span class="o">(</span>v3.2.3<span class="o">)</span>: Loading from cache
- Installing slim/slim <span class="o">(</span>3.9.2<span class="o">)</span>: Loading from cache
Writing lock file
Generating optimized autoload files
test.com <span class="o">></span> deploy:create-package:
<span class="o">[</span><span class="nb">echo</span><span class="o">]</span> Creating dist package test.com-20180301092923.tar.gz
<span class="o">[</span><span class="nb">tar</span><span class="o">]</span> Building <span class="nb">tar</span>: /home/phing_build/test_build01/test.com-20180301092923.tar.gz
test.com <span class="o">></span> clear:
<span class="o">[</span>delete] Deleting: /home/phing_build/test_build01/build/composer.lock
<span class="o">[</span>delete] Deleting: /home/phing_build/test_build01/build/composer.json
<span class="o">[</span>delete] Could not find file /home/phing_build/test_build01/build/<span class="k">*</span>.md to delete.
BUILD FINISHED
Total <span class="nb">time</span>: 58.8522 seconds
</code></pre></div></div>
<h2 id="jenkins-plguin-phing插件">jenkins plguin phing插件</h2>
<blockquote>
<p>在系统设置中安装plguin phing插件</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201803/phing.png" alt="" /></p>
<h2 id="更多">更多</h2>
<blockquote>
<p>参考官方examples: https://github.com/mrook/phing-examples</p>
</blockquote>cmxiaocaiphing是PHP 项目构建工具,在jenkins配合phing使用能够优雅的实现打包部署。 phing安装 装好php后下载phar包即可 wget http://www.phing.info/get/phing-2.16.1.phar cp phing-2.16.1.phar /usr/bin/phing chmod +x /usr/bin/phing phing -h 开始 创建一个’build.xml’配置文件 <?xml version="1.0" encoding="UTF-8"?> <project name="test.com" basedir="./" default="start"> <target name="start"> <echo msg="start..." /> </target> </project> phing运行构建 [root@local test_build01]# phing -f build.xml Buildfile: /home/phing_build/test_build01/build.xml test.com > start: [echo] start... BUILD FINISHED Total time: 0.0310 seconds 完整示例 composer install -> clear -> tar打包 build.xml <?xml version="1.0" encoding="UTF-8"?> <project name="test.com" basedir="./" default="clear"> <property name="build.dir" value="build" /> <property name="project.dir" value="test.com" /> <property name="project.name" value="test.com" /> <target name="prepare"> <echo msg="Making directory ./${build.dir}" /> <delete dir="./${build.dir}" /> <mkdir dir="./${build.dir}" /> </target> <target name="build:copy" depends="prepare"> <copy todir="${build.dir}" > <fileset defaultexcludes="true" expandsymboliclinks="true" dir="${project.dir}"> <include name="**" /> </fileset> </copy> <delete file="./${build.dir}/composer.lock" /> <delete dir="./${build.dir}/vendor" /> <exec checkreturn="true" command="ls -l" dir="./${build.dir}" logoutput="true" /> </target> <target name="build:composer" depends="build:copy"> <if> <available file="./${build.dir}/composer.json" /> <then> <exec checkreturn="true" command="composer install --no-dev --prefer-dist -o --no-progress" passthru="true" logoutput="true" dir="./${build.dir}" /> </then> <else> <echo message="ComposerInstall: composer.json does not exist!" /> </else> </if> </target> <target name="deploy:create-package" depends="build:composer"> <tstamp> <format property="build.timestamp" pattern="%Y%m%d%H%M%S"/> </tstamp> <property name="build.release" value="${project.name}-${build.timestamp}" /> <property name="package.name" value="${build.release}.tar.gz" /> <echo>Creating dist package ${package.name}</echo> <tar destfile="${package.name}" basedir="${build.dir}" /> </target> <target name="clear" depends="deploy:create-package"> <delete file="./${build.dir}/composer.lock" /> <delete file="./${build.dir}/composer.json" /> <delete file="./${build.dir}/*.md" /> </target> </project> result: [root@local test_build01]# phing -f build_01.xml Buildfile: /home/phing_build/test_build01/build_01.xml test.com > prepare: [echo] Making directory ./build [delete] Deleting directory /home/phing_build/test_build01/build [mkdir] Created dir: /home/phing_build/test_build01/build test.com > build:copy: [copy] Created 82 empty directories in /home/phing_build/test_build01/build [copy] Copying 325 files to /home/phing_build/test_build01/build [delete] Deleting: /home/phing_build/test_build01/build/composer.lock [delete] Deleting directory /home/phing_build/test_build01/build/vendor [exec] total 4 [exec] drwxr-xr-x 6 root root 132 Mar 1 17:28 app [exec] -rw-r--r-- 1 root root 206 Mar 1 17:28 composer.json [exec] drwxr-xr-x 2 root root 59 Mar 1 17:28 config [exec] drwxr-xr-x 6 root root 89 Mar 1 17:28 public [exec] drwxr-xr-x 2 root root 22 Mar 1 17:28 scripts [exec] drwxr-xr-x 5 root root 45 Mar 1 17:28 templates test.com > build:composer: Do not run Composer as root/super user! See https://getcomposer.org/root for details Loading composer repositories with package information Updating dependencies Package operations: 10 installs, 0 updates, 0 removals - Installing psr/http-message (1.0.1): Loading from cache - Installing slim/php-view (2.2.0): Loading from cache - Installing guzzlehttp/promises (v1.3.1): Loading from cache - Installing guzzlehttp/psr7 (1.4.2): Loading from cache - Installing guzzlehttp/guzzle (6.3.0): Loading from cache - Installing psr/container (1.0.0): Loading from cache - Installing container-interop/container-interop (1.2.0): Loading from cache - Installing nikic/fast-route (v1.3.0): Downloading (100%) - Installing pimple/pimple (v3.2.3): Loading from cache - Installing slim/slim (3.9.2): Loading from cache Writing lock file Generating optimized autoload files test.com > deploy:create-package: [echo] Creating dist package test.com-20180301092923.tar.gz [tar] Building tar: /home/phing_build/test_build01/test.com-20180301092923.tar.gz test.com > clear: [delete] Deleting: /home/phing_build/test_build01/build/composer.lock [delete] Deleting: /home/phing_build/test_build01/build/composer.json [delete] Could not find file /home/phing_build/test_build01/build/*.md to delete. BUILD FINISHED Total time: 58.8522 seconds jenkins plguin phing插件 在系统设置中安装plguin phing插件 更多 参考官方examples: https://github.com/mrook/phing-examples(一)用python做android游戏自动化测试2018-02-14T00:00:00+08:002018-02-14T00:00:00+08:00http://www.xiaocai.name/2018/02/14/%E7%94%A8python%E5%81%9Aandroid%E6%B8%B8%E6%88%8F%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95(1)<blockquote>
<p>游戏自动化测试痛点在于难以定位控件,这里使用图像识别替代控件定位的方式,来完成游戏的自动化测试。
当然也可以混合使用图像识别+控件定位的方式满足需求,在这里只分享用adb+opencv实现游戏自动化测试过程。</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201702/gametest01/15185039335877.jpg" alt="" /></p>
<h2 id="一测试原理">一、测试原理</h2>
<blockquote>
<p>图像定位测试的核心思路其实是利用adb来操作设备,用opencv实现图像区域匹配,匹配成功后计算目标位置然后触发adb。
简单来说就是如下步骤:
截图 -> 图像识别 -> 计算位置 -> 点击位置</p>
</blockquote>
<h2 id="二创建python运行环境">二、创建python运行环境</h2>
<blockquote>
<p>virtualenv提供隔离的Python运行环境(如果不需要的话可以忽略此步骤)</p>
</blockquote>
<p><strong>1.创建目录:</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir venv_gametest
cd venv_gametest
</code></pre></div></div>
<p><strong>2.创建并运行独立python环境</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>virtualenv --no-site-packages venv
source venv/bin/activate
</code></pre></div></div>
<h2 id="三安装依赖环境">三、安装依赖环境</h2>
<p><strong>1. 安装android adb</strong></p>
<blockquote>
<p>笔者是mac os系统,所以直接通过Homebrew安装。
也可以通过下载android SDK的方式手动安装这里不做说明,下载地址:developer.android.com</p>
</blockquote>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew cask install android-platform-tools
</code></pre></div></div>
<p><strong>2. 测试adb</strong></p>
<blockquote>
<p>连上手机的数据线或打开模拟器,查看能不能找到设备</p>
</blockquote>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>venv<span class="o">)</span> <span class="nv">$ </span>demo5_game adb version
Android Debug Bridge version 1.0.32
<span class="o">(</span>venv<span class="o">)</span> <span class="nv">$ </span>demo5_game adb devices
List of devices attached
192.168.56.100:5555 device
</code></pre></div></div>
<blockquote>
<p>更多的adb操作,可以查看 https://github.com/mzlogin/awesome-adb</p>
</blockquote>
<p><strong>3. 安装opencv</strong></p>
<blockquote>
<p>opencv用来帮助我们完成图像的处理
只需要numpy、Matplotlib、opencv-python三个包
详见:<a href="http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/introduction/table_of_content_introduction/table_of_content_introduction.html#table-of-content-introduction">install opencv</a></p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo pip install --upgrade setuptools
sudo pip install numpy Matplotlib
sudo pip install opencv-python
</code></pre></div></div>
<p><strong>4. 测试opencv</strong></p>
<blockquote>
<p>选一张图片拷贝以下代码,用opencv打开测试是否安装成功</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env python</span>
<span class="c"># coding=utf-8</span>
<span class="kn">import</span> <span class="nn">cv2</span> <span class="k">as</span> <span class="n">cv</span>
<span class="n">img</span> <span class="o">=</span> <span class="n">cv</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s">"test.jpg"</span><span class="p">)</span>
<span class="n">cv</span><span class="o">.</span><span class="n">namedWindow</span><span class="p">(</span><span class="s">"Image"</span>
<span class="n">cv</span><span class="o">.</span><span class="n">imshow</span><span class="p">(</span><span class="s">"Image"</span><span class="p">,</span><span class="n">img</span><span class="p">)</span>
<span class="n">cv</span><span class="o">.</span><span class="n">waitKey</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">destroyAllWindows</span><span class="p">()</span>
</code></pre></div></div>
<p><em>运行结果:</em>
<img src="http://www.xiaocai.name/uploads/201702/gametest01/15185092443953.jpg" alt="" /></p>
<h2 id="四adb截图操作">四、adb截图操作</h2>
<blockquote>
<p>先看如何用adb命令进行截图操作,详见:<a href="https://github.com/mzlogin/awesome-adb#%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE">adb屏幕截图</a></p>
</blockquote>
<p><em>先截图保存到设备里:</em></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell screencap -p /sdcard/screencap.png
</code></pre></div></div>
<p><em>然后将 png 文件导出到电脑:</em></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb pull /sdcard/screencap.png
</code></pre></div></div>
<blockquote>
<p>同样的使用python中<code class="highlighter-rouge">commands</code>模块也能实现相同的效果</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>commands.getstatusoutput('adb shell screencap -p /sdcard/screencap.png')
commands.getstatusoutput('adb pull /sdcard/screencap.png')
</code></pre></div></div>
<blockquote>
<p>将adb的操作做个简单的封装,便于后面使用</p>
</blockquote>
<p><em>file: libs/adb.py</em></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env python</span>
<span class="c"># coding=utf-8</span>
<span class="s">"""ADB"""</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">'xiaocai'</span>
<span class="kn">import</span> <span class="nn">commands</span>
<span class="k">class</span> <span class="nc">adbKit</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">screenshots</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">serialNumber</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">command</span><span class="p">(</span><span class="s">'shell screencap -p /sdcard/screencap.png'</span><span class="p">,</span> <span class="n">serialNumber</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">command</span><span class="p">(</span><span class="s">'pull /sdcard/screencap.png'</span><span class="p">,</span> <span class="n">serialNumber</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">command</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cmd</span><span class="p">,</span> <span class="n">serialNumber</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="n">cmdstr</span> <span class="o">=</span> <span class="s">'adb '</span>
<span class="k">if</span> <span class="n">serialNumber</span><span class="p">:</span>
<span class="n">cmdstr</span> <span class="o">=</span> <span class="n">cmdstr</span><span class="o">+</span><span class="s">'-s '</span><span class="o">+</span><span class="n">serialNumber</span>
<span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span> <span class="o">=</span> <span class="n">commands</span><span class="o">.</span><span class="n">getstatusoutput</span><span class="p">(</span><span class="n">cmdstr</span><span class="o">+</span><span class="n">cmd</span><span class="p">)</span>
<span class="k">return</span> <span class="p">[</span><span class="n">status</span><span class="p">,</span> <span class="n">output</span><span class="p">]</span>
</code></pre></div></div>
<h2 id="五图像匹配">五、图像匹配</h2>
<blockquote>
<p>刚刚完成了第一个截图环节,接下来我们开始尝试用opencv去匹配目标图像的位置
详见:<a href="https://docs.opencv.org/3.0-last-rst/modules/refman.html">opencv文档</a></p>
</blockquote>
<p><strong>1. 选取一张截图</strong></p>
<blockquote>
<p>我们利用上面封装好的<code class="highlighter-rouge">adbKit</code>对当前设备,进行一次截图操作。</p>
</blockquote>
<p><em>file: test.py</em></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env python</span>
<span class="c"># coding=utf-8</span>
<span class="s">"""demo"""</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">'xiaocai'</span>
<span class="kn">import</span> <span class="nn">sys</span><span class="p">,</span> <span class="n">time</span><span class="p">,</span> <span class="n">commands</span>
<span class="kn">from</span> <span class="nn">libs</span> <span class="kn">import</span> <span class="n">adb</span>
<span class="n">adbkit</span> <span class="o">=</span> <span class="n">adb</span><span class="o">.</span><span class="n">adbKit</span><span class="p">()</span>
<span class="n">adbkit</span><span class="o">.</span><span class="n">screenshots</span><span class="p">()</span>
</code></pre></div></div>
<blockquote>
<p>运行完之后可以看到根目录下出现了<code class="highlighter-rouge">screencap.png</code>图片</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
├── libs
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── adb.py
│ └── adb.pyc
├── screencap.png
└── test.py
</code></pre></div></div>
<blockquote>
<p>接下来我们要实现第一个用例,如下图要识别到游戏中右上角的<code class="highlighter-rouge">X</code>按钮</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201702/gametest01/15185116328339.jpg" alt="" /></p>
<blockquote>
<p>用截图工具将<code class="highlighter-rouge">x</code>截取,保持到<code class="highlighter-rouge">images/btn_close_full.png</code>(需要注意分辨率)
<img src="http://www.xiaocai.name/uploads/201702/gametest01/15185119007126.jpg" alt="" /></p>
</blockquote>
<p><strong>2. 载入图像</strong></p>
<blockquote>
<p>读取截图和要匹配的<code class="highlighter-rouge">x</code>图像</p>
</blockquote>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">cv2</span>
<span class="n">target_img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s">"screencap.png"</span><span class="p">)</span>
<span class="n">find_img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s">"images/btn_close_full.png"</span><span class="p">)</span>
</code></pre></div></div>
<p><strong>3. 图像匹配</strong></p>
<blockquote>
<p>使用图像模板匹配<code class="highlighter-rouge">cv::matchTemplate()</code>,通过返回的cvMinMaxLoc计算结果
详见:<a href="https://docs.opencv.org/3.2.0/d4/dc6/tutorial_py_template_matching.html">Template Matching</a>
<code class="highlighter-rouge">matchTemplate</code>会将模板图像在源图像中进行滑动匹配(从左往右,从上往下)</p>
</blockquote>
<p><em>参数说明:</em></p>
<blockquote>
<p><code class="highlighter-rouge">image</code>是源图像,<code class="highlighter-rouge">templ</code>是模板图像,<code class="highlighter-rouge">method</code>是匹配算法</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Python: cv2.matchTemplate(image, templ, method[, result]) → result
Parameters:
image – Image where the search is running. It must be 8-bit or 32-bit floating-point.
templ – Searched template. It must be not greater than the source image and have the same data type.
result – Map of comparison results. It must be single-channel 32-bit floating-point. If image is W \times H and templ is w \times h , then result is (W-w+1) \times (H-h+1) .
method – Parameter specifying the comparison method (see below).
</code></pre></div></div>
<p><em>代码:</em></p>
<blockquote>
<p>这里我们用<code class="highlighter-rouge">cv2.TM_CCOEFF_NORMED</code>
不同的算法对匹配结果会有所差异,具体可参考:<a href="http://blog.csdn.net/firemicrocosm/article/details/48374979">Python+OpenCV学习(7)—模板匹配</a></p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>result = cv2.matchTemplate(target_img, find_img, cv2.TM_CCOEFF_NORMED)
print result
</code></pre></div></div>
<p><em>结果:</em></p>
<blockquote>
<p>从打印的结果看到,matchTemplate会在模板块和输入图像之间寻找匹配最后返回一组匹配结果图像</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[[ 0.07075607 0.08976483 0.10165194 ... 0.07626463 0.06611969
0.04449887]
[ 0.04982539 0.06268624 0.07642973 ... 0.06295352 0.05140355
0.02827941]
[ 0.01129027 0.02193917 0.03671272 ... 0.03954886 0.02578432
0.00115839]
...
[-0.08573136 -0.07838587 -0.06693702 ... -0.03226329 -0.02669666
-0.03457748]
[-0.08250767 -0.07598738 -0.06588773 ... -0.0265673 -0.02382514
-0.02491279]
[-0.07762627 -0.07059815 -0.06101505 ... -0.00854323 -0.0053194
-0.00458878]]
</code></pre></div></div>
<blockquote>
<p>每个匹配图像会有匹配度,比如找出匹配度>0.8的图像(如果需要一次匹配多个结果可以使用这个方法)</p>
</blockquote>
<p><em>示例:</em></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env python</span>
<span class="c"># coding=utf-8</span>
<span class="s">"""demo"""</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">'xiaocai'</span>
<span class="kn">import</span> <span class="nn">commands</span><span class="p">,</span> <span class="n">cv2</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">from</span> <span class="nn">libs</span> <span class="kn">import</span> <span class="n">adb</span>
<span class="n">adbkit</span> <span class="o">=</span> <span class="n">adb</span><span class="o">.</span><span class="n">adbKit</span><span class="p">()</span>
<span class="n">adbkit</span><span class="o">.</span><span class="n">screenshots</span><span class="p">()</span>
<span class="n">target_img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s">"screencap.png"</span><span class="p">)</span>
<span class="n">find_img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s">"images/btn_close_full.png"</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">matchTemplate</span><span class="p">(</span><span class="n">target_img</span><span class="p">,</span> <span class="n">find_img</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">TM_CCOEFF_NORMED</span><span class="p">)</span>
<span class="n">loc</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">where</span><span class="p">(</span> <span class="n">result</span> <span class="o">>=</span> <span class="mf">0.5</span><span class="p">)</span>
<span class="k">for</span> <span class="n">pt</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="o">*</span><span class="n">loc</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]):</span>
<span class="k">print</span> <span class="n">pt</span>
</code></pre></div></div>
<p><strong>4. minMaxLoc方法</strong></p>
<blockquote>
<p>找到匹配图像之后我们需要使用<code class="highlighter-rouge">minMaxLoc</code>函数在给定的矩阵中寻找最大和最小值(包括它们的位置).</p>
</blockquote>
<p><em>代码:</em></p>
<blockquote>
<p>依次是:最小匹配度,最大匹配度,最小匹配位置,最大匹配位置</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>result = cv2.matchTemplate(target_img, find_img, cv2.TM_CCOEFF_NORMED)
min_val,max_val,min_loc,max_loc = cv2.minMaxLoc(result)
print min_val,max_val,min_loc,max_loc
</code></pre></div></div>
<p><em>结果:</em></p>
<blockquote>
<p>这里的<code class="highlighter-rouge">(1019, 74)</code>就是我们模板在源图中的左上角坐标了</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-0.381701976061 0.779404222965 (1181, 519) (1019, 74)
</code></pre></div></div>
<h2 id="六计算点击位置">六、计算点击位置</h2>
<blockquote>
<p>上面我们通过<code class="highlighter-rouge">matchTemplate</code>和<code class="highlighter-rouge">minMaxLoc</code>方法已经获取到模板图像在源图中的坐标了,但这个坐标只是左上角的位置,实际点击应该是模板图像的中间位置</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201702/gametest01/15185783539006.jpg" alt="" /></p>
<blockquote>
<p>首先我们需要先获取模板图像的尺寸</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>find_img = cv2.imread("images/btn_close_full.png")
find_height, find_width, find_channel = find_img.shape[::]
</code></pre></div></div>
<blockquote>
<p>根据max_loc结果计算出中间位置</p>
</blockquote>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pointUpLeft</span> <span class="o">=</span> <span class="n">max_loc</span>
<span class="n">pointLowRight</span> <span class="o">=</span> <span class="p">(</span><span class="n">max_loc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">find_width</span><span class="p">,</span> <span class="n">max_loc</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">find_height</span><span class="p">)</span>
<span class="n">pointCentre</span> <span class="o">=</span> <span class="p">(</span><span class="n">max_loc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="p">(</span><span class="n">find_width</span><span class="o">/</span><span class="mi">2</span><span class="p">),</span> <span class="n">max_loc</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="p">(</span><span class="n">find_height</span><span class="o">/</span><span class="mi">2</span><span class="p">))</span>
</code></pre></div></div>
<blockquote>
<p>为了更直观些,我们把坐标在源图中点出来并显示出图片</p>
</blockquote>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cv2</span><span class="o">.</span><span class="n">circle</span><span class="p">(</span><span class="n">target_img</span><span class="p">,</span> <span class="n">pointUpLeft</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">),</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">circle</span><span class="p">(</span><span class="n">target_img</span><span class="p">,</span> <span class="n">pointCentre</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">),</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">circle</span><span class="p">(</span><span class="n">target_img</span><span class="p">,</span> <span class="n">pointLowRight</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">),</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">namedWindow</span><span class="p">(</span><span class="s">"Image"</span><span class="p">)</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">imshow</span><span class="p">(</span><span class="s">"Image"</span><span class="p">,</span> <span class="n">target_img</span><span class="p">)</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">waitKey</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">destroyAllWindows</span><span class="p">()</span>
</code></pre></div></div>
<p><em>完整代码:</em></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env python</span>
<span class="c"># coding=utf-8</span>
<span class="s">"""demo"""</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">'xiaocai'</span>
<span class="kn">import</span> <span class="nn">commands</span><span class="p">,</span> <span class="n">cv2</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">from</span> <span class="nn">libs</span> <span class="kn">import</span> <span class="n">adb</span>
<span class="c"># 截图</span>
<span class="n">adbkit</span> <span class="o">=</span> <span class="n">adb</span><span class="o">.</span><span class="n">adbKit</span><span class="p">()</span>
<span class="n">adbkit</span><span class="o">.</span><span class="n">screenshots</span><span class="p">()</span>
<span class="c"># 载入图像</span>
<span class="n">target_img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s">"screencap.png"</span><span class="p">)</span>
<span class="n">find_img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s">"images/btn_close_full.png"</span><span class="p">)</span>
<span class="n">find_height</span><span class="p">,</span> <span class="n">find_width</span><span class="p">,</span> <span class="n">find_channel</span> <span class="o">=</span> <span class="n">find_img</span><span class="o">.</span><span class="n">shape</span><span class="p">[::]</span>
<span class="c"># 模板匹配</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">matchTemplate</span><span class="p">(</span><span class="n">target_img</span><span class="p">,</span> <span class="n">find_img</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">TM_CCOEFF_NORMED</span><span class="p">)</span>
<span class="n">min_val</span><span class="p">,</span><span class="n">max_val</span><span class="p">,</span><span class="n">min_loc</span><span class="p">,</span><span class="n">max_loc</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">minMaxLoc</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="c"># 计算位置</span>
<span class="n">pointUpLeft</span> <span class="o">=</span> <span class="n">max_loc</span>
<span class="n">pointLowRight</span> <span class="o">=</span> <span class="p">(</span><span class="n">max_loc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">find_width</span><span class="p">,</span> <span class="n">max_loc</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">find_height</span><span class="p">)</span>
<span class="n">pointCentre</span> <span class="o">=</span> <span class="p">(</span><span class="n">max_loc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="p">(</span><span class="n">find_width</span><span class="o">/</span><span class="mi">2</span><span class="p">),</span> <span class="n">max_loc</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="p">(</span><span class="n">find_height</span><span class="o">/</span><span class="mi">2</span><span class="p">))</span>
<span class="c"># 画点</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">circle</span><span class="p">(</span><span class="n">target_img</span><span class="p">,</span> <span class="n">pointUpLeft</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">),</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">circle</span><span class="p">(</span><span class="n">target_img</span><span class="p">,</span> <span class="n">pointCentre</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">),</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">circle</span><span class="p">(</span><span class="n">target_img</span><span class="p">,</span> <span class="n">pointLowRight</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">),</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="c"># 显示图片</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">namedWindow</span><span class="p">(</span><span class="s">"Image"</span><span class="p">)</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">imshow</span><span class="p">(</span><span class="s">"Image"</span><span class="p">,</span> <span class="n">target_img</span><span class="p">)</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">waitKey</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">cv2</span><span class="o">.</span><span class="n">destroyAllWindows</span><span class="p">()</span>
</code></pre></div></div>
<p><em>结果:</em></p>
<p><img src="http://www.xiaocai.name/uploads/201702/gametest01/15185791428524.jpg" alt="" /></p>
<h2 id="七触发点击">七、触发点击</h2>
<blockquote>
<p>离成功只差最后一步了,先看看adb是如何模拟按键输入的
详见:<a href="https://github.com/mzlogin/awesome-adb#%E6%A8%A1%E6%8B%9F%E6%8C%89%E9%94%AE%E8%BE%93%E5%85%A5">模拟按键/输入</a></p>
</blockquote>
<p><em>命令:</em></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell input tap <x> <y>
</code></pre></div></div>
<p><em>python调用:</em></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">adbKit</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">screenshots</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">serialNumber</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">command</span><span class="p">(</span><span class="s">'shell screencap -p /sdcard/screencap.png'</span><span class="p">,</span> <span class="n">serialNumber</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">command</span><span class="p">(</span><span class="s">'pull /sdcard/screencap.png'</span><span class="p">,</span> <span class="n">serialNumber</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">click</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">point</span><span class="p">,</span> <span class="n">serialNumber</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">command</span><span class="p">(</span><span class="s">'shell input tap '</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">point</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span><span class="o">+</span><span class="s">' '</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">point</span><span class="p">[</span><span class="mi">1</span><span class="p">]),</span> <span class="n">serialNumber</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">command</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cmd</span><span class="p">,</span> <span class="n">serialNumber</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="n">cmdstr</span> <span class="o">=</span> <span class="s">'adb '</span>
<span class="k">if</span> <span class="n">serialNumber</span><span class="p">:</span>
<span class="n">cmdstr</span> <span class="o">=</span> <span class="n">cmdstr</span><span class="o">+</span><span class="s">'-s '</span><span class="o">+</span><span class="n">serialNumber</span>
<span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span> <span class="o">=</span> <span class="n">commands</span><span class="o">.</span><span class="n">getstatusoutput</span><span class="p">(</span><span class="n">cmdstr</span><span class="o">+</span><span class="n">cmd</span><span class="p">)</span>
<span class="k">return</span> <span class="p">[</span><span class="n">status</span><span class="p">,</span> <span class="n">output</span><span class="p">]</span>
</code></pre></div></div>
<p><em>测试:</em></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">adbkit</span> <span class="o">=</span> <span class="n">adb</span><span class="o">.</span><span class="n">adbKit</span><span class="p">()</span>
<span class="n">adbkit</span><span class="o">.</span><span class="n">click</span><span class="p">(</span><span class="n">max_loc</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="八总结">八、总结</h2>
<blockquote>
<p>回过头来看最后的代码,仅需30行即可完成(截图 -> 图像识别 -> 计算位置 -> 点击位置)流程,剩下我们可以思考下如何将整个测试环境串联起来。</p>
</blockquote>
<p><strong>完整的编码:</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env python</span>
<span class="c"># coding=utf-8</span>
<span class="s">"""demo"""</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">'xiaocai'</span>
<span class="kn">import</span> <span class="nn">commands</span><span class="p">,</span> <span class="n">cv2</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">from</span> <span class="nn">libs</span> <span class="kn">import</span> <span class="n">adb</span>
<span class="c"># 截图</span>
<span class="n">adbkit</span> <span class="o">=</span> <span class="n">adb</span><span class="o">.</span><span class="n">adbKit</span><span class="p">()</span>
<span class="n">adbkit</span><span class="o">.</span><span class="n">screenshots</span><span class="p">()</span>
<span class="c"># 载入图像</span>
<span class="n">target_img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s">"screencap.png"</span><span class="p">)</span>
<span class="n">find_img</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s">"images/btn_close_full.png"</span><span class="p">)</span>
<span class="n">find_height</span><span class="p">,</span> <span class="n">find_width</span><span class="p">,</span> <span class="n">find_channel</span> <span class="o">=</span> <span class="n">find_img</span><span class="o">.</span><span class="n">shape</span><span class="p">[::]</span>
<span class="c"># 模板匹配</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">matchTemplate</span><span class="p">(</span><span class="n">target_img</span><span class="p">,</span> <span class="n">find_img</span><span class="p">,</span> <span class="n">cv2</span><span class="o">.</span><span class="n">TM_CCOEFF_NORMED</span><span class="p">)</span>
<span class="n">min_val</span><span class="p">,</span><span class="n">max_val</span><span class="p">,</span><span class="n">min_loc</span><span class="p">,</span><span class="n">max_loc</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">minMaxLoc</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="c"># 计算位置</span>
<span class="n">pointUpLeft</span> <span class="o">=</span> <span class="n">max_loc</span>
<span class="n">pointLowRight</span> <span class="o">=</span> <span class="p">(</span><span class="n">max_loc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="n">find_width</span><span class="p">,</span> <span class="n">max_loc</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="n">find_height</span><span class="p">)</span>
<span class="n">pointCentre</span> <span class="o">=</span> <span class="p">(</span><span class="n">max_loc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">+</span><span class="p">(</span><span class="n">find_width</span><span class="o">/</span><span class="mi">2</span><span class="p">),</span> <span class="n">max_loc</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">+</span><span class="p">(</span><span class="n">find_height</span><span class="o">/</span><span class="mi">2</span><span class="p">))</span>
<span class="c"># 点击</span>
<span class="n">adbkit</span><span class="o">.</span><span class="n">click</span><span class="p">(</span><span class="n">pointCentre</span><span class="p">)</span>
</code></pre></div></div>cmxiaocai游戏自动化测试痛点在于难以定位控件,这里使用图像识别替代控件定位的方式,来完成游戏的自动化测试。 当然也可以混合使用图像识别+控件定位的方式满足需求,在这里只分享用adb+opencv实现游戏自动化测试过程。 一、测试原理 图像定位测试的核心思路其实是利用adb来操作设备,用opencv实现图像区域匹配,匹配成功后计算目标位置然后触发adb。 简单来说就是如下步骤: 截图 -> 图像识别 -> 计算位置 -> 点击位置 二、创建python运行环境 virtualenv提供隔离的Python运行环境(如果不需要的话可以忽略此步骤) 1.创建目录: mkdir venv_gametest cd venv_gametest 2.创建并运行独立python环境 virtualenv --no-site-packages venv source venv/bin/activate 三、安装依赖环境 1. 安装android adb 笔者是mac os系统,所以直接通过Homebrew安装。 也可以通过下载android SDK的方式手动安装这里不做说明,下载地址:developer.android.com brew cask install android-platform-tools 2. 测试adb 连上手机的数据线或打开模拟器,查看能不能找到设备 (venv) $ demo5_game adb version Android Debug Bridge version 1.0.32 (venv) $ demo5_game adb devices List of devices attached 192.168.56.100:5555 device 更多的adb操作,可以查看 https://github.com/mzlogin/awesome-adb 3. 安装opencv opencv用来帮助我们完成图像的处理 只需要numpy、Matplotlib、opencv-python三个包 详见:install opencv sudo pip install --upgrade setuptools sudo pip install numpy Matplotlib sudo pip install opencv-python 4. 测试opencv 选一张图片拷贝以下代码,用opencv打开测试是否安装成功 #!/usr/bin/env python # coding=utf-8 import cv2 as cv img = cv.imread("test.jpg") cv.namedWindow("Image" cv.imshow("Image",img) cv.waitKey(0) cv2.destroyAllWindows() 运行结果: 四、adb截图操作 先看如何用adb命令进行截图操作,详见:adb屏幕截图 先截图保存到设备里: adb shell screencap -p /sdcard/screencap.png 然后将 png 文件导出到电脑: adb pull /sdcard/screencap.png 同样的使用python中commands模块也能实现相同的效果 commands.getstatusoutput('adb shell screencap -p /sdcard/screencap.png') commands.getstatusoutput('adb pull /sdcard/screencap.png') 将adb的操作做个简单的封装,便于后面使用 file: libs/adb.py #!/usr/bin/env python # coding=utf-8 """ADB""" __author__ = 'xiaocai' import commands class adbKit(object): def screenshots(self, serialNumber=None): self.command('shell screencap -p /sdcard/screencap.png', serialNumber) self.command('pull /sdcard/screencap.png', serialNumber) def command(self, cmd, serialNumber=None): cmdstr = 'adb ' if serialNumber: cmdstr = cmdstr+'-s '+serialNumber (status, output) = commands.getstatusoutput(cmdstr+cmd) return [status, output] 五、图像匹配 刚刚完成了第一个截图环节,接下来我们开始尝试用opencv去匹配目标图像的位置 详见:opencv文档 1. 选取一张截图 我们利用上面封装好的adbKit对当前设备,进行一次截图操作。 file: test.py #!/usr/bin/env python # coding=utf-8 """demo""" __author__ = 'xiaocai' import sys, time, commands from libs import adb adbkit = adb.adbKit() adbkit.screenshots() 运行完之后可以看到根目录下出现了screencap.png图片 . ├── libs │ ├── __init__.py │ ├── __init__.pyc │ ├── adb.py │ └── adb.pyc ├── screencap.png └── test.py 接下来我们要实现第一个用例,如下图要识别到游戏中右上角的X按钮 用截图工具将x截取,保持到images/btn_close_full.png(需要注意分辨率) 2. 载入图像 读取截图和要匹配的x图像 import cv2 target_img = cv2.imread("screencap.png") find_img = cv2.imread("images/btn_close_full.png") 3. 图像匹配 使用图像模板匹配cv::matchTemplate(),通过返回的cvMinMaxLoc计算结果 详见:Template Matching matchTemplate会将模板图像在源图像中进行滑动匹配(从左往右,从上往下) 参数说明: image是源图像,templ是模板图像,method是匹配算法 Python: cv2.matchTemplate(image, templ, method[, result]) → result Parameters: image – Image where the search is running. It must be 8-bit or 32-bit floating-point. templ – Searched template. It must be not greater than the source image and have the same data type. result – Map of comparison results. It must be single-channel 32-bit floating-point. If image is W \times H and templ is w \times h , then result is (W-w+1) \times (H-h+1) . method – Parameter specifying the comparison method (see below). 代码: 这里我们用cv2.TM_CCOEFF_NORMED 不同的算法对匹配结果会有所差异,具体可参考:Python+OpenCV学习(7)—模板匹配 result = cv2.matchTemplate(target_img, find_img, cv2.TM_CCOEFF_NORMED) print result 结果: 从打印的结果看到,matchTemplate会在模板块和输入图像之间寻找匹配最后返回一组匹配结果图像 [[ 0.07075607 0.08976483 0.10165194 ... 0.07626463 0.06611969 0.04449887] [ 0.04982539 0.06268624 0.07642973 ... 0.06295352 0.05140355 0.02827941] [ 0.01129027 0.02193917 0.03671272 ... 0.03954886 0.02578432 0.00115839] ... [-0.08573136 -0.07838587 -0.06693702 ... -0.03226329 -0.02669666 -0.03457748] [-0.08250767 -0.07598738 -0.06588773 ... -0.0265673 -0.02382514 -0.02491279] [-0.07762627 -0.07059815 -0.06101505 ... -0.00854323 -0.0053194 -0.00458878]] 每个匹配图像会有匹配度,比如找出匹配度>0.8的图像(如果需要一次匹配多个结果可以使用这个方法) 示例: #!/usr/bin/env python # coding=utf-8 """demo""" __author__ = 'xiaocai' import commands, cv2 import numpy as np from libs import adb adbkit = adb.adbKit() adbkit.screenshots() target_img = cv2.imread("screencap.png") find_img = cv2.imread("images/btn_close_full.png") result = cv2.matchTemplate(target_img, find_img, cv2.TM_CCOEFF_NORMED) loc = np.where( result >= 0.5) for pt in zip(*loc[::-1]): print pt 4. minMaxLoc方法 找到匹配图像之后我们需要使用minMaxLoc函数在给定的矩阵中寻找最大和最小值(包括它们的位置). 代码: 依次是:最小匹配度,最大匹配度,最小匹配位置,最大匹配位置 result = cv2.matchTemplate(target_img, find_img, cv2.TM_CCOEFF_NORMED) min_val,max_val,min_loc,max_loc = cv2.minMaxLoc(result) print min_val,max_val,min_loc,max_loc 结果: 这里的(1019, 74)就是我们模板在源图中的左上角坐标了 -0.381701976061 0.779404222965 (1181, 519) (1019, 74) 六、计算点击位置 上面我们通过matchTemplate和minMaxLoc方法已经获取到模板图像在源图中的坐标了,但这个坐标只是左上角的位置,实际点击应该是模板图像的中间位置 首先我们需要先获取模板图像的尺寸 find_img = cv2.imread("images/btn_close_full.png") find_height, find_width, find_channel = find_img.shape[::] 根据max_loc结果计算出中间位置 pointUpLeft = max_loc pointLowRight = (max_loc[0]+find_width, max_loc[1]+find_height) pointCentre = (max_loc[0]+(find_width/2), max_loc[1]+(find_height/2)) 为了更直观些,我们把坐标在源图中点出来并显示出图片 cv2.circle(target_img, pointUpLeft, 2, (0, 255, 255), -1) cv2.circle(target_img, pointCentre, 2, (0, 255, 255), -1) cv2.circle(target_img, pointLowRight, 2, (0, 255, 255), -1) cv2.namedWindow("Image") cv2.imshow("Image", target_img) cv2.waitKey(0) cv2.destroyAllWindows() 完整代码: #!/usr/bin/env python # coding=utf-8 """demo""" __author__ = 'xiaocai' import commands, cv2 import numpy as np from libs import adb # 截图 adbkit = adb.adbKit() adbkit.screenshots() # 载入图像 target_img = cv2.imread("screencap.png") find_img = cv2.imread("images/btn_close_full.png") find_height, find_width, find_channel = find_img.shape[::] # 模板匹配 result = cv2.matchTemplate(target_img, find_img, cv2.TM_CCOEFF_NORMED) min_val,max_val,min_loc,max_loc = cv2.minMaxLoc(result) # 计算位置 pointUpLeft = max_loc pointLowRight = (max_loc[0]+find_width, max_loc[1]+find_height) pointCentre = (max_loc[0]+(find_width/2), max_loc[1]+(find_height/2)) # 画点 cv2.circle(target_img, pointUpLeft, 2, (255, 255, 255), -1) cv2.circle(target_img, pointCentre, 2, (255, 255, 255), -1) cv2.circle(target_img, pointLowRight, 2, (255, 255, 255), -1) # 显示图片 cv2.namedWindow("Image") cv2.imshow("Image", target_img) cv2.waitKey(0) cv2.destroyAllWindows() 结果: 七、触发点击 离成功只差最后一步了,先看看adb是如何模拟按键输入的 详见:模拟按键/输入 命令: adb shell input tap <x> <y> python调用: class adbKit(object): def screenshots(self, serialNumber=None): self.command('shell screencap -p /sdcard/screencap.png', serialNumber) self.command('pull /sdcard/screencap.png', serialNumber) def click(self, point, serialNumber=None): return self.command('shell input tap '+str(point[0])+' '+str(point[1]), serialNumber) def command(self, cmd, serialNumber=None): cmdstr = 'adb ' if serialNumber: cmdstr = cmdstr+'-s '+serialNumber (status, output) = commands.getstatusoutput(cmdstr+cmd) return [status, output] 测试: adbkit = adb.adbKit() adbkit.click(max_loc) 八、总结 回过头来看最后的代码,仅需30行即可完成(截图 -> 图像识别 -> 计算位置 -> 点击位置)流程,剩下我们可以思考下如何将整个测试环境串联起来。 完整的编码: #!/usr/bin/env python # coding=utf-8 """demo""" __author__ = 'xiaocai' import commands, cv2 import numpy as np from libs import adb # 截图 adbkit = adb.adbKit() adbkit.screenshots() # 载入图像 target_img = cv2.imread("screencap.png") find_img = cv2.imread("images/btn_close_full.png") find_height, find_width, find_channel = find_img.shape[::] # 模板匹配 result = cv2.matchTemplate(target_img, find_img, cv2.TM_CCOEFF_NORMED) min_val,max_val,min_loc,max_loc = cv2.minMaxLoc(result) # 计算位置 pointUpLeft = max_loc pointLowRight = (max_loc[0]+find_width, max_loc[1]+find_height) pointCentre = (max_loc[0]+(find_width/2), max_loc[1]+(find_height/2)) # 点击 adbkit.click(pointCentre)Jenkins构建Docker镜像-无法执行docker命令2017-11-19T00:00:00+08:002017-11-19T00:00:00+08:00http://www.xiaocai.name/2017/11/19/Jenkins%E6%9E%84%E5%BB%BADocker%E9%95%9C%E5%83%8F-%E6%97%A0%E6%B3%95%E6%89%A7%E8%A1%8Cdocker%E5%91%BD%E4%BB%A4<h2 id="问题">问题</h2>
<blockquote>
<p>在Jenkins中执行docker build报错:Cannot connect to the Docker daemon. Is the docker daemon running on this host?</p>
</blockquote>
<p><strong>脚本:</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
id
<span class="nb">echo</span> <span class="s2">"start build..."</span>
<span class="nb">echo</span> <span class="sb">`</span>docker ps<span class="sb">`</span>
</code></pre></div></div>
<p><strong>错误日志:</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Started by remote host 117.28.231.17
Building in workspace /var/lib/jenkins/workspace/test_dockerbuild02
[test_dockerbuild02] $ /bin/sh /tmp/jenkins7776024465780877752.sh
uid=994(jenkins) gid=991(jenkins) groups=991(jenkins)
start build...
Cannot connect to the Docker daemon. Is the docker daemon running on this host?
Finished: SUCCESS
</code></pre></div></div>
<h2 id="解决方案">解决方案</h2>
<p><strong>修复:</strong></p>
<blockquote>
<p>原因是jenkins用户没有加入docker的用户组</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vim /etc/sudoers
------------
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
jenkins ALL=(ALL) ALL
usermod -G docker jenkins
(cat vim /etc/group 看docker是哪个用户组,或者直接加入root用户组)
service jenkins restart
(记得重启服务)
</code></pre></div></div>cmxiaocai问题macos搭建appium+android自动化测试环境2017-11-01T00:00:00+08:002017-11-01T00:00:00+08:00http://www.xiaocai.name/2017/11/01/mac%E6%90%AD%E5%BB%BAappium+android%E7%8E%AF%E5%A2%83<blockquote>
<p>网上的教程写了非常多安装步骤,其实只需要安装「Android Studio」和「appium」这两个dmg包即可节省掉一半的安装步骤。</p>
</blockquote>
<h2 id="环境安装">环境安装</h2>
<p><strong>下载Android Studio</strong></p>
<blockquote>
<p>网络上大部分教程都是推荐只单独安装android sdk,单独安装sdk坑还是蛮多的..并且有些集成在android studio工具无法使用到。</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201711/15095480678841.jpg" alt="" /></p>
<p><strong>安装appium</strong></p>
<blockquote>
<p>appium有两种安装方式,可以下载运行appium.dmg或者通过node命令安装
appium.dmg会有两个版本(蓝色图标和紫色图标,紫色图标是Appium-desktop版本,蓝色图标是Appium-Server版本),Server版本已经不更新了,desktop版本将继承Server。(目前网上的教程大部分都是安装serverb版本)
另外重要的是AppiumDesktop可以定位元素信息,录制脚本!</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Appium-Server下载地址:https://bitbucket.org/appium/appium.app/downloads/
Appium-desktop项目地址:https://github.com/appium/appium-desktop
</code></pre></div></div>
<p><img src="http://www.xiaocai.name/uploads/201711/15095480855461.jpg" alt="" /></p>
<p><strong>Appium-Python-Client</strong></p>
<blockquote>
<p>依赖node 和 pip</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip install selenium
pip install Appium-Python-Client
</code></pre></div></div>
<blockquote>
<p>写个python文件验证</p>
</blockquote>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">selenium</span> <span class="kn">import</span> <span class="n">webdriver</span>
<span class="k">print</span> <span class="s">'ok'</span>
</code></pre></div></div>
<h2 id="运行安卓模拟器">运行安卓模拟器</h2>
<p><strong>打开android studio</strong></p>
<blockquote>
<p>随便创建一个空项目,能够进入到ide界面就行</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201711/15095498789200.jpg" alt="" /></p>
<p><strong>运行AVD Manager工具</strong></p>
<blockquote>
<p>Tools - Android - AVD Manager</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201711/15095499761401.jpg" alt="" /></p>
<p><strong>运行模拟器</strong></p>
<blockquote>
<p>默认会有两个设备,也可以直接新创建设备</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201711/15095501129264.jpg" alt="" /></p>
<p><strong>模拟器</strong></p>
<blockquote>
<p>看见这个界面就说明运行成功了</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201711/15095502734609.jpg" alt="" /></p>
<h2 id="运行appium">运行appium</h2>
<p><strong>打开appium</strong></p>
<blockquote>
<p>这里选择默认参数,然后点击start</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201711/15095503762294.jpg" alt="" /></p>
<p><strong>运行成功</strong></p>
<blockquote>
<p>起了一个server,监听4723端口</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201711/15095504239243.jpg" alt="" /></p>
<h2 id="跑测试用例">跑测试用例</h2>
<blockquote>
<p>我们以默认的「计算器」作为demo</p>
</blockquote>
<p><strong>查看app元素</strong></p>
<blockquote>
<p>和做web自动化测试一样,得原找到目标对象才能进行操作。需要借助Hierarchy View工具。
打开方式 Tools - Android - Android Device Monitor</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201711/15095506725773.jpg" alt="" /></p>
<blockquote>
<p>在模拟器中打开计算器,选中对应的包名。添加Hierarchy View窗口。</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201711/15095508655842.jpg" alt="" /></p>
<blockquote>
<p>比如数字7,对应的id=digit7</p>
</blockquote>
<p><img src="http://www.xiaocai.name/uploads/201711/15095511220342.jpg" alt="" /></p>
<p><strong>编写脚本文件</strong></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">os</span><span class="p">,</span><span class="n">time</span>
<span class="kn">from</span> <span class="nn">selenium</span> <span class="kn">import</span> <span class="n">webdriver</span>
<span class="n">desired_caps</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">desired_caps</span><span class="p">[</span><span class="s">'platformName'</span><span class="p">]</span> <span class="o">=</span> <span class="s">'Android'</span>
<span class="n">desired_caps</span><span class="p">[</span><span class="s">'platformVersion'</span><span class="p">]</span> <span class="o">=</span> <span class="s">'4.4.2'</span>
<span class="n">desired_caps</span><span class="p">[</span><span class="s">'deviceName'</span><span class="p">]</span> <span class="o">=</span> <span class="s">'Android Emulator'</span>
<span class="n">desired_caps</span><span class="p">[</span><span class="s">'appPackage'</span><span class="p">]</span> <span class="o">=</span> <span class="s">'com.android.calculator2'</span>
<span class="n">desired_caps</span><span class="p">[</span><span class="s">'appActivity'</span><span class="p">]</span> <span class="o">=</span> <span class="s">'.Calculator'</span>
<span class="n">driver</span> <span class="o">=</span> <span class="n">webdriver</span><span class="o">.</span><span class="n">Remote</span><span class="p">(</span><span class="s">'http://localhost:4723/wd/hub'</span><span class="p">,</span> <span class="n">desired_caps</span><span class="p">)</span>
<span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">9</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">9</span><span class="p">):</span>
<span class="n">x</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="n">j</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">j</span><span class="p">)</span>
<span class="k">print</span> <span class="n">x</span><span class="o">+</span><span class="s">" x "</span><span class="o">+</span><span class="n">j</span><span class="o">+</span><span class="s">" = "</span>
<span class="n">driver</span><span class="o">.</span><span class="n">find_element_by_id</span><span class="p">(</span><span class="s">"digit"</span><span class="o">+</span><span class="n">x</span><span class="p">)</span><span class="o">.</span><span class="n">click</span><span class="p">()</span>
<span class="n">driver</span><span class="o">.</span><span class="n">find_element_by_id</span><span class="p">(</span><span class="s">"mul"</span><span class="p">)</span><span class="o">.</span><span class="n">click</span><span class="p">()</span>
<span class="n">driver</span><span class="o">.</span><span class="n">find_element_by_id</span><span class="p">(</span><span class="s">"digit"</span><span class="o">+</span><span class="n">j</span><span class="p">)</span><span class="o">.</span><span class="n">click</span><span class="p">()</span>
<span class="n">driver</span><span class="o">.</span><span class="n">find_element_by_id</span><span class="p">(</span><span class="s">"equal"</span><span class="p">)</span><span class="o">.</span><span class="n">click</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<span class="n">driver</span><span class="o">.</span><span class="n">quit</span><span class="p">()</span>
</code></pre></div></div>cmxiaocai网上的教程写了非常多安装步骤,其实只需要安装「Android Studio」和「appium」这两个dmg包即可节省掉一半的安装步骤。 环境安装 下载Android Studio 网络上大部分教程都是推荐只单独安装android sdk,单独安装sdk坑还是蛮多的..并且有些集成在android studio工具无法使用到。 安装appium appium有两种安装方式,可以下载运行appium.dmg或者通过node命令安装 appium.dmg会有两个版本(蓝色图标和紫色图标,紫色图标是Appium-desktop版本,蓝色图标是Appium-Server版本),Server版本已经不更新了,desktop版本将继承Server。(目前网上的教程大部分都是安装serverb版本) 另外重要的是AppiumDesktop可以定位元素信息,录制脚本! Appium-Server下载地址:https://bitbucket.org/appium/appium.app/downloads/ Appium-desktop项目地址:https://github.com/appium/appium-desktop Appium-Python-Client 依赖node 和 pip pip install selenium pip install Appium-Python-Client 写个python文件验证 from selenium import webdriver print 'ok' 运行安卓模拟器 打开android studio 随便创建一个空项目,能够进入到ide界面就行 运行AVD Manager工具 Tools - Android - AVD Manager 运行模拟器 默认会有两个设备,也可以直接新创建设备 模拟器 看见这个界面就说明运行成功了 运行appium 打开appium 这里选择默认参数,然后点击start 运行成功 起了一个server,监听4723端口 跑测试用例 我们以默认的「计算器」作为demo 查看app元素 和做web自动化测试一样,得原找到目标对象才能进行操作。需要借助Hierarchy View工具。 打开方式 Tools - Android - Android Device Monitor 在模拟器中打开计算器,选中对应的包名。添加Hierarchy View窗口。 比如数字7,对应的id=digit7 编写脚本文件 import os,time from selenium import webdriver desired_caps = {} desired_caps['platformName'] = 'Android' desired_caps['platformVersion'] = '4.4.2' desired_caps['deviceName'] = 'Android Emulator' desired_caps['appPackage'] = 'com.android.calculator2' desired_caps['appActivity'] = '.Calculator' driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) for x in xrange(1,9): for j in xrange(1,9): x = str(x) j = str(j) print x+" x "+j+" = " driver.find_element_by_id("digit"+x).click() driver.find_element_by_id("mul").click() driver.find_element_by_id("digit"+j).click() driver.find_element_by_id("equal").click() time.sleep(5) driver.quit()android ADB 操作2017-10-29T00:00:00+08:002017-10-29T00:00:00+08:00http://www.xiaocai.name/2017/10/29/andorid%20ADB%20%E6%93%8D%E4%BD%9C<blockquote>
<p>Android Debug Bridge,我们一般简称为adb,主要存放在sdk安装目录下的platform-tools文件夹中,它是一个非常强大的命令行工具,通过这个工具你能够与你的android设备进行交互</p>
</blockquote>
<h2 id="adb命令格式">adb命令格式</h2>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb [-d|-e|-s <serialNumber>] <command>
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-d: 让唯一连接到该PC端的真实安卓设备执行命令,如果发现USB中连接有多部设备,将会报错
-e: 让唯一连接到该PC端的模拟器执行命令,如果发现开启了多个模拟器,将会报错
-s:通过设备的序列号进行指定设备执行命令
</code></pre></div></div>
<h2 id="常用操作">常用操作</h2>
<p><strong>查看当前连接设备</strong></p>
<blockquote>
<p> I. device 设备已经成功连接到了adb-server</p>
<p> II. offline 设备并没有连接到adb或者没有响应</p>
<p> III. no device 并没有设备/模拟器连接</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb devices
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ~ adb devices
List of devices attached
* daemon not running. starting it now at tcp:5037 *
* daemon started successfully *
emulator-5554 device
</code></pre></div></div>
<p><strong>查看当前adb版本</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb version
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ~ adb version
Android Debug Bridge version 1.0.39
Revision 3db08f2c6889-android
Installed as /usr/local/bin/adb
</code></pre></div></div>
<p><strong>给设备进行软件的安装</strong></p>
<blockquote>
<p>两种格式</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb <span class="nt">-s</span> <serialNumber> install <path-to-apk>
adb <span class="nt">-s</span> <serialNumber> shell pm install <span class="o">[</span>options] <PATH>
</code></pre></div> </div>
<pre><code class="language-Shell">$ ~ adb -s emulator-5554 install /work/work_wwwroot/tool/apk/External_LM_China_176TS.apk
/work/work_wwwroot/tool/apk/External_L...214.6 MB/s (301481420 bytes in 1.339s)
WARNING: linker: libdvm.so has text relocations. This is wasting memory and is a security risk. Please fix.
pkg: /data/local/tmp/External_LM_China_176TS.apk
Success
</code></pre>
</blockquote>
<p><strong>将数据从设备复制到PC中</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb -s <serialNumber> pull <remote> <local>
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb -s emulator-5554 pull /sdcard/xxx.log /home/android_log/xxx.log
</code></pre></div></div>
<p><strong>将数据从PC复制到设备中</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb -s <serialNumber>push <local> <remote>
</code></pre></div></div>
<p><strong>在手机中执行shell命令</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ~ adb shell
root@generic_x86:/ # pwd
/
root@generic_x86:/ # ls
acct
cache
config
</code></pre></div></div>
<p><strong>查看手机中的包</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pm list packages
</code></pre></div></div>
<p><strong>屏幕录像</strong></p>
<blockquote>
<p>例如,屏幕开始录像并且储存到/sdcard中,同时名字为demo.mp4</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell screenrecord /sdcard/demo.mp4
</code></pre></div> </div>
</blockquote>
<p><strong>关闭设备请求,开启设备</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell stop
adb shell start
</code></pre></div></div>
<p><strong>设备关机以及重启</strong></p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb reboot
adb shutdown
</code></pre></div></div>
<p><strong>参考资料</strong></p>
<blockquote>
<p>http://developer.android.com/tools/help/adb.html#IntentSpec</p>
</blockquote>cmxiaocaiAndroid Debug Bridge,我们一般简称为adb,主要存放在sdk安装目录下的platform-tools文件夹中,它是一个非常强大的命令行工具,通过这个工具你能够与你的android设备进行交互macos下安装和配置android-sdk2017-10-16T00:00:00+08:002017-10-16T00:00:00+08:00http://www.xiaocai.name/2017/10/16/MAC%20OS%E4%B8%8B%E5%AE%89%E8%A3%85%E5%92%8C%E9%85%8D%E7%BD%AEandroid-sdk<p>###安装</p>
<p>在MAC上安装android-sdk,标准的安装方法是使用<code class="highlighter-rouge">homebrew</code>,运行如下命令:</p>
<p><code class="highlighter-rouge">brew update</code></p>
<p><code class="highlighter-rouge">brew install android-sdk</code></p>
<p>如果遇到无法通过代理下载安装包的情况时,可以先手动下载<a href="https://homebrew.bintray.com/bottles/android-sdk-24.4.1_1.el_capitan.bottle.tar.gz">安装包</a>, 然后,将安装包放到homebrew的缓存里,</p>
<p><code class="highlighter-rouge">$ cp <path to download file> $(brew --cache android-sdk)</code></p>
<p>再执行</p>
<p><code class="highlighter-rouge">$ brew install android-sdk</code></p>
<p>这样,就可以成功安装android-sdk了。</p>
<hr />
<p>###配置</p>
<p>1、配置<code class="highlighter-rouge">.bash_profile</code>,如果使用的是<code class="highlighter-rouge">Oh-My-Zsh</code>,则配置<code class="highlighter-rouge">.zshrc</code>,在文件的结尾加上下面这句:</p>
<p><code class="highlighter-rouge">export ANDROID_HOME="/usr/local/opt/android-sdk"</code></p>
<p>然后<code class="highlighter-rouge">source ~/.zshrc</code>使其生效</p>
<p>2、安装辅助包</p>
<p>运行<code class="highlighter-rouge">android</code>命令,调出Android SDK Manager,安装下面这些选项:</p>
<ul>
<li>Tools
<ul>
<li>Android SDK Tools</li>
<li>Android SDK Platform-tools</li>
<li>Android SDK Build-tools</li>
</ul>
</li>
<li>Android 5.1.1 (API 22)
<ul>
<li>SDK Platform</li>
<li>Intel x86 Atom_64 System Image</li>
</ul>
</li>
<li>Extras
<ul>
<li>Android Suport Library</li>
<li>Intel x86 Emulator Accelerator(HAXM Installer)</li>
</ul>
</li>
</ul>
<p>3、安装HAXM(Hardware_Accelerated_Execution_Manager)</p>
<p>进入以下目录</p>
<p><code class="highlighter-rouge">$ cd /usr/local/Cellar/android-sdk/24.4.1_1/extras/intel/Hardware_Accelerated_Execution_Manager/</code></p>
<p>然后安装HAXM</p>
<p><code class="highlighter-rouge">$ ./HAXM\ installation</code></p>
<p>就这样,android模拟器就安装好了。</p>
<hr />
<p>###配置模拟器</p>
<p>运行以下命令:</p>
<p><code class="highlighter-rouge">$ android avd</code></p>
<ul>
<li>打开Android Virtual Device Manager</li>
<li>选择<code class="highlighter-rouge">Device Definitions</code>一项</li>
<li>选择需要的模拟器类型,点击<code class="highlighter-rouge">Create AVD</code></li>
</ul>
<p>创建完成AVD之后,回到<code class="highlighter-rouge">Android Virtual Devices</code>选项卡,选择创建好的模拟器,点击<code class="highlighter-rouge">Start</code>就可以启动模拟器了。</p>cmxiaocai###安装mac上运行robotframework2017-09-08T00:00:00+08:002017-09-08T00:00:00+08:00http://www.xiaocai.name/2017/09/08/mac%E4%B8%8A%E8%BF%90%E8%A1%8Crobotframework<blockquote>
<p>安装 robotframework</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>pip install robotframework
</code></pre></div></div>
<blockquote>
<p>安装 wxPython</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install wxpython
<span class="nb">cd</span> /Library/Python/2.7/site-packages
<span class="nb">sudo </span>ln <span class="nt">-s</span> /usr/local/Cellar/wxpython/3.0.2.0/lib/python2.7/site-packages/wx-3.0-osx_cocoa/wx wx
</code></pre></div></div>
<blockquote>
<p>安装RIDE</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://pypi.python.org/pypi/robotframework-ride
<span class="nb">cd </span>robotframework-ride
python setup.py install
</code></pre></div></div>
<blockquote>
<p>打开界面</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/local/bin/ride.py
</code></pre></div></div>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>用 ride.py 啟動 RIDE。
如果環境內沒有安裝符合要求的 wxPython,會發生下面的錯誤:
<span class="nv">$ </span>ride.py
wxPython not found.
You need to install wxPython 2.8.12.1 with unicode support to run RIDE.
wxPython 2.8.12.1 can be downloaded from http://sourceforge.net/projects/wxpython/files/wxPython/2.8.12.1/
如果遇到下面的錯誤,表示 wxPython 安裝的是 32-bit 的版本,連帶地 RIDE 也必須執行在 32-bit mode 下。<span class="o">(</span>自行編譯 wxPython 就沒有這個問題<span class="o">)</span>
<span class="nv">$ </span>ride.py
python should be executed <span class="k">in </span>32-bit mode with wxPython on OSX.
透過 VERSIONER_PYTHON_PREFER_32_BIT 環境變數可以讓 Python 執行在 32-bit mode:
<span class="nv">$ </span><span class="nb">export </span><span class="nv">VERSIONER_PYTHON_PREFER_32_BIT</span><span class="o">=</span>yes
<span class="nv">$ </span>ride.py
</code></pre></div></div>cmxiaocai安装 robotframeworkKVM日常操作笔记2017-07-28T00:00:00+08:002017-07-28T00:00:00+08:00http://www.xiaocai.name/2017/07/28/KVM%E5%88%9B%E5%BB%BA%E7%AC%94%E8%AE%B0<h2 id="为kvm创建存储池">为KVM创建存储池</h2>
<blockquote>
<p>KVM的默认卷是存放在/var/lib/libvirt/images/目录下,此处的磁盘分区大小有限.</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 建立存储池的目录</span>
mkdir /home/kvm_disk
<span class="c"># 配置SELinux文件上下文</span>
semanage fcontext <span class="nt">-a</span> <span class="nt">-t</span> virt_image_t /home/kvm_disk
<span class="c"># 定义一个存储池</span>
virsh pool-define-as kvm_final <span class="nt">--type</span> dir <span class="nt">--target</span> /home/kvm_disk
<span class="c"># 查看创建的存储池</span>
virsh pool-list <span class="nt">--all</span>
<span class="c"># 激活存储池</span>
virsh pool-start kvm_final
<span class="c"># 自动运行</span>
virsh pool-autostart kvm_final
<span class="c"># 验证存储池</span>
virsh pool-info kvm_final
名称: kvm_final
UUID: ffeb623f-9988-433b-b446-cdab27457308
状态: running
持久: 是
自动启动: 否
容量: 672.41 GiB
分配: 733.66 MiB
可用: 671.70 GiB
</code></pre></div></div>
<h2 id="创建kvm虚拟机">创建KVM虚拟机</h2>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 创建卷</span>
virsh vol-create-as kvm_final def_init_centos7.qcow2 30G <span class="nt">--format</span> qcow2
<span class="c"># 查看存储池中所有卷</span>
virsh vol-list kvm_final
<span class="c"># 查看卷信息</span>
virsh vol-info def_init_centos7.qcow2 kvm_final
<span class="c"># 删除卷</span>
virsh vol-delete /home/kvm_disk/def_init_centos7.qcow2
<span class="c"># 调整大小</span>
virsh vol-resize per_clone_test.qcow2 50G <span class="nt">--pool</span><span class="o">=</span>kvm_final
<span class="c"># 创建虚拟机</span>
virt-install <span class="nt">--name</span><span class="o">=</span>centos7_tpl <span class="nt">--vcpus</span><span class="o">=</span>2 <span class="nt">--memory</span><span class="o">=</span>2048 <span class="nt">--hvm</span> <span class="nt">--vnc</span> <span class="nt">--network</span> <span class="nv">bridge</span><span class="o">=</span>enp0s31f6 <span class="nt">--disk</span> <span class="nv">path</span><span class="o">=</span>/home/kvm_disk/def_init_centos7.qcow2,format<span class="o">=</span>qcow2,size<span class="o">=</span>30,bus<span class="o">=</span>virtio <span class="nt">--cdrom</span><span class="o">=</span>/home/download/iso_images/CentOS-7-x86_64-Minimal-1611.iso
</code></pre></div></div>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 查看虚拟机</span>
virsh dominfo kvm_test001
<span class="c"># 关闭虚拟机</span>
virsh shutdown centos7_tpl
<span class="c"># 删除虚拟机</span>
virsh undefine kvm_test001
<span class="c"># 删除卷</span>
virsh vol-delete /home/kvm_disk/kvm_test001.qcow2
</code></pre></div></div>
<h2 id="克隆虚拟机">克隆虚拟机</h2>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 克隆前需要先关闭目标虚拟机</span>
virsh list <span class="nt">--all</span>
virsh shutdown centos7_tpl
<span class="c"># 克隆虚拟机</span>
virt-clone <span class="nt">--original</span><span class="o">=</span>centos7_tpl <span class="nt">--name</span><span class="o">=</span>clone_test <span class="nt">--mac</span><span class="o">=</span>52:54:00:15:c4:3c <span class="nt">--file</span><span class="o">=</span>/home/kvm_disk/per_clone_test.qcow2
<span class="c"># 查看虚拟机</span>
virsh dominfo clone_test
<span class="c"># 调整内存大小</span>
virsh setmaxmem clone_test 4G <span class="c"># 设置内存最大值</span>
virsh setmem clone_test 4G <span class="c"># 修改虚拟机内存</span>
<span class="c"># 增大CPU(只能增大不能减少)</span>
virsh setvcpus clone_test 4 <span class="c"># 修改虚拟处理器的数量</span>
<span class="c"># 查看虚拟机网卡</span>
virsh domiflist clone_test
<span class="c"># 启动</span>
virsh start clone_test
<span class="c"># 连接(默认密码123123)</span>
ssh root@<span class="o">{</span>mac分配的ip<span class="o">}</span>
</code></pre></div></div>
<h1 id="技巧">技巧</h1>
<pre><code class="language-Sh"># 宿主机连接虚拟机会话(ctrl+]退出)
virsh console clone_test
# 如果卡死在虚拟机里面执行
grubby --update-kernel=ALL --args="console=ttyS0"
reboot
</code></pre>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 更丰富的KVM管理工具</span>
yum install libguestfs-tools
<span class="c"># 查看虚拟机目录</span>
virt-ls <span class="nt">-d</span> clone_test /
<span class="c"># 查看虚拟机文件</span>
virt-cat <span class="nt">-d</span> clone_test /etc/fstab
<span class="c"># 更多查看http://libguestfs.org/</span>
</code></pre></div></div>cmxiaocai为KVM创建存储池使用webVNC搭建虚拟机控制台2017-07-14T00:00:00+08:002017-07-14T00:00:00+08:00http://www.xiaocai.name/2017/07/14/%E4%BD%BF%E7%94%A8webVNC%E6%90%AD%E5%BB%BA%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%8E%A7%E5%88%B6%E5%8F%B0<h2 id="安装一台vnc-server">安装一台vnc server</h2>
<blockquote>
<p>安装vnc服务</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum install tigervnc-server <span class="nt">-y</span>
<span class="c">#安装依赖</span>
yum install libXfont
yum install xorg-x11-xfs
yum install xorg-x11-xfs-utils
yum install xorg-x11-xinit
yum install xorg-x11-xdm
yum install xorg-x11-fonts<span class="k">*</span>
<span class="c">#如果是最新安装需要安装桌面</span>
yum <span class="nt">-y</span> groupinstall <span class="s1">'Desktop'</span>
</code></pre></div></div>
<blockquote>
<p>配置vnc账户(root用户,分配率800x600)</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vim /etc/sysconfig/vncservers
</code></pre></div></div>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">VNCSERVERS</span><span class="o">=</span><span class="s2">"1:root"</span>
VNCSERVERARGS[1]<span class="o">=</span><span class="s2">"-geometry 800x600"</span>
</code></pre></div></div>
<blockquote>
<p>设置vnc密码(第一次的时候会要求设置密码)</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>root@cp01-cp01-qt-crontab01 ~]# vncserver
You will require a password to access your desktops.
Password:
Verify:
xauth: creating new authority file /root/.Xauthority
New <span class="s1">'cp01-cp01-qt-crontab01.epc.baidu.com:1 (root)'</span> desktop is cp01-cp01-qt-crontab01.epc.baidu.com:1
Creating default startup script /root/.vnc/xstartup
Starting applications specified <span class="k">in</span> /root/.vnc/xstartup
Log file is /root/.vnc/cp01-cp01-qt-crontab01.epc.baidu.com:1.log
</code></pre></div></div>
<blockquote>
<p>启动vnc</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>root@LVS01 vnc]# service vncserver start
<span class="o">[</span>root@LVS01 vnc]# lsof <span class="nt">-i</span>:5901
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
Xvnc 20670 root 5u IPv4 121064408 0t0 TCP <span class="k">*</span>:5901 <span class="o">(</span>LISTEN<span class="o">)</span>
</code></pre></div></div>
<blockquote>
<p>重启vnc</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> vncserver <span class="nt">-list</span>
vncserver <span class="nt">-kill</span> :1
vncserver
vncserver :1 <span class="nt">-geometry</span> 1920x1080
</code></pre></div></div>
<h2 id="安装novnc">安装novnc</h2>
<blockquote>
<p>下载</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/kanaka/noVNC
</code></pre></div></div>
<blockquote>
<p>运行</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/bin/sh launch.sh <span class="nt">--vnc</span> localhost:5901 <span class="nt">--listen</span> 8066
</code></pre></div></div>
<blockquote>
<p>访问</p>
</blockquote>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://localhost:8066/vnc.html?host<span class="o">=</span>LVS01&port<span class="o">=</span>8066
</code></pre></div></div>
<h2 id="参考">参考</h2>
<ul>
<li>黑屏解决方法 http://ju.outofmemory.cn/entry/222096</li>
<li>novnc使用 https://vosamo.github.io/2016/07/noVNC%E7%9A%84%E4%BD%BF%E7%94%A8%E4%B9%8B%E4%B8%80/</li>
<li>安装桌面环境 https://cnzhx.net/blog/centos-yum-install-desktop/</li>
</ul>cmxiaocai安装一台vnc server