RobotFramework + SeleniumLibrary + Appiumで、iOSシミュレータ上のSafariブラウザでテストする

以前、AndroidChromeを使うテストをRobot Frameworkで書いてみました。
RobotFramework + SeleniumLibraryにて、Android実機上のChromeを使ってテストする - メモ的な思考的な

今回は、iOSシミュレータ上のSafariを使うテストをRobot Frameworkで書いてみます。

なお、DesiredCapabilitiesの内容についてはSeleniumWikiに情報があります。
DesiredCapabilities · SeleniumHQ/selenium Wiki · GitHub

 
目次

 

環境

  • Mac OS X 10.11.6
  • Python 3.6.2
  • RobotFramework 3.0.2
  • SeleniumLibrary 3.0.0b1
  • iOS シミュレータ 10.2
  • Node.js 8.4.0
  • Xcode 8.2.1
  • AppiumとSeleniumは、いずれかの組み合わせで動作

 
今回、iOSシミュレータのSafariを使うために、Appium経由で操作してみます。

なお、RobotFrameworkとAppiumを連携するための robotframework-appiumlibrary ライブラリは不要でした。
serhatbolsu/robotframework-appiumlibrary: AppiumLibrary is an appium testing library for RobotFramework

 

環境構築

Node.jsのインストール

Appiumを使うためにはNode.jsが必要なため、Node.jsをインストールします。

 

既存のNode.jsを削除

手元のNode.jsを確認したところ、Node.jsがあったため、アンインストールします。

$ brew uninstall node
Error: Refusing to uninstall /usr/local/Cellar/node/8.1.4
because it is required by heroku 6.12.8, which is currently installed.
You can override this and force removal with:
  brew uninstall --ignore-dependencies node

以前、Heroku Toolbeltをインストールした時のものが残っているようでした。

 
今回、Heroku ToolbeltのNode.jsは後で入れ直すことにして、アンインストールします。

$ brew uninstall heroku
Uninstalling /usr/local/Cellar/heroku/6.12.8... (5,974 files, 30.7MB)
heroku 5.6.1-0976cf3 1 is still installed.
Remove all versions with `brew uninstall --force heroku`.

 
削除しきれていないようなので、Herokuのドキュメントに従って削除します。
Heroku CLI | Heroku Dev Center

$ brew uninstall --force heroku
Uninstalling heroku... (12,942 files, 79.7MB)

# herokuに書いてあった方法で追加削除
$ rm -rf ~/.local/share/heroku ~/.config/heroku ~/Library/Caches/heroku
(特に結果は表示されない)

 
もう一度確認とまだインストールされていたため、そちらもアンインストールします。

$ node -v
v8.1.4

# アンインストール
$ brew uninstall node
Uninstalling /usr/local/Cellar/node/8.1.4... (3,782 files, 44.7MB)

# 確認
$ node -v
-bash: /usr/local/bin/node: No such file or directory

 

nodebrewのインストール

Node.jsもバージョン管理したいなと思ったところ、nodebrewがありました。

そこで、以下を参考にnodebrewをインストールします。
Homebrewからnodebrewをインストールして、Node.jsをインストールするまで - Qiita

$ brew install nodebrew
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (caskroom/cask).

==> Using the sandbox
==> Downloading https://github.com/hokaccha/nodebrew/archive/v0.9.7.tar.gz
==> Downloading from https://codeload.github.com/hokaccha/nodebrew/tar.gz/v0.9.7
######################################################################## 100.0%
==> /usr/local/Cellar/nodebrew/0.9.7/bin/nodebrew setup_dirs
==> Caveats
Add path:
  export PATH=$HOME/.nodebrew/current/bin:$PATH

To use Homebrew's directories rather than ~/.nodebrew add to your profile:
  export NODEBREW_ROOT=/usr/local/var/nodebrew

Bash completion has been installed to:
  /usr/local/etc/bash_completion.d

zsh completions have been installed to:
  /usr/local/share/zsh/site-functions
==> Summary
🍺  /usr/local/Cellar/nodebrew/0.9.7: 8 files, 38KB, built in 17 seconds

 

nodebrewのセットアップ

nodebrew setup

# セットアップ
$ nodebrew setup
Fetching nodebrew...
Installed nodebrew in $HOME/.nodebrew

========================================
Export a path to nodebrew:

export PATH=$HOME/.nodebrew/current/bin:$PATH
========================================

~/.bash_profileに追記

export PATH=$HOME/.nodebrew/current/bin:$PATH

.bash_profileを再読込

$ source ~/.bash_profile

 

nodebrewを使ったNode.jsのインストール
$ nodebrew install-binary stable
Fetching: https://nodejs.org/dist/v8.4.0/node-v8.4.0-darwin-x64.tar.gz
######################################################################## 100.0%
Installed successfully

 

使うバージョンを指定
$ nodebrew use stable
use v8.4.0

 

npm init

全部Enterで進めます

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (selenium_appium_sample) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to path/to/robotframework_sample/selenium_appium_sample/package.json:

{
  "name": "selenium_appium_sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes) 

 

Appiumのインストール

GitHubのREADMEに従い、Appium本体をインストールします。
GitHub - appium/appium: Automation for iOS, Android, and Windows Apps.

$ npm install -g appium

npm WARN deprecated babel-core@5.8.24: Babel 5 is no longer being maintained. Upgrade to Babel 6.
npm WARN deprecated minimatch@2.0.10: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
npm WARN deprecated line-numbers@0.2.0: Copy its ~20 LOC directly into your code instead.
/Users/you/.nodebrew/node/v8.4.0/bin/appium -> /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/build/lib/main.js

> appium-chromedriver@2.11.2 install /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/appium-chromedriver
> node install-npm.js

info Chromedriver Install Installing Chromedriver version '2.28' for platform 'mac' and architecture '64'
info Chromedriver Install Opening temp file to write chromedriver_mac64 to...
info Chromedriver Install Downloading https://chromedriver.storage.googleapis.com/2.28/chromedriver_mac64.zip...
info Chromedriver Install Writing binary content to /var/folders/h0/l5plp4zd3517r988jpm481g00000gn/T/2017815-1009-nx99nt.vp8p/chromedriver_mac64.zip...
info Chromedriver Install Extracting /var/folders/h0/l5plp4zd3517r988jpm481g00000gn/T/2017815-1009-nx99nt.vp8p/chromedriver_mac64.zip to /var/folders/h0/l5plp4zd3517r988jpm481g00000gn/T/2017815-1009-nx99nt.vp8p/chromedriver_mac64
info Chromedriver Install Creating /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/appium-chromedriver/chromedriver/mac...
info Chromedriver Install Copying unzipped binary, reading from /var/folders/h0/l5plp4zd3517r988jpm481g00000gn/T/2017815-1009-nx99nt.vp8p/chromedriver_mac64/chromedriver...
info Chromedriver Install Writing to /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/appium-chromedriver/chromedriver/mac/chromedriver...
info Chromedriver Install /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/appium-chromedriver/chromedriver/mac/chromedriver successfully put in place

> appium-selendroid-driver@1.6.2 install /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/appium-selendroid-driver
> node ./bin/install.js

dbug AndroidDriver Getting Java version
info AndroidDriver Java version is: 1.8.0_144
info Selendroid Ensuring /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/appium-selendroid-driver/selendroid/download exists
info Selendroid Downloading Selendroid standalone server version 0.17.0 from http://repo1.maven.org/maven2/io/selendroid/selendroid-standalone/0.17.0/selendroid-standalone-0.17.0-with-dependencies.jar --> /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/appium-selendroid-driver/selendroid/download/selendroid-server-7cf7163ac47f1c46eff95b62f78b58c1dabdec534acc6632da3784739f6e9d82.jar
info Selendroid Writing binary content to /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/appium-selendroid-driver/selendroid/download/selendroid-server.jar.tmp
info Selendroid Selendroid standalone server downloaded
info Selendroid Determining AndroidManifest location
info Selendroid Determining server apk location
info Selendroid Extracting manifest and apk to /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/appium-selendroid-driver/selendroid/download
info Selendroid Copying manifest and apk to /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/appium-selendroid-driver/selendroid
info Selendroid Cleaning up temp files
info Selendroid Fixing AndroidManifest icon bug

> appium-uiautomator2-driver@0.3.3 install /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/appium-uiautomator2-driver
> node ./bin/install.js

dbug AndroidDriver Getting Java version
info AndroidDriver Java version is: 1.8.0_144
info UiAutomator2 downloading UiAutomator2 Server APK v0.1.5 : https://github.com/appium/appium-uiautomator2-server/releases/download/v0.1.5/appium-uiautomator2-server-v0.1.5.apk
info UiAutomator2 downloading UiAutomator2 Server test APK v0.1.5 : https://github.com/appium/appium-uiautomator2-server/releases/download/v0.1.5/appium-uiautomator2-server-debug-androidTest.apk
info UiAutomator2 UiAutomator2 Server APKs downloaded

> appium-windows-driver@0.5.0 install /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/appium-windows-driver
> node install-npm.js

Not installing WinAppDriver since did not detect a Windows system

> fsevents@1.1.2 install /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/fsevents
> node install

[fsevents] Success: "/Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/fsevents/lib/binding/Release/node-v57-darwin-x64/fse.node" already installed
Pass --update-binary to reinstall or --build-from-source to recompile

> heapdump@0.3.9 install /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium/node_modules/heapdump
> node-gyp rebuild

  CXX(target) Release/obj.target/addon/src/heapdump.o
  SOLINK_MODULE(target) Release/addon.node
+ appium@1.6.5
added 692 packages in 330.588s

 

Appium-doctorのインストール
$ npm install -g appium-doctor

/Users/you/.nodebrew/node/v8.4.0/bin/appium-doctor -> /Users/you/.nodebrew/node/v8.4.0/lib/node_modules/appium-doctor/appium-doctor.js
+ appium-doctor@1.4.3
added 155 packages in 28.769s

 

インストールされた内容を確認

[Node.js] インストール済みのパッケージ一覧を表示する - Qiita

$ npm -g list --depth=0
/Users/you/.nodebrew/node/v8.4.0/lib
├── appium@1.6.5
├── appium-doctor@1.4.3
└── npm@5.3.0

 

Javaまわりの設定を.bash_profileへ追加

Appium-doctorを実行したところ、 JAVA_HOME/bin のエラーが出ていたため、以下を参考に設定を追加します。
Appium Doctor - unable to set JAVA_HOME/bin - Issues/Bugs - Appium Discuss

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home
export PATH=$PATH:$JAVA_HOME/bin

 

CarthageをHomebrewでインストール

Appium-doctorを実行したところ、 Carthage でエラーが出ました。

$ appium-doctor

info AppiumDoctor Appium Doctor v.1.4.3
info AppiumDoctor ### Diagnostic starting ###
info AppiumDoctor  ✔ The Node.js binary was found at: /Users/you/.nodebrew/current/bin/node
info AppiumDoctor  ✔ Node version is 8.4.0
info AppiumDoctor  ✔ Xcode is installed at: /Applications/Xcode.app/Contents/Developer
info AppiumDoctor  ✔ Xcode Command Line Tools are installed.
info AppiumDoctor  ✔ DevToolsSecurity is enabled.
info AppiumDoctor  ✔ The Authorization DB is set up properly.
WARN AppiumDoctor  ✖ Carthage was NOT found!
info AppiumDoctor  ✔ HOME is set to: /Users/you
info AppiumDoctor  ✔ ANDROID_HOME is set to: /Users/you/android/
info AppiumDoctor  ✔ JAVA_HOME is set to: /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home
info AppiumDoctor  ✔ adb exists at: /Users/you/android/platform-tools/adb
info AppiumDoctor  ✔ android exists at: /Users/you/android/tools/android
info AppiumDoctor  ✔ emulator exists at: /Users/you/android/tools/emulator
info AppiumDoctor  ✔ Bin directory of $JAVA_HOME is set
info AppiumDoctor ### Diagnostic completed, one fix needed. ###
info AppiumDoctor 
info AppiumDoctor ### Manual Fixes Needed ###
info AppiumDoctor The configuration cannot be automatically fixed, please do the following first:
WARN AppiumDoctor - Please install Carthage. Visit https://github.com/Carthage/Carthage#installing-carthage for more information.
info AppiumDoctor ###
info AppiumDoctor 
info AppiumDoctor Bye! Run appium-doctor again when all manual fixes have been applied!
info AppiumDoctor 

 
Carthageの最新版は、Xcode 8.3系が必要です。

ただ、都合により手元のMacSierraにはできないため、以下を参考に古いバージョンのCarthageをインストールします。
Homebrewで過去のバージョンを使いたい - Carpe Diem

Carthage 0.23.0であれば、Xcode 8.2.1でも動作するようでした。

# ログを確認
$ brew log carthage --oneline
Warning: homebrew/core is a shallow clone so only partial output will be shown.
To get a full clone run:
  git -C "$(brew --repo homebrew/core)" fetch --unshallow
75d2a4a carthage: update 0.25.0 bottle.
4253a70 carthage 0.25.0
4a9efba carthage: update 0.24.0 bottle.
f74d9ba carthage 0.24.0
330abb7 carthage: update 0.23.0 bottle.
9e8ceb0 carthage 0.23.0
6ae4f69 carthage: update 0.22.0 bottle.
80e29a8 carthage 0.22.0
632ebc1 carthage: update 0.21.0 bottle.
2cad486 carthage 0.21.0
6d42f60 carthage: update 0.20.1 bottle.
a98dcba carthage 0.20.1
d3e71df carthage: update 0.20.0 bottle.
2a81ba5 carthage 0.20
3e8dea9 carthage: update 0.19.1 bottle.
75270e7 carthage 0.19.1
016acde gosu: update 1.14.2 bottle.

# HomebrewのFormulaディレクトリへ移動
$ cd /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/

# git fetch
$ git fetch --unshallow
remote: Counting objects: 290412, done.
remote: Compressing objects: 100% (128802/128802), done.
remote: Total 290412 (delta 162162), reused 287051 (delta 158817), pack-reused 0
Receiving objects: 100% (290412/290412), 69.61 MiB | 392.00 KiB/s, done.
Resolving deltas: 100% (162162/162162), completed with 3292 local objects.

# ログを再確認
$ brew log carthage --oneline | grep 0.23.0
330abb7 carthage: update 0.23.0 bottle.
9e8ceb0 carthage 0.23.0

 
ハッシュがわかったため、インストールします。

# git checkoutで戻す
$ git checkout 330abb7 carthage.rb 

# brew installでインストールする
$ brew install carthage
==> Downloading https://homebrew.bintray.com/bottles/carthage-0.23.0.el_capitan.bottle.tar.gz
######################################################################## 100.0%
==> Pouring carthage-0.23.0.el_capitan.bottle.tar.gz
==> Caveats
Bash completion has been installed to:
  /usr/local/etc/bash_completion.d

zsh completions have been installed to:
  /usr/local/share/zsh/site-functions
==> Summary
🍺  /usr/local/Cellar/carthage/0.23.0: 64 files, 22.4MB

# Carthageのバージョンを確認
$ carthage --version
Please update to the latest Carthage version: 0.25.0. You currently are on 0.23.0
Unrecognized command: '--version'. See `carthage help`.

 
インストールが終わったため、Homebrewのリポジトリを戻しておきます。

$ git reset HEAD
Unstaged changes after reset:
M   Formula/carthage.rb
$ git checkout .

 
再度確認したところ、OKそうでした。

$ appium-doctor
info AppiumDoctor Appium Doctor v.1.4.3
info AppiumDoctor ### Diagnostic starting ###
info AppiumDoctor  ✔ The Node.js binary was found at: /Users/you/.nodebrew/current/bin/node
info AppiumDoctor  ✔ Node version is 8.4.0
info AppiumDoctor  ✔ Xcode is installed at: /Applications/Xcode.app/Contents/Developer
info AppiumDoctor  ✔ Xcode Command Line Tools are installed.
info AppiumDoctor  ✔ DevToolsSecurity is enabled.
info AppiumDoctor  ✔ The Authorization DB is set up properly.
info AppiumDoctor  ✔ Carthage was found at: /usr/local/bin/carthage
info AppiumDoctor  ✔ HOME is set to: /Users/you
info AppiumDoctor  ✔ ANDROID_HOME is set to: /Users/you/android/
info AppiumDoctor  ✔ JAVA_HOME is set to: /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home
info AppiumDoctor  ✔ adb exists at: /Users/you/android/platform-tools/adb
info AppiumDoctor  ✔ android exists at: /Users/you/android/tools/android
info AppiumDoctor  ✔ emulator exists at: /Users/you/android/tools/emulator
info AppiumDoctor  ✔ Bin directory of $JAVA_HOME is set
info AppiumDoctor ### Diagnostic completed, no fix needed. ###
info AppiumDoctor 
info AppiumDoctor Everything looks good, bye!
info AppiumDoctor 

 

iOS シミュレータの準備

Xcodeを起動し、 Window > Devices より、インストール済のシミュレータ一覧を表示します。

次に左下の + ボタン > Add Simulator よりシミュレータを追加します。

項目名
Simulator Name iOS10.2 for RF
Device Type iPhone 7 Plus
OS Version iOS 10.2

 

Robot Frameworkのテストコード実装

今回はGoogleでログインボタンを押してログイン画面へと遷移するテストを書いてみます。

*** Settings ***

# Libraryは大文字小文字の区別があるようで、libraryとしてしまうとIDEが認識しない
Library  SeleniumLibrary

# テストケースごとにブラウザを閉じる
Test Teardown  close browser

*** Keywords ***
Googleのトップページでスクリーンショットを撮る
    # iOSシミュレータを使うためのDesired Capabilitiesを設定する
    # platformVersionとdeviceNameはXcodeで作成した内容を指定
    ${caps}=  create dictionary  browserName=safari  platformName=iOS  platformVersion=10.2
    ...                          deviceName=iOS10.2 for RF

    # WebDriverにはRemote、command_executorにはAppiumの待ち受けているURL、
    # desired_capabilitiesには作成したDesired Capabilitiesをそれぞれ指定
    create webdriver  Remote  command_executor=http://localhost:4723/wd/hub  desired_capabilities=${caps}

    # Googleのトップページを開く
    go to  https://www.google.co.jp/

    # スクリーンショットを撮る
    capture page screenshot  filename=result_google_top.png


*** TestCases ***

Googleのトップページに関するテスト
    [Tags]  signin
    Googleのトップページでスクリーンショットを撮る

 

テスト実行

Appiumの起動

新しいターミナルを開いて、Appiumを起動します。

$ appium
[Appium] Welcome to Appium v1.6.5
[Appium] Appium REST http interface listener started on 0.0.0.0:4723

 

テストコードの実行

RobotFrameworkのテストを実行します。初回はシミュレータが起動するまでにかなり時間がかかります。

しばらく待つと、以下のようにテストをパスします。

# RobotFrameworkを実行
# iオプションでTag指定(topというタグがついているテストのみ実行)
$ robot -i top ios.robot 
===========================================
Ios
===========================================
Googleのトップページに関するテスト  | PASS |
-------------------------------------------
Ios                             | PASS |
1 critical test, 1 passed, 0 failed

 
また、iOSシミュレータの画面のスクリーンショットが撮られ、テスト実行時のディレクトリへと保存されます。

f:id:thinkAmi:20170915221906p:plain

 

Googleで検索するテスト

Selenium > 3.3.1 & Appium 1.6.5では失敗

続いて、Androidのときと同じようにGooglePythonを検索するテストを書いてみます。

GoogleでPythonを検索してスクリーンショットを撮り、結果を出力する
    ${caps}=  create dictionary  browserName=safari  platformName=iOS  platformVersion=10.2
    ...                          deviceName=iOS10.2 for RF
    create webdriver  Remote  command_executor=http://localhost:4723/wd/hub  desired_capabilities=${caps}
    go to  https://www.google.co.jp/

    # タイトルにGoogleが含まれていることを確認する
    ${page_title} =  get title
    should contain  ${page_title}  Google

    # 検索語を入力して送信する
    input text  name=q  Python
    # Robot FrameworkではEnterキーは\\13になる
    # https://github.com/robotframework/Selenium2Library/issues/4
    press key  name=q  \\13

    # Ajax遷移のため、適当に2秒待つ
    sleep  2sec

    # タイトルにPythonが含まれていることを確認する
    ${result_title} =  get title
    should contain  ${result_title}  Python

    # スクリーンショットを撮る
    capture page screenshot  filename=result_google_python.png

    # ログを見やすくするために改行を入れる
    log to console  ${SPACE}

    # 検索結果を表示する
    @{web_elements} =  get webelements  css=h3 > a
    :for  ${web_element}  in  @{web_elements}
    \  ${text} =  get text  ${web_element}
    \  log to console  ${text}
    \  ${href} =  call method  ${web_element}  get_attribute  href
    \  log to console  ${href}

 
実行します。

$ robot -i python ios.robot 
========================================
Ios
========================================
GoogleでPythonを検索するテスト  | FAIL |
WebDriverException: Message: Parameters were incorrect. We wanted {"required":["value"]} and you sent ["text","value","id","sessionId"]
----------------------------------------
Ios                          | FAIL |
1 critical test, 0 passed, 1 failed

テストが失敗しました。

エラーメッセージで検索すると、以下のIssueがありました。
Python Selenium client incompatible with Appium server 1.6.4. · Issue #8253 · appium/appium · GitHub

SendKeyまわりに不具合があるようです。

v1.6.6には取り込まれるそうなので、その時にまた確認することにします。

もしSendKeyまわりを使う場合には、SeleniumやAppiumを古いバージョンにして使う必要がありそうです。

 

Selenium == 3.3.1 & Appium 1.6.5では成功

どのバージョンなら動くかを調べたところ、以下に情報がありました。Selenium3.3.1 にすれば良さそうです。
send_keys -> Message: Parameters were incorrect. We wanted {"required":["value"]} and you sent ["text","sessionId","id","value"] · Issue #162 · appium/python-client · GitHub

 
バージョンを切り替えます。

# seleniumだけ3.3.1、あとは同じ
$ pip list
pip (9.0.1)
robotframework (3.0.2)
robotframework-seleniumlibrary (3.0.0b1)
selenium (3.3.1)
setuptools (28.8.0)

テストを実行してみます。

$ robot -i python ios.robot 
==============================================================================
Ios                                                                           
==============================================================================
GoogleでPythonを検索するテスト                                         
Python - ウィキペディア
https://ja.m.wikipedia.org/wiki/Python
【入門者必見】Pythonとは?言語の特徴やシェア、仕事市場を徹底解説 - 侍エンジニア塾
http://www.sejuku.net/blog/7720
Python基礎講座(1 Pythonとは) - Qiita
http://qiita.com/Usek/items/ff4d87745dfc5d9b85a4
Pythonとは?何に使えるの?Pythonの特徴や使い道を…|Udemy メディア
https://udemy.benesse.co.jp/development/python.html
Python入門
http://www.tohoho-web.com/python/
初心者でもほぼ無料でPythonを勉強できるコンテンツ10選 - paiza開発日誌
http://paiza.hatenablog.com/entry/2015/04/09/%E5%88%9D%E5%BF%83%E8%80%85%E3%81%A7%E3%82%82%E3%81%BB%E3%81%BC%E7%84%A1%E6%96%99%E3%81%A7Python%E3%82%92%E5%8B%89%E5%BC%B7%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%8410
Python チュートリアル — Python 3.6.1 ドキュメント
https://docs.python.jp/3/tutorial/index.html
Python 3.6.1 ドキュメント
https://docs.python.jp/3/index.html
GoogleでPythonを検索するテスト                                        | PASS |
------------------------------------------------------------------------------
Ios                                                                   | PASS |
1 critical test, 1 passed, 0 failed

成功しました。

スクリーンショットも問題なく撮れています。

f:id:thinkAmi:20170915221930p:plain

 

Selenium == 3.5.0 & Appium 1.7.0では成功 (2017/9/20追記)

Appium1.7.0がリリースされていたため、インストールして試してみました。

# アップデート
$ npm install -g appium
...
+ appium@1.7.0
added 78 packages, removed 110 packages and updated 92 packages in 161.01s

# バージョンを確認
$ appium --version
1.7.0

# Python環境を確認
$ pip list
robotframework (3.0.2)
robotframework-seleniumlibrary (3.0.0b1)
selenium (3.5.0)

 
環境が整いましたので、テストを実行してみます。

$ robot -i python ios.robot 

===============================
Ios
===============================
GoogleでPythonを検索するテスト                                         
Python - ウィキペディア
https://ja.m.wikipedia.org/wiki/Python
Python基礎講座(1 Pythonとは) - Qiita
http://qiita.com/Usek/items/ff4d87745dfc5d9b85a4
【入門者必見】Pythonとは?言語の特徴やシェア、仕事市場を徹底解説 ...
http://www.sejuku.net/blog/7720
Python 3を使うべきでない場合(なんてない) | プログラミング | ...
http://postd.cc/case-python-3/
初心者でもほぼ無料でPythonを勉強できるコンテンツ10選 - paiza開発 .. ...
http://paiza.hatenablog.com/entry/2015/04/09/%E5%88%9D%E5%BF%83%E8%80%85%E3%81%A7%E3%82%82%E3%81%BB%E3%81%BC%E7%84%A1%E6%96%99%E3%81%A7Python%E3%82%92%E5%8B%89%E5%BC%B7%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%8410
Pythonとは?何に使えるの?Pythonの特徴や使い道を… ...
https://udemy.benesse.co.jp/development/python.html
Python入門
http://www.tohoho-web.com/python/
Python 3.6.1 ドキュメント
https://docs.python.jp/3/index.html
Python チュートリアル — Python 3.6.1 ドキュメント
https://docs.python.jp/3/tutorial/index.html
GoogleでPythonを検索するテスト                                        | PASS |

テストがPASSしました。

Appium1.7.0ではsend_keysの不具合は修正されたようです。

 

Appiumの停止

そのままではAppiumは停止しないため、 Cmd + C で停止させます。

 

ソースコード

GitHubに上げました。 selenium_appium_sample/ios.robot ファイルが今回のテストファイルです。
thinkAmi-sandbox/RobotFramework-sample: Robot Framewrok samples

#pyconjp PyCon JP 2017に参加しました

9/8と9/9に、早稲田大学西早稲田キャンパス63号館で開催された「PyCon JP 2017」に参加しました。
PyCon JP 2017 in Tokyo | Sep 7th – Sep 10th

前回PyCon JPに参加したのは2012年だったようで、5年ぶりの参加でした。
PyCon JP 2012に参加してきました(9/15,16) - メモ的な思考的な

 
以下、簡単な感想を2日間まとめてメモしておきます。

目次

 

Keynote

Peter Wang氏

初日のKeynoteです。遅れて会場入りしたこともあり、既に始まっていました。

自分の知識不足もあり、データサイエンス系のところはなかなか難しかったですが、コミュニティのスタイルはその国にあった形があるというところが印象に残りました。

 

Masaaki Horikoshi氏

こちらは2日目のKeynoteです。

OSS活動は良いと分かっていてもハードルが高く感じるために、それを下げてくれるKeynoteでした。初めてOSS活動を開始する時に参考になるお話がいろいろとありました。

特に、貢献先は自分の興味があるOSSに絞ること、解決しやすいIssueから取り組みはじめると良いというのが印象に残りました。

 

参加したトーク

Pythonとパッケージングと私 (Atsushi Odagiri氏)

setup.pyとか雰囲気で書いているため、パッケージングまわりを知りたくて参加しました。

setuptoolsの30.3.0からsetup.cfgが使えるようになるとのことで、こうなればsetup.pyがスッキリして良さそうでした。

ただ、仮想環境を作った時のsetuptoolsのバージョンがまだそこまでいっていないため、実際に使えるようになるのはもう少し先というのが残念です…

 

メディア会議

技術書はどのように作られるのか気になって参加しました。

パネルディスカッション形式で、本を作る裏側が見えて良かったです。

 

Djangoフレームワークのユーザーモデルと認証 (Shinya Okano氏)

昔、Djangoの認証系をさわったことがあったので、知識を最新にアップデートするために参加しました。

class-based viewでいろいろと書けるようになったのは本当に嬉しい感じです。

 

len()関数がオブジェクトの長さを手にいれる仕組み (Takayuki Shimizukawa氏)

Pythonのコアな話はなかなか日常に登場しないので、気になって参加しました。

関数とアダプタの関係、そしてチェック機構はアダプタ側に持たせていると知り、ためになりました。

Pythonにおけるプロトコルの概念や一覧の紹介もあり、こちらも参考になりました。

 

How (and Why) We Speak in Unicode (Devon Peticolas氏)

しばらく前に 'ascii' codec can't decode byte なエラーと格闘したことがあったので、PythonUnicodeまわりを知ろうと思い参加しました。

Unicodeに至るまでの歴史の解説の中に、スライドの中にアスキーアートが登場したり、日本語の事情が含まれていたりと、なかなかおもしろかったです。

 

Secrets of a WSGI master (Graham Dumpleton氏)

WSGIまわりをさわったことがあったため、WSGIの知識を深めたくて参加しました。

PEP3333に基づいたWSGIの説明と、mod_wsgi の使い方と設定、mod_wsgi-expressの例やDockerでの利用方法がありました。

mod_wsgiをさわる機会はまだまだありそうなので、いろいろと参考になったのと、今後使う時は参照しようと思いました。

 

OpenAPIを利用したPythonWebアプリケーション開発 (Takuro Wada氏)

Swaggerという単語は聞いたことがあったので、具体的な内容を知りたくて参加しました。

Open APIと、現在のバージョン(2.0と3.0)、また各言語向けのライブラリが紹介されていて参考になりました。

 

Python におけるドメイン駆動設計(戦術面)の勘どころ (Junya Hayashi氏)

ドメイン駆動設計をどうPythonに落とし込めばよいのかよく分かっていないため、それを知りたくて参加しました。

概念から始まり、Pythonで実装する時にダメな実装方法と良い実装方法の解説がありました。

サンプルソースコードも公開されていたため、あとでコードを読みつつ理解したいと思います。

 

機械学習におけるデータの再現性について (Yuichiro Someya氏)

データの再現性についてどのように実現するのだろうと気になって参加しました。

再現性におけるつらいところが分かるとともに、 akagi というライブラリにより解決した部分の解説がありました。

それでもまだつらい部分は残るようで、この解決はまた大変そうだと感じました。

 

Pythonをとりまく並行/非同期の話 (tell-k氏)

Pythonの並行/非同期についてふわっとしか理解していないため参加しました。

用語の定義から実装例まで分かりやすい解説があり、とてもためになりました。

資料もきちんとまとまっていたのがありがたいです。今後このあたりをさわるときは、もう一度読み返そうと思います。

 

AWS APIGateway + Python Lambda + NEologdで作るサーバレス日本語形態素解析API (Satoru Kadowaki氏)

実装を例に、工夫したところや苦労したところの情報共有がありました。

NEologdの辞書を充実させる方向ではなく、前処理を頑張る方向での実装でした。前処理の頑張りにより辞書が変わっても結果は変わらなそうなのが印象に残りました。

 

ブース

トーク以外の時間は、主にブースにて過ごしました。

Python/Django + HoloLensなアプリをメインに体験していただきました。

 

その他

ごはん

今回は、2日間を通して、朝・昼・おやつ、そして1日目のパーティと、会場の外に出ることなくごはんをいただけました。

いずれもボリューム満点な上、お昼ごはんは種類の選択肢もありました。ありがたい限りです。

 

モノタロウ侍さん

モノタロウ侍さんにポーズをいただきました。

 

PyCon JP 2018について

既に開催が決まっていました。

2018/9/17(月・祝)~9/18(火)、場所は大田区産業プラザPiO(Plaza Industry Ota)とのことです。
交通アクセス|大田区産業プラザPiO

 
最後になりましたが、PyCon JP 2017を運営してくださったみなさま、ありがとうございました。

Bitbucketにて、あるブランチのファイルの最新版をURLで指定する

Bitbucketにて、あるブランチのファイルの最新版をURLで指定しようとして悩んだのでメモ。

 
通常、BitbucketのあるブランチのファイルのURLには、Gitのコミットハッシュが含まれます。

例えば、 python-bitbucket というライブラリのREADME.rstについてブラウザでリンクをクリックしていくと

のように、 src の下に、Gitのコミットハッシュが含まれます。

 
Gitのコミットハッシュを使わない方法を調べたところ、stackoverflowに情報がありました。
hyperlink - Link latest file on Bitbucket Git repository - Stack Overflow

src/[branch_name]/path/to/file を指定すれば良いとのことです。

 
そのため、python-bitbucketのブランチ release/0.10 のREADME.rstの最新版の場合には、

となります。

ブランチ名に / が含まれていても、そのままURLに書けば良いようです。

RobotFramework + SeleniumLibraryにて、Android実機上のChromeを使ってテストする

Android実機上のChromeを使うテストをRobotFrameworkで書く機会があったため、メモを残します。

なお、環境構築などは以下が参考になりました。ありがとうございました。

 
目次

 

環境

 
なお、このMacにはJDK1.8がインストール済です。必要かどうか分かりませんが、うまくいかない場合はJDKも入れてみてください。

 

準備

Android接続環境の構築

手元のMacにはAndroid接続環境がなかったため、環境を構築します。

Slideshareの資料では、Android SDK Toolsをインストールし、 adb devices を使ってAndroid実機が接続されているかを確認していました。

現時点でadb コマンドを使うには Platform-tools のインストールが必要です。
IDE および SDK ツールの更新 | Android Studio

そのため、

  • Android Studioをインストールし、Platform-toolsをGUIでインストール
  • Android SDK Toolsをダウンロードし、Platform-toolsをCUIでインストール
    • 昔はAndroid SDK ToolsのGUIでインストールできたが、最近削除された

のどちらかを行います。

 
今回は

  • Android実機にアプリをデプロイすることはない
  • Android実機上のChromeを動かすだけ
  • インストールは必要最小限にしたい

のため、Android SDK ToolsのCUIでインストールすることにしました。

 

Android SDK Toolsのダウンロード

以下のページの下部にある、「コマンドライン ツールのみ入手する」からダウンロードします。
https://developer.android.com/studio/index.html?hl=ja#downloads

今回は、 sdk-tools-darwin-3859397.zip をダウンロードしました。

ダウンロード後、任意のディレクトリに展開しました。

 

platform-toolsのインストール

以下を参考に、sdkmanager を使ってコマンドラインにてインストールします。
sdkmanager | Android Studio

# インストール可能なものを確認
$ ./bin/sdkmanager --list
...
platform-tools                    | 26.0.0       | Android SDK Platform-Tools       
...

# platform-toolsをインストール
$ ./bin/sdkmanager "platform-tools"
# 途中でAcceptが出るので、y
Accept? (y/N): y
done

 

接続確認

以下の手順に従い、端末の接続を確認します。
ハードウェア端末上でアプリを実行する | Android Studio

なお、MacにはUSB Driverは不要のようです。

$ ./platform-tools/adb devices
List of devices attached
* daemon not running. starting it now at tcp:5037 *
* daemon started successfully *
xxxxxxxx     unauthorized

 
この時点で実機を見ると「USBデバックを許可しますか?」が表示されるため、OKを押します

再度実行します。

$ ./platform-tools/adb devices
List of devices attached
xxxxxxxx     device

認識されたようです。

 

.bashprofileの設定

以下を参考に、末尾に設定を追加します。
android sdk tools 25.3.0 に関しての備忘メモ - exception think

# for Android
export ANDROID_HOME=$HOME/android/
PATH=$ANDROID_HOME/tools:$PATH
PATH=$ANDROID_HOME/tools/bin:$PATH
PATH=$ANDROID_HOME/platform-tools:$PATH

 

ChromeDriverの起動

事前にChromeDriverを起動しておくことで、Android実機上のChromeに接続できるようになります。

$ chromedriver
Starting ChromeDriver 2.31.488774 (7e15618d1bf16df8bf0ecf2914ed1964a387ba0b) on port 9515
Only local connections are allowed.

 
以上で準備が完了しました。

 

テストコードの実行

RobotFrameworkのテストコードを作成
Create Webdriverについて

以前同様、GooglePythonを検索するテストコードを作成します。

ポイントはCreate Webdriverのところです。

PythonSelenium

options = webdriver.ChromeOptions()
options.add_experimental_option('androidPackage', 'com.android.chrome')
driver = webdriver.Chrome(chrome_options=options)

となる場合、RobotFrameworkでは

${options} =  evaluate  sys.modules['selenium.webdriver'].ChromeOptions()  sys
call method  ${options}  add_experimental_option  androidPackage  com.android.chrome
create webdriver  Chrome  chrome_options=${options}

となります。

 

ソースコード全体
*** Settings ***

Library  SeleniumLibrary


*** Keywords ***
GoogleでPythonを検索してスクリーンショットを撮り、結果を出力する
    # 以下のコードをRobot Framework風にした
    # http://qiita.com/orangain/items/db4594113c04e8801aad

    # 以下を参考に、Chromeのオプションを追加して、Chromeを起動する
    # https://sites.google.com/a/chromium.org/chromedriver/getting-started/getting-started---android
    ${options} =  evaluate  sys.modules['selenium.webdriver'].ChromeOptions()  sys
    call method  ${options}  add_experimental_option  androidPackage  com.android.chrome
    create webdriver  Chrome  chrome_options=${options}

    # Googleのトップ画面を開く
    go to  https://www.google.co.jp/

    # タイトルにGoogleが含まれていることを確認する
    ${page_title} =  get title
    should contain  ${page_title}  Google

    # 検索後を入力してEnter
    input text  name=q  Python
    # Robot FrameworkではEnterキーは\\13になる
    # https://github.com/robotframework/Selenium2Library/issues/4
    press key  name=q  \\13

    # Ajax遷移のため、適当に2秒待つ
    sleep  2sec

    # タイトルにPythonが含まれていることを確認する
    ${result_title} =  get title
    should contain  ${result_title}  Python

    # スクリーンショットを撮る
    capture page screenshot  filename=result_google_python.png

    # ログを見やすくするために改行を入れる
    log to console  ${SPACE}

    # 検索結果を表示する
    @{web_elements} =  get webelements  css=h3 > a
    :for  ${web_element}  in  @{web_elements}
    \  ${text} =  get text  ${web_element}
    \  log to console  ${text}
    \  ${href} =  call method  ${web_element}  get_attribute  href
    \  log to console  ${href}

    # ブラウザを終了する
    close browser


*** TestCases ***

GoogleでPythonを検索するテスト
    GoogleでPythonを検索してスクリーンショットを撮り、結果を出力する

 

テストの実行

Android実機上でChromeが起動し、テストコードが実行されました。

$ robot google.robot 
================================
Google
================================
GoogleでPythonを検索するテスト
Python - ウィキペディア
https://ja.wikipedia.org/wiki/Python
...
GoogleでPythonを検索するテスト    | PASS |

 
スクリーンショットAndroid上のChromeの画面が撮れていました。

f:id:thinkAmi:20170901220911p:plain

 

ソースコード

GitHubに上げました。 selenium_android_sample/android_chrome.robot ファイルが今回のテストファイルです。
thinkAmi-sandbox/RobotFramework-sample: Robot Framewrok samples

RobotFrameworkのSelenium2Libraryの名前が、SeleniumLibraryへと変更されてた

RobotFrameworkのSelenium2LibraryのGitHubを見ていたところ、名前が変更されたのに気づいたため、メモ。

 
目次

 

環境

  • Mac OS X 10.11.6
  • Python 3.6.2
  • Google Chrome 60.0.3112.113 (stable)
  • ChromeDriver 2.31 (stable)
  • RobotFramework 3.0.2
  • SeleniumLibrary 3.0.0a2

 

公式サイトの確認

GitHubを見たところ、以下のコメントがありました。

Selenium2Library 3.0 and newer extend the new SeleniumLibrary and thus contain exactly the same code and functionality. There have been lot of internal changes in the library, but external functionality provided by keywords should be fully backwards compatible. Libraries and tools using Selenium2Library internally may need to be updated to support Selenium2Library 3, though. Selenium2Library 1.8 is the latest, and last, legacy version with the old architecture and code.

Selenium2Library 3 supports Python 2.7 as well as Python 3.3 and newer. Selenium2Library 1.8 supports Python 2.6-2.7.

https://github.com/robotframework/Selenium2Library

 
そこでリンク先(https://github.com/robotframework/SeleniumLibrary)を確認したところ、たしかに今までのものが移動されていました。
https://github.com/robotframework/SeleniumLibrary

 
また、すでにdeprecatedしていた初期のライブラリについては、リポジトリ名が OldSeleniumLibrary へと変わっていました。
robotframework/OldSeleniumLibrary: Deprecated Selenium library for Robot Framework

 
PyPiを見ると、こんな感じでした。

 

影響

ライブラリ名が変わったことの影響を記載します。

pipでインストールするライブラリ名の変更

robotframework-selenium2library から robotframework-seleniumlibrary へと変わりました。

今まで

pip install robotframework-selenium2library

 
これから

現時点では移行段階のため pre などのオプションが必要ですが、将来は不要になるかと。

pip install --pre --upgrade robotframework-seleniumlibrary

 

Settingsで読み込むライブラリ名

過去のテストコードを修正しないといけないため、こちらのほうが影響が大きそうです。

今まで

Library  Selenium2Library

 
これから

Library  SeleniumLibrary

 

ドキュメントへのリンク

READMEにもありますが、ドキュメントも移動しています。

今まで

http://robotframework.org/Selenium2Library/Selenium2Library-1.8.0.html

 
これから

http://robotframework.org/SeleniumLibrary/SeleniumLibrary.html

 

自作のサンプルコード

過去に作成したサンプルコードについては、SeleniumLibrary 3.x系へとバージョンアップし、すべての動作を確認しました。
thinkAmi-sandbox/RobotFramework-sample: Robot Framewrok samples

Robot Framework + Selenium2Libraryで、リファラを書き換えるChrome拡張をWebDriverに入れてテストする

リファラによって挙動が変わるWebアプリをRobot Frameworkでテストすることがありました。

Webアプリのコードを書き換えたくないため、なにか良い方法がないかを探したところ、

を組み合わせれば良さそうでした。

その時に試した内容をメモしておきます。

 
2017/9/2追記

Selenium2Libraryですが、バージョン3からは SeleniumLibrary へと名称が変更されています。詳しくはこちらに書きました。
RobotFrameworkのSelenium2Libraryの名前が、SeleniumLibraryへと変更されてた - メモ的な思考的な

なお、本文はSelenium2Libraryのままにしてあります。

2017/9/2追記ここまで

 
目次

 

環境

 

Seleniumで開くChromeDriverに拡張を入れる 

packedとunpacked、どちらの方式の拡張を入れるか

Seleniumで使うChromeDriverに拡張を入れる方法は、ChromeDriverの公式サイトにありました。
Chrome Extensions - ChromeDriver - WebDriver for Chrome

方法としては、

の2つがあるようです。

紹介されているコードはJavaっぽいため、Pythonのコードを探したところ、前者については以下などがありました。
google chrome - Running Selenium WebDriver using Python with extensions (.crx files) - Stack Overflow

一方、後者のDesiredCapabilitiesオブジェクトの setCapability() メソッドを使う方法を調べてみましたが、それらしい事例はなく、Pythonライブラリのソースコードにもそれらしいメソッドは見当たりませんでした。
selenium/desired_capabilities.py at master · SeleniumHQ/selenium

そこで今回は、前者の crxファイルを使う方法で実装することにしました。

 

packedな拡張を作成する方法

Chrome拡張がどこにインストールされるかを調べたところ、 ~/Library/Application Support/Google/Chrome のようでした。
User Data Directory

また、Chrome拡張のID(ディレクトリ名)は、以下を参考にして確認しました。
Google Chrome のアプリや拡張機能、テーマが保存されている場所 - Qiita

そのディレクトリを見たところ、unpackedな状態だったため、何らかの方法で crxファイルを取得する必要がありました。

 
調べてみたところ、以下のページにpackedな拡張の作り方が記載されていました。
CRX Package Format - Google Chrome

ただ、pemファイルを用意するなどの手間がかかりそうなため、もっと容易な方法を探してみたところ、Chrome拡張のページからcrxファイルを取得するChrome拡張 Get CRX がありました。
Get CRX - Chrome ウェブストア

試しに Referer Control のページで使ってみたところ、crxファイルが取得できました。

今回はそのファイルを、テストを実行するディレクトリ直下に referer_control.crx として保存します。

 

Robot Frameworkで実装する

HeadlessなChromeだとChrome拡張ページが開けないため、今回は普通のChromeを使って実装します。

${options} =  evaluate  sys.modules['selenium.webdriver'].ChromeOptions()  sys

# HeadlessだとChrome拡張ページが開けない
# call method  ${options}  add_argument  --headless

# ${OUTPUT DIR}は、テストを実行するディレクトリ
call method  ${options}  add_extension  ${OUTPUT DIR}/referer_control.crx
create webdriver  Chrome  chrome_options=${options}

 

Referer Controlでリファラを書き換える

上記のままではChrome拡張を入れるだけで何も設定がされないため、Referer Controlの設定を行います。

 

Referer Controlの設定方法を調べる

以下が参考になりました。
Chromeでリファラ偽装する方法(リファラコントロールの使い方)

 

Robot Frameworkで実装する

上記の通り、Referer Controlの設定はブラウザから行うため、Robot Frameworkで実装できそうでした。

Robot Frameworkのlocatorに何を使うか悩みましたが、Chrome拡張のHTMLはそれほど変わらないだろうと考えました。

そこで、Chrome Developer Toolを使ってXPathを取得し、それをlocatorとして使いました。*1

あとは、Robot Frameworkで実装します。

今回は http://localhost:8084/referer/target というURLの場合のみ、リファラhttps://www.google.co.jp へと書き換えるようにしました。

# 「Referer Control」拡張の設定ページを開く
go to  chrome-extension://hnkcfpcejkafcihlgbojoidoihckciin/chrome/content/options.html

wait until page contains element  xpath=//*[@id="settingsTable"]/tbody/tr/td[2]/input
click element  xpath=//*[@id="settingsTable"]/tbody/tr/td[2]/input

# site filterに入力する
# 今回は、「http://localhost:8084/referer/target」というサイトであればリファラを書き換える
input text     xpath=//*[@id="settingsTable"]/tbody/tr/td[2]/input  http://localhost:8084/referer/target

# Customボタンを押す
click element  xpath=//*[@id="settingsTable"]/tbody/tr/td[8]

# Custom refererを入力する(Googleから来たことにする)
# 1秒待たないとうまくいかない
sleep  1s
input text  xpath=//*[@id="settingsTable"]/tbody/tr[5]/td[2]/table/tbody/tr[1]/td[1]/input  https://www.google.co.jp

# 適当なところをクリックして保存
click element  xpath=//*[@id="settingsTable"]/tbody/tr/td[2]/input

 

Bottleアプリの実装と動作確認

Bottleアプリの実装

上記のRobot Frameworkのテストコードが正しく動作するかを確認するため、Bottleアプリを作成して動作を確認します。

今回は、単純にリクエストヘッダの Referer を表示するだけにします。

target.py

@get('/referer/target')
@get('/referer/exclude')
def get_referer_target():
    referer = request.get_header('Referer')
    return f'<p id="referer">Referer: {referer}</p>'

 

Robot Frameworkで動作確認するコードを実装

先ほどのChrome拡張の設定に続いて以下のコードを書きます。

# リファラが書き換わるページへアクセス
go to  http://localhost:8084/referer/target
${target} =  get text  id=referer

# リファラが書き換わらないページへアクセス
go to  http://localhost:8084/referer/exclude
${exclude} =  get text  id=referer

# 検証してブラウザを閉じる
log to console  ${EMPTY}
log to console  ${target}
log to console  ${exclude}
should not be equal  ${target}  ${exclude}
close browser

 

動作確認

テストがパスしました。Chrome拡張の設定がうまくいき、リファラが書き換わっているようです。

$ robot selenium_modify_referer_test.robot 
===================================
Selenium Modify Referer Test                   
===================================
Chrome拡張でリファラを書き換えるテスト                                
Referer: https://www.google.co.jp
Referer: None
Chrome拡張でリファラを書き換えるテスト | PASS |
...

 

その他参考

Firefoxを使う場合

Firefoxを使う場合の例が以下にありました。
webdriver - setting request headers in selenium - Stack Overflow

 

Chromeコマンドライン・オプション

以下にまとまっていました。
List of Chromium Command Line Switches « Peter Beverloo

 

OpenSSLでのpemファイル作成

もしpemファイルを作る場合は、以下を参考にします。
ssl - How to create a self-signed certificate with openssl? - Stack Overflow

 

ソースコード

GitHubに上げました。 selenium2_library_sample/tests/selenium_modify_referer_test.robot ファイルが今回のテストファイルです。
thinkAmi-sandbox/RobotFramework-sample: Robot Framewrok samples

*1:該当Element上で右クリック、Copy > Copy XPathXpathを取得できます

「Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版」をBoto3とAnsibleで写経してみた

社内ではAWSが普通に使われているため、常々基礎からきちんと学びたいと考えていました。

そんな中、書籍「Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版」の社内勉強会が開催されることになりました。

Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版

Amazon Web Services 基礎からのネットワーク&サーバー構築 改訂版

 
これはちょうどよい機会だと思い、書籍をひと通り読んでみました。

書籍では、オンプレでやってたことをAWSで実現するにはどうすればよいかなど、AWSのインフラまわりの基礎的なところが書かれており、とてもためになりました。

ただ、手動でAWS環境を構築したため、このまま何もしないと忘れそうでした。

何か良い方法がないかを探したところ、AWS SDK for Python(Boto3)がありました。これを使ってPythonスクリプトとして残しておけば忘れないだろうと考えました。

 
なお、Boto3ではなくAnsibleでも構築できそうでしたが、今回はBoto3の使い方に慣れようと思い、

として、写経してみることにしました。

 
目次

 

環境

現在では、Boto3・AnsibleともにPython3で動くようです。

 

IAMユーザーの作成と設定

書籍では、AWSアカウントについては特に触れられていませんでした。

そこで、今年の3/11に開かれたJAWS DAYS 2017のセッション「不安で夜眠れないAWSアカウント管理者に送る処方箋という名のハンズオン」に従い、IAMユーザーを作成・使用することにしました。

 
主な設定内容は

としました。

 

Boto3の準備

今回はawscliを使わず、Boto3だけで環境を準備します。

READMEのQuick Startに従って、環境ファイルを用意します。
boto/boto3: AWS SDK for Python

~/.aws/credentials

こちらに aws_access_key_idaws_secret_access_keyを設定します。
【鍵管理】~/.aws/credentials を唯一のAPIキー管理場所とすべし【大指針】 | Developers.IO

今回は試しにデフォルト以外のprofile(my-profile)を使うことにしてみました。
boto3 で デフォルトprofile以外を使う - Qiita

[my-profile]
aws_access_key_id = YOUR_KEY
aws_secret_access_key = YOUR_SECRET

 

~/.aws/config

こちらにはデフォルトリージョンを設定します。

READMEにはus-east-1と記載されています。

東京リージョンの場合は何を指定すればよいかを探したところ、 ap-northeast-1 を使うのが良さそうでした。
AWS のリージョンとエンドポイント - アマゾン ウェブ サービス

[default]
region=ap-northeast-1

ただ、自分の書き方が悪いせいか、上記のように書いてもデフォルトリージョンとして設定されなかったため、Boto3を使うところでリージョンを指定しています。

 
あとは、

# ~/.aws/credentialsのmy-profileにあるキーを使う
session = boto3.Session(profile_name='my-profile')

# Clientを使う場合
client = session.client('ec2', region_name='ap-northeast-1')

# Resourceを使う場合
resource = session.resource('ec2', region_name='ap-northeast-1')

のようにして、アクセスキーなどを持ったclientとresourceを取得し、それを使って操作します。

なお、clientやresourceの第一引数にはAWS サービスの名前空間を使えば良さそうでした。
AWS サービスの名前空間 | Amazon リソースネーム (ARN) と AWS サービスの名前空間 - アマゾン ウェブ サービス

今回使用するAmazon EC2Amazon VPC名前空間は、ともに ec2 のようでした。

 

Ansibleまわり

途中でEC2の設定をするため、Ansibleまわりの準備もしておきます。
ansible.cnfでssh_configを設定する | Developers.IO

 

ssh_config

HostNameのxxx.xxx.xxx.xxxは、EC2を立てた時にパブリックIPアドレスへと差し替えます。

Host webserver
  User ec2-user
  HostName xxx.xxx.xxx.xxx
  # IdentityFileはカレントディレクトリに置いておけば良い
  IdentityFile syakyo_aws_network_server2.pem
  StrictHostKeyChecking no
  UserKnownHostsFile /dev/null

 

ansible.cnf

Fオプションのファイル(ssh_config)は、プルパスでなくても問題ありませんでした。

[ssh_connection]
ssh_args = -F ssh_config

 

Inventoryファイル(hosts)

ここに記述するホスト名は、ssh_configのHostの値と一致させます。今回はwebserverとなります。

[web]
webserver

 
あとは、ターミナルから

$ ansible-playbook -i hosts ch4_apache.yml

のようにして、EC2インスタンスに対して実行します。

 

写経

今回写経したコードを載せておきます。

なお、だいたいのものはClientとResourceServiceのどちらでも操作できました。

ただ、戻り値が

  • Clientは、dict
  • ResourceServiceは、各オブジェクト

だったので、ResourceServiceの方が扱いやすいのかなと感じました。

 

Chapter2
VPCの作成

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.create_vpc

response = ec2_client.create_vpc(
    CidrBlock='192.168.0.0/16',
    AmazonProvidedIpv6CidrBlock=False,
)
vpc_id = response['Vpc']['VpcId']

 

VPCに名前をつける

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Vpc.create_tags

vpc = ec2_resource.Vpc(vpc_id)
tag = vpc.create_tags(
    Tags=[
        {
            'Key': 'Name',
            'Value': 'VPC領域2'
        },
    ]
)

 

VPCの一覧を確認

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_vpcs

response = ec2_client.describe_vpcs(
    Filters=[
        {
            'Name': 'tag:Name',
            'Values': [
                'VPC領域',
            ]
        }
    ]
)

 

アベイラビリティゾーンの確認

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_availability_zones

response = ec2_client.describe_availability_zones(
    Filters=[{
        'Name': 'state',
        'Values': ['available'],
    }]
)

 

VPCにサブネットを作成

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Vpc.create_subnet

vpc = ec2_resource.Vpc(vpc_id)
response = vpc.create_subnet(
    AvailabilityZone=availability_zone,
    CidrBlock=cidr_block,
)

 

サブネットに名前をつける

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Subnet.create_tags

tag = ec2_subnet.create_tags(
    Tags=[{
        'Key': 'Name',
        'Value': subnet_name,
    }]
)

 

インターネットゲートウェイを作成

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.create_internet_gateway

response = ec2_client.create_internet_gateway()

 

インターネットゲートウェイに名前をつける

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.InternetGateway.create_tags

internet_gateway = ec2_resource.InternetGateway(internet_gateway_id)
tags = internet_gateway.create_tags(
    Tags=[{
        'Key': 'Name',
        'Value': 'インターネットゲートウェイ2',
    }]
)

 

インターネットゲートウェイVPCに紐づける

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.InternetGateway.attach_to_vpc

internet_gateway = ec2_resource.InternetGateway(internet_gateway_id)
response = internet_gateway.attach_to_vpc(
    VpcId=vpc_id,
)

 

ルートテーブルを作成する

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.create_route_table

response = ec2_client.create_route_table(VpcId=vpc_id)

 

ルートテーブルに名前をつける
route_table = ec2_resource.RouteTable(route_table_id)
tag = route_table.create_tags(
    Tags=[{
        'Key': 'Name',
        'Value': 'パブリックルートテーブル2',
    }]
)

 

ルートテーブルをサブネットに割り当てる

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.RouteTable.associate_with_subnet

route_table = ec2_resource.RouteTable(route_table_id)
route_table_association = route_table.associate_with_subnet(SubnetId=subnet_id)

 

デフォルトゲートウェイをインターネットゲートウェイに割り当てる

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.RouteTable.create_route

route_table = ec2_resource.RouteTable(route_table_id)
route = route_table.create_route(
    DestinationCidrBlock='0.0.0.0/0',
    GatewayId=internet_gateway_id,
)

 

ルートテーブルを確認する

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_route_tables

response = ec2_client.describe_route_tables()

 

Chapter3
キーペアを作成してローカルに保存する

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.create_key_pair

# キーペアを作成していない場合はキーペアを作成する
if not os.path.exists(KEY_PAIR_FILE):
    response = ec2_client.create_key_pair(KeyName=KEY_PAIR_NAME)
    print(inspect.getframeinfo(inspect.currentframe())[2], response['KeyName'])
    with open(KEY_PAIR_FILE, mode='w') as f:
        f.write(response['KeyMaterial'])

 

ローカルに保存したキーのパーミッションを変更する
# modeは8進数表記がわかりやすい:Python3からはprefixが`0o`となった
os.chmod(KEY_PAIR_FILE, mode=0o400)

 

セキュリティグループを作成する

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.create_security_group

response = ec2_client.create_security_group(
    Description=name,
    GroupName=name,
    VpcId=vpc_id,
)

 

セキュリティグループでSSHのポートを開ける
security_group = ec2_resource.SecurityGroup(security_group_id)
response = security_group.authorize_ingress(
    CidrIp='0.0.0.0/0',
    IpProtocol='tcp',
    # ポートは22だけ許可したいので、From/Toともに22のみとする
    FromPort=22,
    ToPort=22,
)

 

EC2インスタンスを立てる
response = ec2_resource.create_instances(
    ImageId=IMAGE_ID,
    # 無料枠はt2.micro
    InstanceType='t2.micro',
    # 事前に作ったキー名を指定
    KeyName=key_pair_name,
    # インスタンス数は、最大・最小とも1にする
    MaxCount=1,
    MinCount=1,
    # モニタリングはデフォルト = Cloud Watchは使わないはず
    # Monitoring={'Enabled': False},
    # サブネットにavailability zone が結びついてるので、明示的なセットはいらないかも
    # Placement={'AvailabilityZone': availability_zone},
    # セキュリティグループIDやサブネットIDはNetworkInterfacesでセット(詳細は以下)
    # SecurityGroupIds=[security_group_id],
    # SubnetId=subnet_id,
    NetworkInterfaces=[{
        # 自動割り当てパブリックIP
        'AssociatePublicIpAddress': is_associate_public_ip,
        # デバイスインタフェースは1つだけなので、最初のものを使う
        'DeviceIndex': 0,
        # セキュリティグループIDは、NetworkInterfacesの方で割り当てる
        # インスタンスの方で割り当てると以下のエラー:
        # Network interfaces and an instance-level security groups may not be specified on the same request
        'Groups': [security_group_id],
        # プライベートIPアドレス
        'PrivateIpAddress': private_ip,
        # サブネットIDも、NetworkInterfacesの方で割り当てる
        # インスタンスの方で割り当てると以下のエラー:
        # Network interfaces and an instance-level subnet ID may not be specified on the same request
        'SubnetId': subnet_id,
    }],
    TagSpecifications=[{
        'ResourceType': 'instance',
        'Tags': [{
            'Key': 'Name',
            'Value': instance_name,
        }]
    }],
)

 

EC2インスタンスがrunningになるまで待つ

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Instance.wait_until_running

ec2_instance.wait_until_running()

 

Chapter4
GUIの「DNSホスト名の編集」を実行する

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.modify_vpc_attribute

response = ec2_client.modify_vpc_attribute(
    EnableDnsHostnames={'Value': True},
    VpcId=vpc_id,
)

 

AnsibleでApacheをインストールし、起動設定にする
# hostsにはhostsファイルの[web]かホスト名(webserver)を指定する
- hosts: webserver
  become: yes
  tasks:
    - name: install Apache
      yum: name=httpd
    - name: Apache running and enabled
      service: name=httpd state=started enabled=yes

 

Chapter6

Chapter6ではプライベートサブネットにEC2インスタンスを立てます。

その方法はパブリックサブネットと同様なため、ここでは差分のみ記載します。

 

パブリックサブネットのAvailability Zoneを取得する

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#subnet

ec2_subnet = ec2_resource.Subnet(subnet_id)
return ec2_subnet.availability_zone

 

セキュリティグループでICMPのポートを開ける

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.SecurityGroup.authorize_ingress

security_group = ec2_resource.SecurityGroup(security_group_id)
response = security_group.authorize_ingress(
    CidrIp='0.0.0.0/0',
    IpProtocol='icmp',
    # ICMPの場合、From/Toともに -1 を設定
    FromPort=-1,
    ToPort=-1,
)

 

Ansibleで、ローカルのSSH鍵をWebサーバへSCPを使って転送する
# hostsにはhostsファイルの[web]かホスト名(webserver)を指定する
- hosts: webserver
  tasks:
    - name: copy private-key to webserver
      copy: src=./syakyo_aws_network_server2.pem dest=~/ owner=ec2-user group=ec2-user mode=0400

 

Chapter7
Elastic IPを取得する

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.allocate_address

# DomainのvpcはVPC、standardはEC2-Classic向け
response = ec2_client.allocate_address(Domain='vpc')

 

NATゲートウェイを作成し、Elastic IPを割り当て、パブリックサブネットに置く

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.create_nat_gateway

response = client.create_nat_gateway(
    AllocationId=allocation_id,
    SubnetId=subnet_id,
)

 

NATゲートウェイがavailableになるまで待つ

NATゲートウェイを作成した直後はまだavailableになっていないため、availableになるまで待ちます。

手元では約2分ほどかかりました。

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Waiter.NatGatewayAvailable

waiter = ec2_client.get_waiter('nat_gateway_available')
response = waiter.wait(
    Filters=[{
        'Name': 'state',
        'Values': ['available']
    }],
    NatGatewayIds=[nat_gateway_id]
)

 

メインのルートテーブルのIDを取得する

NATゲートウェイのエントリを追加するため、メインのルートテーブルのIDを取得します。

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_route_tables

response = ec2_client.describe_route_tables(
    Filters=[
        {
            'Name': 'association.main',
            'Values': ['true'],
        },
        {
            'Name': 'vpc-id',
            'Values': [vpc_id],
        }
    ]
)
main_route_table_id = response['RouteTables'][0]['RouteTableId']

 

メインのルートテーブルにNATゲートウェイのエントリを追加する

https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.RouteTable.create_route

route_table = ec2_resource.RouteTable(route_table_id)
route = route_table.create_route(
    DestinationCidrBlock='0.0.0.0/0',
    NatGatewayId=nat_gateway_id,
)

 

写経時に作成したものを削除する

ここまででGUIで行った内容をBoto3 & Ansibleで実装しました。

せっかくなので、作成したものをBoto3で削除するPythonスクリプトも作成してみました (boto3_ansible/clear_all.py)。

流れとしては、作成したのとは逆順で削除していく形となります。

 

その他参考

 

ソースコード

GitHubに上げました。
thinkAmi-sandbox/syakyo-aws-network-server-revised-edition-book