pyjab 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyjab/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ """
2
+ Python implementation for Java application UI automation with Java Access Bridge.
3
+ """
4
+ __author__ = "Gary Gao"
5
+ __email__ = "gaozhao89@qq.com"
6
+ __license__ = "GPLv2"
7
+ __url__ = "https://github.com/gaozhao1989/pyjab"
8
+ __version__ = "1.2.0"
9
+
10
+ # NOTE: no platform guard here on purpose. pyjab.config and pyjab.common.service
11
+ # are platform independent and are unit tested on every OS, so `import pyjab`
12
+ # must stay harmless. The Windows-only entry points (pyjab.jabdriver,
13
+ # pyjab.jabelement) raise a descriptive ImportError instead of letting a bare
14
+ # "No module named 'win32process'" escape.
@@ -0,0 +1,183 @@
1
+ from ctypes import c_float
2
+ from ctypes import c_int
3
+ from ctypes import c_wchar
4
+ from ctypes import c_bool
5
+ from ctypes import Structure
6
+ from ctypes.wintypes import BOOL
7
+ from ctypes.wintypes import WCHAR
8
+ from pyjab.common.types import JOBJECT64
9
+ from pyjab.config import (
10
+ MAX_STRING_SIZE,
11
+ MAX_VISIBLE_CHILDREN,
12
+ SHORT_STRING_SIZE,
13
+ MAX_KEY_BINDINGS,
14
+ MAX_RELATION_TARGETS,
15
+ MAX_RELATIONS,
16
+ MAX_ACTION_INFO,
17
+ MAX_ACTIONS_TO_DO,
18
+ )
19
+
20
+
21
+ class AccessBridgeVersionInfo(Structure):
22
+ _fields_ = [
23
+ ("VMVersion", WCHAR * SHORT_STRING_SIZE),
24
+ ("bridgeJavaClassVersion", WCHAR * SHORT_STRING_SIZE),
25
+ ("bridgeJavaDLLVersion", WCHAR * SHORT_STRING_SIZE),
26
+ ("bridgeWinDLLVersion", WCHAR * SHORT_STRING_SIZE),
27
+ ]
28
+
29
+
30
+ class AccessibleContextInfo(Structure):
31
+ _fields_ = [
32
+ ("name", WCHAR * MAX_STRING_SIZE),
33
+ ("description", WCHAR * MAX_STRING_SIZE),
34
+ ("role", WCHAR * SHORT_STRING_SIZE),
35
+ ("role_en_US", WCHAR * SHORT_STRING_SIZE),
36
+ ("states", WCHAR * SHORT_STRING_SIZE),
37
+ ("states_en_US", WCHAR * SHORT_STRING_SIZE),
38
+ ("indexInParent", c_int),
39
+ ("childrenCount", c_int),
40
+ ("x", c_int),
41
+ ("y", c_int),
42
+ ("width", c_int),
43
+ ("height", c_int),
44
+ ("accessibleComponent", BOOL),
45
+ ("accessibleAction", BOOL),
46
+ ("accessibleSelection", BOOL),
47
+ ("accessibleText", BOOL),
48
+ ("accessibleValue", BOOL),
49
+ ]
50
+
51
+
52
+ class AccessibleTextInfo(Structure):
53
+ _fields_ = [
54
+ ("charCount", c_int),
55
+ ("caretIndex", c_int),
56
+ ("indexAtPoint", c_int),
57
+ ]
58
+
59
+
60
+ class AccessibleTextItemsInfo(Structure):
61
+ _fields_ = [
62
+ ("letter", WCHAR),
63
+ ("word", WCHAR * SHORT_STRING_SIZE),
64
+ ("sentence", WCHAR * MAX_STRING_SIZE),
65
+ ]
66
+
67
+
68
+ class AccessibleTextSelectionInfo(Structure):
69
+ _fields_ = [
70
+ ("selectionStartIndex", c_int),
71
+ ("selectionEndIndex", c_int),
72
+ ("selectedText", WCHAR * MAX_STRING_SIZE),
73
+ ]
74
+
75
+
76
+ class AccessibleTextRectInfo(Structure):
77
+ _fields_ = [
78
+ ("x", c_int),
79
+ ("y", c_int),
80
+ ("width", c_int),
81
+ ("height", c_int),
82
+ ]
83
+
84
+
85
+ class AccessibleTextAttributesInfo(Structure):
86
+ _fields_ = [
87
+ ("bold", BOOL),
88
+ ("italic", BOOL),
89
+ ("underline", BOOL),
90
+ ("strikethrough", BOOL),
91
+ ("superscript", BOOL),
92
+ ("subscript", BOOL),
93
+ ("backgroundColor", WCHAR * SHORT_STRING_SIZE),
94
+ ("foregroundColor", WCHAR * SHORT_STRING_SIZE),
95
+ ("fontFamily", WCHAR * SHORT_STRING_SIZE),
96
+ ("fontSize", c_int),
97
+ ("alignment", c_int),
98
+ ("bidiLevel", c_int),
99
+ ("firstLineIndent", c_float),
100
+ ("LeftIndent", c_float),
101
+ ("rightIndent", c_float),
102
+ ("lineSpacing", c_float),
103
+ ("spaceAbove", c_float),
104
+ ("spaceBelow", c_float),
105
+ ("fullAttributesString", WCHAR * MAX_STRING_SIZE),
106
+ ]
107
+
108
+
109
+ class AccessibleRelationInfo(Structure):
110
+ _fields_ = [
111
+ ("key", WCHAR * SHORT_STRING_SIZE),
112
+ ("targetCount", c_int),
113
+ ("targets", JOBJECT64 * MAX_RELATION_TARGETS),
114
+ ]
115
+
116
+
117
+ class AccessibleRelationSetInfo(Structure):
118
+ _fields_ = [
119
+ ("relationCount", c_int),
120
+ ("relations", AccessibleRelationInfo * MAX_RELATIONS),
121
+ ]
122
+
123
+
124
+ class AccessibleActionInfo(Structure):
125
+ _fields_ = (("name", c_wchar * SHORT_STRING_SIZE),)
126
+
127
+
128
+ class AccessibleActions(Structure):
129
+ _fields_ = (
130
+ ("actionsCount", c_int),
131
+ ("actionInfo", AccessibleActionInfo * MAX_ACTION_INFO),
132
+ )
133
+
134
+
135
+ class AccessibleActionsToDo(Structure):
136
+ _fields_ = (
137
+ ("actionsCount", c_int),
138
+ ("actions", AccessibleActionInfo * MAX_ACTIONS_TO_DO),
139
+ )
140
+
141
+
142
+ class AccessibleTableInfo(Structure):
143
+ _fields_ = [
144
+ ("caption", JOBJECT64),
145
+ ("summary", JOBJECT64),
146
+ ("rowCount", c_int),
147
+ ("columnCount", c_int),
148
+ ("accessibleContext", JOBJECT64),
149
+ ("accessibleTable", JOBJECT64),
150
+ ]
151
+
152
+
153
+ class AccessibleTableCellInfo(Structure):
154
+ _fields_ = [
155
+ ("accessibleContext", JOBJECT64),
156
+ ("index", c_int),
157
+ ("row", c_int),
158
+ ("column", c_int),
159
+ ("rowExtent", c_int),
160
+ ("columnExtent", c_int),
161
+ ("isSelected", c_bool),
162
+ ]
163
+
164
+
165
+ class AccessibleKeyBindingInfo(Structure):
166
+ _fields_ = [
167
+ ("character", c_wchar),
168
+ ("modifiers", c_int),
169
+ ]
170
+
171
+
172
+ class AccessibleKeyBindings(Structure):
173
+ _fields_ = [
174
+ ("keyBindingsCount", c_int),
175
+ ("keyBindingInfo", AccessibleKeyBindingInfo * MAX_KEY_BINDINGS),
176
+ ]
177
+
178
+
179
+ class VisibleChildrenInfo(Structure):
180
+ _fields_ = [
181
+ ("returnedChildrenCount", c_int),
182
+ ("children", JOBJECT64 * MAX_VISIBLE_CHILDREN),
183
+ ]
File without changes
@@ -0,0 +1,48 @@
1
+ from collections import deque
2
+ from pyjab.common.logger import Logger
3
+ from pyjab.common.singleton import singleton
4
+
5
+
6
+ @singleton
7
+ class ActorScheduler:
8
+ """Message queue for run generator func as thread.
9
+
10
+ Sample:
11
+
12
+ sched = ActorScheduler()
13
+ sched.new_actor("jab", win32utils.setup_msg_pump())
14
+ sched.run()
15
+ """
16
+
17
+ def __init__(self):
18
+ self.actors = {} # Mapping of names to actors
19
+ self.msg_queue = deque() # Message queue
20
+ self.logger = Logger("pyjab")
21
+
22
+ def new_actor(self, name, actor):
23
+ """
24
+ Admit a newly started actor to the scheduler and give it a name
25
+ """
26
+ self.logger.debug(f"msg queue append new actor '{name}'")
27
+ self.msg_queue.append((actor, None))
28
+ self.actors[name] = actor
29
+
30
+ def send(self, name, msg):
31
+ """
32
+ Send a message to a named actor
33
+ """
34
+ if actor := self.actors.get(name):
35
+ self.logger.debug(f"send msg '{msg}' to actor '{actor}'")
36
+ self.msg_queue.append((actor, msg))
37
+
38
+ def run(self):
39
+ """
40
+ Run as long as there are pending messages.
41
+ """
42
+ while self.msg_queue:
43
+ actor, msg = self.msg_queue.popleft()
44
+ try:
45
+ self.logger.debug(f"run actor '{actor}' with msg '{msg}'")
46
+ actor.send(msg)
47
+ except StopIteration:
48
+ self.logger.debug("stop run action in scheduler")
pyjab/common/by.py ADDED
@@ -0,0 +1,18 @@
1
+ """
2
+ The By implementation.
3
+ """
4
+
5
+
6
+ class By(object):
7
+ """
8
+ Set of supported locator strategies.
9
+ """
10
+
11
+ XPATH = "xpath"
12
+ NAME = "name"
13
+ DESCRIPTION = "description"
14
+ ROLE = "role"
15
+ STATES = "states"
16
+ OBJECT_DEPTH = "objectdepth"
17
+ CHILDREN_COUNT = "childrencount"
18
+ INDEX_IN_PARENT = "indexinparent"
@@ -0,0 +1,21 @@
1
+ class CommonException(Exception):
2
+ def __init__(self, message: str = None, status: str = None) -> None:
3
+ super().__init__(message, status)
4
+ self.message = message
5
+ self.status = status
6
+
7
+
8
+ class JABException(CommonException):
9
+ """
10
+ Raised by Java Access Bridge if func internal error
11
+ """
12
+
13
+ pass
14
+
15
+
16
+ class XpathParserException(CommonException):
17
+ """
18
+ Raised by Xpath Parser if error
19
+ """
20
+
21
+ pass
pyjab/common/logger.py ADDED
@@ -0,0 +1,31 @@
1
+ import logging
2
+ from pyjab.common.singleton import singleton
3
+
4
+
5
+ @singleton
6
+ class Logger(object):
7
+ LOGGER_INFO = logging.INFO
8
+ LOGGER_DEBUG = logging.DEBUG
9
+ LOGGER_WARN = logging.WARN
10
+ LOGGER_ERROR = logging.ERROR
11
+ LOGGER_CRITICAL = logging.CRITICAL
12
+
13
+ def __init__(self, name=None, level=logging.INFO):
14
+ self.FORMAT = "%(asctime)-15s %(levelname)s %(name)s %(message)s"
15
+ self.log = logging.getLogger(name)
16
+ logging.basicConfig(format=self.FORMAT, level=level)
17
+
18
+ def info(self, msg, *args, **kwargs):
19
+ self.log.info(msg, *args, **kwargs)
20
+
21
+ def debug(self, msg, *args, **kwargs):
22
+ self.log.debug(msg, *args, **kwargs)
23
+
24
+ def warning(self, msg, *args, **kwargs):
25
+ self.log.warning(msg, *args, **kwargs)
26
+
27
+ def error(self, msg, *args, **kwargs):
28
+ self.log.error(msg, *args, **kwargs)
29
+
30
+ def critical(self, msg, *args, **kwargs):
31
+ self.log.critical(msg, *args, **kwargs)
pyjab/common/role.py ADDED
@@ -0,0 +1,71 @@
1
+ from enum import Enum
2
+
3
+ """
4
+ The Role implementation.
5
+ """
6
+
7
+
8
+ class Role(str, Enum):
9
+ """
10
+ Set of supported role strategies.
11
+ """
12
+
13
+ ALERT = "alert"
14
+ CANVAS = "canvas"
15
+ CHECK_BOX = "check box"
16
+ COLOR_CHOOSER = "color chooser"
17
+ COLUMN_HEADER = "column header"
18
+ COMBO_BOX = "combo box"
19
+ DATE_EDITOR = "date editor"
20
+ DESKTOP_ICON = "desktop icon"
21
+ DESKTOP_PANE = "desktop pane"
22
+ DIALOG = "dialog"
23
+ DIRECTORY_PANE = "directory pane"
24
+ EDIT_BAR = "edit bar"
25
+ FILE_CHOOSER = "file chooser"
26
+ FILLER = "filler"
27
+ FONT_CHOOSER = "font chooser"
28
+ FOOTER = "footer"
29
+ FRAME = "frame"
30
+ GLASS_PANE = "glass pane"
31
+ GROUP_BOX = "group box"
32
+ HEADER = "header"
33
+ HYPERLINK = "hyperlink"
34
+ ICON = "icon"
35
+ INTERNAL_FRAME = "internal frame"
36
+ LABEL = "label"
37
+ LAYERED_PANE = "layered pane"
38
+ LIST = "list"
39
+ LIST_ITEM = "list item"
40
+ MENU = "menu"
41
+ MENU_BAR = "menu bar"
42
+ MENU_ITEM = "menu item"
43
+ OPTION_PANE = "option pane"
44
+ PAGE_TAB = "page tab"
45
+ PAGE_TAB_LIST = "page tab list"
46
+ PANEL = "panel"
47
+ PARAGRAPH = "paragraph"
48
+ PASSWORD_TEXT = "password text"
49
+ POPUP_MENU = "popup menu"
50
+ PROGRESS_BAR = "progress bar"
51
+ PUSH_BUTTON = "push button"
52
+ RADIO_BUTTON = "radio button"
53
+ ROOT_PANE = "root pane"
54
+ ROW_HEADER = "row header"
55
+ RULER = "ruler"
56
+ SCROLL_BAR = "scroll bar"
57
+ SCROLL_PANE = "scroll pane"
58
+ SEPARATOR = "separator"
59
+ SLIDER = "slider"
60
+ SPIN_BOX = "spinbox"
61
+ SPLIT_PANE = "split pane"
62
+ STATUS_BAR = "status bar"
63
+ TABLE = "table"
64
+ TEXT = "text"
65
+ TOGGLE_BUTTON = "toggle button"
66
+ TOOL_BAR = "tool bar"
67
+ TOOL_TIP = "tool tip"
68
+ TREE = "tree"
69
+ UNKNOWN = "unknown"
70
+ VIEW_PORT = "viewport"
71
+ WINDOW = "window"
@@ -0,0 +1,95 @@
1
+ from ctypes import cdll
2
+ from ctypes import CDLL
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from pyjab.common.logger import Logger
7
+ from pyjab.common.singleton import singleton
8
+ from pyjab.config import A11Y_PROPS_CONTENT
9
+ from pyjab.config import A11Y_PROPS_PATH
10
+ from pyjab.config import describe_bridge_dll_search
11
+ from pyjab.config import find_bridge_dll
12
+
13
+
14
+ @singleton
15
+ class Service(object):
16
+ def __init__(self) -> None:
17
+ self.logger = Logger("pyjab")
18
+ self.init_bridge()
19
+
20
+ def enable_bridge(self) -> None:
21
+ with open(A11Y_PROPS_PATH, "wt") as fp:
22
+ try:
23
+ self.logger.debug("enable bridge")
24
+ fp.write(A11Y_PROPS_CONTENT)
25
+ except (OSError, IOError):
26
+ self.logger.error("enable bridge failed")
27
+
28
+ def is_bridge_enabled(self) -> bool:
29
+ if not Path(A11Y_PROPS_PATH).is_file():
30
+ return False
31
+ with open(A11Y_PROPS_PATH, "rt") as fp:
32
+ try:
33
+ data = fp.read()
34
+ except (OSError, IOError):
35
+ self.logger.error("bridge is not enabled")
36
+ return False
37
+ is_enabled = data == A11Y_PROPS_CONTENT
38
+ self.logger.debug("is bridge enabled => '{}'".format(is_enabled))
39
+ return is_enabled
40
+
41
+ def init_bridge(self) -> None:
42
+ self.logger.debug("init bridge")
43
+ if not self.is_bridge_enabled():
44
+ self.enable_bridge()
45
+
46
+ def find_bridge_dll(self, bridge_dll: Optional[str] = None) -> Optional[Path]:
47
+ """Locate the Java Access Bridge DLL without loading it.
48
+
49
+ Useful for diagnostics: it reports *where* the DLL would be loaded from
50
+ without actually loading it into the process.
51
+
52
+ Args:
53
+ bridge_dll: An explicit DLL path supplied by the caller. When given
54
+ and valid it takes precedence over all automatic discovery.
55
+
56
+ Returns:
57
+ The path to the DLL, or ``None`` when it could not be located.
58
+ """
59
+ if bridge_dll:
60
+ explicit = Path(str(bridge_dll))
61
+ if explicit.is_file():
62
+ return explicit
63
+ self.logger.warning(
64
+ "The explicit bridge_dll path does not exist: '{}'. "
65
+ "Falling back to automatic discovery.".format(bridge_dll)
66
+ )
67
+ return find_bridge_dll()
68
+
69
+ def load_library(self, bridge_dll: str = "") -> CDLL:
70
+ """Load the Java Access Bridge DLL.
71
+
72
+ Resolution order:
73
+
74
+ 1. The ``bridge_dll`` argument, when it points at an existing file.
75
+ 2. :func:`pyjab.config.find_bridge_dll`, which probes
76
+ ``%JAVA_HOME%\\bin`` (JDK 11+), ``%JAVA_HOME%\\jre\\bin`` (JDK 8-10),
77
+ ``%JRE_HOME%``, ``%JAB_HOME%``, common vendor install locations and
78
+ finally performs a bounded recursive search.
79
+
80
+ Args:
81
+ bridge_dll: Explicit path to ``WindowsAccessBridge-XX.dll``.
82
+ Defaults to "" (automatic discovery).
83
+
84
+ Raises:
85
+ FileNotFoundError: When no DLL could be located. The message lists
86
+ every location that was probed and how to fix the situation.
87
+ """
88
+ self.logger.debug("load library of bridge")
89
+
90
+ resolved = self.find_bridge_dll(bridge_dll)
91
+ if resolved is None:
92
+ raise FileNotFoundError(describe_bridge_dll_search())
93
+
94
+ self.logger.debug("Loading Java Access Bridge DLL from '{}'".format(resolved))
95
+ return cdll.LoadLibrary(str(resolved))
@@ -0,0 +1,164 @@
1
+ from pyjab.common.logger import Logger
2
+ from pyjab.common.win32utils import Win32Utils
3
+
4
+
5
+ class ShortcutKeys(object):
6
+ def __init__(self) -> None:
7
+ self.logger = Logger("pyjab")
8
+ self.win32_utils = Win32Utils()
9
+ self.press_hold_release_key = self.win32_utils._press_hold_release_key
10
+ self.press_key = self.win32_utils._press_key
11
+
12
+ # Common shortcut keys
13
+ def clear_text(self) -> None:
14
+ self.logger.debug("send shortcut command clear text")
15
+ self.press_key("home")
16
+ self.press_hold_release_key("ctrl", "shift", "end")
17
+ # TODO: need find out method to clear text for JABElement
18
+ for _ in range(100):
19
+ self.press_key("backspace")
20
+
21
+ def paste(self) -> None:
22
+ self.logger.debug("paste text from clipboard")
23
+ self.press_hold_release_key("ctrl", "v")
24
+
25
+ # Oracle Form shortcut keys
26
+ def block_menu(self) -> None:
27
+ self.logger.debug("send shortcut command block menu")
28
+ self.press_hold_release_key("ctrl", "b")
29
+
30
+ def clear_block(self) -> None:
31
+ self.logger.debug("send shortcut command clear block")
32
+ self.press_key("F7")
33
+
34
+ def clear_field(self) -> None:
35
+ self.logger.debug("send shortcut command clear field")
36
+ self.press_key("F5")
37
+
38
+ def clear_form(self) -> None:
39
+ self.logger.debug("send shortcut command clear form")
40
+ self.press_key("F8")
41
+
42
+ def clear_record(self) -> None:
43
+ self.logger.debug("send shortcut command clear record")
44
+ self.press_key("F6")
45
+
46
+ def commit(self) -> None:
47
+ self.logger.debug("send shortcut command commit")
48
+ self.press_hold_release_key("ctrl", "s")
49
+
50
+ def count_query(self) -> None:
51
+ self.logger.debug("send shortcut command count query")
52
+ self.press_key("F12")
53
+
54
+ def delete_record(self) -> None:
55
+ self.logger.debug("send shortcut command delete record")
56
+ self.press_hold_release_key("ctrl", "up_arrow")
57
+
58
+ def display_error(self) -> None:
59
+ self.logger.debug("send shortcut command display error")
60
+ self.press_hold_release_key("shift", "ctrl", "e")
61
+
62
+ def down(self) -> None:
63
+ self.logger.debug("send shortcut command down")
64
+ self.press_key("down_arrow")
65
+
66
+ def duplicate_field(self) -> None:
67
+ self.logger.debug("send shortcut command duplicate field")
68
+ self.press_hold_release_key("shift", "F5")
69
+
70
+ def duplicate_record(self) -> None:
71
+ self.logger.debug("send shortcut command duplicate record")
72
+ self.press_hold_release_key("shift", "F6")
73
+
74
+ def edit(self) -> None:
75
+ self.logger.debug("send shortcut command edit")
76
+ self.press_hold_release_key("ctrl", "e")
77
+
78
+ def enter_query(self) -> None:
79
+ self.logger.debug("send shortcut command enter query")
80
+ self.press_key("F11")
81
+
82
+ def execute_query(self) -> None:
83
+ self.logger.debug("send shortcut command execute query")
84
+ self.press_hold_release_key("ctrl", "F11")
85
+
86
+ def _exit(self) -> None:
87
+ self.logger.debug("send shortcut command exit")
88
+ self.press_key("F4")
89
+
90
+ def _help(self) -> None:
91
+ self.logger.debug("send shortcut command help")
92
+ self.press_hold_release_key("ctrl", "h")
93
+
94
+ def insert_record(self) -> None:
95
+ self.logger.debug("send shortcut command insert record")
96
+ self.press_hold_release_key("ctrl", "down_arrow")
97
+
98
+ def list_of_values(self) -> None:
99
+ self.logger.debug("send shortcut command list of values")
100
+ self.press_hold_release_key("ctrl", "l")
101
+
102
+ def list_tab_pages(self) -> None:
103
+ self.logger.debug("send shortcut command list tab pages")
104
+ self.press_key("F2")
105
+
106
+ def next_block(self) -> None:
107
+ self.logger.debug("send shortcut command next block")
108
+ self.press_hold_release_key("shift", "page_down")
109
+
110
+ def next_field(self) -> None:
111
+ self.logger.debug("send shortcut command next field")
112
+ self.press_key("tab")
113
+
114
+ def next_primary_key(self) -> None:
115
+ self.logger.debug("send shortcut command next primary key")
116
+ self.press_hold_release_key("shift", "F7")
117
+
118
+ def next_record(self) -> None:
119
+ self.logger.debug("send shortcut command next record")
120
+ self.press_key("down_arrow")
121
+
122
+ def next_set_of_records(self) -> None:
123
+ self.logger.debug("send shortcut command next set of records")
124
+ self.press_key("shift", "F8")
125
+
126
+ def previous_block(self) -> None:
127
+ self.logger.debug("send shortcut command previous block")
128
+ self.press_key("shift", "page_up")
129
+
130
+ def previous_field(self) -> None:
131
+ self.logger.debug("send shortcut command previous field")
132
+ self.press_key("shift", "tab")
133
+
134
+ def previous_record(self) -> None:
135
+ self.logger.debug("send shortcut command previous record")
136
+ self.press_key("shift", "up_arrow")
137
+
138
+ def _print(self) -> None:
139
+ self.logger.debug("send shortcut command print")
140
+ self.press_key("ctrl", "p")
141
+
142
+ def _return(self) -> None:
143
+ self.logger.debug("send shortcut command return")
144
+ self.press_key("clear")
145
+
146
+ def scroll_down(self) -> None:
147
+ self.logger.debug("send shortcut command scroll down")
148
+ self.press_key("page_down")
149
+
150
+ def scroll_up(self) -> None:
151
+ self.logger.debug("send shortcut command scroll up")
152
+ self.press_key("page_up")
153
+
154
+ def show_keys(self) -> None:
155
+ self.logger.debug("send shortcut command show keys")
156
+ self.press_key("ctrl", "k")
157
+
158
+ def up(self) -> None:
159
+ self.logger.debug("send shortcut command up")
160
+ self.press_key("up_arrow")
161
+
162
+ def update_record(self) -> None:
163
+ self.logger.debug("send shortcut command update record")
164
+ self.press_key("ctrl", "u")
@@ -0,0 +1,13 @@
1
+ from functools import wraps
2
+
3
+
4
+ def singleton(cls):
5
+ instances = {}
6
+
7
+ @wraps(cls)
8
+ def wrapper(*args, **kwargs):
9
+ if cls not in instances:
10
+ instances[cls] = cls(*args, **kwargs)
11
+ return instances[cls]
12
+
13
+ return wrapper
pyjab/common/states.py ADDED
@@ -0,0 +1,28 @@
1
+ from enum import Enum
2
+
3
+ """
4
+ The States implementation.
5
+ """
6
+
7
+
8
+ class States(str, Enum):
9
+ """
10
+ Set of supported states strategies.
11
+ """
12
+
13
+ BUSY = "busy"
14
+ CHECKED = "checked"
15
+ ENABLED = "enabled"
16
+ FOCUSED = "focused"
17
+ SELECTED = "selected"
18
+ PRESSED = "pressed"
19
+ EXPANDED = "expanded"
20
+ COLLAPSED = "collapsed"
21
+ ICONIFIED = "iconified"
22
+ MODAL = "modal"
23
+ MULTI_LINE = "multi_line"
24
+ FOCUSABLE = "focusable"
25
+ EDITABLE = "editable"
26
+ VISIBLE = "visible"
27
+ SHOWING = "showing"
28
+ UNKNOWN = "unknown"