Managed Service Column <システム運用コラム>

Webシナリオ監視

Category: 実践編

2017.05.31

はじめに

中西です。前回の記事で、架空の会社「あーるわーくす商店」のECサイトを作成しました。今回の記事は、このECサイトの機能を監視するために、スクレイピングによるWeb監視のためのスクリプトを実装します。

監視する対象は商品購入機能とします。前回の記事で会員登録、商品購入、お問い合わせの3つの機能を監視することを考えましたが、このECサイトでは会員登録機能とお問い合わせ機能は送信されたメールを確認する必要があることが分かりました。Webシナリオ監視のみで監視する場合は内容が全てWebアクセスで完結する必要があるため、メールの確認は出来ません。そこで、スクレイピングによるWeb監視との比較を行うために今回監視する対象を商品購入機能に絞ります。

これからエンドユーザがあーるわーくす商店のECサイトで人気商品である「ディナーフォーク」を1本注文して、注文内容が正しいものであることを確認するまで、これを疑似的に再現するスクリプトを実装していきます。

監視内容の考察と実装

エンドユーザは以下の手順で商品購入操作します。

  1. トップページにアクセスする
  2. 登録済みユーザでログインする
  3. ディナーフォークの詳細ページにアクセスする
  4. ディナーフォークをショッピングカートに入れる
  5. カートの中身にディナーフォークが入っていることを確認してレジに進む
  6. 注文内容にディナーフォークが入っていることを確認して注文する
  7. 注文完了ページが表示されることを確認する
  8. マイページで注文履歴を確認する

従って、上記の操作を再現するようなスクリプトを作ります。また、この操作をした場合に正規でない注文記録が増えてしまうので、注文キャンセルの操作を加えます。一方でこのECサイトにはエンドユーザによる注文キャンセルの機能が備わっていません。そこで、上記の操作の後に管理画面から注文記録を削除する操作をステップに加えます。

準備

まず操作をスクリプトに落とし込む前に、商品購入用と管理画面用のダミーユーザを用意をしました。そして作成するPerlスクリプトの準備します。使用するライブラリは以下です。

そして、処理の流れを見やすくするために、得られた結果の成否を判定する関数 on_stepend() を用意しました。この関数は以下のように使います。

&on_stepsed(<ステップの説明(表示用)>, <URL (表示用)>, <ステップの判定条件>)

Step1. トップページにアクセスする

トップページにアクセスします。

