summaryrefslogtreecommitdiffstats
path: root/tests/screenshot.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/screenshot.py106
1 files changed, 62 insertions, 44 deletions
diff --git a/tests/screenshot.py b/tests/screenshot.py
index 6ccb593..0786d48 100644
--- a/tests/screenshot.py
+++ b/tests/screenshot.py
@@ -14,7 +14,7 @@ except ImportError:
from tests import PROJECT_ROOT
-STARTF_USESHOWWINDOW = getattr(subprocess, 'STARTF_USESHOWWINDOW', 1)
+STARTF_USESHOWWINDOW = getattr(subprocess, "STARTF_USESHOWWINDOW", 1)
STILL_ACTIVE = 259
SW_MAXIMIZE = 3
@@ -23,24 +23,24 @@ class StartupInfo(ctypes.Structure):
"""STARTUPINFO structure."""
_fields_ = [
- ('cb', ctypes.c_ulong),
- ('lpReserved', ctypes.c_char_p),
- ('lpDesktop', ctypes.c_char_p),
- ('lpTitle', ctypes.c_char_p),
- ('dwX', ctypes.c_ulong),
- ('dwY', ctypes.c_ulong),
- ('dwXSize', ctypes.c_ulong),
- ('dwYSize', ctypes.c_ulong),
- ('dwXCountChars', ctypes.c_ulong),
- ('dwYCountChars', ctypes.c_ulong),
- ('dwFillAttribute', ctypes.c_ulong),
- ('dwFlags', ctypes.c_ulong),
- ('wShowWindow', ctypes.c_ushort),
- ('cbReserved2', ctypes.c_ushort),
- ('lpReserved2', ctypes.c_char_p),
- ('hStdInput', ctypes.c_ulong),
- ('hStdOutput', ctypes.c_ulong),
- ('hStdError', ctypes.c_ulong),
+ ("cb", ctypes.c_ulong),
+ ("lpReserved", ctypes.c_char_p),
+ ("lpDesktop", ctypes.c_char_p),
+ ("lpTitle", ctypes.c_char_p),
+ ("dwX", ctypes.c_ulong),
+ ("dwY", ctypes.c_ulong),
+ ("dwXSize", ctypes.c_ulong),
+ ("dwYSize", ctypes.c_ulong),
+ ("dwXCountChars", ctypes.c_ulong),
+ ("dwYCountChars", ctypes.c_ulong),
+ ("dwFillAttribute", ctypes.c_ulong),
+ ("dwFlags", ctypes.c_ulong),
+ ("wShowWindow", ctypes.c_ushort),
+ ("cbReserved2", ctypes.c_ushort),
+ ("lpReserved2", ctypes.c_char_p),
+ ("hStdInput", ctypes.c_ulong),
+ ("hStdOutput", ctypes.c_ulong),
+ ("hStdError", ctypes.c_ulong),
]
def __init__(self, maximize=False, title=None):
@@ -49,7 +49,7 @@ class StartupInfo(ctypes.Structure):
:param bool maximize: Start process in new console window, maximized.
:param bytes title: Set new window title to this instead of exe path.
"""
- super(StartupInfo, self).__init__()
+ super().__init__()
self.cb = ctypes.sizeof(self)
if maximize:
self.dwFlags |= STARTF_USESHOWWINDOW
@@ -62,14 +62,14 @@ class ProcessInfo(ctypes.Structure):
"""PROCESS_INFORMATION structure."""
_fields_ = [
- ('hProcess', ctypes.c_void_p),
- ('hThread', ctypes.c_void_p),
- ('dwProcessId', ctypes.c_ulong),
- ('dwThreadId', ctypes.c_ulong),
+ ("hProcess", ctypes.c_void_p),
+ ("hThread", ctypes.c_void_p),
+ ("dwProcessId", ctypes.c_ulong),
+ ("dwThreadId", ctypes.c_ulong),
]
-class RunNewConsole(object):
+class RunNewConsole:
"""Run the command in a new console window. Windows only. Use in a with statement.
subprocess sucks and really limits your access to the win32 API. Its implementation is half-assed. Using this so
@@ -84,20 +84,27 @@ class RunNewConsole(object):
:param bytes title: Set new window title to this. Needed by user32.FindWindow.
"""
if title is None:
- title = 'pytest-{0}-{1}'.format(os.getpid(), random.randint(1000, 9999)).encode('ascii')
+ title = "pytest-{}-{}".format(
+ os.getpid(), random.randint(1000, 9999)
+ ).encode("ascii")
self.startup_info = StartupInfo(maximize=maximized, title=title)
self.process_info = ProcessInfo()
- self.command_str = subprocess.list2cmdline(command).encode('ascii')
- self._handles = list()
+ self.command_str = subprocess.list2cmdline(command).encode("ascii")
+ self._handles = []
self._kernel32 = ctypes.LibraryLoader(ctypes.WinDLL).kernel32
- self._kernel32.GetExitCodeProcess.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_ulong)]
+ self._kernel32.GetExitCodeProcess.argtypes = [
+ ctypes.c_void_p,
+ ctypes.POINTER(ctypes.c_ulong),
+ ]
self._kernel32.GetExitCodeProcess.restype = ctypes.c_long
def __del__(self):
"""Close win32 handles."""
while self._handles:
try:
- self._kernel32.CloseHandle(self._handles.pop(0)) # .pop() is thread safe.
+ self._kernel32.CloseHandle(
+ self._handles.pop(0)
+ ) # .pop() is thread safe.
except IndexError:
break
@@ -111,9 +118,9 @@ class RunNewConsole(object):
False, # bInheritHandles
subprocess.CREATE_NEW_CONSOLE, # dwCreationFlags
None, # lpEnvironment
- str(PROJECT_ROOT).encode('ascii'), # lpCurrentDirectory
+ str(PROJECT_ROOT).encode("ascii"), # lpCurrentDirectory
ctypes.byref(self.startup_info), # lpStartupInfo
- ctypes.byref(self.process_info) # lpProcessInformation
+ ctypes.byref(self.process_info), # lpProcessInformation
):
raise ctypes.WinError()
@@ -125,7 +132,9 @@ class RunNewConsole(object):
self.hwnd = 0
for _ in range(int(5 / 0.1)):
# Takes time for console window to initialize.
- self.hwnd = ctypes.windll.user32.FindWindowA(None, self.startup_info.lpTitle)
+ self.hwnd = ctypes.windll.user32.FindWindowA(
+ None, self.startup_info.lpTitle
+ )
if self.hwnd:
break
time.sleep(0.1)
@@ -141,7 +150,9 @@ class RunNewConsole(object):
status = ctypes.c_ulong(STILL_ACTIVE)
while status.value == STILL_ACTIVE:
time.sleep(0.1)
- if not self._kernel32.GetExitCodeProcess(self.process_info.hProcess, ctypes.byref(status)):
+ if not self._kernel32.GetExitCodeProcess(
+ self.process_info.hProcess, ctypes.byref(status)
+ ):
raise ctypes.WinError()
assert status.value == 0
finally:
@@ -154,9 +165,11 @@ class RunNewConsole(object):
:return: Yields region the new window is in (left, upper, right, lower).
:rtype: tuple
"""
- rect = ctypes.create_string_buffer(16) # To be written to by GetWindowRect. RECT structure.
+ rect = ctypes.create_string_buffer(
+ 16
+ ) # To be written to by GetWindowRect. RECT structure.
while ctypes.windll.user32.GetWindowRect(self.hwnd, rect):
- left, top, right, bottom = struct.unpack('llll', rect.raw)
+ left, top, right, bottom = struct.unpack("llll", rect.raw)
width, height = right - left, bottom - top
assert width > 1
assert height > 1
@@ -179,8 +192,7 @@ def iter_rows(pil_image):
:rtype: tuple
"""
iterator = izip(*(iter(pil_image.getdata()),) * pil_image.width)
- for row in iterator:
- yield row
+ yield from iterator
def get_most_interesting_row(pil_image):
@@ -224,11 +236,13 @@ def count_subimages(screenshot, subimg):
for x_pos in range(screenshot.width - si_width + 1):
if row[x_pos] != si_pixel:
continue # First pixel does not match.
- if row[x_pos:x_pos + si_width] != si_row:
+ if row[x_pos : x_pos + si_width] != si_row:
continue # Row does not match.
# Found match for interesting row of subimg in screenshot.
y_corrected = y_pos - si_y
- with screenshot.crop((x_pos, y_corrected, x_pos + si_width, y_corrected + si_height)) as cropped:
+ with screenshot.crop(
+ (x_pos, y_corrected, x_pos + si_width, y_corrected + si_height)
+ ) as cropped:
if list(cropped.getdata()) == si_pixels:
occurrences += 1
@@ -248,11 +262,12 @@ def try_candidates(screenshot, subimg_candidates, expected_count):
:rtype: int
"""
from PIL import Image
+
count_found = 0
for subimg_path in subimg_candidates:
with Image.open(subimg_path) as rgba_s:
- with rgba_s.convert(mode='RGB') as subimg:
+ with rgba_s.convert(mode="RGB") as subimg:
# Make sure subimage isn't too large.
assert subimg.width < 256
assert subimg.height < 256
@@ -277,14 +292,17 @@ def screenshot_until_match(save_to, timeout, subimg_candidates, expected_count,
:param iter gen: Generator yielding window position and size to crop screenshot to.
"""
from PIL import ImageGrab
- assert save_to.endswith('.png')
+
+ assert save_to.endswith(".png")
stop_after = time.time() + timeout
# Take screenshots until subimage is found.
while True:
with ImageGrab.grab(next(gen)) as rgba:
- with rgba.convert(mode='RGB') as screenshot:
- count_found = try_candidates(screenshot, subimg_candidates, expected_count)
+ with rgba.convert(mode="RGB") as screenshot:
+ count_found = try_candidates(
+ screenshot, subimg_candidates, expected_count
+ )
if count_found == expected_count or time.time() > stop_after:
screenshot.save(save_to)
assert count_found == expected_count