0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[T-DASH] XPathの要素を画像にして、期待画像と比較する

Last updated at Posted at 2025-04-29

Outline

フルスクリーンショットで画像を比較する機能は、T-DASHの標準動作でサポートされている
ただ、特定の要素の画像を比較したい場合、工夫がいる。

Yahoo Top Pageを例とする
https://www.yahoo.co.jp/

以下図のように、赤枠の部分を期待画像と比較したい場合である

image.png

技術的情報

pythonで3つの機能を用いる
・selenium libraryを用いてフルスクリーンショットを取る
・selenium libraryを用いてxpathの要素のlocation情報を取得する
・Image libraryを用いて画像を切り抜く

これにより、現在のページのxpathの画像ファイルを作成することができる。

あとは、2つの画像比較を行い、一致度を計算し、指定数値以上であるかを検証すればよい

T-DASH 実装

カスタム動作

robotframework library

以下設定ファイルを Image.yaml で保存する

@Image.yaml
ACT-CAT-CUSTOM-53c469f0-2a3e-4504-9e31-aa8d3da6ff99:
  action_category_name: Image
  icon: ''
  color: '#57fe30'
  custom_data:
    file_name: Image
    pip_list: []
    library_list:
    - lib_image.py
  actions:
    ACT-CUSTOM-bc9e7d9e-7db8-4549-bbe4-c6265f25bf92:
      action_name: 数字が大きいことを検証
      action_type: expectation
      action_format: 「設定値1」の値が期待値「設定値2」より高いことを検証
      action_note: ''
      action_args:
      - value1
      - value2
      action_def:
      - - Log
        - ${value1}
      - - Log
        - ${value2}
      - - Should Be True
        - ${value1} > ${value2}
    ACT-CUSTOM-344faacc-ce34-4438-8a7c-e1eb7d8ac943:
      action_name: xpathの要素をPNG画像にする
      action_type: operation
      action_format: 画面「画面名」要素「要素名」を「設定値」にPNG画像する
      action_note: ''
      action_args:
      - screen
      - element
      - value
      action_def:
      - - save_xpath_element_screenshot
        - ${location}
        - ${value}
      - - Log
        - ' <img src="${value}">'
        - html=true
    ACT-CUSTOM-e73dd0bb-100b-4a53-ae65-a7c52779a9e5:
      action_name: 2つの画像を比較する
      action_type: operation
      action_format: 画像1「設定値1」と画像2「設定値2」を比較する
      action_note: ''
      action_args:
      - value1
      - value2
      action_def:
      - - ${vars}=
        - Get Variables
      - - Log
        - ${vars['\${OUTPUT_DIR}']}
      - - Set Test Variable
        - ${image}
        - \\diff_image.png
      - - Set Test Variable
        - ${diff_image}
        - ${vars['\${OUTPUT_DIR}']}${image}
      - - save_diff_image
        - ${value1}
        - ${value2}
        - ${diff_image}
      - - 'Log '
        - <img src="${diff_image}">
        - html=true
      - - ${diff_ratio} =
        - calculate_matching
        - ${value1}
        - ${value2}
      - - Log
        - ${diff_ratio}
      - - Set Suite Variable
        - ${share_diff_ratio}
        - ${diff_ratio}

次に、Image.yamlをT-DASHの動作定義 -> カスタム動作 -> カスタム動作をインポート にてインポートし、実際にインポートされた設定内容を開く

image.png

「xpathの要素をPNG画像にする」の設定を開く

image.png

ここで、「ライブラリを追加する」を開く
⇒pythonのライブラリ追加に続く。

python ライブラリ

以下スクリプトを lib_image.pyのファイル名として保存する

@lib_image.py
import cv2
import numpy as np
import base64
from robot.libraries.BuiltIn import BuiltIn
from selenium import webdriver
from selenium.webdriver.common.by import By
from PIL import Image
import io

