Setapp 最多可免费用6个月

今天看了 v2ex 上一帖子说 setapp 推荐新用户免费用,刚好也蛮认同他们的理念的,就来用一波。

先上我的邀请链接,有需要的朋友可以帮个忙点击注册下哈o(^▽^)o: https://my.setapp.com/signup?referral=31a121c9-36ac-4e4e-a616-adccbfd8e8f7

每邀请一人可以免费试用1个月,最多6个月。

Mac 上的软件是蛮好用的,除了一点——开发者开发商利用系统升级各种圈钱。 别的不说,就说虚拟机软件 Parallel ,每次升级都要搞个200多才能用,实在恼人。还有就是 Tweetbot,iOS 上已经圈了几波钱了,虽然 Mac 里也有购买,总是担心它哪天缺钱来再搞一次 100 多人民币的购买费,钱包就受不了了。

Setapp 是一个专门提供 Mac 付费软件包月服务的应用商城,由著名的软件公司 MacPaw 所开发。收费标准是 10 美元一个月。这对开发者来说也是好事,卖出软件获得了一次性收入后没有收入进账,付费购买大版本更新,用户会认为作者是在借更新敛财。通过 Setapp,用户缴纳的 9.99 美元的包月费用,可以帮软件作者解决了经常性收入的问题。 也不用再在营销上花费精力和金钱,去和其他质量更低的软件竞争。

对于用户来说,个人感觉对新用户非常友好,一方面是可以用到了你需要的软件,另一方面,你可以接触到很多也很优秀的软件,试用下说不定你就喜欢上了,试用一个月,对里面的软件基本上也就了如指掌了。这时候如果不想再用他们的服务了,直接买断里面自己喜欢的app就可以了。


robot framework 简介与相关应用

自动化测试

  • 自动化测试是定义一种统一的方式来书写和组织测试用例
  • 控制测试用例的执行过程
  • 生成测试报告和测试日志

自动化测试适用于功能成熟(需求变动较小),产品更新维护周期长,软件开发比较规范,具有可测试性的项目。

robot framework

Robot Framework 的架构是由python编写的,开源通用的自动化测试框架,主要用于多轮次的验收测试和验收测试驱动开发(ATDD-Acceptance Test-Driven Development) ,具有易于使用的表格来组织测试过程和测试数据。

支持jenkins持续化集成

常常与 RF 搭配的工具

  • jenkins 持续集成工具
  • slack 效率沟通工具
  • docker 统一环境

参考资料


使用 php 驱动 selenium

网上看到 selenium 的教程,大多是 python 或者 java 驱动的,搞得我这种 php 出身的人好尴尬。 虽然 java 和 python 都会,既然是开发,那还是挑自己拿手的先解决任务吧。于是就有了这篇文章。本文只涉及 selenium webdriver,基于 chrome 浏览器,分布式的就先不看了。

文章基本上是 SeleniumFacebook 的文档简化后的翻译。

  1. 介绍
  2. Hello World
  3. 参考手册
  4. 窗口控制
  5. 寻找元素
  6. 等待
  7. Ajax
  8. 截图

WebDriver介绍

Selenium 2 一个主要的新功能就是移植了 WebDriver 的 Api。

WebDriver 项目的出发点是提供一个简单快捷的编程接口,来代替 Selenium-RC 的 Api。同事,Selenium WebDriver 对动态网页的支持更好,而且它的接口是面向对象的api,对浏览器支持更加完善。

WebDriver 与 Selenium-RC 驱动浏览器的区别

Selenium-Driver 直接调用浏览器底层支持,不同的浏览器有不同的方式。 而 Selenium-RC 在不同浏览器间的调用方式是一样的,都是以 javascript 驱动。

开始开发 - php版本

php 驱动 Selenium 可以参考以下三个项目的使用手册。

我使用的是 Facebook 版本的,所以直接看第三个。

在项目中使用 facebook/php-webdriver 很简单,在composer.json 的 require 中加入下面一行即可:

   "facebook/webdriver": "^1.3"

hello world

use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverBy;

public function test(){
    $host = 'http://localhost:4444/wd/hub'; // this is the default
    $capabilities = DesiredCapabilities::chrome();
    $driver = RemoteWebDriver::create($host, $capabilities, 5000);
    $baseUrl = 'http://www.bilibili.com/';
    $driver->get($baseUrl);
}

参考手册

打开浏览器

# start an instance of firefox with selenium-webdriver

$browser_type = 'firefox'
$host = 'http://localhost:4444/wd/hub'

