「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

Robot Frameworkにて使える時間のフォーマットについて調べてみた

Robot Frameworkでは、 sleep 1 seconds のように、数値 + 単位で時間を表現できます。

ただ、サイトによって secondssが使われるなど、単位のフォーマットが異なっていました。

そこで今回、どういうフォーマットが使えるのか調べてみました。

 

環境

 

調査結果

公式サイトに記載がありました。

・days, day, d

・hours, hour, h

・minutes, minute, mins, min, m

・seconds, second, secs, sec, s

・milliseconds, millisecond, millis, ms

6.5 Time format | Robot Framework User Guide

また、 4 s4s のように、数字と単位は、離す・くっつけるのどちらでも良いようです。

 
試してみます。

なお、

とします。

*** Settings ***

Library  DateTime


*** Keywords ***
現在時刻を出力する
    [Arguments]  ${label}=start
    ${now} =  get current date
    log to console  ${now}${SPACE * 3}[${label}]


*** TestCases ***
引数の秒数のフォーマットを確認する
    log to console  ${SPACE}

    現在時刻を出力する

    sleep  1 seconds
    現在時刻を出力する  label=seconds

    sleep  1 second
    現在時刻を出力する  label=second

    sleep  1 secs
    現在時刻を出力する  label=secs

    sleep  1 sec
    現在時刻を出力する  label=sec

    sleep  1 s
    現在時刻を出力する  label=s

    sleep  1sec
    現在時刻を出力する  label=sec_without_space

    sleep  1s
    現在時刻を出力する  label=s_without_space

    sleep  1.5s
    現在時刻を出力する  label=s_with_dot

 
結果です。いずれのフォーマットでも正しく動作しているようです。

$ robot datetime_library_sample/time_format_test.robot 
...
==============================================================================
Time Format Test                                                              
==============================================================================
引数の秒数のフォーマットを確認する                                     
.2017-08-11 07:38:36.420   [start]
..2017-08-11 07:38:37.424   [seconds]
..2017-08-11 07:38:38.427   [second]
..2017-08-11 07:38:39.431   [secs]
引数の秒数のフォーマットを確認する                                    .2017-08-11 07:38:40.435   [sec]
..2017-08-11 07:38:41.438   [s]
..2017-08-11 07:38:42.441   [sec_without_space]
..2017-08-11 07:38:43.444   [s_without_space]
引数の秒数のフォーマットを確認する                                    .2017-08-11 07:38:44.947   [s_with_dot]
引数の秒数のフォーマットを確認する                                    | PASS |
...

 

ソースコード

GitHubに上げました。datetime_library_sampleの中にあるtime_format_test.robotファイルが今回のテストファイルです。
thinkAmi-sandbox/RobotFramework-sample

Robot Framework + Selenium2Libraryで、「Go To」を使う時はURIスキームを付ける

Selenium2Libraryでは、 Go To キーワードを使って引数のURLに移動します。
Go To | Selenium2Library

 
今までのサンプルでは、localhostへの移動はURIスキームを省略した localhost:8084 みたいな値を引数として渡していました。

 
しかし、通常のChromeとHeadlessなChromeでは挙動が異なったため、Go Toを使う時はURIスキームを付けておくべきと感じました。

その例をメモしておきます。

 
目次

 

環境

  • Mac OS X 10.11.6
  • Python 3.6.2
  • Google Chrome 60.0.3112.78 (stable)
  • ChromeDriver 2.31 (stable)
  • RobotFramework 3.0.2
  • Selenium2Library 1.8.1dev1
    • GitHubから現在の最新版をインストール
    • PyPIにある1.8.0では、Python3系だとエラーが出てインストールできない
  • Selenium 3.4.3
  • Bottle 0.12.13
    • Webアプリ用

 

キーワードの用意

今回、通常のChromeとHeadlessなChromeでの挙動を比較するため、以下のキーワードを用意します。

*** Keywords ***
URIスキーム無しでlocalhostへアクセスする
    go to  localhost:8084/
    location should be  http://localhost:8084/

URIスキームありでlocalhostへアクセスする
    go to  http://localhost:8084/
    location should be  http://localhost:8084/

URIスキーム無しでGoogleへアクセスする
    go to  www.google.co.jp/
    location should be  https://wwww.google.co.jp/

URIスキームありでGoogleへアクセスする
    go to  https://www.google.co.jp/
    location should be  https://www.google.co.jp/

キーワード「${キーワード}」が失敗することを確認する
    ${エラー有無}  ${エラーメッセージ} =  run keyword and ignore error  ${キーワード}
    should be equal  ${エラー有無}  FAIL
    log to console  エラーメッセージ:\n${エラーメッセージ}\n_______________

 

通常のChromeの場合

通常のChromeの場合、localhostへはURIスキームの有無に関係なくアクセスできます。一方、Googleへはアクセスできません。

ノーマルなChromeでURIスキームを扱う
    create webdriver  Chrome
    log to console  ${SPACE}

    URIスキーム無しでlocalhostへアクセスする
    URIスキームありでlocalhostへアクセスする
    キーワード「URIスキーム無しでGoogleへアクセスする」が失敗することを確認する
    URIスキームありでGoogleへアクセスする

実行結果です。

ノーマルなChromeでURIスキームを扱う                                   . 
...エラーメッセージ:
WebDriverException: Message: unknown error: unhandled inspector error: {"code":-32000,"message":"Cannot navigate to invalid URL"}
  (Session info: chrome=60.0.3112.90)
  (Driver info: chromedriver=2.31.488774 (7e15618d1bf16df8bf0ecf2914ed1964a387ba0b),platform=Mac OS X 10.11.6 x86_64)