def save_xpath_element_screenshot(xpath, output_path):
	"""
	save xpath element by png format

	Args:
		xpath (str): スクリーンショットを撮りたい要素のXPath。
		output_path (str): スクリーンショットの保存パス。
	"""
	driver = None
	driver = BuiltIn().get_library_instance('SeleniumLibrary').driver
	element = driver.find_element(By.XPATH, xpath)

	# get element size & location
	location = element.location
	size = element.size


	cdp_browser = ['chrome', 'MicrosoftEdge', 'chrome-headless-shell', 'edge-headless-shell']
	browser_name = driver.capabilities['browserName']

	image = None
	if browser_name in cdp_browser:
		width = driver.execute_script("return document.body.scrollWidth;")
		height = driver.execute_script("return document.body.scrollHeight;")

		# 高さが0の場合は、htmlの高さを取得
		if height == 0:
			height = driver.execute_script("return document.documentElement.scrollHeight;")
		if height == 0:
			height = 10000

		print(f"full screen size is {width} x {height}")

		viewport = {
			"x": 0,
			"y": 0,
			"width": width,
			"height": height,
			"scale": 1
		}
		# Chrome Devtools Protocolコマンドを実行し、取得できるBase64形式の画像データをデコードしてファイルに保存
		image_base64 = driver.execute_cdp_cmd(
			"Page.captureScreenshot", {"clip": viewport, "captureBeyondViewport": True})
		img_bytes  = base64.b64decode(image_base64["data"])
	else:
		image_base64 = driver.get_full_page_screenshot_as_base64()
		img_bytes  = base64.b64decode(image_base64)

	img = Image.open(io.BytesIO(img_bytes ))
	left = location['x']
	top = location['y']
	right = location['x'] + size['width']
	bottom = location['y'] + size['height']

	# cut element
	element_image = img.crop((left, top, right, bottom))
	element_image.save(output_path)

	print(f"save XPath element in {output_path}")

def calculate_matching (file1,file2):
	img1 = cv2.imread(file1)
	img2 = cv2.imread(file2)
	if img1.shape[0] > img2.shape[0]:
		print( "crop image1 " )
		cropped_img = img1[0:img2.shape[0], 0:img1.shape[1]]
		img1 = cropped_img
	elif img1.shape[0] < img2.shape[0]:
		print( "crop image2 " )
		cropped_img = img2[0:img1.shape[0], 0:img2.shape[1]]
		img2 = cropped_img
	height = img1.shape[0]
	width = img1.shape[1]
	print( "image size = " + str(height) + ' ' + str(width)  )
	img_size = (int(width), int(height))
	rate = np.count_nonzero(img1 == img2) / img1.size
	return rate

def save_diff_image (file1,file2,file3):
	img1 = cv2.imread(file1)
	img2 = cv2.imread(file2)
	print( "image1 size = " + str (img1.shape[0]) + ' ' + str (img1.shape[1])  )
	print( "image2 size = " + str (img2.shape[0]) + ' ' + str (img2.shape[1])  )
	if img1.shape[0] > img2.shape[0]:
		if img1.shape[1] > img2.shape[1]:
			print( "crop image1 {img2.shape[0]} x {img2.shape[1]}" )
			cropped_img = img1[0:img2.shape[0], 0:img2.shape[1]]
			img1 = cropped_img
		else:
			print( "crop image1&2 {img2.shape[0]} x {img1.shape[1]}" )
			cropped_img = img1[0:img2.shape[0], 0:img1.shape[1]]
			img1 = cropped_img
			cropped_img = img2[0:img2.shape[0], 0:img1.shape[1]]
			img2 = cropped_img
	elif img1.shape[0] < img2.shape[0]:
		if img1.shape[1] > img2.shape[1]:
			print( "crop image1&2 {img1.shape[0]} x {img2.shape[1]}" )
			cropped_img = img1[0:img1.shape[0], 0:img2.shape[1]]
			img1 = cropped_img
			cropped_img = img2[0:img1.shape[0], 0:img2.shape[1]]
			img2 = cropped_img
		else:
			print( "crop image2 {img1.shape[0]} x {img1.shape[1]}" )
			cropped_img = img2[0:img1.shape[0], 0:img1.shape[1]]
			img2 = cropped_img
	im_diff = img1.astype(int) - img2.astype(int)
	im_diff_abs = np.abs(im_diff)
	im_diff_abs_norm = im_diff_abs / im_diff_abs.max() * 255
	cv2.imwrite(file3, im_diff_abs_norm)


①作成したpython scriptを自作ライブラリのフォルダに格納する
フォルダは、「ファイルを開く」を選択すると、格納先がエクスプローラーで指定される。
②自作ライブラリから、今回追加した「lib_image.py」を選び、⊕をクリックする。
すると、Libraryにリストされる

image.png

テストケース

image.png

2行目で、画像を取得したいxpath(画面定義)を指定し、保存先を指定する
3行目で、期待値画像と2行目で作成された画像を比較する 差分が変数${share_diff_ratio}に格納される
4行目で、期待値0.95と比較を行う

実行結果

期待値(左)と実際値(真ん中)が異なる(エラー)の例を示す。
差分画像(右)が黒であると、差分がないことを示す。

image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?