$capabilities = array(\WebDriverCapabilityType::BROWSER_NAME => $browser_type);
$driver = RemoteWebDriver::create($host, $capabilities);

$browser_type
# :firefox => firefox
# :chrome  => chrome
# :ie      => iexplore

打开页面

https://php-webdriver.github.io/php-webdriver/master/Facebook/WebDriver/WebDriver.html

$driver->get('http://google.com');
$driver->navigate()->to('http://google.com');

元素操作

参考api: https://php-webdriver.github.io/php-webdriver/master/Facebook/WebDriver/WebDriverElement.html

  1. 获得元素内容

     $result = $driver->findElement(WebDriverBy::id('signin'))->getText();
    
  2. 触发鼠标划过事件

     $element = $driver->findElement(WebDriverBy::id('some_id'));
     $driver->getMouse()->mouseMove( $element->getCoordinates() );
    
  3. 点击元素(link, checkbox, etc.)

     $driver->findElement(WebDriverBy::id('signin'))->click();
    
  4. 文字输入框写入文字

     $driver->findElement(WebDriverBy::id("element id"))->sendKeys("I want to send this");
    
  5. 文字框清除文字

     $driver->findElement(WebDriverBy::id("element id"))->clear();
    
  6. 查看元素是否可见

     $element = $driver->findElement(WebDriverBy::id('element id'));
     if ($element->isDisplayed()) {
         // do something...
     }
    
  7. 刷新页面

     $driver->navigate()->refresh();
    

    或者使用js方法,具体看后文。

  8. 是否可见

     $driver->findElement(WebDriverBy::id('element'))->isDisplayed();
    
  9. 获取attribute

     $driver->findElement(WebDriverBy::id('element'))->getAttribute('class');
    

警告框、确认框和提示框操作

  1. 警告框

     $driver->switchTo()->alert()->dismiss(); // dismiss
     $driver->switchTo()->alert()->getText(); // get alert text, works for confirmations and prompts
    
  2. 确认框

     $driver->switchTo()->alert()->accept(); // accept
     $driver->switchTo()->alert()->dismiss(); // dismiss
    
  3. 提示框

     $driver->switchTo()->alert()->sendKeys('Test'); // enter text
     $driver->switchTo()->alert()->accept(); // submit
    
     $driver->switchTo()->alert()->dismiss(); // dismiss
    
  4. 选择框

    $select = new WebDriverSelect($driver->findElement(WebDriverBy::xpath('xxx')));
    $select->selectByValue("zh-CN");
    

窗口

  1. 最大化窗口

    $driver->manage()->window()->maximize();

运行js

  1. 同步脚本

    js 脚本会完整地运行在闭包中。所以访问全局变量的时候必须把变量名打全,比如:window.document

     $sScriptResult = $session->executeScript('return window.document.location.hostname',array());
     $sScriptResult now holds the value of the current document hostname
    
  2. 异步脚本

    必须告诉服务器需要等待的时长,否则服务器会抛出超时异常错误。

     // wait at most 5 seconds before giving up with a timeout exception
     $session->timeouts()->async_script(array('ms'=>5000));
    

    异步脚本也是运行在闭包中。

    • 简单的例子

        $sResult = $session->executeAsyncScript('arguments[arguments.length-1]("done");', array());
      
    • 复杂的例子

    In the example below, we poll the global window.MY_STUFF_DONE value at regular intervals, waiting for it to exist with a non-false value. Once we see it, we return back to the calling php-webdriver code with the value “done”.

     // define the javascript code to execute. This just checks at a periodic
     // interval to see if your page created the window.MY_STUFF_DONE variable
     $sJavascript = <<<END_JAVASCRIPT
    
     var callback = arguments[arguments.length-1], // webdriver async script callback
         nIntervalId; // setInterval id to stop polling
    
     function checkDone() {
       if( window.MY_STUFF_DONE ) {
         window.clearInterval(nIntervalId); // stop polling
         callback("done"); // return "done" to PHP code
       }
     }
    
     nIntervalId = window.setInterval( checkDone, 50 ); // start polling
     END_JAVASCRIPT;
    
     $sResult = $session->executeAsyncScript($sJavascript,array());
     $sResult == "done"
    
  # By name
  $driver->manage()->deleteCookieNamed('CookieName');

  # Or all of them
  $driver->manage()->deleteAllCookies();