_______________

 

HeadlessなChrome

通常のChromeと異なり、localhostの場合にもURIスキームが必要になります。

HeadlessなChromeでURIスキームを扱う
    ${options} =  evaluate  sys.modules['selenium.webdriver'].ChromeOptions()  sys
    call method  ${options}  add_argument  --headless
    create webdriver  Chrome  chrome_options=${options}
    log to console  ${SPACE}

    キーワード「URIスキーム無しでlocalhostへアクセスする」が失敗することを確認する
    URIスキームありでlocalhostへアクセスする
    キーワード「URIスキーム無しでGoogleへアクセスする」が失敗することを確認する
    URIスキームありでGoogleへアクセスする

実行結果です。

HeadlessなChromeでURIスキームを扱う                                   ... 
.エラーメッセージ:
Location should have been 'http://localhost:8084/' but was 'data:,'
_______________
..エラーメッセージ:
WebDriverException: Message: unknown error: unhandled inspector error: {"code":-32000,"message":"Cannot navigate to invalid URL"}
  (Session info: headless chrome=60.0.3112.90)
  (Driver info: chromedriver=2.31.488774 (7e15618d1bf16df8bf0ecf2914ed1964a387ba0b),platform=Mac OS X 10.11.6 x86_64)
_______________

 
これらより、キーワード Go To を使う場合はURIスキームを付けるほうが、エラーが出ることなく移動できることが分かりました。

 

ソースコード

GitHubに上げました。selenium2_library_sample/tests/の中にあるselenium_uri_scheme_test.robotファイルが今回のテストファイルです。
thinkAmi-sandbox/RobotFramework-sample

Robot Framework + Selenium2Library + robotframework-requestsで、静的ファイルが本当にあるのか確認する

以前、Robot Framework + Selenium2Libraryで、静的HTMLをテストしてみました。
Robot Framework + Selenium2Libraryで、静的HTMLをテストする - メモ的な思考的な

 
その時、

<img id="img_200" src="/static/image/a.png" alt="あの字" title="画像タイトル">

というHTMLに対し、

page should contain image  jquery=img[alt='あの字']

というテストコードを書きました。

 
しかし、実際にはそのテストコードは静的ファイルが存在するかまではチェックしていないため、

<!--存在する画像ファイル-->
<img id="img_200" src="/static/image/200.png" alt="存在します" title="画像タイトル">

<!--存在しない画像ファイル-->
<img id="img_404" src="/static/image/404.png" alt="存在しません" title="画像タイトル2">

というHTMLがあり、実際には

f:id:thinkAmi:20170809202012p:plain:w200

となっていた場合でも、

page should contain image  id=img_404
element should be visible  id=img_404

というテストがパスしてしまいます。

 
静的ファイルが本当にあるのかを確認する方法を探したところ、stackoverflowに robotframework-requests を使う方法が書かれていました。内部で requests を使っているようです。

そのため、今回 robotframework-requests を試してみます。

目次

 

環境

  • Mac OS X 10.11.6
  • Python 3.6.2
  • Google Chrome 60.0.3112.78 (stable)
  • ChromeDriver 2.31 (stable)
  • RobotFramework 3.0.2
  • robotframework-requests 0.4.7
  • Selenium2Library 1.8.1dev1
    • GitHubから現在の最新版をインストール
    • PyPIにある1.8.0では、Python3系だとエラーが出てインストールできない
  • Selenium 3.4.3
  • Bottle 0.12.13
    • Webアプリ用

 

実装

GitHubにあるREADMEのUsageに使い方があるため、それに従って実装してみます。

SettingsにLibraryを追加

今回、Selenium2Libraryも使うため、2つをSettingsに追加します。

*** Settings ***
Library  Selenium2Library
Library  RequestsLibrary

 

imgタグのsrc属性から、URLを取得

Get Element Attributeキーワードを使って、imgタグのsrc属性から対象の静的ファイルのURLを取得します。
Get Element Attribute | Selenium2Library

${src404} =  get element attribute  id=img_404@src
log to console  ${src404}
# => http://localhost:8084/static/image/404.png

 

セッションオブジェクトを作成

requestsと同様、 requests.Session オブジェクトを作成します。
Create Session | RequestsKeywords

今回は任意のセッション名とURLのみ指定していますが、他にもCookieなどいろいろと指定できますので、詳しくはドキュメントを見てください。

# 第一引数は任意のセッション名、第二引数はURL
create session  not-found-session  ${src404}

 

画像のURLをGET

stackoverflowにはGetキーワードが書かれています。

しかし、

  • ドキュメントでは Deprecated
  • 実行すると、 Deprecation Warning: Use Get Request in the future ワーニング

ですので、キーワード Get Request を代わりに使います。
Get Request | RequestsKeywords

なお、上記のstackoverflowによると、URLは URL is relative to the session URL とのことなので、今回も同じように / を指定します。

${response404} =  get request  not-found-session  /
log to console  ${response404}
#=> <Response [404]>

 

ステータスコードの検証

上記の通り、Get Requestキーワードの戻り値はResponseオブジェクトになります。

そのため、requestsのResponseオブジェクトの属性 status_code を使って、ステータスコードを検証します。
status_code - class requests.Response | Developer Interface — Requests 2.18.3 documentation

 
なお、検証で使うキーワードは Should Be Equal As IntegersShould Be Equal As Strings 、どちらでも同じ結果になります。