$mech->get(http://172.31.60.56/eccube/html/);
&on_stepend($summary, $mech->uri, $mech->success());

見出しに「あーるわーくす商店」という文字列が存在することをチェックして、コンテンツが正しく取得できていることを確認します。

$tree->parse($mech->content);
&on_stepend($summary, $mech->uri, $tree->look_down('class', 'header_logo')->as_text eq 'あーるわーくす商店');

Step2. 登録済みユーザでログインする

トップページからログインページへのリンクを探して、アクセスします。

$mech->follow_link(text => 'ログイン');
&on_stepend($summary, $mech->uri, $mech->success());

見出しに「ログイン」という文字列が存在することをチェックして、コンテンツが正しく取得できていることを確認します。

$tree->parse($mech->content);
&on_stepend($summary, $mech->uri, $tree->look_down('class', 'page-heading')->as_text eq 'ログイン');

ダミーユーザのログイン情報としてメールアドレスとパスワードを入力してログインします。

$mech->submit_form(
form_number => 2,
fields => {
	'login_email' => 'kounyutest@rworks.jp',
	'login_pass'  => 'aaaaaaaa',
	}
);
&on_stepend($summary, $mech->uri, $mech->success());

画面右上のリンクに「ログアウト」と表示されていることをチェックして正常にログインできたことを確認します。

$tree->parse($mech->content);
my $cnt = 0;
foreach my $listitem ($tree->look_down('class', 'member_link')->look_down('_tag', 'li')) {
	foreach my $link ($listitem->look_down('_tag', 'a')) {
		if ($link->as_text =~ /^\s*\Qログアウト\E\s*$/) {
			$cnt++;
			last;
		}
	}
}
&on_stepend($summary, $mech->uri, $cnt);

Step3. ディナーフォークの詳細ページにアクセスする

詳細ページにアクセスするために、商品一覧ページへアクセスします。

$mech->get('http://172.31.60.56/eccube/html/products/list?category_id=5');
&on_stepend($summary, $mech->uri, $mech->success());

商品の画像の下に「ディナーフォーク」という文字列が存在するかをチェックして、商品一覧ページにディナーフォークがあることを確認します。同時にディナーフォーク詳細ページのURLを取得します。

$tree->parse($mech->content);
my $tag = $tree->look_down('id', 'result_list__item--1')->look_down('id', 'result_list__detail--1')->look_down('id', 'result_list__name--1');
$next_link = $tag->{_parent}->{_parent}->attr('href');
&on_stepend($summary, $mech->uri, $tag->as_text eq 'ディナーフォーク');

詳細ページにアクセスします。

$mech->get($next_link);
&on_stepend($summary, $mech->uri, $mech->success());

商品名に「ディナーフォーク」という文字列が存在することをチェックして、詳細ページに正しくアクセスできたことを確認します。

$tree->parse($mech->content);
&on_stepend($summary, $mech->uri, $tree->look_down('class', 'item_name')->as_text eq 'ディナーフォーク');

Step4. ディナーフォークをショッピングカートに入れる

以下のパラメータを送信してショッピングカートにディナーフォークを入れます。

項目名 フォーム上の項目名 フォーム上の値
classcategory_id1 プラチナ 3
大きさ classcategory_id2 150cm 6
個数 quantity 1個 1
フォーム動作の指定 mode カートに商品を入れる add_cart
商品識別 product_id ディナーフォーク 1
product_class_id 1

「_token」という名前でアクセスの度に変わるランダム値も一緒に送られていたので、この値はコンテンツ内から取得して送信します。

$tree->parse($mech->content);
my $token = $tree->look_down('id', '_token')->attr('value');
$mech->submit_form(
	form_number => 2,
		fields => {
			classcategory_id1 => 3,
			classcategory_id2 => 6,
			quantity => 1,
			mode => 'add_cart',
			product_id => 1,
			product_class_id => 1,
			_token => $token,
		},
	);
);
&on_stepend($summary, $mech->uri, $mech->success());

Step5. カートの中身にディナーフォークが入っていることを確認してレジに進む

商品内容に「ディナーフォーク」という文字列が存在することをチェックして、ディナーフォークがショッピングカートに正しく入っていることを確認します。

	my $summary = 'step 3-6. check string in content';
	my $tree = HTML::TreeBuilder->new;
	$tree->parse($mech->content);
	my $cnt = 0;
	foreach my $tag ($tree->look_down('class', 'item_name')) {
		if ($tag->as_text eq 'ディナーフォーク') {
			$cnt++;
		}
	}
	&on_stepend($summary, $mech->uri, $cnt);

レジに進むボタンのリンク先に遷移します。

	$mech->follow_link(text => 'レジに進む');
	&on_stepend($summary, $mech->uri, $mech->success());

Step6. 注文内容にディナーフォークが入っていることを確認して注文する

「ディナーフォーク」という文字列が存在していることを確認して、ディナーフォークが注文内容に入っているかを確認します。

$tree->parse($mech->content);
my $cnt = 0;
foreach my $tag ($tree->look_down('class', 'item_name')) {
	if ($tag->as_text eq 'ディナーフォーク') {
		$cnt++;
	}
}
&on_stepend($summary, $mech->uri, $cnt);

注文を確定します。配送情報やお支払方法はデフォルトの設定で注文処理が進むようします。

