def test_that_results_dont_overlap(match_method): # This is a regression test for a bug seen in an earlier implementation of # `match_all`. frame = stbt.load_image("action-panel.png") all_matches = set() for m in stbt.match_all("action-panel-template.png", frame=frame, match_parameters=mp(match_method=match_method)): print(m) assert m.region not in all_matches, "Match %s already seen:\n %s" % ( m, "\n ".join(str(x) for x in all_matches)) assert all(stbt.Region.intersect(m.region, x) is None for x in all_matches) all_matches.add(m.region) assert all_matches == set([ stbt.Region(x=135, y=433, width=222, height=40), stbt.Region(x=135, y=477, width=222, height=40), stbt.Region(x=135, y=521, width=222, height=40), stbt.Region(x=135, y=565, width=222, height=40), stbt.Region(x=135, y=609, width=222, height=40)])
def test_match_all_with_an_image_that_matches_everywhere(match_method): matches = sorted(m.region for m in stbt.match_all( "repeating-pattern.png", frame=stbt.load_image("repeating-pattern-full-frame.png"), match_parameters=mp(match_method=match_method))) expected_matches = sorted([stbt.Region(x, y, width=16, height=16) for x in range(0, 320, 16) for y in range(0, 240, 16)]) print(matches) assert matches == expected_matches
def test_crop(): img = stbt.load_image("action-panel.png") cropped = stbt.crop(img, stbt.Region(x=1045, y=672, right=1081, bottom=691)) reference = stbt.load_image("action-panel-blue-button.png") assert numpy.array_equal(reference, cropped) # It's a view onto the same memory: assert cropped[0, 0, 0] == img[672, 1045, 0] assert img[672, 1045, 0] != 0 cropped[0, 0, 0] = 0 assert img[672, 1045, 0] == 0 assert img.filename == "action-panel.png" assert cropped.filename == img.filename # Region is clipped to the frame boundaries: assert numpy.array_equal( stbt.crop(img, stbt.Region(x=1045, y=672, right=1280, bottom=720)), stbt.crop(img, stbt.Region(x=1045, y=672, right=1281, bottom=721))) assert numpy.array_equal( stbt.crop(img, stbt.Region(x=0, y=0, right=10, bottom=10)), stbt.crop( img, stbt.Region(x=float("-inf"), y=float("-inf"), right=10, bottom=10))) # But a region entirely outside the frame is not allowed: with pytest.raises(TypeError): stbt.crop(img, None) with pytest.raises(ValueError): stbt.crop(img, stbt.Region(x=-10, y=-10, right=0, bottom=0))
def test_match_error_message_for_too_small_frame_and_region(): stbt.match("videotestsrc-redblue.png", frame=black(width=92, height=160)) stbt.match("videotestsrc-redblue.png", frame=black(), region=stbt.Region(x=1188, y=560, width=92, height=160)) with pytest.raises(ValueError) as excinfo: stbt.match("videotestsrc-redblue.png", frame=black(width=91, height=160)) assert ( "Frame (160, 91, 3) must be larger than reference image (160, 92, 3)" in str(excinfo.value)) with pytest.raises(ValueError) as excinfo: stbt.match("videotestsrc-redblue.png", frame=black(width=92, height=159)) assert ( "Frame (159, 92, 3) must be larger than reference image (160, 92, 3)" in str(excinfo.value)) with pytest.raises(ValueError) as excinfo: # Region seems large enough but actually it extends beyond the frame stbt.match("videotestsrc-redblue.png", frame=black(), region=stbt.Region(x=1189, y=560, width=92, height=160)) assert ( "Region(x=1189, y=560, right=1280, bottom=720) must be larger than " "reference image (160, 92, 3)" in str(excinfo.value)) with pytest.raises(ValueError) as excinfo: # Region seems large enough but actually it extends beyond the frame stbt.match("videotestsrc-redblue.png", frame=black(), region=stbt.Region(x=1188, y=561, width=92, height=160)) assert ( "Region(x=1188, y=561, right=1280, bottom=720) must be larger than " "reference image (160, 92, 3)" in str(excinfo.value))
def message(self): """ This property demonstrates an advantage of Frame Objects over stand-alone helper functions. We are using the position of the "info" icon to find this message. Because the private ``_info`` property is shared between this property and ``is_visible`` we don't need to compute it twice -- the ``FrameObject`` base class will remember the value from the first time it was computed. """ right_of_info = stbt.Region(x=self._info.region.right, y=self._info.region.y, width=390, height=self._info.region.height) return stbt.ocr(region=right_of_info, frame=self._frame) \ .replace('\n', ' ')
def test_detect_motion_region_and_mask(): def dm(**kwargs): return next(stbt.detect_motion(frames=wipe(), **kwargs)) r = stbt.Region(0, 0, right=640, bottom=1280) # Just check no exceptions dm() dm(mask="mask-out-left-half-720p.png") dm(mask=numpy.zeros((720, 1280), dtype=numpy.uint8)) dm(region=r) dm(region=r, mask=numpy.zeros((720, 640), dtype=numpy.uint8)) with pytest.raises(ValueError): dm(region=r, mask="mask-out-left-half-720p.png")
def test_merge_regions_performance(n): random.seed(1) regions = [] for _ in range(n): x = random.randint(0, 1280) y = random.randint(0, 720) right = random.randint(0, 1280) bottom = random.randint(0, 720) x, w = min(x, right), max(x, right) - min(x, right) + 1 y, h = min(y, bottom), max(y, bottom) - min(y, bottom) + 1 regions.append(stbt.Region(x, y, w, h)) times = timeit.repeat(lambda: _merge_regions(regions[:]), number=1, repeat=10) print(times) print(min(times)) assert min(times) < (0.001 * n / 20)
def test_that_slicing_a_Frame_is_still_a_Frame(): f = stbt.Frame(numpy.zeros((720, 1280, 3), dtype=numpy.uint8), time=1234) f1 = f[10:20, 10:, 0] assert isinstance(f1, stbt.Frame) assert f1.time == 1234 f2 = stbt.crop(f, stbt.Region(10, 10, 20, 20)) assert isinstance(f2, stbt.Frame) assert f2.time == 1234 f3 = f.copy() assert isinstance(f3, stbt.Frame) assert f3.time == 1234 assert (f.__array_interface__["data"][0] != f3.__array_interface__["data"][0]) f4 = stbt.Frame(f) assert f4.time == 1234
def test_is_screen_black_debug(): # So that the output directory name doesn't depend on how many tests # were run before this one. ImageLogger._frame_number = itertools.count(1) # pylint:disable=protected-access f = stbt.load_image("videotestsrc-full-frame.png") with scoped_curdir(), scoped_debug_level(2): stbt.is_screen_black(f) stbt.is_screen_black(f, mask="videotestsrc-mask-non-black.png") stbt.is_screen_black(f, mask="videotestsrc-mask-no-video.png") stbt.is_screen_black(f, region=stbt.Region(0, 0, 160, 120)) files = subprocess.check_output("find stbt-debug | sort", shell=True) \ .decode("utf-8") assert files == dedent("""\ stbt-debug stbt-debug/00001 stbt-debug/00001/grey.png stbt-debug/00001/index.html stbt-debug/00001/non_black.png stbt-debug/00001/source.png stbt-debug/00002 stbt-debug/00002/grey.png stbt-debug/00002/index.html stbt-debug/00002/mask.png stbt-debug/00002/non_black.png stbt-debug/00002/source.png stbt-debug/00003 stbt-debug/00003/grey.png stbt-debug/00003/index.html stbt-debug/00003/mask.png stbt-debug/00003/non_black.png stbt-debug/00003/source.png stbt-debug/00004 stbt-debug/00004/grey.png stbt-debug/00004/index.html stbt-debug/00004/non_black.png stbt-debug/00004/source.png """) assert_expected("stbt-debug-expected-output/is_screen_black")
def _test_that_cache_speeds_up_ocr(): frame = load_image('red-black.png') def ocr(): return stbt.ocr(frame=frame) # pylint:disable=protected-access _cache = imgproc_cache._cache imgproc_cache._cache = None uncached_result = ocr() uncached_time = min(timeit.repeat(ocr, repeat=10, number=1)) imgproc_cache._cache = _cache cached_result = ocr() # prime the cache cached_time = min(timeit.repeat(ocr, repeat=10, number=1)) print("ocr with cache: %s" % (cached_time, )) print("ocr without cache: %s" % (uncached_time, )) assert uncached_time > (cached_time * 10) assert type(cached_result) == type(uncached_result) # pylint:disable=unidiomatic-typecheck assert cached_result == uncached_result r = stbt.Region(x=0, y=32, right=91, bottom=59) frame2 = load_image("red-black-2.png") def cached_ocr1(): return stbt.ocr(frame=frame, region=r) def cached_ocr2(): return stbt.ocr(frame=frame2, region=r) cached_ocr1() # prime the cache time1 = timeit.timeit(cached_ocr1, number=1) time2 = timeit.timeit(cached_ocr2, number=1) print("ocr with cache (frame 1): %s" % (time1, )) print("ocr with cache (frame 2): %s" % (time2, )) assert time2 < (time1 * 10) assert cached_ocr1() == cached_ocr2()
def test_region_replace(): r = stbt.Region(x=10, y=20, width=20, height=30) def t(kwargs, expected): assert r.replace(**kwargs) == expected def e(kwargs): with pytest.raises(ValueError): r.replace(**kwargs) # No change yield t, dict(x=10), r yield t, dict(x=10, width=20), r yield t, dict(x=10, right=30), r # Not allowed yield e, dict(x=1, width=2, right=3) yield e, dict(y=1, height=2, bottom=3) # Allowed # pylint:disable=line-too-long yield t, dict(x=11), stbt.Region(x=11, y=r.y, width=19, height=r.height) yield t, dict(width=19), stbt.Region(x=10, y=r.y, width=19, height=r.height) yield t, dict(right=29), stbt.Region(x=10, y=r.y, width=19, height=r.height) yield t, dict(x=11, width=20), stbt.Region(x=11, y=r.y, width=20, height=r.height) yield t, dict(x=11, right=21), stbt.Region(x=11, y=r.y, width=10, height=r.height) yield t, dict(x=11, right=21, y=0, height=5), stbt.Region(x=11, y=0, width=10, height=5)
def test_region_translate(): with pytest.raises(TypeError): # Both region and y provided stbt.Region(2, 3, 2, 1).translate(stbt.Region(0, 0, 1, 1), 5)
def requires_tesseract(func): """Decorator for tests that require Tesseract to be installed.""" try: _tesseract_version() except: raise SkipTest("tesseract isn't installed") return func @requires_tesseract @pytest.mark.parametrize("image, expected_text, region, mode", [ # pylint: disable=line-too-long ("Connection-status--white-on-dark-blue.png", "Connection status: Connected", stbt.Region.ALL, None), ("Connection-status--white-on-dark-blue.png", "Connected", stbt.Region(x=210, y=0, width=120, height=40), None), # ("Connection-status--white-on-dark-blue.png", "", None, None), # uncomment when region=None doesn't raise -- see #433 ("programme--white-on-black.png", "programme", stbt.Region.ALL, None), ("UJJM--white-text-on-grey-boxes.png", "", stbt.Region.ALL, None), ("UJJM--white-text-on-grey-boxes.png", "UJJM", stbt.Region.ALL, stbt.OcrMode.SINGLE_LINE), ]) def test_ocr_on_static_images(image, expected_text, region, mode): kwargs = {"region": region} if mode is not None: kwargs["mode"] = mode text = stbt.ocr(load_image("ocr/" + image), **kwargs) assert text == expected_text # Don't leak python future newtypes assert type(text).__name__ in ["unicode", "str"]
@pytest.mark.parametrize( "frame,mask,threshold,region,expected", [ # pylint:disable=line-too-long ("black-full-frame.png", None, None, stbt.Region.ALL, True), ("videotestsrc-full-frame.png", None, None, stbt.Region.ALL, False), ("videotestsrc-full-frame.png", "videotestsrc-mask-non-black.png", None, stbt.Region.ALL, True), ("videotestsrc-full-frame.png", "videotestsrc-mask-no-video.png", None, stbt.Region.ALL, False), ("videotestsrc-full-frame.png", "videotestsrc-mask-no-video.png", None, stbt.Region.ALL, False), ("videotestsrc-full-frame.png", None, 20, stbt.Region(x=160, y=180, right=240, bottom=240), True), # Threshold bounds for almost-black frame: ("almost-black.png", None, 3, stbt.Region.ALL, True), ("almost-black.png", None, 2, stbt.Region.ALL, False), ]) def test_is_screen_black(frame, mask, threshold, region, expected): frame = stbt.load_image(frame) assert expected == bool( stbt.is_screen_black(frame, mask, threshold, region)) def test_is_screen_black_result(): frame = stbt.load_image("almost-black.png") result = stbt.is_screen_black(frame) assert result assert numpy.all(result.frame == frame)
def test_ocr_debug(): # So that the output directory name doesn't depend on how many tests # were run before this one. ImageLogger._frame_number = itertools.count(1) # pylint:disable=protected-access f = stbt.load_image("action-panel.png") r = stbt.Region(0, 370, right=1280, bottom=410) c = (235, 235, 235) nonoverlapping = stbt.Region(2000, 2000, width=10, height=10) with scoped_curdir(), scoped_debug_level(2): stbt.ocr(f) stbt.ocr(f, region=r) stbt.ocr(f, region=r, text_color=c) stbt.ocr(f, region=nonoverlapping) stbt.match_text("Summary", f) # no match stbt.match_text("Summary", f, region=r) # no match stbt.match_text("Summary", f, region=r, text_color=c) stbt.match_text("Summary", f, region=nonoverlapping) files = subprocess.check_output("find stbt-debug | sort", shell=True) \ .decode("utf-8") assert files == dedent("""\ stbt-debug stbt-debug/00001 stbt-debug/00001/index.html stbt-debug/00001/source.png stbt-debug/00001/tessinput.png stbt-debug/00001/upsampled.png stbt-debug/00002 stbt-debug/00002/index.html stbt-debug/00002/source.png stbt-debug/00002/tessinput.png stbt-debug/00002/upsampled.png stbt-debug/00003 stbt-debug/00003/index.html stbt-debug/00003/source.png stbt-debug/00003/tessinput.png stbt-debug/00003/text_color_difference.png stbt-debug/00003/text_color_threshold.png stbt-debug/00003/upsampled.png stbt-debug/00004 stbt-debug/00004/index.html stbt-debug/00004/source.png stbt-debug/00005 stbt-debug/00005/index.html stbt-debug/00005/source.png stbt-debug/00005/tessinput.png stbt-debug/00005/upsampled.png stbt-debug/00006 stbt-debug/00006/index.html stbt-debug/00006/source.png stbt-debug/00006/tessinput.png stbt-debug/00006/upsampled.png stbt-debug/00007 stbt-debug/00007/index.html stbt-debug/00007/source.png stbt-debug/00007/tessinput.png stbt-debug/00007/text_color_difference.png stbt-debug/00007/text_color_threshold.png stbt-debug/00007/upsampled.png stbt-debug/00008 stbt-debug/00008/index.html stbt-debug/00008/source.png """)
def test_composing_complex_keyboards(): """The YouTube keyboard on Roku looks like this:: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z - ' SPACE CLEAR SEARCH The first 4 rows behave normally within themselves. The bottom row behaves normally within itself. But navigating to or from the bottom row is a bit irregular: No matter what column you're in, when you press KEY_DOWN you always land on SPACE. Then when you press KEY_UP, you go back to the column you were last on -- even if you had pressed KEY_RIGHT/KEY_LEFT to move within the bottom row. It's almost like they're two separate state machines, and we can model them as such, with a few explicit connections between the two. """ letters = stbt.Grid(stbt.Region(x=540, y=100, right=840, bottom=280), data=["ABCDEFG", "HIJKLMN", "OPQRSTU", "VWXYZ-'"]) space_row = stbt.Grid(stbt.Region(x=540, y=280, right=840, bottom=330), data=[[" ", "CLEAR", "SEARCH"]]) # Technique #0: Write the entire edgelist manually (as per previous tests) K0 = stbt.Keyboard(GRAPH) # Technique #1: Manipulate the graph (manually or programmatically) directly G1 = nx.compose(stbt.grid_to_navigation_graph(letters), stbt.grid_to_navigation_graph(space_row)) # Pressing down from the bottom row always goes to SPACE: for k in letters.data[-1]: G1.add_edge(k, " ", key="KEY_DOWN") # Pressing back up from the space/clear/search row can go to any column # in the bottom row: for k in space_row.data[0]: for j in letters.data[-1]: G1.add_edge(k, j, key="KEY_UP") K1 = stbt.Keyboard(G1) assert sorted(K0.G.edges(data=True)) == sorted(K1.G.edges(data=True)) # Technique #2: Use manually-written edgelist only for the irregular edges # Note that Keyboard.__init__ will normalise "SPACE" -> " " so it doesn't # matter if the 3 different graphs have different representations for # "SPACE". connections = stbt.Keyboard.parse_edgelist(""" V SPACE KEY_DOWN W SPACE KEY_DOWN X SPACE KEY_DOWN Y SPACE KEY_DOWN Z SPACE KEY_DOWN - SPACE KEY_DOWN ' SPACE KEY_DOWN SPACE V KEY_UP SPACE W KEY_UP SPACE X KEY_UP SPACE Y KEY_UP SPACE Z KEY_UP SPACE - KEY_UP SPACE ' KEY_UP CLEAR V KEY_UP CLEAR W KEY_UP CLEAR X KEY_UP CLEAR Y KEY_UP CLEAR Z KEY_UP CLEAR - KEY_UP CLEAR ' KEY_UP SEARCH V KEY_UP SEARCH W KEY_UP SEARCH X KEY_UP SEARCH Y KEY_UP SEARCH Z KEY_UP SEARCH - KEY_UP SEARCH ' KEY_UP """) G2 = nx.compose_all([ stbt.grid_to_navigation_graph(letters), stbt.grid_to_navigation_graph(space_row), connections ]) K2 = stbt.Keyboard(G2) assert sorted(K0.G.edges(data=True)) == sorted(K2.G.edges(data=True))
def test_motion_debug(): # So that the output directory name doesn't depend on how many tests # were run before this one. ImageLogger._frame_number = itertools.count(1) # pylint:disable=protected-access def fake_frames(): for i, f in enumerate( ["box-00001.png", "box-00002.png", "box-00003.png"]): yield stbt.Frame(stbt.load_image(f), time=i) with scoped_curdir(), scoped_debug_level(2): for _ in stbt.detect_motion(frames=fake_frames()): pass for _ in stbt.detect_motion(frames=fake_frames(), mask="box-00000.png"): pass for _ in stbt.detect_motion(frames=fake_frames(), region=stbt.Region(0, 0, 320, 400)): pass files = subprocess.check_output("find stbt-debug | sort", shell=True) \ .decode("utf-8") assert files == dedent("""\ stbt-debug stbt-debug/00001 stbt-debug/00001/absdiff.png stbt-debug/00001/absdiff_threshold_erode.png stbt-debug/00001/absdiff_threshold.png stbt-debug/00001/gray.png stbt-debug/00001/index.html stbt-debug/00001/previous_frame_gray.png stbt-debug/00001/source.png stbt-debug/00002 stbt-debug/00002/absdiff.png stbt-debug/00002/absdiff_threshold_erode.png stbt-debug/00002/absdiff_threshold.png stbt-debug/00002/gray.png stbt-debug/00002/index.html stbt-debug/00002/previous_frame_gray.png stbt-debug/00002/source.png stbt-debug/00003 stbt-debug/00003/absdiff_masked.png stbt-debug/00003/absdiff.png stbt-debug/00003/absdiff_threshold_erode.png stbt-debug/00003/absdiff_threshold.png stbt-debug/00003/gray.png stbt-debug/00003/index.html stbt-debug/00003/mask.png stbt-debug/00003/previous_frame_gray.png stbt-debug/00003/source.png stbt-debug/00004 stbt-debug/00004/absdiff_masked.png stbt-debug/00004/absdiff.png stbt-debug/00004/absdiff_threshold_erode.png stbt-debug/00004/absdiff_threshold.png stbt-debug/00004/gray.png stbt-debug/00004/index.html stbt-debug/00004/mask.png stbt-debug/00004/previous_frame_gray.png stbt-debug/00004/source.png stbt-debug/00005 stbt-debug/00005/absdiff.png stbt-debug/00005/absdiff_threshold_erode.png stbt-debug/00005/absdiff_threshold.png stbt-debug/00005/gray.png stbt-debug/00005/index.html stbt-debug/00005/previous_frame_gray.png stbt-debug/00005/source.png stbt-debug/00006 stbt-debug/00006/absdiff.png stbt-debug/00006/absdiff_threshold_erode.png stbt-debug/00006/absdiff_threshold.png stbt-debug/00006/gray.png stbt-debug/00006/index.html stbt-debug/00006/previous_frame_gray.png stbt-debug/00006/source.png """) assert_expected("stbt-debug-expected-output/motion")
expected = stbt.Region(5, 5, right=20, bottom=10) assert expected == stbt.Region.intersect(r1, r2) with pytest.raises(AttributeError): r1.intersect(r2) # pylint:disable=no-member def test_region_bounding_box(): r1 = stbt.Region(0, 0, right=20, bottom=10) r2 = stbt.Region(5, 5, right=25, bottom=15) expected = stbt.Region(0, 0, right=25, bottom=15) assert expected == stbt.Region.bounding_box(r1, r2) with pytest.raises(AttributeError): r1.bounding_box(r2) # pylint:disable=no-member r = stbt.Region(x=10, y=20, width=20, height=30) @pytest.mark.parametrize( "kwargs,expected", [ # No change (dict(x=10), r), (dict(x=10, width=20), r), (dict(x=10, right=30), r), # Allowed # pylint:disable=line-too-long (dict(x=11), stbt.Region(x=11, y=r.y, width=19, height=r.height)), (dict(width=19), stbt.Region(x=10, y=r.y, width=19, height=r.height)), (dict(right=29), stbt.Region(x=10, y=r.y, width=19, height=r.height)), (dict(x=11,
try: _tesseract_version() except: raise SkipTest("tesseract isn't installed") return func @requires_tesseract @pytest.mark.parametrize( "image, expected_text, region, mode", [ # pylint: disable=line-too-long ("Connection-status--white-on-dark-blue.png", "Connection status: Connected", stbt.Region.ALL, None), ("Connection-status--white-on-dark-blue.png", "Connected", stbt.Region(x=210, y=0, width=120, height=40), None), ("programme--white-on-black.png", "programme", stbt.Region.ALL, None), ("UJJM--white-text-on-grey-boxes.png", "", stbt.Region.ALL, None), ("UJJM--white-text-on-grey-boxes.png", "UJJM", stbt.Region.ALL, stbt.OcrMode.SINGLE_LINE), ]) def test_ocr_on_static_images(image, expected_text, region, mode): kwargs = {"region": region} if mode is not None: kwargs["mode"] = mode text = stbt.ocr(load_image("ocr/" + image), **kwargs) assert text == expected_text @requires_tesseract def test_ocr_doesnt_leak_python_future_newtypes():
@pytest.mark.parametrize("match_method", [ stbt.MatchMethod.SQDIFF, stbt.MatchMethod.SQDIFF_NORMED, ]) def test_that_if_image_doesnt_match_match_all_returns_empty_array( match_method): assert [] == list( stbt.match_all('button.png', frame=stbt.load_image('black-full-frame.png'), match_parameters=mp(match_method=match_method))) plain_buttons = [ stbt.Region(_x, _y, width=135, height=44) for _x, _y in [(28, 1), (163, 1), (177, 75), (177, 119), (177, 163), (298, 1)] ] labelled_buttons = [ stbt.Region(_x, _y, width=135, height=44) for _x, _y in [(1, 65), (6, 137)] ] overlapping_button = stbt.Region(123, 223, width=135, height=44) overlapped_button = stbt.Region(3, 223, width=135, height=44) @pytest.mark.parametrize("match_method", [ stbt.MatchMethod.SQDIFF, stbt.MatchMethod.SQDIFF_NORMED, stbt.MatchMethod.CCORR_NORMED, stbt.MatchMethod.CCOEFF_NORMED,
expected = stbt.Region(5, 5, right=20, bottom=10) assert expected == stbt.Region.intersect(r1, r2) with pytest.raises(AttributeError): r1.intersect(r2) # pylint:disable=no-member def test_region_bounding_box(): r1 = stbt.Region(0, 0, right=20, bottom=10) r2 = stbt.Region(5, 5, right=25, bottom=15) expected = stbt.Region(0, 0, right=25, bottom=15) assert expected == stbt.Region.bounding_box(r1, r2) with pytest.raises(AttributeError): r1.bounding_box(r2) # pylint:disable=no-member r = stbt.Region(x=10, y=20, width=20, height=30) @pytest.mark.parametrize("kwargs,expected", [ # No change (dict(x=10), r), (dict(x=10, width=20), r), (dict(x=10, right=30), r), # Allowed # pylint:disable=line-too-long (dict(x=11), stbt.Region(x=11, y=r.y, width=19, height=r.height)), (dict(width=19), stbt.Region(x=10, y=r.y, width=19, height=r.height)), (dict(right=29), stbt.Region(x=10, y=r.y, width=19, height=r.height)), (dict(x=11, width=20), stbt.Region(x=11, y=r.y, width=20, height=r.height)), (dict(x=11, right=21), stbt.Region(x=11, y=r.y, width=10, height=r.height)), (dict(x=11, right=21, y=0, height=5), stbt.Region(x=11, y=0, width=10, height=5)),
def test_that_keyboard_catches_errors_at_definition_time(): kb = stbt.Keyboard() # Can't add the same key twice: kb.add_key("a") with pytest.raises(ValueError) as excinfo: kb.add_key("a") assert_repr_equal( "Key already exists: Key(name='a', text='a', region=None, mode=None)", str(excinfo.value)) # Can't add transition to key that doesn't exist: with pytest.raises(ValueError) as excinfo: kb.add_transition("a", "b", "KEY_RIGHT") assert_repr_equal("Query 'b' doesn't match any key in the keyboard", str(excinfo.value)) # ...but add_edgelist creates keys as needed: kb.add_edgelist("a b KEY_RIGHT") # All keys must have modes or none of them can kb.add_key(" ") with pytest.raises(ValueError) as excinfo: kb.add_key(" ", mode="uppercase") assert_repr_equal( "Key ...'name': ' '...'mode': 'uppercase'... specifies 'mode', but none of the other keys in the keyboard do", # pylint:disable=line-too-long str(excinfo.value)) # All keys must have regions or none of them can with pytest.raises(ValueError) as excinfo: kb.add_grid(stbt.Grid( region=stbt.Region(x=0, y=0, right=200, bottom=100), data=[["a", "b", "c", "d"]])) assert_repr_equal( "Key ...'a'... specifies 'region', but none of the other keys in the keyboard do", # pylint:disable=line-too-long str(excinfo.value)) # Can't add grid with no data: with pytest.raises(ValueError) as excinfo: kb.add_grid(stbt.Grid( region=stbt.Region(x=0, y=0, right=200, bottom=100), cols=6, rows=6)) assert_repr_equal("Grid cell [0,0] doesn't have any data", str(excinfo.value)) # Now a keyboard with modes: ############################################# kb = stbt.Keyboard() kb.add_key("a", mode="lowercase") kb.add_key("A", mode="uppercase") kb.add_key(" ", mode="lowercase") # All keys must have modes or none of them can with pytest.raises(ValueError) as excinfo: kb.add_key(" ") assert_repr_equal( "Key already exists: Key(name=' ', text=' ', region=None, mode='lowercase')", # pylint:disable=line-too-long str(excinfo.value)) with pytest.raises(ValueError) as excinfo: kb.add_key("b") assert_repr_equal( "Key ...'name': 'b'... doesn't specify 'mode', but all the other keys in the keyboard do", # pylint:disable=line-too-long str(excinfo.value)) # add_edgelist is happy as long as it can uniquely identify existing keys: kb.add_edgelist("a SPACE KEY_DOWN") # ...but if it's ambiguous, it's an error: kb.add_key(" ", mode="uppercase") with pytest.raises(ValueError) as excinfo: kb.add_edgelist("a SPACE KEY_DOWN") assert_repr_equal( "Ambiguous key {'name': ' '}: Could mean Key(name=' ', text=' ', region=None, mode='lowercase') or Key(name=' ', text=' ', region=None, mode='uppercase')", # pylint:disable=line-too-long str(excinfo.value)) # ...so we need to specify the mode explicitly: kb.add_edgelist("a SPACE KEY_DOWN", mode="lowercase")
print(transition) assert not transition assert transition.status == stbt.TransitionStatus.STABLE_TIMEOUT transition = stbt.press_and_wait("ball", stable_secs=0, _dut=FakeDeviceUnderTest()) print(transition) assert transition assert transition.status == stbt.TransitionStatus.COMPLETE @pytest.mark.parametrize("mask,region,expected", [ (None, stbt.Region.ALL, stbt.TransitionStatus.STABLE_TIMEOUT), ("mask-out-left-half-720p.png", stbt.Region.ALL, stbt.TransitionStatus.START_TIMEOUT), (None, stbt.Region(x=640, y=0, right=1280, bottom=720), stbt.TransitionStatus.START_TIMEOUT), (None, stbt.Region(x=0, y=0, right=1280, bottom=360), stbt.TransitionStatus.STABLE_TIMEOUT), ]) def test_press_and_wait_with_mask_or_region(mask, region, expected): transition = stbt.press_and_wait( "ball", mask=mask, region=region, timeout_secs=0.2, stable_secs=0.1, _dut=FakeDeviceUnderTest()) print(transition) assert transition.status == expected def test_wait_for_transition_to_end(): _stbt = FakeDeviceUnderTest()
def refresh(self, frame=None, **kwargs): page = SearchPage(self.dut, self.kb) logging.debug("SearchPage.refresh: Now on %r", page.selection) return page def enter_text(self, text): return self.kb.enter_text(text, page=self) def navigate_to(self, target, verify_every_keypress=False): return self.kb.navigate_to(target, page=self, verify_every_keypress=verify_every_keypress) kb1 = stbt.Keyboard() # Full model with modes, defined using Grids MODES_GRID = stbt.Grid( region=stbt.Region(x=125, y=95, right=430, bottom=140), data=[["lowercase", "uppercase", "symbols"]]) MIDDLE_REGION = stbt.Region(x=125, y=140, right=430, bottom=445) MIDDLE_GRIDS = { "lowercase": stbt.Grid(region=MIDDLE_REGION, data=[ "abcdef", "ghijkl", "mnopqr", "stuvwx", "yz1234", "567890"]), "uppercase": stbt.Grid(region=MIDDLE_REGION, data=[ "ABCDEF", "GHIJKL",
def test_that_match_converts_greyscale_reference_image(filename): stbt.match(filename, frame=black()) # Doesn't raise stbt.match(stbt.load_image(filename), frame=black()) @pytest.mark.parametrize("match_method", [ stbt.MatchMethod.SQDIFF, stbt.MatchMethod.SQDIFF_NORMED, ]) def test_that_if_image_doesnt_match_match_all_returns_empty_array(match_method): assert [] == list(stbt.match_all( 'button.png', frame=stbt.load_image('black-full-frame.png'), match_parameters=mp(match_method=match_method))) plain_buttons = [stbt.Region(_x, _y, width=135, height=44) for _x, _y in [ (28, 1), (163, 1), (177, 75), (177, 119), (177, 163), (298, 1)]] labelled_buttons = [stbt.Region(_x, _y, width=135, height=44) for _x, _y in [ (1, 65), (6, 137)]] overlapping_button = stbt.Region(123, 223, width=135, height=44) overlapped_button = stbt.Region(3, 223, width=135, height=44) @pytest.mark.parametrize("match_method", [ stbt.MatchMethod.SQDIFF, stbt.MatchMethod.SQDIFF_NORMED, stbt.MatchMethod.CCORR_NORMED, stbt.MatchMethod.CCOEFF_NORMED, ]) def test_that_match_all_finds_all_matches(match_method): matches = list(m.region for m in stbt.match_all(