should be equal as integers  ${response404.status_code}  404
should be equal as strings  ${response404.status_code}  404

 
以上で、ファイルが存在しないことを確認できました。

 
なお、静的ファイルが存在することを確認するには、

${src200} =  get element attribute  id=img_200@src
create session  found-session  ${src200}
${response200} =  get request  found-session  /
should be equal as strings  ${response200.status_code}  200

となります。

 

ソースコード

GitHubに上げました。selenium2_library_sample/tests/の中にあるselenium_requests_test.robotファイルが今回のテストファイルです。
thinkAmi-sandbox/RobotFramework-sample

Robot Framework + Selenium2Libraryで、HTML + JavaScriptまわりをテストする

前回、Robot Framework + Selenium2Libraryで、HTMLのフォームまわりをテストしてみました。
Robot Framework + Selenium2Libraryで、HTMLフォームまわりをテストする - メモ的な思考的な

 
今回は、Robot Framework + Selenium2Libraryで、HTML + JavaScriptまわりをテストしてみます。

 
目次

 

環境

  • Mac OS X 10.11.6
  • Python 3.6.2
  • Google Chrome 60.0.3112.78 (stable)
  • ChromeDriver 2.31 (stable)
  • RobotFramework 3.0.2
  • Selenium2Library 1.8.1dev1
    • GitHubから現在の最新版をインストール
    • PyPIにある1.8.0では、Python3系だとエラーが出てインストールできない
  • Selenium 3.4.3
  • Bottle 0.12.13
    • Webアプリ用

 

Elementが出てくるまで待つ

<div id="show_delay"></div>

というHTMLに対し、

window.addEventListener("load", function () {
    setTimeout(function(){
        document.getElementById('show_delay').innerHTML = '<p id="hello">Hello, world!</p>'
    }, 3000);
});

と、3秒後にp要素を追加するJavaScriptがあるとします。

 
この場合、Wait Until Page Contains Elementを使い、p要素が出てくるまで待機します。
Wait Until Page Contains Element | Selenium2Library

# p要素が出てくるまで待つ
wait until page contains element  id=hello

# 出てきたら、p要素のテキストが正しいかを確認
element text should be  id=hello  Hello, world!

 

マウスオーバーすると出てくるElementをクリックする

<div id="on_mouse_area">このあたりをマウスオーバーするとボタンが見える
    <button id="mouse_target" style="display: none;">
        mouseoverのみ見える
    </button>
</div>

というdisplay: noneなHTMLがあり、

var on_mouse_area = document.getElementById('on_mouse_area');
on_mouse_area.onmouseover = function () {
    document.getElementById('mouse_target').style.display = 'block';
};
on_mouse_area.onmouseout = function () {
    document.getElementById('mouse_target').style.display = 'none';
};
var mouse_target = document.getElementById('mouse_target');
mouse_target.onclick = function () {
    window.location.href = '/'
};

と、マウスオーバー時に表示するJavaScriptがあるとします。

 
この場合、Mouse Overを使いマウスオーバーした後、Click Elementします。
Mouse Over | Selenium2Library

# マウスオーバーする
mouse over  id=on_mouse_area

# クリックする
click element  id=mouse_target

 

スクロールしてクリックする

JavaScriptでスクロールする
<div class="bottom" style="padding-top: 1200px">
    <button id="bottom_button">下にあるボタン</button>
</div>

と、縦長のページの一番下にボタンがあるとします。

 
この場合、JavaScriptを実行するExecute Javascriptを使い、JavaScriptwindow.scrollTo()などを使います。

# スクロールする
Execute JavaScript  window.scrollTo(0, 1200)

# クリックする
click element  id=bottom_button

 

画面サイズを変更する

スクロールするとは関係ないですが、画面サイズについて、

などがあります。

# 現在の画面サイズを取得する
${幅}  ${高さ} =  get window size

# 画面サイズを変更する
set window size  10  10

# 画面サイズを元に戻す
set window size  ${幅}  ${高さ}

 

display:noneなボタンをクリックする

<button id="display_none" style="display: none;">見えない</button>

という、display:noneなStyleのボタンがあるとします。

この場合、JavaScriptを使うことで、強引にクリックすることができます。
Example to click on element which is hidden using Execute Javascript? - Google グループ

execute javascript  document.getElementById('display_none').click();

 

JavaScriptによるリロードがあったことを確認する

Assign Id To Elementを使う
<button id="run_reload" class="reload_element">リロード</button>

というHTMLがあり、このボタンを押すと

var reload = document.getElementById('run_reload');
reload.onclick = function () {
    location.reload();
};

と、リロードするJavaScriptがあるとします。

 
この場合、Assign Id To Elementを使って

  • リロード前にタグを設定
  • リロード後にタグが存在しないことを確認

を行います。

Assign Id To Elementで設定したidはリロードすると消えてしまう特性を利用します。
Assign Id To Element | Selenium2Library

# id="run_reload"をid="not_reload"に付け替える
assign id to element  run_reload  not_reload

 

Assign Id To Elementの注意点
assign id to element  id=reload_dummy  not_reload

と、locatorにidを指定すると、

Keyword 'Selenium2Library.Assign Id To Element' got positional argument after named arguments.

というエラーになります。

一方、locatorがclassやxpathであればエラーは出ません。

assign id to element  class=reload_element  not_reload

どうしてもlocatorにidを指定したい場合は、id=を省略すると、デフォルトの挙動であるidnameで検索してくれます。

assign id to element  run_reload  not_reload

 
また、Assign Id To Elementはidを付け替えるため、

<button id="run_reload" class="reload_element">リロード</button>

というHTMLの場合、

# idの割り当て
assign id to element  run_reload  not_reload

# not_reload なidは存在する
page should contain element  id=not_reload

# run_reload なidが存在しない
page should not contain element  id=run_reload

という挙動となります。

idの付け替えが困る場合には、idがないElementなど、他の適当なElementを指定します。

 

JavaScriptのalertまわり

alertのOKボタンを押す
<button id="show_alert">警告</button>

というHTMLに対し、

var show_alert = document.getElementById('show_alert');
show_alert.onclick = function () {
    alert('OKのみです');
};

とOKのみのalertを表示するJavaScriptがあるとします。

 
この場合、Dismiss Alertを使い、alertのOKボタンを押します。
Dismiss Alert | Selenium2Library

# alertを表示する
click element  id=show_alert

# alertでOKを押す
dismiss alert

 

alertのメッセージを取得してOKを押す

同じく、

<button id="show_alert">警告</button>

というHTMLに対し、

var show_alert = document.getElementById('show_alert');
show_alert.onclick = function () {
    alert('OKのみです');
};

というJavaScriptがあった場合、メッセージの内容を取得してからOKを押したいとします。

 
この場合、Get Alert Messageを使い、alertのメッセージを取得します。
Get Alert Message | Selenium2Library

# alertを表示する
click element  id=show_alert

# alertのメッセージを取得する
${アラートメッセージ} =  get alert message

# alertのメッセージを確認する
should be equal  ${アラートメッセージ}  OKのみです

 

alertが表示されていることを確認してからOKを押す

同じく、

<button id="show_alert">警告</button>

というHTMLに対し、

var show_alert = document.getElementById('show_alert');
show_alert.onclick = function () {
    alert('OKのみです');
};

というJavaScriptがあった場合、alertが表示されていることを確認してからOKを押したいとします。

 
この場合、Alert Should Be Presentを使い、alertが表示されていることを確認してからOKを押します。
Alert Should Be Present | Selenium2Library

# alertを表示する
click element  id=show_alert

# alertが表示されていることを確認してからOKを押す
alert should be present

 

alertの表示とメッセージの一致を確認してからOKを押す

同じく、

<button id="show_alert">警告</button>

というHTMLに対し、

var show_alert = document.getElementById('show_alert');
show_alert.onclick = function () {
    alert('OKのみです');
};

というJavaScriptがあった場合、alertが表示され、メッセージも一致していることを確認してからOKを押したいとします。

 
この場合、Alert Should Be Presentで引数を使い、alert表示とメッセージ一致の確認後にOKを押します。
Alert Should Be Present | Selenium2Library

# alertを表示する
click element  id=show_alert

# alertが表示され、メッセージも一致していることを確認してからOKを押す
alert should be present  OKのみです

 

alertの表示に時間がかかった場合でもOKを押す
<button id="wait_alert">2秒後に警告</button>

というHTMLに対し、

var wait_alert = document.getElementById('wait_alert');
wait_alert.onclick = function () {
    setTimeout(function(){
        alert('2秒後のアラート');
    }, 2000);
};

と、2秒後にalertを表示するJavaScriptがあるとします。

 
この場合、Wait Until Keyword Succeedsとalertを処理するキーワードを併用します。
Wait Until Keyword Succeeds | BuiltIn

 
Wait Until Keyword Succeedsの引数は

  • 第一引数は、リトライ時間
  • 第二引数は、リトライ間隔
  • 第三引数は、実行するキーワード

です。

# 5秒の間、1秒ごとに、「Get Alert Message」キーワードを実行する
${アラートメッセージ} =  wait until keyword succeeds  5 sec  1 sec  get alert message

# alertに「2秒後のアラート」というメッセージがあったことを確認
should be equal  ${アラートメッセージ}  2秒後のアラート

 

JavaScriptのconfirmまわり

confirmのOKを押す
<button id="show_confirm">確認</button>

というHTMLに対し、

var show_confirm = document.getElementById('show_confirm');
show_confirm.onclick = function () {
    var answer = confirm('OKとキャンセルです');
    if (answer){
        alert('OKが押されました');
    }
    else{
        alert('キャンセルされました');
    }
};

と、ボタンを押したらconfirmが表示されるJavaScriptがあるとします。

 
この場合、Confirm Actionを使い、confirmでOKを押します。
Confirm Action | Selenium2Library

ちなみに、Confirm ActionのデフォルトではOKを押します。そのため、デフォルトを変更しない限り、Choose Ok On Next Confirmationは不要です。
Choose Ok On Next Confirmation | Selenium2Library

# OKボタンを押して、confirmのメッセージを取得する
${確認メッセージ} =  confirm action

# confirmのメッセージを確認する
should be equal  ${確認メッセージ}  OKとキャンセルです

# OKを押した時にalertが表示され、「OKが押されました」というメッセージがあるかを確認する
${アラートメッセージ} =  get alert message
should be equal  ${アラートメッセージ}  OKが押されました

 

confirmのキャンセルを押す

同じく、

<button id="show_confirm">確認</button>

というHTMLに対し、

var show_confirm = document.getElementById('show_confirm');
show_confirm.onclick = function () {
    var answer = confirm('OKとキャンセルです');
    if (answer){
        alert('OKが押されました');
    }
    else{
        alert('キャンセルされました');
    }
};

と、ボタンを押したらconfirmが表示されるJavaScriptがあるとします。

 
この場合、Choose Cancel On Next ConfirmationConfirm Actionを使い、confirmでキャンセルを押します。

# 次の確認処理ではキャンセルボタンを押す
choose cancel on next confirmation

# confirmが表示され、キャンセルを押す
${確認メッセージ} =  confirm action
should be equal  ${確認メッセージ}  OKとキャンセルです

# キャンセルを押した時にalertが表示され、「キャンセルされました」というメッセージがあるかを確認する
${アラートメッセージ} =  get alert message
should be equal  ${アラートメッセージ}  キャンセルされました

 

JavaScriptのpromptに入力する

<button id="show_prompt">入力</button>

というHTMLに対し、

var show_prompt = document.getElementById('show_prompt');
show_prompt.onclick = function () {
    var input_message = prompt('値を入力します');
    alert(input_message);
};

と、JavaScriptのpromptが表示されるとします。

 
この場合、Input Text Into Promptを使い、promptに入力します。
Input Text Into Prompt | Selenium2Library

また、Input Text Into Promptはpromptに入力するだけなので、Confirm ActionによりOKボタンを押します。

なお、ここまでの処理でChoose Cancel On Next Confirmationが使われていた場合、Confirm Actionしてもキャンセルボタンが押されてしまうため、promptへの入力が成功しません。

そのため、Choose Cancel On Next Confirmationが使われている可能性がある場合は、明示的にChoose Ok On Next Confirmationを実行した後にConfirm Actionを実行するとよいでしょう。

# promptを表示する
click element  id=show_prompt

# ここまででChoose Cancel On Next Confirmationが使われている可能性があるため、
# 明示的にChoose Ok On Next Confirmationを実行し、promptでOKを押すように指定する
choose ok on next confirmation

# promptに入力する
input text into prompt  プロンプトに入力しました

# OKボタンを押す
confirm action

# alertではpromptに入力した値が表示されているはずなので、一致しているかを確認する
${アラートメッセージ} =  get alert message
should be equal  ${アラートメッセージ}  プロンプトに入力しました

 

Headlessブラウザの制限

Headless Chrome v60やPhantomJSの場合、alertやpopup(acceptPopup, cancelPopup)などが未サポートです。
How to run Headless Chrome in Codeception - Codeception / Cookbook - PHP Test Club

そのため、stackoverflowにあるようにモンキーパッチすれば良いかもしれません。
selenium webdriver - chromedriver headless alerts - Stack Overflow

なお、Capybaraでは対応策があるようです。
RSpec + Capybaraでアラート/確認ダイアログを操作する場合は page.driver.browser.switch_to.alert.accept じゃなくて page.accept_confirm を使おう - Qiita

もっとも、Headless Chromeの今後のバージョンでは実装されるかもしれないため、今回はこの部分の対応方法は省略します。

 

ソースコード

GitHubに上げました。tests/の中にあるselenium_js_test.robotファイルが今回のテストファイルです。
thinkAmi-sandbox/RobotFramework-sample

Robot Framework + Selenium2Libraryで、HTMLフォームまわりをテストする

前回、Robot Framework + Selenium2Libraryで、静的HTMLをテストしてみました。
Robot Framework + Selenium2Libraryで、静的HTMLをテストする - メモ的な思考的な

 
今回は、Robot Framework + Selenium2Libraryで、HTMLフォームまわりをテストしてみます。

 
目次

 

環境

  • Mac OS X 10.11.6
  • Python 3.6.2
  • Google Chrome 60.0.3112.78 (stable)
  • ChromeDriver 2.31 (stable)
  • RobotFramework 3.0.2
  • Selenium2Library 1.8.1dev1
    • GitHubから現在の最新版をインストール
    • PyPIにある1.8.0では、Python3系だとエラーが出てインストールできない
  • Selenium 3.4.3
  • Bottle 0.12.13
    • Webアプリ用

 

フォームの送信ボタンを押す場合

こんな感じでフォームの送信ボタンがあるとします。

<form id="id_form" action="/form" method="POST">
    <input id="id_submit" type="submit">
</form>

この送信ボタンを押す方法を考えてみます。

 

フォームの送信ボタンをクリックする

Click Buttonを使って、フォームの送信ボタンをクリックします。
Click Button | Selenium2Library

click button  id=id_submit

 

フォームの送信ボタンに対してEnterキーを押す

Press Keyを使って、フォームの送信ボタンに対してEnterキーを押すこともできます。

なお、Enterキーのコードは \\13 となります。

press key  id=id_submit  \\13

 

リンクをクリックする

フォームとは関係ないような気もしますが、画面遷移になるので、ここに記載します。

このようなリンクがあるとします。

<a id="back_to_top" href="/">トップへ戻る</a>

この時のリンクのクリック方法を考えます。

 

Click Element

Click Elementでリンクをクリックします。
Click Element | Selenium2Library

click element  id=back_to_top

 

Click Linkでもリンクをクリックできます。
Click Link | Selenium2Library

click link  id=back_to_top

 

type=textなinput要素に値を入力する

<input type="text" id="id_subject" name="subject">

というHTMLの場合、

input text  id=id_subject  件名です

とすると、input要素に件名ですが入力されます。

 

type=passwordなinput要素に値を入力する

<input type="password" id="id_password" name="aikotoba">

というHTMLの場合、

input password  id=id_password  パスワードです

とすると、input要素にパスワードですが入力されます。

 

ラジオボタンを選択する

<p>
    <label for="id_apple">リンゴ</label>
    <input id="id_apple" type="radio" name="fruit" value="りんご">
    <label for="id_mandarin">ミカン</label>
    <input id="id_mandarin" type="radio" name="fruit" value="みかん">
    <label for="id_grape">ブドウ</label>
    <input id="id_grape" type="radio" name="fruit" value="ぶどう">