項目名 フォーム上の項目名 フォーム上の値
配送方法 shopping[shippings][0][delivery] サンプル業者 1
お届け日 shopping[shippings][0][shippingDeliveryDate] 指定なし (空)
お届け時間 shopping[shippings][0][deliveryTime] 指定なし 1
お問い合わせ内容 shopping[message] なし (空)

$tree->parse($mech->content);
my $token = $tree->look_down('id', 'shopping__token')->attr('value');
$mech->submit_form(
	form_number => 2,
	fields => {
		'shopping[_token]' => $token,
		'shopping[shippings][0][delivery]' => 1,
		'shopping[shippings][0][shippingDeliveryDate]' => '',
		'shopping[shippings][0][deliveryTime]' => '',
		'shopping[payment]' => 1,
		'shopping[message]' => '',
		},
	);
&on_stepend($summary, $mech->uri, $mech->success());

Step7. 注文完了ページが表示されることを確認する

画面中央に「ご注文ありがとうございました」という見出しが存在していることをチェックし、注文が正しく完了したことを確認します。

$tree->parse($mech->content);
&on_stepend($summary, $mech->uri, $tree->look_down('class', 'heading01')->as_text eq 'ご注文ありがとうございました');

Step8. マイページで注文履歴を確認する

マイページへのリンクは注文完了ページの「マイページ」というテキストある部分にあるので、それを探してアクセスします。

$tree->parse($mech->content);
my $uri;
foreach my $listitem ($tree->look_down('class', 'member_link')->look_down('_tag', 'li')) {
	foreach my $link ($listitem->look_down('_tag', 'a')) {
		if ($link->as_text =~ /^\s*\Qマイページ\E\s*$/) {
			$uri = $link->attr('href');
			last;
		}
	}
}
if (!$uri) {
	&on_error($summary, $mech->uri);
}
$mech->get($uri);
&on_stepend($summary, $mech->uri, $mech->success());

後の処理で使うので、注文番号が書かれている要素を探して注文番号を取得します。また、詳細ページへのリンクを探してアクセスします。

$tree->parse($mech->content);
$order_number = $tree->look_down('id', qr(history_list__order_id--\d+))->as_text;
my $uri = $tree->look_down('id', qr/history_list__detail_button--\d+/)->find('a')->attr('href');
$mech->get($uri);
&on_stepend($summary, $mech->uri, $mech->success());

以下の項目を確認し、注文内容が正しいこと確認します。

商品 パラメータ 名前 住所 支払方法
ディナーフォーク プラチナ/150cm 購入 テスト 東京都新宿区揚場町1-18飯田橋ビル6F 郵便振替

$tree->parse($mech->content);
my $itemlist = $tree->look_down('id', 'detail_list_box__list');
my @conditions;
push @conditions, { value => $itemlist->look_down('id', qr(detail_list__product_name--\d+))->find('a')->as_text, regex => qr(\Qディナーフォーク\E) };
push @conditions, { value => $itemlist->look_down('id', qr(detail_list__classcategory_name--\d+))->as_text, regex => qr(\Qプラチナ / 150cm\E) };

