きり丸の技術日記

技術・エンジニアのイベント・資格等はこちらにまとめる予定です

E2EツールのPlayWrightでセレクタに一致したものを全部操作する

セレクタで検索してヒットしたDOMすべてに対して操作をするという方法はわかりませんでした。

今回の記事では、複数件のDOMにヒットするセレクタを使用したうえで、ヒットしたDOMに対してイベントを操作できるようにします。

環境

  • PlayWright
    • 1.17

ゴール

  • 複数件のDOMがヒットするセレクタを使用する
  • ヒットしたすべてのDOMに対して操作する

ユースケース

画面上に表示された削除ボタンをすべてクリックして、画面上から削除する。

対応

$$でセレクタを指定すると、返却値がヒットしたDOMの配列になります。

次のコードは、画面上に表示されたリンクの削除ボタンをすべてクリックします。

const actualDomList = await page.$$("//a[text()='削除']");
await actualDomList.forEach(actual => {
    actual.click()
})

ソースコード

なし。

終わりに

Seleniumにてクリックイベントを発生させる場合、コードの流れは次のようになります。

  1. セレクタでDOMを検索する
  2. 検索したDOMからクリックイベントを発生させる

PlayWrightにてクリックイベントで発生させる場合、コードの流れは次のようになります。

  1. クリックイベントのパラメータに、セレクタを指定する

本来であれば、Seleniumの方が「Aに対してBの操作をする」と日本語として意味を理解しやすいです。PlayWrightの場合、「Bの操作をする。Aに対して」と倒置法になってしまうので、日本語としては理解しづらいです。英語としてはPlayWrightの方が自然ですが。

どちらの方が優れている、ということはわかりません。ただ、セレクタをXPathで指定すると1文が長くなってしまい、実施したい操作を文章の後半まで追う必要があるため、Selenium式だとコードの意味を読み込むのがたいへんです。書き方にも依存しますが、PlayWright式だと操作したいイベントがだいたい同じ文字数に来るのでコードリーディングもやりやすいです。


ただしPlayWright式のように書くと、複数のDOMにヒットするセレクタを指定した場合に、初回にヒットするセレクタのみしか選択できません。PlayWright公式にも、次のように記載されています。

A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used

意図しない操作をしない為にも、イベントのオプションにstrict:booleanを指定することで、複数件ヒットした場合にエラーとすることもできます。

複数件ヒットさせた場合に、発生させたいDOMの順番が分かっている場合は、nth-match(n)等で目的のDOMでイベントを発生できます。


XPath等で1件だけヒットするようにするやり方は分かったのですが、雑に更新したいユースケースに対応する方法が分かりませんでした。英語のヘルプが読めるなら一瞬で分かりそうですが、英語に苦手意識持っているせいでこの情報にアクセスするまでに時間がかかってしまいました。

もうちょっと技術系な英単語を覚えて、Multiple, Arrayのような英語のまま絞り込める単語も今後覚えていきたいです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考情報

f:id:nainaistar:20211231145839p:plain

Dockerで簡単に暗号化したUNIXパスワードのハッシュを生成する(DockerでPythonスクリプトを実行)

Dockerを使用するとローカルに暗号化したUNIXパスワードのハッシュで必要なPython等を導入する必要がありません。

今回の記事では、Pythonのスクリプトで暗号化したUNIXパスワードのハッシュを簡単に作る方法を記します。

なお、このハッシュを作る方法は正しいかわかりませんので、実施する際は自己責任でお願いします。


※ 「Pythonをローカルにインストールせずに、DockerでPythonスクリプトを実行したい」という考えが先にあり、その後にユースケースを考えています。そのため、ユースケースに特化して処理をするならば、OpenSSLのDockerイメージを使用する等々の方が適切です。

前提

  • pipを使わない

環境

  • Docker
    • version 20.10.10, build b485636
  • Python

対応

ワンライナーで実施する

パスワードを暗号化してハッシュで取得するスクリプトがワンライナーで作っている場合は、次のコマンドで十分です。

docker run -it --rm python python3 -c "import crypt; import getpass; print(crypt.crypt(getpass.getpass()))"
# 実行後
# Password:
# $6$BuxmkdTtpoJRk2Uq$cfjdbWHMTi8Y72hK6xg56Z5JzkdwwhnN51tKltJzYF/xZX4wRBhJ.Q9UL5YR0l2kfDdC/C3viyrcCK8EGQM0b0

解説

# 元
docker run -it --rm python python3 -c "import crypt; import getpass; print(crypt.crypt(getpass.getpass()))"
# Dockerのコンテナで実行する
docker run 
# インタラクティブにターミナルを使用する
-it
# 処理終了時にコンテナ停止する
-rm
# コンテナ名
python
# コンテナで実行するコマンド
python3 -c "import crypt; import getpass; print(crypt.crypt(getpass.getpass()))"
# pythonにコマンド実行させるオプション
-c
# pythonが実行するコマンド
"import crypt; import getpass; print(crypt.crypt(getpass.getpass()))"

Pythonのコマンドはワンライナーにしているだけで、実際には次のスクリプトです。

import crypt # ハッシュ生成(本来はUnixパスワード検証)
import getpass # 入力したパスワードを画面に表示させない

print(crypt.crypt(getpass.getpass()))

スクリプトで実施する

スクリプトとして用意している場合、スクリプトをDockerコンテナにマウントすることで特にローカルに準備することなく実施できます。

ワンライナーで実施していたスクリプトをscript.pyという名称にしたうえで、パスを設定すると実施できます。

docker run -v /home:/root -it --rm python python3 /root/script.py
docker run -v {ローカルのscript.pyが存在する絶対パス}:/root -it --rm python python3 /root/script.py

終わりに

他の暗号化する方法を探していましたが、pipする必要があり、pipを必要としない方法として探しました。

pipしたコンテナを生成すれば簡単ですが、コンテナ生成が地味に面倒なので素直にdocker run python bashでログインして、pipして普通にコマンドを打ったほうが楽そうでした。

もし、コンテナに複数行のコマンドを渡す方法が分かっていれば、それも検証してみたいので、もしご存じの方がいらっしゃれば教えていただきたいです。

※ ワンライナーでやりたかったもの。SHA512で暗号化する方法(passlibにpipが必要)

from passlib.hash import sha512_crypt; 
import getpass; 
print(sha512_crypt.encrypt(getpass.getpass()))

参考情報

f:id:nainaistar:20220103002054p:plain

Rubyで法律に従った暦による期間計算(日割りにならない1ヵ月を求める)

以前、法律に従った暦上の計算方法を記事にし、Javaでも同様の記事を作成しました。

当記事ではRubyで法律に従った暦による期間の計算を求める方法を記します。

環境

  • Ruby
    • 3.0.2p107
  • Rails
    • 6.0.3.7
  • RSpec

対応

テストデータとロジックに関してはJavaと同じです。

Rubyらしい書き方をしているだけで、新規の内容はありません。テストコードもParameterizedTestを使用していますが、あまり面白いことはしていないので特に記載しません。

beginning_of_month?のような、月初、月末であることがわかるメソッドがあればもうちょっと綺麗に書けるとは思うのですが…。そういうメソッドが生えていない以上、泥臭い書き方なのは仕方ないです。

# contractDate(契約日):datetime
# expireDate(解約日):datetime
class Contract
  attr_reader :contractDate
  attr_reader :expireDate

  def initialize(contractDate, expireDate)
    @contractDate = contractDate
    @expireDate = expireDate
  end

  ##
  # 解約可能か
  # 日割とはならない契約日と解約日の関係であること
  #
  # @see https://nainaistar.hatenablog.com/entry/2021/05/02/120000
  def canExpire?
    return true if canExpireByStartOfMonth?
    return true if canExpireByHasEndOfMonth?
    canExpireByHasNotEndOfMonth?
  end

  private

  ##
  # 月の初日から起算する場合は、最終月の末日
  #
  def canExpireByStartOfMonth?
    return expireDate.next_day(1).day == 1 if contractDate.day == 1

    false
  end

  ##
  # 月の途中から起算し,最終月に応当日のある場合は、最終月の応当日の前日
  # -1日すると、月を跨ぐ可能性があるのでNG
  #
  def canExpireByHasEndOfMonth?
    contractDate.day == expireDate.next_day(1).day
  end

  ##
  # 月の途中から起算し,最終月に応当日のない場合は、最終月の末日
  #
  def canExpireByHasNotEndOfMonth?
    return expireDate.next_day(1).day == 1 if contractDate.day > expireDate.day

    false
  end
end

ソースコード

終わりに

Javaよりも書きやすいですね。ただ、後置ifを多用してしまっているので、ちょっとわかりづらくなっている気もします。

Publicメソッドの後置ifはともかく、Privateメソッドの後置ifはやめておいた方がいいかもしれません。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考情報

関連情報

f:id:nainaistar:20211231205644p:plain