</p>
<p>
    <label for="id_big"></label>
    <input id="id_big" type="radio" name="fruit_size" value="大きいもの">
    <label for="id_small"></label>
    <input id="id_small" type="radio" name="fruit_size" value="小さいもの">
</p>

という2つのラジオボタングループがあった場合に、正しいラジオボタンを選択する方法を考えます。

 

Select Radio Button

Select Radio Buttonキーワードを使います。
Select Radio Button | Selenium2Library

select radio button  fruit  りんご

とすると、name=fruitなラジオボタングループのうち、value=“りんご"なラジオボタンを選択します。

 

Click Element

Click Elementキーワードでも選択できます。

click element  id=id_small

 

ラジオボタンの選択確認

ラジオボタンの選択確認はRadio Button Should Be Set Toキーワードを使います。
Radio Button Should Be Set To | Selenium2Library

radio button should be set to  fruit  りんご

 

単一値のみ選択可能なSelect要素の選択と検証

<p>
    <label for="id_quantity">個数</label>
    <select id="id_quantity" name="quantity">
        <option id="id_selected1_1" name="select1_1" value="1個">1</option>
        <option id="id_selected1_2" name="select1_2" value="2個">2</option>
        <option id="id_selected1_3" name="select1_3" value="3個">3</option>
    </select>
</p>

という、単一値のみ選択可能なSelect要素での選択と検証について考えます。

 

ラベルで選択する

ラベルとは、

<option>ここ</option>

でのここの値を指します。

 
ラベルの場合、Select From List By Labelを使います。
Select From List By Label | Selenium2Library

select from list by label  id=id_quantity  2

 

valueで選択する

option要素のvalue属性の値で選択する場合、Select From List By Valueを使います。
Select From List By Value | Selenium2Library

select from list by value  id=id_quantity  3個

 

indexで選択する

option要素のindexで選択する場合、Select From List By Indexを使います。
Select From List By Index | Selenium2Library

なお、indexは0始まりになります。

select from list by index  id=id_quantity  0

 

選択されているoption要素をラベルやvalue属性で確認する

List Selection Should Beを使います。
List Selection Should Be | Selenium2Library

なお、第二引数には、ラベルとvalue属性の値のどちらかを指定します。

# ラベル
list selection should be  id=id_quantity  2

# value属性
list selection should be  id=id_quantity  2個

 

選択したラベルを取得する

Get Selected List Labelを使います。
Get Selected List Label | Selenium2Library

以下の例では、一度変数に入れた後、内容を検証しています。

${選択ラベル} =  get selected list label  id=id_quantity
should be equal  ${選択ラベル}  3

 

選択したvalue属性を取得する

Get Selected List Valueを使います。
Get Selected List Value | Selenium2Library

こちらも、一度変数に入れて内容を検証する例になります。

${選択値} =  get selected list value  id=id_quantity
should be equal  ${選択値}  3個

 

Selectに含まれるOptionの件数を調べる

Optionの件数が動的に変わる場合、その件数を調べたくなるかもしれません。

その場合、2つの方法にて検証できます。
selenium webdriver - Check whether SelectBox has items, Robot-Framework, Selenium2Library - Stack Overflow

 

Get List ItemsとGet Lengthを組み合わせる

を組み合わせて検証します。

なお、Get List Itemsの戻り値はリスト型である一方、Get Lengthの引数はスカラ変数をとるため、波括弧の前を@から$へと切り替えます。

@{items} =  get list items  id=id_quantity
${list_length} =  get length  ${items}
should be equal as integers  ${list_length}  3

 

Get Matching Xpath Countを使う

XPathでの検索となりますが、Get Matching Xpath Countも使えます。
Get Matching Xpath Count | Selenium2Library

${list_count} =  get matching xpath count  //select[@id="id_quantity"]/option
should be equal as integers  ${list_count}  3

複数選択可能なSelectに含まれるOptionの選択と検証

<p>
    <label for="id_accessories">付属品</label>
    <select id="id_accessories" name="accessories" multiple>
        <option id="id_selected2_1" name="select2_1" value="paper"></option>
        <option id="id_selected2_2" name="select2_2" value="box">容器</option>
        <option id="id_selected2_3" name="select2_3" value="rope">ロープ</option>
    </select>
</p>

という、複数選択可能なSelect要素独特の選択と検証について考えます。

 

全選択する

Select All From Listを使います。
Select All From List | Selenium2Library

select all from list  id=id_accessories

 

選択をやめる

Unselect From ListUnselect From List By [Index/Lable/Value]を使います。

なお、Unselect From Listに It's faster to use 'by index/value/label' functions. とある通り、by系の方が速いようです。

unselect from list  id=id_accessories

 

何も選択されていないことを確認する

List Should Have No Selectionsを使います。
List Should Have No Selections | Selenium2Library

list should have no selections  id=id_accessories

 

チェックボックスの選択と確認

<p>
    <label for="id_takeout">持ち帰る</label>
    <input type="checkbox" id="id_takeout" name="takeout" value="自分で持ち帰る">
</p>

というチェックボックスのチェックと検証について考えます。

 

チェックボックスをチェックする

Select Checkboxを使います。
Select Checkbox | Selenium2Library

select checkbox  id=id_takeout

 

チェックボックスのチェックをやめる

Unselect Checkboxを使います。
Unselect Checkbox | Selenium2Library

unselect checkbox  id=id_takeout

 

チェックボックスがチェックされているか検証する

Checkbox Should Be Selectedを使います。
Checkbox Should Be Selected | Selenium2Library

checkbox should be selected  id=id_takeout

なお、否定形のCheckbox Should Not Be Selectedもあります。

 

テキストエリアの入力と検証

<textarea id="id_memo" name="memo"></textarea>

というテキストエリアの入力と検証を考えます。

 

テキストエリアに入力する

input type=textと同じく、Input Textを使います。

input text  id=id_memo  テキストエリアです

 

入力文字のクリア

こちらもinput type=textと同じく、Clear Element Textを使います。

clear element text  id=id_memo

 

テキストエリアの検証
完全一致

Textarea Value Should Beを使います。
Textarea Value Should Be | Selenium2Library

textarea value should be  id=id_memo  テキストエリアです

 

部分一致

Textarea Should Containを使います。
Textarea Should Contain | Selenium2Library

textarea should contain  id=id_memo  エリア

 

type=hiddenなinput要素のvalue属性を取得する

<input type="hidden" id="id_hidden_value" class="hidden_class" name="hidden_value" value="隠しデータ">

という、type="hidden"なinput要素があるとします。

Get Element Attributeを使うことで、hiddenなinput要素であってもvalue属性を取得できます。
Get Element Attribute | Selenium2Library

なお、<locator>@<対象の属性>のように書きます。

${hidden_value1} =  get element attribute  id_hidden_value@value

# 検証
should be equal  ${hidden_value1}  隠しデータ

 

ソースコード

GitHubに上げました。tests/の中にあるselenium_form_test.robotファイルが今回のテストファイルです。
thinkAmi-sandbox/RobotFramework-sample

Robot Framework + Selenium2Libraryで、静的HTMLをテストする

以前、Robot FrameworkとSelenium2Libraryを使ってみました。

 
今回は Robot Framework + Selenium2Libraryで、静的HTMLをテストしてみます。

 
目次

 

環境

  • Mac OS X 10.11.6
  • Python 3.6.2
  • Google Chrome 60.0.3112.78 (stable)
  • ChromeDriver 2.31 (stable)
  • RobotFramework 3.0.2
  • Selenium2Library 1.8.1dev1
    • GitHubから現在の最新版をインストール
    • PyPIにある1.8.0では、Python3系だとエラーが出てインストールできない
  • Selenium 3.4.3
  • Bottle 0.12.13
    • Webアプリ用

 
Selenium2LibraryをGitHubからインストールする方法は以下と同様ですので、今回は省略します。
Robot Framework + Selenium2Library + ImageMagickで、スクリーンショットの差分を確認する - メモ的な思考的な

 

ブラウザの起動・移動・終了

起動

が使えます。

Crate Webdriverだと起動時のオプションを利用できます。

通常のChromeを起動する方法です。

create webdriver  Chrome

 
HeadlessなChromeを起動する方法です。オプションとして--headlessを使います。

${options} =  evaluate  sys.modules['selenium.webdriver'].ChromeOptions()  sys
call method  ${options}  add_argument  --headless
create webdriver  Chrome  chrome_options=${options}

 

移動

Go Toを使います。
Go To | Selenium2Library

以下の例では、localhostの8084ポートへ移動しています。

go to  http://localhost:8084

 
なお、URIスキームの有無で動作の違いがあります。詳しくは以下に書きました。
Robot Framework + Selenium2Libraryで、「Go To」を使う時はURIスキームを付ける - メモ的な思考的な

 

終了

などが使えます。

close all browsers

 

ページに文字やElementが存在するか確認する

いわゆるassert系のキーワードです。キーワードの条件を満たさない場合、テストが失敗します。

ページに文字が存在するか

Page Should Containを使います。
Page Should Contain | Selenium2Library

# ページに「Hello, world!」の文字列が含まれるか
page should contain  Hello,${SPACE}world!

 
他に

  • ページに画像が存在するか(Page Should Contain Image)
  • ページに文字が存在しないか(Page Should Not Contain)

などのキーワードがあります。詳しくはドキュメントを見てください。

 

ページにElementが存在するか

Page Should Contain Elementを使います。
Page Should Contain Element | Selenium2Library

# ページにid=helloのelementが含まれるか
page should contain element  id=hello

 
なお、キーワードの後ろにあるid=helloの部分はlocatorと呼ばれるものです。詳しくは次で説明します。

 

locatorについて

locatorとは、ページのElementを探す時のキーとなるものです。

いくつか種類があるため、今回は主なものだけ記載します。詳細は以下のドキュメントを確認してください。
Locating or specifying elements | Selenium2Library

 

id

Elementのid属性を使って検索します。

<button id="locator_id" name="locator_name" class="locator_class">
    locator_text1
</button>

に対し、

page should contain element  id=locator_id

と使います。

 

name

Elementのname属性を使って検索します。

<button id="locator_id" name="locator_name" class="locator_class">
    locator_text1
</button>

に対し

page should contain element  name=locator_name

と使います。

 

identifier

Elementのidもしくはname属性を使って検索します。

<button id="locator_id" name="locator_name" class="locator_class">
    locator_text1
</button>

に対し、

page should contain element  identifier=locator_id
page should contain element  identifier=locator_name

と使います。

 

XPath

XPathを使うこともできます。

<button id="locator_id" name="locator_name" class="locator_class">
    locator_text1
</button>

に対し、

page should contain element  xpath=/html/body/button[@class="locator_class"]
page should contain element  xpath=//button[@class="locator_class"]

と使います。
参考:xpathまとめ - Qiita

 
また、

<button class="locator_class" data-hoge="ham spam" disabled>
    locator_text2
</button>
<button class="locator_class_foo" data-hoge="ham egg">
    locator_text3