my $shiplist = $tree->look_down('id', qr(shipping_list--\d+));
push @conditions, { value => $shiplist->look_down('id', qr(shipping_list__address--\d+))->as_text, regex => qr(\Q購入\E\xA0\Qテスト\E) };
push @conditions, { value => $shiplist->look_down('id', qr(shipping_list__address--\d+))->as_text, regex => qr(\Q東京都新宿区揚場町1-18飯田橋ビル6F\E) };
push @conditions, { value => $shiplist->look_down('id', qr(shipping_list__delivery--\d+))->as_text, regex => qr(\Qサンプル業者\E) };
push @conditions, { value => $tree->look_down('id', 'detail_box__payment_method'->find('p')->as_text, regex => qr(\Q郵便振替\E) };
&on_stepend($summary, $mech->uri, !scalar(grep { ! $_ } map { $_->{value} =~ $_->{regex} ? 1 : 0 } @conditions));

Step9. 注文記録を削除する

管理画面へのログインページへアクセスします。

$mech->get(http://172.31.60.56/eccube/html/rworks);
&on_stepend($summary, $mech->uri, $mech->status() == '401');

ダミーユーザのログイン情報としてメールアドレスとパスワードを入力してログインします。ログイン処理で「_csrf_token」というランダム値も送信していることが分かったので、コンテンツ内の要素から取得して、ログイン情報と合わせて送信します。

$tree->parse($mech->content);
my $token = $tree->look_down('name', '_csrf_token')->attr('value');
$mech->submit_form(
form_number => 1,
fields => {
	login_id => 'rworks',
	password => 'aaaaaaaa',
	_csrf_token => $token,
	},
);
&on_stepend($summary, $mech->uri, $mech->success());

注文情報の管理画面へのリンクはサイドバーの中にあるリンクのうち、上から10番目なので、このリンクを追います。

$tree->parse($mech->content);
my $uri = ($tree->look_down('class', 'nav nav-sidebar')->find_by_tag_name('li'))[9]->find('a')->attr('href');
$mech->get($uri);
&on_stepend($summary, $mech->uri, $mech->success());

注文情報検索フォームから、step8. で保持した注文番号で検索します。また、検索する際に「admin_search_order[_token]」というランダム値が検索ワードと合わせて送信されていたので、コンテンツ内の要素から値を取得します。

$tree->parse($mech->content);
my $token = $tree->look_down('id', 'admin_search_order__token')->attr('value');
$mech->submit_form(
	form_number => 1,
	fields => {
		'admin_search_order[_token]' => $token,
		'admin_search_order[multi]' => $order_number,
		},
	);
&on_stepend($summary, $mech->uri, $mech->success());

検索した注文番号の情報が表示されることをチェックして、注文情報の存在を確認します。

$tree->parse($mech->content);
my $hits = scalar(grep { defined $_->find('a') && $_->find('a')->as_text == $order_number } $tree->look_down('id', qr/result_list_main__id--\d+/));
&on_stepend($summary, $mech->uri, $hits eq 1);

商品削除のリンク先にアクセスして注文情報の削除をします。また、削除の際に「_token」というランダム値も同時に送信されていたので、コンテンツ内の要素から値を取得します。

$tree->parse($mech->content);
my $link = ($tree->look_down('id', 'result_list_main__item_menu--' . $order_number)->find('ul')->find('li'))[1]->find('a');
my $token = $link->attr('token-for-anchor');
my $uri = $link->attr('href');
$mech->post($uri, [ _token => $token, _method => 'delete' ]);
&on_stepend($summary, $mech->uri, $mech->success());

削除が正しく完了すると、「受注情報を削除しました。」というメッセージが表示されるので、これをチェックして削除できたか否かを確認します。

$tree->parse($mech->content);
my $tag = $tree->look_down('class', qr/alert-success/);
&on_stepend($summary, $mech->uri, defined $tag && $tag->as_text =~ /\Q受注情報を削除しました。\E/);

ここまでで商品購入操作と注文情報削除操作をスクリプトで疑似的に再現することができました。

まとめとおわりに

前回の記事で作成したECサイトの商品購入機能を監視するため、監視項目について考察を行い、それに対応するユーザの操作をスクリプトで疑似的に再現しました。このスクリプトを定期的に監視サーバから実行することで、商品購入機能の監視をすることができます。次回の記事ではWebシナリオ監視によって同等の内容を監視します。

Rworks の Webシナリオ監視サービスの料金や導入フローにつきましては、 Webシナリオ監視サービス紹介ページに記載しています。どうぞお気軽にお問い合わせください。

Tag: Webシナリオ監視

Contactお問い合わせ

お見積もり・ご相談など、お気軽にお問い合わせください。

single.php