目的
pythonでkey入力のテストを行う。
seleniumでのwebへの自動入力でテストを行う方法は良く使われます。
ただ、もっと単純にキー入力のエミュレートだけしたい場合もあると思います。
pythonのライブラリでkeyboardというものがありましたので、使ってみたいと思います。
keyboardライブラリについて
keyboardはキーボード入力のエミュレート等ができます。
今回の目的に必要なのは、以下の関数となります。
keyboard.press_and_release(keyboard.send)
とりあえず、複雑なことは考えずにキーコードで送るだけを考えます。
各機能の実装
入力データ
スクリプトや文字列で入力データを指定できるように考えます。
また、wait時間も欲しいため、文字列中に埋め込めるようにします。
入力仕様概要
入力文字列の仕様は以下のような感じにします。
-
@wait(<num>)
<num> [sec] 待機する -
"<string>"
任意の文字列(<string>)を送信する
エスケープ文字列に対応する -
空白や改行はスキップする
key送信処理について
キーコード送信には、keyboard.press_and_release を使用します。、
エスケープシーケンス文字列をコードに戻すために、codecs.decodeを使用します。
def send_keys(self, keys: str):
"""Send keys to emulate keyboard actions."""
for key in keys:
keyboard.press_and_release(key)
def send_escape_text(self, text: str):
"""Send escape text to emulate keyboard actions."""
keys = codecs.decode(text, 'unicode-escape')
self.send_keys(keys)
入力データ解析
今回はお手軽に正規表現で対応します。
def __init__(self):
"""Initialize EmuKeyboard class."""
self.pattern = r'(@wait\((?P<wait>\d+(\.\d+)?)\)|"(?P<string>[^"]*)"|(?P<other>\S+))'
self.text = ""
def analyze(self, text: str):
"""Analyze the given text and yield key actions."""
matches = re.finditer(self.pattern, text)
for match in matches:
groups = match.groupdict()
if groups['wait']:
yield ('wait', float(groups['wait']))
elif groups['string']:
yield ('string', groups['string'])
elif groups['other']:
yield ('string', groups['other'])
analyze関数に文字列を渡すと、正規表現を使って分類して返します。
全体
コマンドライン引数を受け取る処理も追加して、全体を作成します。
import keyboard
import re
import time
import codecs
import argparse
import sys
class EmuKeyboard:
def __init__(self):
"""Initialize EmuKeyboard class."""
self.pattern = r'(@wait\((?P<wait>\d+(\.\d+)?)\)|"(?P<string>[^"]*)"|(?P<other>\S+))'
self.text = ""
def wait(self, sec: float = 1.0):
"""Wait for the specified seconds."""
time.sleep(sec)
def send_keys(self, keys: str):
"""Send keys to emulate keyboard actions."""
for key in keys:
keyboard.press_and_release(key)
def send_escape_text(self, text: str):
"""Send escape text to emulate keyboard actions."""
keys = codecs.decode(text, 'unicode-escape')
self.send_keys(keys)
def load(self, script: str):
"""Load script file and return its content."""
try:
with open(script, 'r', encoding='utf-8') as f:
self.text = f.read()
return self.text
except FileNotFoundError:
print(f"File not found: {script}")
return None
def send_text(self, text: str):
"""Send text to emulate keyboard actions."""
self.text = text
self.run()
def run(self):
"""Run the script to emulate keyboard actions."""
for keypair in self.analyze(self.text):
if keypair[0] == 'wait':
self.wait(keypair[1])
else:
self.send_escape_text(keypair[1])
def analyze(self, text: str):
"""Analyze the given text and yield key actions."""
matches = re.finditer(self.pattern, text)
for match in matches:
groups = match.groupdict()
if groups['wait']:
yield ('wait', float(groups['wait']))
elif groups['string']:
yield ('string', groups['string'])
elif groups['other']:
yield ('string', groups['other'])
else:
pass
def argparser() -> argparse.Namespace:
"""Parse command-line arguments and return them."""
parser = argparse.ArgumentParser(description='Key Emulator')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-k', '--keys', type=str, default=None, help='Keys to be emulated')
group.add_argument('-s', '--script', type=str, default=None, help='Script file containing keys to be emulated')
parser.add_argument('-w', '--wait', type=float, default=1.0, help='Wait time before sending the keys')
try:
return parser.parse_args()
except argparse.ArgumentError as e:
print(f"Error: {str(e)}")
sys.exit(1)
except SystemExit:
parser.print_help()
sys.exit(2)
def main() -> None:
"""Main function to parse arguments and execute EmuKeyboard."""
args = argparser()
ek = EmuKeyboard()
ek.wait(args.wait)
if args.script is not None:
if (ek.load(args.script) is not None):
ek.run()
else:
ek.send_text(args.keys)
if __name__ == '__main__':
main()