窗口控制

  1. 获得当前窗口句柄

     $handle = $session->getWindowHandle();
    
  2. 获得所有窗口句柄array

     $handles = $session->getWindowHandles(); //now can iterate through array to get desired handle
    
  3. 切换窗口

     $driver->switchTo()->window($handle);
    
  4. 切换Frame

     //Find an iframe
     $iframe = $session->findElement(WebDriverBy::tagName('iframe'));  //Get the iframe id
     $frameId = $iframe->getAttribute('id');
    

    //Switch the driver to the iframe $session->switchTo()->frame($iframe);

    //Go back to the main document $session->switchTo()->defaultContent();

    frame() 方法需要的是一个 element,所以用 WebDriverBy::xpath 也可以的。

     driver.switchTo().parentFrame();
     driver.switchTo().defaultContent();
    

寻找元素

  • Css选择器 - WebDriverBy::cssSelector(‘h1.foo > small’)
  • Xpath - WebDriverBy::xpath(‘(//hr)[1]/following-sibling::div[2]’)
  • Id - WebDriverBy::id(‘heading’)
  • Class - WebDriverBy::className(‘warning’)
  • input属性 - WebDriverBy::name(‘email’)
  • Tag - WebDriverBy::tagName(‘h1’)
  • 文字 - WebDriverBy::linkText(‘Sign in here’)
  • Partial link text - WebDriverBy::partialLinkText(‘Sign in’)

在下面的例子中 $driver 是 RemoteWebDriver 的实例

单个元素

$element = $driver->findElement(WebDriverBy::cssSelector('div.header'));
// $element will be instance of RemoteWebElement

$headerText = $element->getText();

多个元素

$elements = $driver->findElements(WebDriverBy::cssSelector('ul.foo > li'));
// $elements is now array - containing instances of RemoteWebElement (or empty, if no element is found)

foreach ($elements as $element) {
    var_dump($element->getText());
}

等待元素

$element = $driver->wait()->until(
    WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::cssSelector('div.bar'))
);

$elements = $driver->wait()->until(
    WebDriverExpectedCondition::presenceOfAllElementsLocatedBy(WebDriverBy::cssSelector('ul > li'))
);

这几个方法也有同样的效果 visibilityOfElementLocated, visibilityOf, stalenessOf, elementToBeClickable.

等待

// Wait for the page tile to be 'My Page'. 

// Default wait (= 30 sec)
$driver->wait()->until(
  WebDriverExpectedCondition::titleIs('My Page')
);

// Wait for at most 10s and retry every 500ms if it the title is not correct.
$driver->wait(10, 500)->until(
  WebDriverExpectedCondition::titleIs('My Page')
);

title

titleIs()
titleContains()
titleMatches()

URL

urlIs()
urlContains()
urlMatches()

元素

presenceOfElementLocated()
presenceOfAllElementsLocatedBy()
elementTextIs()
elementTextContains()
elementTextMatches()
textToBePresentInElementValue()

元素可见性

visibilityOfElementLocated()
visibilityOf()
invisibilityOfElementLocated()
invisibilityOfElementWithText()

Frames, 窗口, 警告

frameToBeAvailableAndSwitchToIt()
elementToBeClickable()
alertIsPresent()
numberOfWindowsToBe()

其它

stalenessOf()
refreshed()
not()
elementToBeSelected()
elementSelectionStateToBe()

自定义条件

until方法

$driver->wait()->until(
    function () use ($driver) {
        $elements = $driver->findElements(WebDriverBy::cssSelector('li.foo'));

        return count($elements) > 5;
    },
    'Error locating more than five elements'
);

确定的等待

$driver->manage()->timeouts()->implicitlyWait(10);

Ajax

$submitButton = $driver->findElement(WebDriverBy::id('Submit'));
$submitButton->click();
waitForAjax($driver); // Default is jquery same with waitForAjax($driver, 'jquery');
//waitForAjax($driver, 'prototype');
//waitForAjax($driver, 'dojo');
$anotherButton = $driver->findElement(WebDriverBy::id('secondButton'));

关于 waitForAjax() 有两种实现,可以选择其中一张。

function waitForAjax($driver, $framework='jquery')
{
    // javascript framework
    switch($framework){
        case 'jquery':
            $code = "return jQuery.active;"; break;
        case 'prototype':
            $code = "return Ajax.activeRequestCount;"; break;
        case 'dojo':
            $code = "return dojo.io.XMLHTTPTransport.inFlight.length;"; break;
        default:
            throw new Exception('Not supported framework');
    }

    do {
        sleep(2);
    } while ($driver->executeScript($code));
}

function waitForAjax($driver, $framework='jquery')
{
    // javascript framework
    switch($framework){
        case 'jquery':
            $code = "return jQuery.active;"; break;
        case 'prototype':
            $code = "return Ajax.activeRequestCount;"; break;
        case 'dojo':
            $code = "return dojo.io.XMLHTTPTransport.inFlight.length;"; break;
        default:
            throw new Exception('Not supported framework');
    }

    // wait for at most 30s, retry every 2000ms (2s)
    $driver->wait(30, 2000)->until(
        function ($driver, $code) {
            return !$driver->executeScript($code);
        }
    );
}    

其他的实现方法看这里 http://agilesoftwaretesting.com/?p=111

截图

使用

$full_screenshot = TakeScreenshot();
$screenshot_of_element = TakeScreenshot($this->driver->findElement(WebDriverBy::xpath("//img[@class='test']"));

实现

public function TakeScreenshot($element=null) {
    // Change the Path to your own settings
    $screenshot = $this->TempDirectoryPath . time() . ".png";

    // Change the driver instance
    $this->driver->takeScreenshot($screenshot);
    if(!file_exists($screenshot)) {
        throw new Exception('Could not save screenshot');
    }
    
    if( ! (bool) $element) {
        return $screenshot;
    }
    
    $element_screenshot = $this->TempDirectoryPath . time() . ".png"; // Change the path here as well
    
    $element_width = $element->getSize()->getWidth();
    $element_height = $element->getSize()->getHeight();
    
    $element_src_x = $element->getLocation()->getX();
    $element_src_y = $element->getLocation()->getY();
    
    // Create image instances
    $src = imagecreatefrompng($screenshot);
    $dest = imagecreatetruecolor($element_width, $element_height);

    // Copy
    imagecopy($dest, $src, 0, 0, $element_src_x, $element_src_y, $element_width, $element_height);
    
    imagepng($dest, $element_screenshot);
    
    // unlink($screenshot); // unlink function might be restricted in mac os x.
    
    if( ! file_exists($element_screenshot)) {
        throw new Exception('Could not save element screenshot');
    }
    
    return $element_screenshot;
}

参考资料


Windows 下安装 Docker

平时都是直接在 Linux 服务器上使用 Docker,原以为 Windows 下应该也差不多,没想到最终还是一波三折。还是得记录一下。

使用 docker toolbox 安装 docker

docker toolbox是一个工具集,它主要包含以下一些内容:

  • Docker CLI 客户端,用来运行docker引擎创建镜像和容器
  • Docker Machine. 可以让你在windows的命令行中运行docker引擎命令
  • Docker Compose. 用来运行docker-compose命令
  • Kitematic. 这是Docker的GUI版本
  • Docker QuickStart shell. 这是一个已经配置好Docker的命令行环境
  • Oracle VM Virtualbox. 虚拟机

Docker引擎的守护进程使用的是Linux的内核,需要运行Docker Machine 命令 docker-machine, 在你的机器上创建和获得一个Linux虚拟机,用这个虚拟机才可以在你的windows系统上运行Docker引擎

要想在 Windows 上使用 Docker,Windows 还必须满足以下几个条件:

  • Win 7 或以上的版本
  • 64 位操作系统
  • 必须支持硬件虚拟化技术(Hardware Virtualization Technology)并且已被启用 可以在任务管理器的 性能 -> CPU 一栏查看系统的虚拟化是否启用

安装Docker Toolbox

进入官网下载: 下载地址

Docker Toolbox 将会安装以下几个软件:

  • Windows版的Docker客户端
  • Docker Toolbox管理工具和ISO镜像
  • Virtualbox
  • Git MSYS-git Unix 工具

如果电脑已经装了Virtualbox,在安装的时候取消勾选就可以了

Docker 初始化配置

安装完成后,系统的开始菜单中会生成两个快捷方式

  • Docker Quickstart Terminal
  • Kitematic (Alpha)

双击第一个进行配置。

然后你会收到docker去github上下载iso文件,然后就是下载失败的警告。。

嗯,主要是。。。中国人。。。会遇到这个问题。

因为,Docker 源是被墙的。。。。

没关系,下一步再切换源,目前出现的原因是下载最新的boot2docker.iso。实际上我们在安装 Docker Toolbox 时安装目录已经存在了 iso 文件了。将它拷贝到 docker-machine 的缓存文件夹中。各人的文件位置可能不一样,比如我的:

C:\Users\kelu\.docker\machine\cache

所以下一步是切换源。

切换 Docker 源

docker machine 默认的配置文件名为 default,所以首先我们要进入它的环境修改配置:

docker-machine ssh default
vi /var/lib/boot2docker/profile

界面还蛮好看的2333333,大大的鲸鱼logo,下面跟着一行boot2docker。

在EXTRA_ARGS中配置地址:

  EXTRA_ARGS='
  --label provider=virtualbox
  --registry-mirror http://XXX.m.daocloud.io
  '

daocloud和阿里云都有各自的源。xxx是你注册daocloud时系统分配的地址

如果打算新建docker-machine,应该使用这个:

docker-machine create -d virtualbox --engine-registry-mirror=http://XXX.m.daocloud.io new-machine

结束

接下来,就可以按照linux下的使用习惯操作docker了。

参考资料


Selenium 介绍

之前写了一篇很初级的《selenium入门》,仅仅算是 Selenium 2 的 hello world。现在的这篇,主要是记录我对 Selenium 2(又名 WebDriver) 的一些认识。

前言

Selenium 是 ThroughtWorks 公司开发的针对Web应用的开源测试框架,支持多种浏览器和多种编程语言。Selenium 2 集成了 WebDriver(曾经是 Selenium 1的竞争对手)。

Selenium 和 WebDriver 开发人员都认为两个工具各有优势,二者合并将创造更强大的web测试框架。于是它们就合并了。真是一阵祥和。

更多 Selenium 和 WebDriver 的历史,可以查看官方blog:Selenium项目简史

Selenium 1 和 Selenium 2 的区别

习惯上 Selenium1.x 时通常指的是 Selenium RC, WebDirver 指的是 Selenium 2,反过来也是。

Selenium RC在浏览器中运行JavaScript应用,而WebDriver通过原生浏览器支持或者浏览器扩展直接控制浏览器。

Selenium 2包括Selenium Server,通过Selenium Grid支持分布式测试。

Selenium 3

与2的发布相隔了5年,2016-10-13,Selenium 的官方博客宣布了 Selenium 3 正式发布

新版本中 Selenium Core 的实现改由 WebDriver 的一个模块实现,这将影响所有 Selenium 1代 Selenium RC 的 API接口。对于 Selenium 2 用户基本没影响。 Selenium 1 被 WebDriver 替代的趋势较之 Selenium 2 更为强烈。

对于 Selenium 2/WebDriver 的用户,这个升级对 Api 接口没什么影响,只是解决了一些 bug。

Selenium 名字的来源

Selenium 的中文名为“硒”,是一种化学元素的名字,它对汞 (Mercury)有天然的解毒作用。

而 Mercury 公司开发了一系列的测试工具(QTP,QC,LR,WR…),他们功能强大,但是却很贵。

故 thoughtworks 特意把他们的 Web 开源测试工具命名为 Selenium,以此帮助大家脱离汞毒。

不由想到了 IBM 开发的 eclipse 意图对抗 Sun,嘿嘿。当然最后 Sun 确实被收购了。

Selenium 组件

刚接触时候我被各种 Selenium 绕晕了。一方面原因是 Selenium 混乱的组件,Selenium 2 的内容和 1 混淆起来就找不着北了。

从官网上可以看到,Selenium项目包含4个组件:

  • Selenium WebDriver 可本地运行或远程运行
  • Selenium Grid 分布式Selenium
  • Selenium IDE Firefox插件,有录制脚本的功能。支持自动录制动作和自动生成其他语言的自动化脚本。
  • Selenium RC(Selenium 1) 服务器客户端组件,可远程控制子节点

参考资料


我的开源项目 - KeluLinuxKit

因为一个人管着好几台服务器,每一次部署相同的环境都很揪心。这个项目也断断续续搞了好久,终于在最近弄好了,可以算是我服务器管理的一些经验。以后部署新服务器再也不纠结了。

如果你打算入门服务器管理,应该会对你有所帮助。

KeluLinuxKit 主要是面向 Debian 系机器的。在 Linode 上使用没有问题。安装了以下软件:

  • oh my zsh
  • Maximum Awesome
  • tmux-powerline
  • iptables
  • openresty
  • php5.6
  • postgresql9.4
  • composer
  • docker
  • docker_pptp
  • docker_shadowsocks
  • l2tp
  • Clusters pptp & shadowsocks

pptp、l2tp和shadowsocks基于集群主从模式的。通过cron自动同步和下发用户数据。虽然这个模式需要和我的某个网站项目配套才能使用,但是机制已经搭建好了,如果你也有,只需要稍作修改便可。

当然这个项目我会持续更新下去,有新软件需要使用的话也会添加进去。

服务器的终端界面如下: