dogtail 2.0.dev10__tar.gz → 2.0.dev11__tar.gz

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.
Files changed (94) hide show
  1. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/CHANGES +1 -1
  2. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/PKG-INFO +1 -1
  3. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/config.ini +0 -2
  4. dogtail-2.0.dev11/dogtail/config.py +315 -0
  5. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/scripts/dogtail_create_config.py +6 -12
  6. dogtail-2.0.dev11/dogtail/scripts/dogtail_get_config.py +38 -0
  7. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.egg-info/PKG-INFO +1 -1
  8. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/pyproject.toml +1 -0
  9. dogtail-2.0.dev10/dogtail/config.py +0 -255
  10. dogtail-2.0.dev10/dogtail/scripts/dogtail_get_config.py +0 -16
  11. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/.fmf/version +0 -0
  12. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/.gitignore +0 -0
  13. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/.gitlab-ci.yml +0 -0
  14. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/.packit.yaml +0 -0
  15. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/.vscode/settings.json +0 -0
  16. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/COPYING +0 -0
  17. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/MAINTAINERS +0 -0
  18. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/Makefile +0 -0
  19. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/NEWS +0 -0
  20. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/README.md +0 -0
  21. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/configure +0 -0
  22. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/__init__.py +0 -0
  23. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/__init__.py +0 -0
  24. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_actions.py +0 -0
  25. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_component.py +0 -0
  26. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_editable_text.py +0 -0
  27. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_hypertext.py +0 -0
  28. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_image.py +0 -0
  29. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_object.py +0 -0
  30. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_root.py +0 -0
  31. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_selection.py +0 -0
  32. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_state.py +0 -0
  33. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_table.py +0 -0
  34. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_table_cell.py +0 -0
  35. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_text.py +0 -0
  36. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_utilities.py +0 -0
  37. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/accessibles/accessible_value.py +0 -0
  38. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/distro.py +0 -0
  39. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/dump.py +0 -0
  40. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/errors.py +0 -0
  41. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/logging.py +0 -0
  42. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/ponytail_helper.py +0 -0
  43. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/predicate.py +0 -0
  44. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/rawinput.py +0 -0
  45. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/scripts/__init__.py +0 -0
  46. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/scripts/dogtail_headless.py +0 -0
  47. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/tree.py +0 -0
  48. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/utils.py +0 -0
  49. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail/version.py +0 -0
  50. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.egg-info/SOURCES.txt +0 -0
  51. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.egg-info/dependency_links.txt +0 -0
  52. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.egg-info/entry_points.txt +0 -0
  53. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.egg-info/requires.txt +0 -0
  54. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.egg-info/top_level.txt +0 -0
  55. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/dogtail.spec +0 -0
  56. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/epydoc.conf +0 -0
  57. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/examples/atspi_events_listener_script.py +0 -0
  58. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/examples/data/UTF-8-demo.txt +0 -0
  59. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/examples/gnome_shell_stress_test_tree_api.py +0 -0
  60. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/examples/text_editor_utf8_tree_api.py +0 -0
  61. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/icons/dogtail-head-48.png +0 -0
  62. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/icons/dogtail-head.svg +0 -0
  63. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/icons/dogtail-tail-48.png +0 -0
  64. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/icons/dogtail-tail.svg +0 -0
  65. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/setup.cfg +0 -0
  66. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/__init__.py +0 -0
  67. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/main.fmf +0 -0
  68. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_config.py +0 -0
  69. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_gtk_demo.py +0 -0
  70. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_predicate.py +0 -0
  71. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_rawinput.py +0 -0
  72. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_rawinput_drag.py +0 -0
  73. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_rawinput_keyboard_gtk3.py +0 -0
  74. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_rawinput_keyboard_gtk4.py +0 -0
  75. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_rawinput_mouse_buttons_gtk3.py +0 -0
  76. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_rawinput_mouse_buttons_gtk4.py +0 -0
  77. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_gtk3.py +0 -0
  78. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_gtk4.py +0 -0
  79. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_search_gtk3.py +0 -0
  80. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_search_gtk4.py +0 -0
  81. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_selection_gtk3.py +0 -0
  82. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_selection_gtk4.py +0 -0
  83. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_states_gtk3.py +0 -0
  84. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_states_gtk4.py +0 -0
  85. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_string_representation_gtk3.py +0 -0
  86. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_string_representation_gtk4.py +0 -0
  87. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_value_gtk3.py +0 -0
  88. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_tree_value_gtk4.py +0 -0
  89. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_utils_accessibility.py +0 -0
  90. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_utils_delay.py +0 -0
  91. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_utils_lock.py +0 -0
  92. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_utils_run_gtk3.py +0 -0
  93. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_utils_run_gtk4.py +0 -0
  94. {dogtail-2.0.dev10 → dogtail-2.0.dev11}/tests/test_version.py +0 -0
@@ -1,4 +1,4 @@
1
- 2024-10-29 Michal Odehnal <modehnal@redhat.com>
1
+ 2025-10-29 Michal Odehnal <modehnal@redhat.com>
2
2
 
3
3
  dogtail 2.0.dev2
4
4
  ================
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dogtail
3
- Version: 2.0.dev10
3
+ Version: 2.0.dev11
4
4
  Summary: GUI test tool and automation framework that uses Accessibility (AT-SPI) technologies to communicate with desktop applications.
5
5
  Author-email: Zack Cerza <zcerza@redhat.com>, Ed Rousseau <rousseau@redhat.com>, David Malcolm <dmalcolm@redhat.com>, Vitezslav Humpa <vhumpa@redhat.com>, Michal Odehnal <modehnal@redhat.com>
6
6
  License-Expression: GPL-2.0
@@ -47,5 +47,3 @@ check_for_a11y = True
47
47
 
48
48
  [user_config]
49
49
  user_value_x = user_value_x
50
- user_value_y = user_value_y
51
- user_value_z = user_value_z
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ Experimental dogtail changes for 2.0 release.
4
+ """
5
+
6
+ # pylint: disable=line-too-long
7
+ # ruff: noqa: E501
8
+
9
+ import configparser
10
+
11
+ from typing import get_origin, get_args
12
+
13
+ import copy
14
+
15
+ from dogtail.logging import logging_class
16
+ LOGGING = logging_class.logger
17
+
18
+
19
+ YES_VALUES = ["y", "yes", "t", "true", "True", "1"]
20
+ NO_VALUES = ["", "n", "no", "f", "false", "False", "0"]
21
+
22
+
23
+ CONFIG_SCHEMA = {
24
+ "config": {
25
+ "action_delay": ([float, int], 1),
26
+ "typing_delay": ([float, int], 0.1),
27
+ "default_delay":([float, int], 0.5),
28
+ "double_click_delay": ([float, int], 0.1),
29
+ "search_back_off_delay": ([float, int], 0.5),
30
+ "search_warning_threshold": (int, 3),
31
+ "search_cut_off_limit": (int, 20),
32
+ "search_showing_only": (bool, False),
33
+ "children_limit": (int, 100),
34
+ "run_interval": ([float, int], 0.5),
35
+ "run_timeout": ([float, int], 30),
36
+ "gtk4_offset": ([list[int], tuple[int]], [12, 12]),
37
+ "debug_dogtail": (bool, False),
38
+ "debug_file": (str, "/tmp/dogtail_debug.log"),
39
+ "debug_searching": (bool, False),
40
+ "debug_sleep": (bool, False),
41
+ "debug_search_paths": (bool, False),
42
+ "absolute_node_paths": (bool, False),
43
+ "ensure_sensitivity": (bool, False),
44
+ "fatal_errors": (bool, False),
45
+ "check_for_a11y": (bool, True),
46
+ },
47
+ "user_config": {
48
+ "user_value_x": (object, "user_value_x"),
49
+ },
50
+ }
51
+
52
+
53
+ class ConfigValidationError(Exception):
54
+ pass
55
+
56
+
57
+ def cast_basic(value: str, expected_type):
58
+ """
59
+ Casts primitive types.
60
+ """
61
+
62
+ if isinstance(value, str) and expected_type is bool:
63
+ lower = value.lower()
64
+
65
+ if lower in YES_VALUES:
66
+ return True
67
+
68
+ if lower in NO_VALUES:
69
+ return False
70
+
71
+ raise ValueError(f"Invalid boolean: {value}")
72
+
73
+ return expected_type(value)
74
+
75
+
76
+ def cast_sequence(value: str, expected_container):
77
+ """
78
+ Casts comma-separated values into list[...] or tuple[...].
79
+ """
80
+
81
+ origin = get_origin(expected_container)
82
+ (item_type,) = get_args(expected_container)
83
+
84
+ # Strip optional list/tuple brackets like [1,2,3] or (1,2,3).
85
+ cleaned = value.strip()
86
+ if (cleaned.startswith("[") and cleaned.endswith("]")) or \
87
+ (cleaned.startswith("(") and cleaned.endswith(")")):
88
+ cleaned = cleaned[1:-1].strip()
89
+
90
+ # Split by comma.
91
+ items = [x.strip() for x in cleaned.split(",") if x.strip()]
92
+
93
+ casted = [cast_value(x, item_type) for x in items]
94
+
95
+ if origin is list:
96
+ return casted
97
+
98
+ if origin is tuple:
99
+ return tuple(casted)
100
+
101
+ raise ValueError(f"Unsupported sequence type: {origin}")
102
+
103
+
104
+ def cast_value(value, expected_type):
105
+ # --- Union-of-types support ---
106
+ if isinstance(expected_type, list):
107
+ errors = []
108
+ for t in expected_type:
109
+ try:
110
+ return cast_value(value, t)
111
+ except Exception as e:
112
+ errors.append(str(e))
113
+ raise ValueError(" | ".join(errors))
114
+
115
+ origin = get_origin(expected_type)
116
+ args = get_args(expected_type)
117
+
118
+ # --- Handle sequences ---
119
+ if origin in (list, tuple):
120
+ # If value is already the correct container type, just validate inner types
121
+ if isinstance(value, origin):
122
+ if args:
123
+ item_type = args[0]
124
+ for i, item in enumerate(value):
125
+ if not isinstance(item, item_type):
126
+ raise ValueError(f"Element '{i}' in '{value}' is not of type '{item_type}'.")
127
+ return value
128
+ # Otherwise parse string into sequence
129
+ return cast_sequence(value, expected_type)
130
+
131
+ # --- Handle simple types ---
132
+ # If value is already of correct type, return.
133
+ if isinstance(value, expected_type):
134
+ return value
135
+
136
+ # If the value is object.
137
+ if expected_type is object:
138
+ return value
139
+
140
+ # Otherwise, cast string to type
141
+ return cast_basic(value, expected_type)
142
+
143
+
144
+ def load_and_validate_config(path: str, schema: dict):
145
+ config = configparser.ConfigParser()
146
+ config.read(path)
147
+
148
+ validated = {}
149
+
150
+ # We do not care about config sections in dogtail, just set the variables to a dict.
151
+
152
+ # Step 1: Process all sections found in the file, even if not in schema.
153
+ LOGGING.debug("Process all variables.")
154
+ for section in config.sections():
155
+
156
+ for key, raw_value in config[section].items():
157
+ # If schema defines type → validate
158
+ if section in schema and key in schema[section]:
159
+ expected_type, default_value = schema[section][key]
160
+
161
+ try:
162
+ value = cast_value(raw_value, expected_type)
163
+ except Exception as e:
164
+ raise ConfigValidationError(
165
+ f"Type error in [{section}].{key}: {e}"
166
+ )
167
+
168
+ validated[key] = value
169
+ LOGGING.debug(f"Set Validated key '{key}' to value '{value}'")
170
+
171
+ else:
172
+ # Unknown key → keep raw value as string
173
+ validated[key] = raw_value
174
+ LOGGING.debug(f"Set Validated key '{key}' to raw value '{raw_value}'")
175
+
176
+ LOGGING.debug("Process default variables.")
177
+ # Step 2: Ensure missing schema sections & keys get default values.
178
+ for section, fields in schema.items():
179
+ for key, (expected_type, default_value) in fields.items():
180
+ if key not in validated:
181
+ validated[key] = default_value
182
+ LOGGING.debug(f"Set Default key '{key}' to value '{default_value}'")
183
+
184
+ return validated
185
+
186
+
187
+ class _Config:
188
+ """
189
+ Config class to keep backwards compatibility and to have getters and setters.
190
+ """
191
+
192
+ validated_config = load_and_validate_config("dogtail_config.ini", CONFIG_SCHEMA)
193
+ LOGGING.debug(f"Validated config: '{validated_config}'")
194
+
195
+ # Create a deep copy instead of shallow copy to keep values for reset method.
196
+ _default_values_storage = copy.deepcopy(validated_config)
197
+
198
+ # Handle dogtail debug logging.
199
+ if validated_config["debug_dogtail"]:
200
+ LOGGING.info("Debugging dogtail to console.")
201
+ logging_class.debug_to_console()
202
+
203
+
204
+ def reset_configuration(self):
205
+ """
206
+ Reset configuration to default values.
207
+ """
208
+
209
+ LOGGING.debug(logging_class.get_func_params_and_values())
210
+
211
+ self.validated_config.update(self._default_values_storage)
212
+
213
+
214
+ def adjust_casing(self, string_to_fix):
215
+ """
216
+ Transforms a string to snake_case.
217
+
218
+ :param string_to_fix: String to transform to snake_case
219
+ :type string_to_fix: str
220
+
221
+ :return: Transformed string.
222
+ :rtype: str
223
+ """
224
+
225
+ LOGGING.debug(logging_class.get_func_params_and_values())
226
+
227
+ string_in_snake_case = ""
228
+ for character in string_to_fix:
229
+ if character.isalpha and character.isupper():
230
+ string_in_snake_case += "_" + character.lower()
231
+ else:
232
+ string_in_snake_case += character
233
+
234
+ return string_in_snake_case
235
+
236
+
237
+ def __setattr__(self, option_id, value_to_set):
238
+ LOGGING.debug(logging_class.get_func_params_and_values())
239
+
240
+ # Set a variable to use for the setter logic.
241
+ set_option_id = option_id
242
+
243
+ # Attempt to have some backwards compatibility and support camelCase.
244
+ if any(char.isupper() for char in set_option_id):
245
+ set_option_id = self.adjust_casing(set_option_id)
246
+ LOGGING.debug(f"Config variable was transformed to '{set_option_id}'.")
247
+
248
+ # Set custom user value to use in dogtail run.
249
+ # Current logic will fail on any attempt not set in config.ini.
250
+ # Should we allow setting values during a dogtail execution?
251
+ # if "custom_" in set_option_id:
252
+ # LOGGING.info(f"Setting a custom user value '{set_option_id}'.")
253
+ # self.options[set_option_id] = value_to_set
254
+
255
+ # In other cases check that the value exists.
256
+ if set_option_id not in self.validated_config:
257
+ raise AttributeError(f"Attempt to use invalid option '{set_option_id}'.")
258
+ # LOGGING.info(f"Attempt to use invalid option '{set_option_id}'.")
259
+ # return
260
+
261
+ # Set the value if the value is not already present.
262
+ if self.validated_config[set_option_id] != value_to_set:
263
+
264
+ validated_expected_type = None
265
+
266
+ # First attempt to get defaults from the schema.
267
+ for section in CONFIG_SCHEMA.keys():
268
+ if set_option_id in CONFIG_SCHEMA[section]:
269
+ validated_expected_type, default_value = CONFIG_SCHEMA[section][set_option_id]
270
+
271
+ # If unsuccessful mark the expected type as anything.
272
+ if not validated_expected_type:
273
+ LOGGING.debug("Setting expected type to object.")
274
+ validated_expected_type = object
275
+
276
+ try:
277
+ # Set the value.
278
+ self.validated_config[set_option_id] = cast_value(value_to_set, validated_expected_type)
279
+ except Exception:
280
+ # Log the message but do not end, use the default.
281
+ LOGGING.info(" ".join((
282
+ f"Attempt to set value of type '{type(value_to_set)}'",
283
+ f"to key with accepted types '{validated_expected_type}'",
284
+ )))
285
+ # Setting the value.
286
+ LOGGING.info(f"Fallback of config option '{set_option_id}' to '{default_value}'.")
287
+ self.validated_config[set_option_id] = default_value
288
+
289
+ if set_option_id == "debug_dogtail" and self.validated_config[set_option_id]:
290
+ LOGGING.info("Debugging dogtail to console.")
291
+ logging_class.debug_to_console()
292
+
293
+ if set_option_id == "debug_dogtail" and not self.validated_config[set_option_id]:
294
+ LOGGING.info("Disabling debugging dogtail to console.")
295
+ logging_class.disable_debug_to_console()
296
+
297
+
298
+ def __getattr__(self, option_id):
299
+ # Set a variable to use for the getter logic.
300
+ get_option_id = option_id
301
+
302
+ # Attempt to have some backwards compatibility and support camelCase.
303
+ if any(char.isupper() for char in get_option_id):
304
+ get_option_id = self.adjust_casing(get_option_id)
305
+ LOGGING.debug(f"Config variable was transformed to '{get_option_id}'.")
306
+ LOGGING.debug(f"Its value is '{self.validated_config[get_option_id]}'.")
307
+
308
+ try:
309
+ return self.validated_config[get_option_id]
310
+ except KeyError as error:
311
+ raise AttributeError(f"Attempt to use invalid option '{get_option_id}'.") from error
312
+ # LOGGING.info(f"Attempt to use invalid option '{get_option_id}'.")
313
+
314
+
315
+ config = _Config()
@@ -7,24 +7,18 @@ Create configuration in your current directory.
7
7
 
8
8
  import os
9
9
  import sys
10
- from dogtail.config import config
10
+ from dogtail.config import CONFIG_SCHEMA as schema
11
11
 
12
12
  def main():
13
13
  directory = os.path.realpath(os.getcwd())
14
14
 
15
-
16
- # Print the retrieved values
17
-
18
15
  configuration = ""
19
- configuration += "[config]\n"
20
-
21
- for key, value in config.options.items():
22
- configuration += f"{key} = {value.value}\n"
23
16
 
24
- configuration += "[user_config]\n"
25
- configuration += "user_value_x = user_value_x\n"
26
- configuration += "user_value_y = user_value_y\n"
27
- configuration += "user_value_z = user_value_z\n"
17
+ for section in schema.keys():
18
+ configuration += f"[{section}]\n"
19
+ for key, scheme_value in schema[section].items():
20
+ _, raw_value = scheme_value
21
+ configuration += f"{key} = {raw_value}\n"
28
22
 
29
23
  try:
30
24
  if os.path.isfile("dogtail_config.ini"):
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Print default configuration.
4
+ """
5
+
6
+ from dogtail.config import config, CONFIG_SCHEMA
7
+
8
+ def main():
9
+ # Print the retrieved values
10
+
11
+ print("Configuration.")
12
+
13
+ expected_types = {}
14
+ header_options = "Config Options"
15
+ header_value = "Value"
16
+ header_type = "Acceptable Types"
17
+
18
+ max_string_options_length = len(header_options)
19
+ max_string_value_length = len(header_value)
20
+
21
+ for section, fields in CONFIG_SCHEMA.items():
22
+ for key, (expected_type, default_value) in fields.items():
23
+ expected_types[key] = (expected_type, default_value)
24
+
25
+ for key, value in config.validated_config.items():
26
+ max_string_options_length = max(max_string_options_length, len(key))
27
+ max_string_value_length = max(max_string_value_length, len(str(value)))
28
+
29
+ print(f"{header_options:<{max_string_options_length+2}} {header_value:<{max_string_value_length+1}} {header_type}")
30
+
31
+ for key, value in config.validated_config.items():
32
+ expected_type = "object"
33
+ if key in expected_types.keys():
34
+ expected_type = str(expected_types[key][0])
35
+ print(f"{key:<{max_string_options_length + 1}}: {str(value):<{max_string_value_length + 1}} '{expected_type}'")
36
+
37
+ if __name__ == "__main__":
38
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dogtail
3
- Version: 2.0.dev10
3
+ Version: 2.0.dev11
4
4
  Summary: GUI test tool and automation framework that uses Accessibility (AT-SPI) technologies to communicate with desktop applications.
5
5
  Author-email: Zack Cerza <zcerza@redhat.com>, Ed Rousseau <rousseau@redhat.com>, David Malcolm <dmalcolm@redhat.com>, Vitezslav Humpa <vhumpa@redhat.com>, Michal Odehnal <modehnal@redhat.com>
6
6
  License-Expression: GPL-2.0
@@ -4,6 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dogtail"
7
+ #version = "v2.0.dev0"
7
8
  dynamic = ["version"]
8
9
  description = "GUI test tool and automation framework that uses Accessibility (AT-SPI) technologies to communicate with desktop applications."
9
10
  license = "GPL-2.0"
@@ -1,255 +0,0 @@
1
- #!/usr/bin/python3
2
- """
3
- Experimental dogtail changes for 2.0 release.
4
- """
5
-
6
- # pylint: disable=line-too-long
7
- # ruff: noqa: E501
8
-
9
- import configparser
10
- from configparser import NoSectionError
11
-
12
- import copy
13
-
14
- from dogtail.logging import logging_class
15
- LOGGING = logging_class.logger
16
-
17
-
18
- NO_VALUES = ["", "n", "no", "f", "false", "False", "0"]
19
-
20
-
21
- class Value:
22
- """
23
- Value class to keep value and its types.
24
- """
25
-
26
- def __init__(self, set_value, defined_types=list):
27
- self.value = set_value
28
- self.defined_types = defined_types
29
-
30
- def __str__(self):
31
- return f"Default Value: '{self.value}'. Defined Types: '{str(self.defined_types)}'"
32
-
33
- def __repr__(self):
34
- return self.__str__()
35
-
36
-
37
- class LoadDefaultConfiguration:
38
- """
39
- Configuration class for default values.
40
- """
41
-
42
- def __init__(self, config_parser):
43
- # Access values from the configuration file.
44
- self.cfg = config_parser
45
-
46
- # Timing Delay section.
47
- section = "config"
48
- self.action_delay = float(self.cfg.get(section, "action_delay", fallback=1))
49
- self.typing_delay = float(self.cfg.get(section, "typing_delay", fallback=0.1))
50
- self.default_delay = float(self.cfg.get(section, "default_delay", fallback=0.5))
51
- self.double_click_delay = float(self.cfg.get(section, "double_click_delay", fallback=0.1))
52
- self.search_back_off_delay = float(self.cfg.get(section, "search_back_off_delay", fallback=0.5))
53
-
54
- # Searching section.
55
- self.search_warning_threshold = int(self.cfg.get(section, "search_warning_threshold", fallback=3))
56
- self.search_cut_off_limit = int(self.cfg.get(section, "search_cut_off_limit", fallback=20))
57
- self.search_showing_only = self.cfg.get(section, "search_showing_only", fallback="False") not in NO_VALUES
58
-
59
- # Children Limit section.
60
- self.children_limit = int(self.cfg.get(section, "children_limit", fallback=100))
61
-
62
- # Util Scripts section.
63
- self.run_interval = float(self.cfg.get(section, "run_interval", fallback=0.5))
64
- self.run_timeout = int(self.cfg.get(section, "run_timeout", fallback=30))
65
-
66
- # GTK4Offset section.
67
- self.gtk4_offset = list(self.cfg.get(section, "gtk4_offset", fallback=(12, 12)))
68
-
69
- # Debug section.
70
- self.debug_dogtail = self.cfg.get(section, "debug_dogtail", fallback="False") not in NO_VALUES
71
- self.debug_file = self.cfg.get(section, "debug_file", fallback="/tmp/dogtail_debug.log")
72
-
73
- # Other debug section.
74
- self.debug_searching = self.cfg.get(section, "debug_searching", fallback="False") not in NO_VALUES
75
- self.debug_sleep = self.cfg.get(section, "debug_sleep", fallback="False") not in NO_VALUES
76
- self.debug_search_paths = self.cfg.get(section, "debug_search_paths", fallback="False") not in NO_VALUES
77
-
78
- # Debug section. ?
79
- #self.log_debug_to_std_out = self.cfg.get(section, "log_debug_to_std_out", fallback="True") not in NO_VALUES
80
- self.absolute_node_paths = self.cfg.get(section, "absolute_node_paths", fallback="False") not in NO_VALUES
81
- self.ensure_sensitivity = self.cfg.get(section, "ensure_sensitivity", fallback="False") not in NO_VALUES
82
- self.fatal_errors = self.cfg.get(section, "fatal_errors", fallback="False") not in NO_VALUES
83
- self.check_for_a11y = self.cfg.get(section, "check_for_a11y", fallback="True") not in NO_VALUES
84
-
85
- if self.debug_dogtail:
86
- LOGGING.info("Debugging dogtail to console.")
87
- logging_class.debug_to_console()
88
-
89
-
90
- class _Config:
91
- """
92
- Config class to keep backwards compatibility and to have getters and setters.
93
- """
94
-
95
- # Create a ConfigParser object.
96
- config_parser = configparser.ConfigParser()
97
-
98
- # Read the configuration file.
99
- successful_file_parsed = config_parser.read("dogtail_config.ini")
100
- _default = LoadDefaultConfiguration(config_parser)
101
-
102
- # Return a dictionary with the retrieved values.
103
- # Define allowed type so that user cannot by mistake set different type than allowed.
104
- options = {
105
- "action_delay": Value(_default.action_delay, [float, int]),
106
- "typing_delay": Value(_default.typing_delay, [float, int]),
107
- "default_delay": Value(_default.default_delay, [float, int]),
108
- "double_click_delay": Value(_default.double_click_delay, [float, int]),
109
- "search_back_off_delay": Value(_default.search_back_off_delay, [float, int]),
110
-
111
- "search_warning_threshold": Value(_default.search_warning_threshold, [int]),
112
- "search_cut_off_limit": Value(_default.search_cut_off_limit, [int]),
113
- "search_showing_only": Value(_default.search_showing_only, [bool]),
114
-
115
- "children_limit": Value(_default.children_limit, [int]),
116
-
117
- "run_interval": Value(_default.run_interval, [float, int]),
118
- "run_timeout": Value(_default.run_timeout, [float, int]),
119
-
120
- "gtk4_offset": Value(_default.gtk4_offset, [list, tuple]),
121
-
122
- "debug_dogtail": Value(_default.debug_dogtail, [bool]),
123
- "debug_file": Value(_default.debug_file, [str]),
124
-
125
- "debug_searching": Value(_default.debug_searching, [bool]),
126
- "debug_sleep": Value(_default.debug_sleep, [bool]),
127
- "debug_search_paths": Value(_default.debug_search_paths, [bool]),
128
-
129
- #"log_debug_to_std_out": Value(_default.log_debug_to_std_out, [bool]),
130
- "absolute_node_paths": Value(_default.absolute_node_paths, [bool]),
131
- "ensure_sensitivity": Value(_default.ensure_sensitivity, [bool]),
132
- "fatal_errors": Value(_default.fatal_errors, [bool]),
133
- "check_for_a11y": Value(_default.check_for_a11y, [bool]),
134
- }
135
-
136
- # Create a deep copy instead of shallow copy to keep values for reset method.
137
- _default_values_storage = copy.deepcopy(options)
138
-
139
- # User configuration.
140
- section = "user_config"
141
- try:
142
- user_setting_dictionary = dict(_default.cfg.items(section))
143
-
144
- # Set all user setup.
145
- for key, value in user_setting_dictionary.items():
146
- # Should we force 'custom_' prefix on users?
147
- # As of now I opted to not do that, users can define any option they want.
148
- options[key] = Value(value, [type(value)])
149
-
150
- except NoSectionError:
151
- # Do nothing, no user setting defined, which is ok.
152
- pass
153
-
154
- except Exception as error:
155
- raise RuntimeError("Unexpected exception caught.") from error
156
-
157
-
158
- def reset_configuration(self):
159
- """
160
- Reset configuration to default values.
161
- """
162
-
163
- LOGGING.debug(logging_class.get_func_params_and_values())
164
-
165
- self.options.update(self._default_values_storage)
166
-
167
-
168
- def adjust_casing(self, string_to_fix):
169
- """
170
- Transforms a string to snake_case.
171
-
172
- :param string_to_fix: String to transform to snake_case
173
- :type string_to_fix: str
174
-
175
- :return: Transformed string.
176
- :rtype: str
177
- """
178
-
179
- LOGGING.debug(logging_class.get_func_params_and_values())
180
-
181
- string_in_snake_case = ""
182
- for character in string_to_fix:
183
- if character.isalpha and character.isupper():
184
- string_in_snake_case += "_" + character.lower()
185
- else:
186
- string_in_snake_case += character
187
-
188
- return string_in_snake_case
189
-
190
-
191
- def __setattr__(self, option_id, value_to_set):
192
- LOGGING.debug(logging_class.get_func_params_and_values())
193
-
194
- # Set a variable to use for the setter logic.
195
- set_option_id = option_id
196
-
197
- # Attempt to have some backwards compatibility and support camelCase.
198
- if any(char.isupper() for char in set_option_id):
199
- set_option_id = self.adjust_casing(set_option_id)
200
- LOGGING.debug(f"Value was transformed to '{set_option_id}'.")
201
-
202
- # Set custom user value to use in dogtail run.
203
- # Current logic will fail on any attempt not set in config.ini.
204
- # Should we allow setting values during a dogtail execution?
205
- # if "custom_" in set_option_id:
206
- # LOGGING.info(f"Setting a custom user value '{set_option_id}'.")
207
- # self.options[set_option_id] = value_to_set
208
-
209
- # In other cases check that the value exists.
210
- if set_option_id not in self.options:
211
- raise AttributeError(f"Attempt to use invalid option '{set_option_id}'.")
212
- # LOGGING.info(f"Attempt to use invalid option '{set_option_id}'.")
213
- # return
214
-
215
- # Set the value if the value is not already present.
216
- if self.options[set_option_id].value != value_to_set:
217
- # Defined types variable.
218
- _defined_types = self.options[set_option_id].defined_types
219
-
220
- # Type checking.
221
- if type(value_to_set) not in _defined_types:
222
- raise ValueError(" ".join((
223
- f"Attempt to set value of type '{type(value_to_set)}'",
224
- f"to key with accepted types '{_defined_types}'",
225
- )))
226
-
227
- # Type was checked, keep the defined types.
228
- self.options[set_option_id] = Value(value_to_set, _defined_types)
229
-
230
- if set_option_id == "debug_dogtail" and self.options[set_option_id].value:
231
- LOGGING.info("Debugging dogtail to console.")
232
- logging_class.debug_to_console()
233
-
234
- if set_option_id == "debug_dogtail" and not self.options[set_option_id].value:
235
- LOGGING.info("Disabling debugging dogtail to console.")
236
- logging_class.disable_debug_to_console()
237
-
238
-
239
- def __getattr__(self, option_id):
240
- # Set a variable to use for the getter logic.
241
- get_option_id = option_id
242
-
243
- # Attempt to have some backwards compatibility and support camelCase.
244
- if any(char.isupper() for char in get_option_id):
245
- get_option_id = self.adjust_casing(get_option_id)
246
- LOGGING.debug(f"Value was transformed to '{get_option_id}'.")
247
-
248
- try:
249
- return self.options[get_option_id].value
250
- except KeyError as error:
251
- raise AttributeError(f"Attempt to use invalid option '{get_option_id}'.") from error
252
- # LOGGING.info(f"Attempt to use invalid option '{get_option_id}'.")
253
-
254
-
255
- config = _Config()
@@ -1,16 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Print default configuration.
4
- """
5
-
6
- from dogtail.config import config
7
-
8
- def main():
9
- # Print the retrieved values
10
-
11
- print("Configuration.")
12
- for key, value in config.options.items():
13
- print(f"{key:25}: {value.value} {value.defined_types}")
14
-
15
- if __name__ == "__main__":
16
- main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes