def define_keyboard(): kb = stbt.Keyboard() # pylint:disable=redefined-outer-name for mode in ["lowercase", "uppercase", "symbols"]: kb.add_grid(top_grid, mode=mode) kb.add_grid(bottom_grid, mode=mode) g = middle_grids[mode] kb.add_grid(g, mode=mode) # Transitions between grids: # # abc ABC #+- (top grid) # ↕ ↕ ↕ ↕ ↕ ↕ # a b c d e f (first row of middle grid) kb.add_transition(g[0, 0].data, "abc", "KEY_UP", mode=mode) kb.add_transition(g[1, 0].data, "abc", "KEY_UP", mode=mode) kb.add_transition(g[2, 0].data, "ABC", "KEY_UP", mode=mode) kb.add_transition(g[3, 0].data, "ABC", "KEY_UP", mode=mode) kb.add_transition(g[4, 0].data, "#+-", "KEY_UP", mode=mode) kb.add_transition(g[5, 0].data, "#+-", "KEY_UP", mode=mode) # 5 6 7 8 9 0 (last row of middle grid) # ↕ ↕ ↕ ↕ ↕ ↕ # SPC DEL CLR (bottom grid) kb.add_transition(g[0, 5].data, " ", "KEY_DOWN", mode=mode) kb.add_transition(g[1, 5].data, " ", "KEY_DOWN", mode=mode) kb.add_transition(g[2, 5].data, "DELETE", "KEY_DOWN", mode=mode) kb.add_transition(g[3, 5].data, "DELETE", "KEY_DOWN", mode=mode) kb.add_transition(g[4, 5].data, "CLEAR", "KEY_DOWN", mode=mode) kb.add_transition(g[5, 5].data, "CLEAR", "KEY_DOWN", mode=mode) # Transitions between modes: for source_mode in ["lowercase", "uppercase", "symbols"]: for name, target_mode in [("abc", "lowercase"), ("ABC", "uppercase"), ("#+-", "symbols")]: kb.add_transition(kb.find_key(name=name, mode=source_mode), kb.find_key(name=name, mode=target_mode), "KEY_OK") # Pressing KEY_PLAY cycles between the modes for source_mode, target_mode in [ ("lowercase", "uppercase"), ("symbols", "lowercase"), # Pressing KEY_PLAY from "uppercase" goes to "lowercase" if you # have already typed an uppercase letter since entering uppercase # mode; if you haven't, it goes to "symbols". We don't keep track # of this past state in our model, so we model it as a # non-deterministic state machine -- that is, pressing KEY_PLAY # from "uppercase" might go to "symbols" or to "lowercase", we just # have to press it and then look at the screen to find out where we # landed. ("uppercase", "symbols"), ("uppercase", "lowercase")]: for key in kb.find_keys(mode=source_mode): target = kb.find_key(region=key.region, mode=target_mode) kb.add_transition(key, target, "KEY_PLAY") return kb
def test_keyboard_with_hash_sign(): """Regression test. `networkx.parse_edgelist` treats "#" as a comment.""" kb = stbt.Keyboard(""" @hotmail.com !#$ KEY_DOWN @hotmail.com @ KEY_DOWN @ # KEY_RIGHT # @ KEY_LEFT L K KEY_LEFT K L KEY_RIGHT """) keys = list(_keys_to_press(kb.G, "@hotmail.com", "@")) assert keys == [('KEY_DOWN', {'@', '!#$'})] assert list(_keys_to_press(kb.G, "@", "#")) == [('KEY_RIGHT', {'#'})] assert list(_keys_to_press(kb.G, "#", "@")) == [('KEY_LEFT', {'@'})] # L is the first character of the random comments delimiter in # `Keyboard.parse_edgelist`. Check that networkx uses the whole string, not # just the first character. assert list(_keys_to_press(kb.G, "K", "L")) == [('KEY_RIGHT', {'L'})] assert list(_keys_to_press(kb.G, "L", "K")) == [('KEY_LEFT', {'K'})]
class _Keyboard(stbt.FrameObject): """Immutable FrameObject representing the test's view of the Device Under Test (``dut``). The keyboard 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 """ def __init__(self, dut): super(_Keyboard, self).__init__( frame=numpy.zeros((720, 1280, 3), dtype=numpy.uint8)) self._dut = dut # Device Under Test -- i.e. ``YouTubeKeyboard`` self._selection = self._dut.selection @property def is_visible(self): return True @property def selection(self): return self._selection def refresh(self, frame=None, **kwargs): print("_Keyboard.refresh: Now on %r" % self._dut.selection) return _Keyboard(dut=self._dut) KEYBOARD = stbt.Keyboard(GRAPH, navigate_timeout=0.1) def enter_text(self, text): return self.KEYBOARD.enter_text(text.upper(), page=self) def navigate_to(self, target, verify_every_keypress=False): return self.KEYBOARD.navigate_to( target, page=self, verify_every_keypress=verify_every_keypress)
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))