</button>
<button class="locator_class_foo" data-hoge="ham spam">
    locator_text4
</button>

のような場合は、

# andを使う
element text should be  xpath=//button[@class="locator_class" and @disabled]  locator_text2

# containを使う(第二引数は文字列をクォートで囲む)
element text should be  xpath=//button[@class="locator_class" and contains(@data-hoge, "spam")]  locator_text2

# notを使う
element text should be
...  xpath=//button[@class="locator_class_foo" and not(contains(@data-hoge, "egg"))]  locator_text4

のような、 andcontainnotなども使えます。

 

CSS

CSSセレクタもlocatorとして使えます。

<div id="locator_css">
    <ul><li>1-1</li></ul>
    <ul>
        <li>2-1</li>
        <li>2-2</li>
        <li><p>対象</p></li>
    </ul>
    <ul><li>3-1</li></ul>
</div>

に対し、

page should contain element  css=#locator_id
page should contain element  css=button.locator_class

と使います。

 
また、CSSのnth-childなども使えます(nth-childは1始まり)。
:nth-childの使い方 | CSSPRO

element text should be  css=#locator_css > ul:nth-child(2) > li:last-child > p  対象

 

jQuery

jQueryもlocatorとして使えます。

ただし、対象のHTMLにてjQueryがロードされていない場合、WebDriverException: Message: unknown error: jQuery is not definedというエラーが発生して使えません。
参考:Document that jQuery is not provided by Selenium2Library · Issue #262 · robotframework/Selenium2Library

<img id="img_200" src="/static/image/a.png" alt="あの字" title="画像タイトル">

に対し、

page should contain image  jquery=img[alt='あの字']
page should contain image  jquery=img[title='画像タイトル']

と使います。

画像のalttitle属性で検索したい時は便利かもしれません。

 

Elementの属性を取得する

Get Element Attributeを使います。
Get Element Attribute | Selenium2Library

 
例えば、

<img id="img_200" src="/static/image/a.png" alt="あの字" title="画像タイトル">

に対し、画像のsrc属性を取得し、比較する例です。

${src} =  get element attribute  css=#img_200@src
${url} =  get location
should be equal  ${src}  ${url}static/image/a.png

 

Elementに対するvisibleとenableとcontainの違い

似たようなキーワードとして、

があります。

 
例えば

<button id="element_display_none" style="display: none">
    None
</button>

<button id="element_visibility_hidden" style="visibility: hidden">
    Hidden
</button>

<button id="element_disabled" disabled>
    Disabled
</button>

があった場合、パスするテストの書き方は

# not be visible / be visible
element should not be visible  id=element_display_none
element should not be visible  id=element_visibility_hidden
element should be visible  id=element_disabled

# be enable / be disable
element should be enabled  id=element_display_none
element should be enabled  id=element_visibility_hidden
element should be disabled  id=element_disabled

# いずれもcontain element
page should contain element  id=element_display_none
page should contain element  id=element_visibility_hidden
page should contain element  id=element_disabled

となります。

そのため、

  • display:nonevisibility:hiddennot visibledisabledvisible
  • display:nonevisibility:hiddenenableddisableddisabled
  • いずれもcontain

という挙動です。

 

Elementが見えている/見えていない時に何かする

例えば、

<button id="visible_button">
    見えてる
</button>

<button id="invisible_button" style="display: none">
    見えてない
</button>

があった時に、見えている/見えていないで処理を分ける場合、

# 見えてる
${visible} =  run keyword and return status  element should be visible  id=visible_button
run keyword if  ${visible}  log to console  見えてます

# 見えてない
${invisible} =  run keyword and return status  element should be visible  id=invisible_button
# 「run keyword unless」を使うことで、第一引数がfalseの時に log to console する
run keyword unless  ${invisible}  log to console  見えてないです

となります。

否定形の処理は、Run Keyword Unlessを使います。

 

テーブルに対する処理

例えば、

<div id="table_search">
    <table>
        <tr>
            <th>key</th>
            <th>value</th>
        </tr>
        <tr>
            <td>一行目のキー</td>
            <td>一行目の値</td>
        </tr>
    </table>
    <table>
        <tr>
            <th>項目</th>
            <th></th>
        </tr>
        <tr>
            <td>項目1</td>
            <td>値1</td>
        </tr>
        <tr>
            <td>項目2</td>
            <td>値2</td>
        </tr>
    </table>
</div>

というテーブルがあった時に、行・列・セルの処理をしたい場合は、

# 行番号(row)にはヘッダ行(th)も含まれていることに注意
# また、「table row should contain」は、指定した行のどこかの列にその文字列があればPASSする
table row should contain  css=#table_search > table:nth-child(2)  3  項目2
table row should contain  css=#table_search > table:nth-child(2)  3  値2

# 行・列の組み合わせで調べたい時は、「table cell should contain」を使う
# 行、列、値の順で指定する
table cell should contain  css=#table_search > table:nth-child(2)  3  2  値2

となります。

他にも、Table Header Should Containなど色々とありますので、ドキュメントを見ると良いかと思います。

 

ソースコード

GitHubに上げました。
thinkAmi-sandbox/RobotFramework-sample

selenium2_library_sampleディレクトリの中にあるものが今回のコードです。

構成は

  • target_app/target.py : Bottleアプリ
  • target_app/views/index.html : 対象のHTML
  • tests/selenium_page_test.robot : Selenium2Libraryを使ったテストコード

です。

python target.pyでBottleアプリを起動した後、RobotFrameworkのテストコードを実行して動作確認できます。